Uninformed: Informative Information for the Uninformed

Vol 8» 2007.Sep

Two-Stage Code Deobfuscation

One of the more interesting defensive features of PatchGuard 2 and PatchGuard 3 is the mechanism by which it obfuscates the PatchGuard check context, or the code and data necessary to verify system integrity. PatchGuard contexts are obfuscated such that they are completely randomized in-memory while inactive, and change their location and obfuscation keys (and thus contents) each time the context is invoked to check system integrity.

The decryption phase of PatchGuard is split into two stages. The first stage is essentially a small stub that remains completely obfuscated in-memory until just before it is called. The caller overwrites the first instruction in the stub that is called with a "lock xor qword ptr [rcx], rdx" instruction. The arguments to the stub are the address of the stub itself (in rcx), and the decryption key (in rdx). Thus, the first instruction now modifies itself (and more importantly the subsequent instruction, as each instruction is 4 bytes long but modifies 8 bytes of opcode bytes), which results in being another xor instruction. A small series of these xor instructions continues until the second stage of the decoding stub is completely decoded.

At this point, the second stage of the decoding stub is plaintext and may now execute. The second stage consists of a loop of xor operations starting at the end of the PatchGuard context and moving backward until the entire check routine is decoded. Additionally, the decryption key is shifted each xor round during the second stage decoding process.

After the second stage decoding loop is complete, control is transferred to the now-plaintext integrity check routine (all of the supporting data, such as critical function pointers into the kernel, will also have been translated into plaintext at this point by the second stage decoding loop).

Source code to a basic program to decrypt a PatchGuard memoy context is included with the article. The program expects to be supplied with a file containing "dq" logs from the kernel debugger that cover the entire memory context, along with the decryption key (at KDPC + 0x40) and KDPC->DeferredContext values.