I published a new tool called meow that disables PatchGuard on Windows 8.1 on-the-fly. Though qertmeow has some interesting technical details I could explain such as support of ARM (Windows RT) and detection of the end of a function for installing an epilogue hook, on this entry, I am going to explain some techniques that help researchers analyze PatchGuard on your own rather than how this specific exploitation works.
Those techniques are worthwhile to share because, you have to be able to analyze it if you hope to do something with PatchGuard as it is a moving target, and meow is not going to work forever due to updates of implementation of PatchGuard, or meow may not be perfect even at the time of publication of this article.
Summary
As your regular reverse engineering work, you can analyze PatchGuard in both static and dynamic means, but there are some hurdles specific to PatchGuard analysis on both sides, for example:- PatchGuard related functions do not have descriptive names or do not have names at all unlike other functions in the kernel
- Most of function calls in PatchGuard functions are indirect calls like C++ code
- Kernel debugging is not an option in some situations
- Code is copied into random locations and stored in an encrypted form, and you cannot easily spot where to monitor at the run-time
Those are significant difficulties you face at the initial stage of analysis, but also ones you can easily overcome if you know some tricks I describe here. The tricks are as follows:
- Identifying PatchGuard functions
- Locating an initialization function and checking cross-references
- Naming functions in a consistent manner
- Checking the existence of SEH
- Analyzing 0x109 Crash Dump for Re-constructing the PatchGuard context
- Dissecting bug check parameters
- Applying the format of the context to IDA
- Discovering Threads Executing PatchGuard Code
- Finding system threads on memory
let us through them one by one.
Identifying PatchGuard functions
Firstly, you can easily find an initialization function of PatchGuard by sorting a function list by length. The largest function in the ntoskrnl.exe is the initialization function executed at the time of system initialization and sets up a large structure so called the PatchGuard context(s) on non-pagable memory (I am going to describe the structure of the context later). I call this function as Pg_xInitializePatchGuard() in this article.Image 1: The largest functions on x64 |
Image 2: The largest functions on ARM |
Secondly, you can identify other PatchGuard related functions with cross-referencing function calls. If a function is referenced from only other PatchGuard related functions, it is safe to assume that the function is PatchGuard dedicated and needs to be analyzed. As an example, let us take a look at a caller of Pg_xInitializePatchGuard(), KiFilterFiberContext(). You see that this function is referenced from Pg_xInitializePatchGuard() and another unnamed function sub_1407339C3() which is not called by anywhere. At this stage, it is safe to say that KiFilterFiberContext() and sub_1407339C3() are only used for PatchGuard.
Image 3: Callers of Pg_xInitializePatchGuard() |
Image 4: Callers of sub_1407339C3() |
Image 5: Filtering functions with the prefix |
Image 6: Reflected SEH information |
Image 7: Where the corresponding __try is |
Similarly, you can repeat the same process against all functions and global variables referenced from each Pg_*() function using the Proximity browser of IDA. This gives you a fairly comprehensive list of Pg_ functions, which can be discouraging enough to most of casual reverse engineers ;)
Analyzing 0x109 Crash Dump for Re-constructing the PatchGuard Context
As soon as you start to read Pg_*() functions, you discover that there are countless of indirect calls with specific registers. Those are accesses to the PatchGuard context, and it is essential to know what are stored and how they are used to understand the internals of PatchGuard.Image 8: References to the PatchGuard context |
There are some difficulties to perform effective run-time analysis, however.
First of all, you do not know where to monitor at the beginning of analysis since most of core code are copied onto random memory locations and stored in an encoded form except for the time of execution. In addition to that, setting breakpoints or installing hooks onto the kernel causes bug check 0x109 unless you know how integrity check is carried out. Moreover, you may not able to attach a kernel debugger to the system running on some non-PC devices such as Windows RT and Windows Phone.
It may sounds pretty bad to us, but a good news is that we can still uncover the contents of the PatchGuard context with analyzing crash dump. Specifically, you can interpret each 'reserved' bug check parameter in the following ways on x64:
- Arg1 - 0xA3A03F5891C8B4E8 = An address of the PatchGuard context
- Arg2 - 0xB3B74BDEE4453415 = An address of a validation structure that detected corruption
- Arg3 = An address of corrupted data (in most cases)
NB: You can easily spot those magic values in Pg_FsRtlMdlReadCompleteDevEx() before a call to Pg_SdbpCheckDll() as well as code setting bug check parameters.
Let us take a look at an example on Windows 10. This is what you get on bug check 0x109:
----
0: kd> !analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
CRITICAL_STRUCTURE_CORRUPTION (109)
...
Arguments:
Arg1: a3a01f597768b4f0, Reserved
Arg2: b3b72bdfc9e65cc3, Reserved
Arg3: fffff80100af8074, Failure type dependent information
Arg4: 0000000000000001, Type of corrupted region, can be
...
----
Then, check the first parameter:
----
kd> ? a3a01f597768b4f0 - 0xA3A03F5891C8B4E8
Evaluate expression: -35180519620600 = ffffe000`e5a00008
kd> dps ffffe000`e5a00008 l200
ffffe000`e5a00008 70047266`b0b8a753
...
Then, check the first parameter:
----
kd> ? a3a01f597768b4f0 - 0xA3A03F5891C8B4E8
Evaluate expression: -35180519620600 = ffffe000`e5a00008
kd> dps ffffe000`e5a00008 l200
ffffe000`e5a00008 70047266`b0b8a753
...
ffffe000`e5a000e0 00000000`00000000
ffffe000`e5a000e8 fffff801`00453b80 nt!ExAcquireResourceSharedLite
ffffe000`e5a000f0 fffff801`004537f0 nt!ExAcquireResourceExclusiveLite
ffffe000`e5a000f8 fffff801`00688930 nt!ExAllocatePoolWithTag
ffffe000`e5a00100 fffff801`006896d0 nt!ExFreePool
...
...
ffffe000`e5a004b8 fffff801`00b850b0 nt!HandleTableListLock
ffffe000`e5a004c0 ffffc001`0c614000 ; nt!ObpKernelHandleTable
ffffe000`e5a004c8 fffff780`00000000 ; nt!KiUserSharedData
ffffe000`e5a004d0 ff73c402`76affdcd ; a copy of nt!KiWaitNever
ffffe000`e5a004d8 fffff801`00b292c0 nt!SeProtectedMapping
...
...
----
In this example, ffffe000`e5a00008 is an address of the PatchGuard context starting with random-looking bytes followed by a bunch of function pointers and variables. Although you may not tell what some variables are at a glance, defining the PatchGuard structure in IDA with this result is fundamental to uncover how PatchGuard works.
The second parameter is an address to the validation structure that detected corruption. There are multiple structures and each corresponds to a type of corrupted region (Arg4). Their formats vary but are mostly made up of at least: type of corrupted region, address(es) to verify, checksum(s) to be expected as valid value(s).
The following is dump of the structure in this example (I commented with some guesswork):
----
kd> ? b3b72bdfc9e65cc3 - 0xB3B74BDEE4453415
Evaluate expression: -35180519544658 = ffffe000`e5a128ae
kd> dps ffffe000`e5a128ae
ffffe000`e5a128ae 00000000`00000001 ; type of corrupted region
ffffe000`e5a128b6 fffff801`00789000 nt!BcpCursor <PERF> (nt+0x36d000)
; an address of .pdata
ffffe000`e5a128be 244e1425`0004a9e8 ; checksum?, a virtual size of .pdata
ffffe000`e5a128c6 fffff801`00789000 nt!BcpCursor <PERF> (nt+0x36d000)
; an address of .pdata
ffffe000`e5a128ce fffff801`0041c000 nt!WerLiveKernelInitSystem <PERF> (nt+0x0)
; an address of nt image base
ffffe000`e5a128d6 0004a9e8`00842000 ; a virtual size of .pdata, a size of nt image
ffffe000`e5a128de 39e90701`406ebd95 ; chehcksums?
ffffe000`e5a128e6 78ca89f0`62a1f735
...
Image 9: Defining the structure in IDA |
Image 10: Applied the structure definition |
The second parameter is an address to the validation structure that detected corruption. There are multiple structures and each corresponds to a type of corrupted region (Arg4). Their formats vary but are mostly made up of at least: type of corrupted region, address(es) to verify, checksum(s) to be expected as valid value(s).
The following is dump of the structure in this example (I commented with some guesswork):
----
kd> ? b3b72bdfc9e65cc3 - 0xB3B74BDEE4453415
Evaluate expression: -35180519544658 = ffffe000`e5a128ae
kd> dps ffffe000`e5a128ae
ffffe000`e5a128ae 00000000`00000001 ; type of corrupted region
ffffe000`e5a128b6 fffff801`00789000 nt!BcpCursor <PERF> (nt+0x36d000)
; an address of .pdata
ffffe000`e5a128be 244e1425`0004a9e8 ; checksum?, a virtual size of .pdata
ffffe000`e5a128c6 fffff801`00789000 nt!BcpCursor <PERF> (nt+0x36d000)
; an address of .pdata
ffffe000`e5a128ce fffff801`0041c000 nt!WerLiveKernelInitSystem <PERF> (nt+0x0)
; an address of nt image base
ffffe000`e5a128d6 0004a9e8`00842000 ; a virtual size of .pdata, a size of nt image
ffffe000`e5a128de 39e90701`406ebd95 ; chehcksums?
ffffe000`e5a128e6 78ca89f0`62a1f735
...
----
Those structures are stored at the end of the PatchGuard context as a variable length of an array following other structures and code to recover corruption for reliable bug check and referenced using variable fields containing an offset and a number of arrays.
As I mentioned earlier, PatchGuard contexts including their code are allocated on memory, which is either on executable NonPagedPool or independent pages allocated by MmAllocateIndependentPages(), and it exhibits uncommon outputs in the thread stack trace.
----
kd> !process 4
...
THREAD ffffe00137df7040 Cid 0004.0064 Teb: ...
...
Win32 Start Address nt!ExpWorkerThread (0xfffff803d16ac3f0)
...
Child-SP RetAddr Call Site
ffffd001`043ccdb0 fffff803`d1658ab9 nt!KiSwapContext+0x76
ffffd001`043ccef0 fffff803`d1657fb8 nt!KiSwapThread+0x689
ffffd001`043ccfb0 fffff803`d1621d0c nt!KiCommitThreadWait+0x148
ffffd001`043cd040 ffffe001`37ede587 nt!KeDelayExecutionThread+0x1dc
ffffd001`043cd0b0 4c91448e`dcd4c0fd 0xffffe001`37ede587
ffffd001`043cd0b8 00000000`00000000 0x4c91448e`dcd4c0fd
...
----
From this output, you can see that the thread 0x64 is calling KeDelayExecutionThread() from somewhere outside images. Obviously, it is not common unless you have malware in your system, especially considering the fact that the thread is a worker thread and even not a dedicated thread.
Once you find a thread like this, you are free to set a break point at the return address and get control with the debugger.
kd> bp 0xffffe001`37ede587
This trick does not always work because PatchGuard sometimes skips sleep functions (KeDelayExecutionThread() or KeWaitForSingleObject()) and you do not catch the moment when a thread is executing code on memory, or PatchGuard sometimes runs inside of ntoskrn.exe and not on pool. But it is worth trying some times of reboot and checking if those threads exist.
Note that if you want to read code around the return address with IDA, you can search the byte sequence at the return address with [Alt-B].
----
Another option is using a hypervisor to monitor and detect PatchGuard threads based on execution of some uncommon instructions if the system is running on the Intel platform. See my PoC Sushi as an example.
Those structures are stored at the end of the PatchGuard context as a variable length of an array following other structures and code to recover corruption for reliable bug check and referenced using variable fields containing an offset and a number of arrays.
Discovering Threads Executing PatchGuard Code
another trick for run-time analysis is discovering threads running on memory and setting break points there. It is possible only when you are able to attach a kernel debugger to the system.As I mentioned earlier, PatchGuard contexts including their code are allocated on memory, which is either on executable NonPagedPool or independent pages allocated by MmAllocateIndependentPages(), and it exhibits uncommon outputs in the thread stack trace.
----
kd> !process 4
...
THREAD ffffe00137df7040 Cid 0004.0064 Teb: ...
...
Win32 Start Address nt!ExpWorkerThread (0xfffff803d16ac3f0)
...
Child-SP RetAddr Call Site
ffffd001`043ccdb0 fffff803`d1658ab9 nt!KiSwapContext+0x76
ffffd001`043ccef0 fffff803`d1657fb8 nt!KiSwapThread+0x689
ffffd001`043ccfb0 fffff803`d1621d0c nt!KiCommitThreadWait+0x148
ffffd001`043cd040 ffffe001`37ede587 nt!KeDelayExecutionThread+0x1dc
ffffd001`043cd0b0 4c91448e`dcd4c0fd 0xffffe001`37ede587
ffffd001`043cd0b8 00000000`00000000 0x4c91448e`dcd4c0fd
...
----
From this output, you can see that the thread 0x64 is calling KeDelayExecutionThread() from somewhere outside images. Obviously, it is not common unless you have malware in your system, especially considering the fact that the thread is a worker thread and even not a dedicated thread.
Once you find a thread like this, you are free to set a break point at the return address and get control with the debugger.
----
kd> u 0xffffe001`37ede587
ffffe001`37ede587 jmp ffffe001`37ede5b5
ffffe001`37ede589 lea rax,[rbp+1A8h]
ffffe001`37ede590 xor r9d,r9d
ffffe001`37ede593 xor r8d,r8d
ffffe001`37ede596 mov qword ptr [rsp+20h],rax
ffffe001`37ede59b mov rcx,r13
ffffe001`37ede59e call qword ptr [rbp+68h]
ffffe001`37ede5a1 test eax,eax
----
Image 11: Woohoo! Enjoy debugging. |
Note that if you want to read code around the return address with IDA, you can search the byte sequence at the return address with [Alt-B].
----
kd> db 0xffffe001`37ede587 l10
ffffe001`37ede587 eb 2c 48 8d 85 a8 01 00-00 45 33 c9 45 33 c0 48 .,H......E3.E3.H
----
Image 12: Finding where the PatchGuard context is running in IDA |
Another option is using a hypervisor to monitor and detect PatchGuard threads based on execution of some uncommon instructions if the system is running on the Intel platform. See my PoC Sushi as an example.
No comments:
Post a Comment