Copy Link
Add to Bookmark
Report
uninformed 08 02
PatchGuard Reloaded: A Brief Analysis of PatchGuard Version 3
September, 2007
Skywing
skywing@valhallalegends.com
http://www.nynaeve.net/
Abstract: Since the publication of previous bypass or circumvention techniques
for Kernel Patch Protection (otherwise known as ``PatchGuard''), Microsoft has
continued to refine their patch protection system in an attempt to foil
known bypass mechanisms. With the release of Windows Server 2008 Beta 3,
and later a full-blown distribution of PatchGuard to Windows Vista /
Windows Server 2003 via Windows Update, Microsoft has introduced the next
generation of PatchGuard to the general public (``PatchGuard 3''). As with
previous updates to PatchGuard, version three represents a set of
incremental changes that are designed to address perceived weaknesses and
known bypass vectors in earlier versions. Additionally, PatchGuard 3
expands the set of kernel variables that are protected from unauthorized
modification, eliminating several mechanisms that might be used to
circumvent PatchGuard while co-existing (as opposed to disabling) it. This
article describes some of the changes that have been made in PatchGuard 3.
This article also proposes several new techniques that can be used to
circumvent PatchGuard's defenses. Countermeasures for these techniques are
also discussed.
1) Introduction
PatchGuard is a controversial feature of Windows x64 editions, starting with
Windows Server 2003 x64 / Windows XP x64, and continuing on with Windows Vista
x64 and Windows Server 2008 x64. The design goals behind PatchGuard are to
prevent the kind of rampant hooking and modification of various kernel code
and data structures that has been so common on x86 versions of Windows.
Microsoft has stated that the vast majority of kernel crashes are caused by
third party drivers, and the author's experiences with Windows firmly support
this supposition. Because accessing internal kernel data structures and
hooking kernel functions typically requires intricate synchronization with the
rest of the system in order to be performed in a completely safe fashion,
especially on multiprocessor machines, many third party drivers that perform
these sorts of dangerous tasks have historically made egregious mistakes that
have often lead to system stability or a compromise of system security. The
latter is especially common in cases where third party programs hook
functions, such as system calls, and subsequently fail to perform sufficient
parameter validation.
Microsoft's solution to this problem is to attempt to forcibly prevent third
party code from making unauthorized modifications to internal kernel data
structures and code through technical means in addition to discouraging
developers from performing such tasks. However, due to the nature of how the
Windows kernel (and its supporting drivers) are designed, it is not feasible
for kernel mode drivers to run at a lower effective privilege level than the
kernel itself. This poses a problem with respect to Microsoft's goal of
blocking unauthorized kernel patches due to the fact that there is no
hardware-enforced separation between the kernel itself and third-party
drivers. As such, said third party drivers have free reign to manipulate
kernel code and data as desired.
Although emerging technologies such as TPM and hardware-assisted
virtualization (hypervisors) may eventually provide a mechanism to deploy a
hardware-enforced boundary between certain key parts of the kernel and the
third party drivers that interact with it, such an approach is not generally
applicable to most computers sold today, given the current state of the
technology involved (with respect to both hardware and software capabilities).
Lacking a complete, hardware-enforced solution, Microsoft has turned to other
approaches to dissuade third party software from making unauthorized kernel
modifications. Specifically, the resulting kernel patch protection mechanism
("PatchGuard") is instead based on highly obfuscated code that, while running
at the same effective privilege level as both the kernel itself and third
party drivers, is designed to be resilient against detection and/or
modification by third party drivers. This code is responsible for
periodically checking the integrity of key kernel code and data structures and
will bring down the system if such modifications are detected. By virtue of
the fact that attempting to blithely patch the kernel as was once possible on
Windows x86 editions, attempting to perform the same operations will result in
a system crash on x64 versions of Windows. As such, third party drivers are
effectively preventing from making such modifications on a large-scale basis
with respect to code deployed on customer systems.
However, like all systems that are founded upon the principal of security
through obscurity, PatchGuard has inherent weaknesses. These weaknesses can
be exploited by third party drivers to either disable PatchGuard entirely or
circumvent its checks altogether while peacefully co-existing with PatchGuard.
Microsoft is fully aware of these deficiencies with respect to the fundamental
approach taken by PatchGuard and has resorted to periodically updating
PatchGuard in such a way as to block known public bypass techniques. The net
result is that Microsoft gives the impression of a ``moving target'' to any
ISV that would defy Microsoft's wishes with respect to circumventing
PatchGuard. This helps to show that any code designed to stop or disable
PatchGuard may become invalidated at some point in the future such as when
Microsoft releases a new update for PatchGuard. This has resulted in a small
arms race with code to circumvent PatchGuard being written by third parties,
and Microsoft responding by developing and deploying countermeasures in the
form of an updated version of PatchGuard that is not susceptible to these
bypass techniques. This cycle has continued through several iterations
already; in fact, PatchGuard is now being deployed to the general public in
its third iteration.
2) Protection Improvements
PatchGuard 3 implements several incremental improvements designed to protect
PatchGuard from third party code attempting to disable it as compared to
PatchGuard 2. The majority of the alterations to PatchGuard's self-defense
logic appear to be direct responses to previously published, publicly-known
bypass techniques, rather than general improvements meant to make PatchGuard 3
more resilient to analysis and attack. In this vein, while the alterations to
PatchGuard 3 (over PatchGuard 2) are effective at disabling most
previously-published bypass mechanisms that the author is aware of, it is not
exceedingly difficult to alter many previous attack mechanisms to be effective
against PatchGuard 3. Many of the protection systems that were implemented in
PatchGuard 2 are still present in PatchGuard 3 in some form or another, though
some of them have been altered to resist previously-published attacks.
This chapter will describe a number of specific improvements that have been
made.
2.1) Multiple Concurrent PatchGuard Check Contexts
In previous PatchGuard releases, there existed a single PatchGuard check
context that would periodically be used to verify the integrity of protected
regions. Some bypass techniques relied on the fact that there existed only
one PatchGuard context by virtue of disabling any invasive kernel patching
that would be required to ``catch PatchGuard in the act'' after locating
PatchGuard. PatchGuard 3 improves upon this by creating at least one
PatchGuard context if PatchGuard is enabled, with a probability of a second
context being initialized at system boot time This is randomized based on the
processor time stamp counter, as all other PatchGuard randomization is done.
Both PatchGuard check contexts, which include all of the data used by
PatchGuard to check system integrity (including the self-decrypting check
routine in non-paged pool memory), operate completely independently if two
contexts happen to be used for a particular system boot.
There are several advantages to randomly creating more than one check context.
First of all, because the second context is not always created, an element of
uncertainty is (theoretically) introduced into the testing and development
process for PatchGuard bypass techniques, as it is possible that at first
glance, an individual that is researching PatchGuard 3 might not notice that
there is a chance to create more than one context. This may result in lost
time during the debugging process, as some bypass techniques are affected by
the number of active contexts. For example, the original bypass technique
described by the author for PatchGuard 2 [1] effectively turned itself off
after the first positive indication that PatchGuard was caught (although in
this particular instance, the PatchGuard-catching hooks could have allowed to
remain in place afterwards).
A better example of bypass techniques that might be affected by this sort of
scheme are those that rely on searching system pool memory for a sign of
PatchGuard. For example, a theoretical bypass scheme that operates by
pro-actively locating the PatchGuard context in non-paged pool and disabling
it somehow (perhaps by rewriting the self-decrypting code stub to expand into
a no-operation function) might run afoul of this approach randomly during
testing if it were not designed to re-try a pool memory scan after a positive
hit on PatchGuard. It also eliminates the degree of confidence that such
memory scan approaches provide, as previously, if one had a way to locate the
PatchGuard context in non-paged pool memory, one would either know for certain
that PatchGuard had in fact been disabled by getting a single hit (which could
be taken as an indication that it would now be safe to perform actions
blockedg by PatchGuard). With multiple check contexts having a probability to
run, it is no longer possible for a bypass technique to have logic along the
lines of ``if a PatchGuard context has been located and disabled, then it is
safe to continue'', because there may exist a non-constant number of contexts
in the wild.
2.2) Filtering of Exception Codes Used to Trigger PatchGuard Execution
Like PatchGuard 2, and PatchGuard 1 before it, the third iteration of
PatchGuard is primarily executed through an unhandled exception in a DPC
routine which, through the use of a series of structured exception handlers,
eventually results in the self-decrypting PatchGuard stub being called in
non-paged pool memory (based off of the DPC arguments). This presented itself
as a liability, as evidenced by the previous article [2] published on
Uninformed on the subject of disabling PatchGuard (release 2). The problem
with using SEH to trigger execution is that there are a number of points in
the SEH dispatching mechanism that can easily be modified by an external
caller in order to gain execution after an exception is raised, but before a
registered exception handler itself might be called.
Previous techniques exploited this weakness by positioning themselves in after
the access violation exception raised when a PatchGuard-repurposed DPC routine
dereferenced a specially-crafted invalid pointer argument but before the SEH
logic that invokes the PatchGuard check context in response to the access
violation exception. Specifically, the operating system exported routine used
by the Microsoft C/C++ compiler for all compiler-generated SEH frames,
_C_specific_handler, was targeted by bypass attempts described in the
aforementioned articles. As the SEH frame responsible for running PatchGuard
appears to have been written in C for PatchGuard releases 1 and 2,
_C_specific_handler would be called before the user-supplied SEH logic which
would be responsible for executing the PatchGuard integrity check logic
contained within the current PatchGuard context. At this point, a bypass
technique need only abort the execution of the PatchGuard check routine and
cleanly extricate itself from the call stack to a known-good location in order
to disable PatchGuard.
However, in order for such a bypass mechanism to properly function, one would
need to ensure that the particular exception being examined by
_C_specific_handler is in fact PatchGuard and not a legitimate kernel mode
exception. Applying a PatchGuard-style bypass to the latter case would be
disastrous and almost certainly result in the system crashing or being
corrupted immediately after the fact. Given this, positively identifying an
exception from the exception dispatcher interception point is key to any
bypass technique built upon exception dispatcher redirection. While the
previous two PatchGuard releases made identifying PatchGuard a trivial task.
In both cases, a special form of invalid address, a ``non-canonical address'',
is dereferenced to trigger the access violation that ultimately results in
PatchGuard's check context being executed A non-canonical address is an
address that does not fall within the subset of a 64-bit address space
presented by modern x64 processors.
The advantage of using a non-canonical address is clear when one examines the
PatchGuard execution environment for a moment. In Windows kernel mode
programming, it is not generally possible to blindly dereference a bogus
kernel mode pointer. This often results in a sequence of events that bring
down the system, depending on where the dereferenced location is. A
non-canonical address is a special (undocumented) exception to this rule, as
the processor reports the exception via a general protection fault and not the
typical page fault mechanism. In this case, the operating system reports the
exception as an access violation related to an access of the highest kernel
address (0xFFFFFFFFFFFFFFFF). This distinct signature can be used to locate
and disable PatchGuard in a relatively safe fashion, as bogus kernel mode
addresses should never make it to SEH dispatching (in kernel mode) unless the
system is about to crash due to a fatal driver or kernel bug (PatchGuard being
a special case). Thus, it was previously possible to positively identify
PatchGuard by looking for an access violation that referenced
0xFFFFFFFFFFFFFFFF.
PatchGuard 3 improves the situation somewhat by performing some pre-filtering
of the exception data through an exception handler written in assembly (which
thus does not invoke _C_specific_handler) before the _C_specific_handler based logic
that actually invokes the PatchGuard check routine is executed. Specifically,
the pre-filtering exception handler, whose code is given below, alters the
exception code to take on a random value which overlaps with many valid kernel
mode exceptions. For example, some status codes that are applicable to the
file system space are used, such as STATUS_INSUFFICIENT_RESOURCES,
STATUS_DISK_FULL, STATUS_CANT_WAIT. Additionally, the exception address is
altered as well (in some cases even set to be pointing into the middle of an
instruction), and the dereferenced address (the second exception parameter for
access violations) is also set to a randomized value. After these alterations
are made, the assembly-language exception handler passes control on to the
_C_specific_handler based exception handler, which invokes PatchGuard. Annotated
disassembly for one of the assembly-language pre-filter exception handlers is
provided below:
;
; EXCEPTION_DISPOSITION
; KiCustomAccessHandler8 (
; /* rcx */ IN PEXCEPTION_RECORD ExceptionRecord,
; /* rdx */ IN ULONG64 EstablisherFrame,
; /* r8 */ IN OUT PCONTEXT ContextRecord,
; /* r9 */ IN OUT struct _DISPATCHER_CONTEXT* DispatcherContext
; );
KiCustomAccessHandler8 proc near
test [rcx+_EXCEPTION_RECORD.ExceptionFlags], 66h
loc_14009B4C7:
jnz short retpoint
rdtsc
; Randomize ExceptionInformation[ 1 ]
; ( This is the "referenced address" for
; an access violation exception.)
;
; ( Note that rax is not set to any
; specific defined value in this
; context. It depends upon the value
; that RtlpExecututeHandlerForException
; and by extension RtlDispatchException
; last set rax to. )
mov [rcx+(_EXCEPTION_RECORD.ExceptionInformation+8)], rax
xor [rcx+(_EXCEPTION_RECORD.ExceptionInformation+8)], rdx
shr eax, 5
and eax, 70h
sub [r8+98h], rax
and edx, 7Fh
or edx, 0C0000000h
; Set ExceptionCode to a random value. The code
; always has 0xC0000000 set, and the lowest byte
; is always masked with 7F. This often results
; in an exception code that appears like a
; legitimate exception code.)
mov [rcx+_EXCEPTION_RECORD.ExceptionCode], edx
lea rax, loc_14009B4C7+1
; Set ExceptionAddress to a bogus value. In this case,
; it is set to in the middle of an instruction. This
; may interfere with attempts to unwind successfully from
the exception.
mov [rcx+_EXCEPTION_RECORD.ExceptionAddress], rax
; Set Context->Rip to the same
; bogus exception address value.
mov [r8+0F8h], rax
and qword ptr [r8+88h], 0
retpoint:
mov eax, 1
retn
KiCustomAccessHandler8 endp
As a direct result of scrubbing the exception and context records by the
assembly-language exception routine, it is no longer possible to use the old
mechanism of looking for an access violation referencing 0xFFFFFFFFFFFFFFFF in
order to differentiate a PatchGuard exception from the many legitimate kernel
mode exceptions. In other words, PatchGuard attempts to hide in plain sight
amongst the normal background noise of kernel mode exceptions, the vast
majority of which exist inside filesystem-related code.
2.3) Executing PatchGuard Without SEH
One recurring theme that has continued to remain a staple for PatchGuard since
its inception is the use of structured exception handling to obfuscate the
calls to PatchGuard. The intention here is to use the many differences of SEH
between x64 and x86, and the lack of disassembler support for x64 SEH to make
it difficult to understand what is happening when calls to PatchGuard are
being made. Ironically, this use of x64 SEH as an obfuscation mechanism has
been a catalyst for much of the author's research [2] into Windows x64 SEH.
Today, it is the author's opinion that x64 exception handling is now publicly
documented to an extent that is comparable (or even exceeds) that available
for x86 SEH.
Although x64 SEH may have been useful as an obfuscation technology initially,
it had clearly worked its way up to a major liability after PatchGuard 2 had
been released. This is due to the fact that SEH-related aspects of PatchGuard
had been successfully used to defeat PatchGuard on multiple occasions. With
the advent of PatchGuard 3, the authors of PatchGuard siezed the opportunity
to extricate themselves in some respect from the liability that x64 SEH had
become.
PatchGuard 3 introduces a special mode of operation that allows it to function
without using SEH. This is a significant change (and improvement) with
respect to how PatchGuard has traditionally operated. It eliminates a major
class of single points of failure in that the exception dispatching path is
particularly vulnerable to external interference in terms of third party
drivers intercepting SEH dispatching before control is transferred to actual
exception handlers. The SEH-less mode of PatchGuard 3 operates by copying a
small section of code into non-paged pool memory (as part of a PatchGuard
context block). This code is then referenced by a timer object's
DeferredRoutine at the non-paged pool location in question. The code referred
to by the timer object is essentially a stripped down version of what happens
when any of the re-purposed DPC routines are invoked by PatchGuard: it sets up
a call to the first stage self-decrypting stub that ultimately calls the
system check routine.
By completely eliminating SEH as a launch vector for PatchGuard, many bypass
techniques that hinged on being able to catch PatchGuard in the SEH
dispatching code path are completely invalidated. In an example of defense in
depth in terms of software protection systems, the old, SEH-based system is
still retained (with the previously mentioned modifications), such that a
would-be attacker now has multiple isolated launch vectors that he or she must
deal with in order to block PatchGuard from executing. Annotated disassembly
of the direct call routine that is copied to non-paged pool and invoked
without SEH is presented below:
KiTimerDispatch proc near
pushf
sub rsp, 20h
mov eax, [rsp+28h+var_8]
xor r9d, r9d
xor r8d, r8d
mov [rsp+28h+arg_0], rax
; [rcx+40] -> PatchGuard Decryption Key
mov rax, [rcx+40h]
mov rcx, 0FFFFF80000000000h
xor rax, rdx
; Form a valid address for the PatchGuard context block by
; xoring the decryption key with the DeferredContext
; argument.
or rax, rcx
; Set the initial code for the stage 1 self-decrypting stub.
mov rcx, 8513148113148F0h
mov rdx, [rax]
mov dword ptr [rax], 113148F0h
xor rdx, rcx
mov rcx, rax
; Call the stage 1 self-decrypting stub.
call rax
add rsp, 20h
pop rcx
retn
KiTimerDispatch endp
2.4) Randomized Call Frames in Repurposed DPC Routine Exception Paths
One of the bypass vectors proposed for PatchGuard 2 was to intercept execution
at _C_specific_handler, detect PatchGuard, and resume execution at the return
point of the PatchGuard DPC (i.e. inside the timer or DPC dispatcher). This
is trivially possible due to the extensive unwind metadata present on Windows
x64 combined with the fact that a DPC that has been re-purposed by PatchGuard
does no useful work (other than invoking PatchGuard) and has no meaningful
effect on any out parameters or return value.
In order to counteract this weakness, PatchGuard 3 introduces a random number
of function calls when a re-purposed DPC is called, but before any exception
is triggered. The intent with this randomization of the call frame stack is
to invalidate the approach of always unwinding one level deep in order to
effect a return from the DPC routine in question. Because there are a random
number of call frames between the point at which an exception is raised and
the start of the PatchGuard DPC routine, and the fact that the PatchGuard DPC
routines are not exported, it is more difficult to safely return out of a
PatchGuard DPC routine from the anywhere in the SEH dispatching code path.
An example of the call frame randomization code is provided below (in this
case, ecx is initialized to small, random number that denotes the number of
calls to make). There are a number of routines in the form of
KiCustomRecurseRoutineN where N is [0..9], each identical.
KiCustomRecurseRoutine4 proc near
sub rsp, 28h
dec ecx
jz short retpoint
call KiCustomRecurseRoutine5
retpoint:
mov eax, [rdx]
add rsp, 28h
retn
KiCustomRecurseRoutine4 endp
Although unwinds can still be performed, an attacker would need to be able to
locate the actual return address of the PatchGuard DPC routine which might
involve differentiating between the bogus KiCustomRecurseRoutine calls and the
actual call into the DPC routine itself.
3) Additional Protection Mechanisms
PatchGuard 3 and PatchGuard 2 both share some additional protection mechanisms
that have not been previously described. This chapter includes a description
of these protection mechanisms.
3.1) Timer List Obfuscation
PatchGuard 2 and PatchGuard 3 employ an obfuscation scheme that is used to
obfuscate timer and DPC object pointers in the timer list. This obfuscation
scheme hinges around two special kernel variables, KiWaitAlways and
KiWaitNever that represent two random obfuscation keys that are calculated at
boot time. These obfuscation keys are used to encode various pointers (such
as links to DPC objects in a KTIMER object residing in the kernel timer list)
that are intended to be protected from outside interference. For example, the
following algorithm is used to decode the KDPC link in a KTIMER object when a
timer DPC is going to be executed at expiration:
ULONGLONG Deobfuscated;
PKDPC RealDpc;
Deobfuscated = Timer->Dpc ^ KiWaitNever;
Deobfuscated = _rotl64(Deobfuscated, (UCHAR)KiWaitNever);
Deobfuscated = Deobfuscated ^ Timer;
Deobfuscated = _byteswap_uint64(Deobfuscated);
Deobfuscated = Deobfuscated ^ KiWaitAlways;
RealDpc = (PKDPC)Deobfuscated;
By virtue of being non-exported kernel variables, the original intention of
such a scheme was to make it difficult for third party drivers to easily
interfere with the timer list or certain other protected pointers. However,
the algorithm itself is fairly easy to understand once one locates code that
references it (such as most any timer-related code in the kernel), which
simply leaves detecting the values of KiWaitAlways and KiWaitNever at runtime
as the only remaining protection for the timer list to DPC object obfuscation.
Ironically, the kernel debugger extension !kdexts.timer implements the
decoding algorithm (in kdexts!KiDecodePointer) so that a valid timer list can
be presented to the user if the timer display command is invoked. Because the
kernel debugger has access to PDB symbols for the kernel, it can trivially
locate KiWaitAlways and KiWaitNever.
3.2) Anti-Debugging Code at PatchGuard Initialization Time
As with PatchGuard 2, PatchGuard 3 includes a sizable amount of anti-debugging
code at runtime that is intended to frustrate attempts to step through the
PatchGuard initialization routines with a debugger. Most of this code is
based upon checking if a debugger is present while the PatchGuard
initialization routines are executing (which should not typically occur as the
PatchGuard initializtion routines are only called if a debugger is not
attached), and if a debugger is so detected, disable interrupts and entering a
spin loop so as to unrecoverably freeze the system.
Although this anti-debugging code may appear intimidating at first, disabling
them is only a matter of locating all references to KdDebuggerNotPresent
within the PatchGuard initialization routine and patching out the checks into
the debugger. For example, the author used the following set of commands in
the debugger at initialization time to disable the anti-debugging checks for
Windows Vista x64 SP0, kernel version 6.0.6000.16514:
bp nt!KeInitAmd64SpecificState + 12 "r @edx = 1 ; r @eax = 1 ; g"
bp nt!KiFilterFiberContext
eb nt!KiFilterFiberContext+0x20 eb
eb nt!KiFilterFiberContext+0x19a eb
eb fffff800`01c63d22 eb
eb fffff800`01c64686 eb
eb fffff800`01c652be eb
eb fffff800`01c65334 eb
eb fffff800`01c65880 eb
eb fffff800`01c65a65 eb
eb fffff800`01c67479 eb
eb fffff800`01c68798 eb
eb fffff800`01c6a940 eb
eb fffff800`01c6b7a9 90 90
eb fffff800`01c6b7dd eb
eb fffff800`01c6bad9 eb
eb fffff800`01c6d0e7 eb
eb fffff800`01c6d2f6 eb
eb fffff800`01c6d650 eb
eb fffff800`01c65c3a 90 90 90 90 90 90
eb fffff800`01c690b1 90 90 90 90 90 90
3.3) KeBugCheckEx Protection
One of the first bypass mechanisms proposed for PatchGuard 1 was to hook the
code responsible for bugchecking the system[4]. From there, an
attacker would simply resume normal system execution.
There are several defensive mechanisms in place to prevent this. In the the
current version of PatchGuard, the entire contents of the thread stack are
filled with zeros, making it difficult to resume execution of whichever thread
was responsible for calling into PatchGuard. Furthermore, PatchGuard appears
to make a copy of KeBugCheckEx at system initialization time, and copy this
version over the actual code residing within the kernel at runtime just before
bringing down the system in a bug check. This is clearly visible by making a
modification to KeBugCheckEx in the debugger just as one enters the PatchGuard
check context, and then setting a breakpoint on the internal function in the
PatchGuard context to call KeBugCheckEx after clearing the stack and all
registers. If one then examines KeBugCheckEx, any modifications that have
been made will have vanished.
Additionally, PatchGuard appears to disable DbgPrint (patching it out with a
"ret" opcode) before calling KeBugCheckEx. This may have been a (failed)
attempt to prevent easy access to execution within KeBugCheckEx without
actually patching KeBugCheckEx itself, which would circumvent the
aformentioned protection on modifications to the bugcheck code itself.
(KeBugCheckEx ordinarily utilizes DbgPrintEx to display a banner to the
debugger when a bug check occurs. However, because PatchGuard only patches
DbgPrint, there is no little to no effect in terms of what ends up happening
when the bug check finally does happen.)
This code can be seen in the PatchGuard check routine, just before a call to
the KeBugCheckEx wrapper is made. The pointer to DbgPrint is established
during PatchGuard initialization at boot time.
mov rax, [rbx+PATCHGUARD_CONTEXT.DbgPrint]
mov byte ptr [rax], 0C3h ; '+' ; ret
3.4) 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.
4.5) Code Patching Support
Given PatchGuard's penchant for blocking attempts to patch the kernel, one
would think that all kernel code is essentially expected to be fixed in stone
at boot time. However, this is not really the case. There are a number of
approved kernel patches that PatchGuard supports. For example, several
functions (such as SwapContext) can be patched in approved ways if hypervisor
support is enabled. In the case of SwapContext, for instance, a runtime patch
is made to redirect execution to EnlightenedSwapContext through a jump
instruction being written to the start of the routine. PatchGuard appears to
detect and permits patches to these functions through special exemptions (one
can observe the address of functions such as SwapContext being stored in the
PatchGuard context at initialization time, presumed to be for such a purpose).
The code responsible for checking the integrity of the SwapContext patch is
provided below. Because the check ensures that a branch can only occur to
EnlightenedSwapContext, it would be difficult to utilize this code to perform
an arbitrary patch at SwapContext.
cmp rdi, [rbx+PATCHGUARD_CONTEXT.SwapContext]
jnz short NotSwapContextExemption
cmp byte ptr [rdi], 0EBh ; 'd' ; backward jmps (short)
jnz short NotSwapContextExemption
cmp byte ptr [rdi+1], 0F9h ; '·'
jnz short NotSwapContextExemption
cmp byte ptr [rdi-5], 0E9h ; 'T' ; jmp (long)
jnz short NotSwapContextExemption
mov rcx, [rbx+PATCHGUARD_CONTEXT.EnlightenedSwapContext]
movsxd rax, dword ptr [rdi-4]
sub rcx, rdi
cmp rax, rcx
jz short BadSwapContextHook
There also exists a second set of patches that PatchGuard must allow for
compatibility with older processors. Very early releases of x64 processors by
Intel did not implement the prefetch instruction, and so the kernel has
support for detecting an illegal opcode fault on a prefetch instruction, and
reacting by patching out the prefetch opcode on-the-fly. However, this sort
of on-the-fly patching is not normally permitted by PatchGuard (for obvious
reasons), at least not without special support. During initialization,
PatchGuard generates some code that executes a prefetch operation, and then
checks whether the the count of patched prefix instructions was incremented
after executing the patch code. Assuming that the processor is an older model
without prefetch support, then a special exemption (the "prefetch whitelist")
is activated the exempts a list of RVAs from the image base from PatchGuard's
checks. This list of RVAs is stored in a binary resource appended to
ntoskrnl.exe (named "PREFETCHWLIST").
The code for detecting if the prefetch exemption should be enabled at boot
time is as follows (the result of the check is, for Windows Server 2008 Beta
3, stored at offset 2B1 into the PatchGuard context):
call KeGetPrcb
mov ecx, 2
cmp [rax+63Dh], cl ; Prcb->CpuVendor
mov [rsp+0EC8h+var_D48], rax
jnz short SkipEnablePrefetchPatchExemption
lea rdx, [rsi+214h] ; PrefetchRoutineCode
mov dword ptr [rdx], 0C3090D0Fh ; prefetch [rcx] ; ret
mov ebx, cs:KiOpPrefetchPatchCount
lea rcx, [rsp+0EC8h+arg_18]
call rdx
mov ecx, cs:KiOpPrefetchPatchCount
cmp ebx, ecx
jz short SkipEnablePrefetchPatchExemption
mov [rsi+2B1h], dil ; EnablePrefetchPatchExemption
SkipEnablePrefetchPatchExemption:
;
; Initialization continues ...
;
mov eax, 100000h
4) Bypass Mechanisms and Countermeasures
Like PatchGuard 2, it would be folly to state that PatchGuard 3 is
invulnerable to assault by third party driver code intent on performing
operations blocked by PatchGuard. There are many possible attacks for the new
defenses in PatchGuard 3 (as well as several possible countermeasures that
Microsoft could take in order to break the proposed bypass mechanisms in a
future PatchGuard iteration). This article will describe specific attacks
that is capable of defeating PatchGuard 3.
4.1) Hybrid Exception Interception and Memory Searching
As PatchGuard 3 utilizes completely randomized (self-decrypting) blocks of
code and data for its constituent PatchGuard contexts in the SEH execution
case, it is not generally possible to trivially locate and disable PatchGuard
contexts through a non-paged pool scan. Additionally, due to PatchGuard 3's
break on relying upon SEH to invoke PatchGuard in all cases, it is also not
generally possible to disable PatchGuard 3 reliably via interception of the
SEH dispatching code path.
While these defenses do complement one another, there still exists weaknesses
that can be exploited by a third party. Specifically, when PatchGuard is
running through a re-purposed DPC routine that is invoked via SEH, it is
vulnerable in that the SEH dispatching code path can be intercepted to locate
(and disable) PatchGuard just before it is executed. Furthermore, in the case
where PatchGuard runs without any SEH obfuscation, it is vulnerable to a
memory search, as there is (necessarily) some static code placed in non-paged
pool memory which makes the translation between the DPC function calling
convention and the PatchGuard stage 1 decryption routine's calling convention.
By combining a memory search approach with the previously described SEH
interception approach, it is possible to attack both launch vectors of
PatchGuard simultaneously, with the effect of disabling it no matter which
vector(s) are used in a particular boot.
However, there are still some sticking points that need to be resolved in the
SEH interception case. As previously mentioned, the SEH-obfuscation-based
launch vector was significantly improved over PatchGuard 2, with obfuscation
of the exception information and randomization of the call stack from the
point of view of the exception dispatcher logic itself. These obstacles must
be overcome in order to successfully mount an attack using this approach
against PatchGuard 3.
The first problem relating to the obfuscation and randomization of the
exception information turns out to not be the roadblock that one might think
at first glance. There are some weaknesses of the obfuscation logic that
allow the true colors of the exception to show through if one is clever about
examining the information available at the point of _C_specific_handler.
Furthermore, it is also possible to hook at a lower level than
_C_specific_handler, such as KiGeneralProtectionFault (easily located by
examining the IDT), which would get one in before the assembly-language
exception handler logic has a chance to fudge the exception information.
Although the KiGeneralProtectionFault vector is easier to implement in that it
completely bypasses one of the new defensive mechanisms with respect to the
SEH-related PatchGuard execution code path, it is again still possible to
attack PatchGuard using _C_specific_handler by relying upon information
leakage when _C_specific_handler is called. Specifically, all exceptions
altered by PatchGuard originate within the confines of the kernel itself, all
of the exceptions have two parameters (most of the "legitimate" versions of
exceptions like STATUS_INSUFFICIENT_RESOURCES always have zero parameters,
because they originate from within RtlRaiseStatus which never stores any
exception parameters in the exception record), and somewhere in the call stack
the kernel routine responsible for dispatching DPCs or timer DPCs is going to
be present.
By combining these facts, it is possible to make a highly accurate
determination as to whether an exception is caused by PatchGuard. The latter
piece of information (checking whether the routine responsible for calling the
DPC or timer DPC is in the call stack) also proves valuable when one must
later counteract the second defense added to the SEH code path, that is, the
randomization of the call stack.
In order to determine whether the DPC or timer DPC dispatcher is in a given
call stack, it is first necessary to locate it in the kernel image. There are
some complications here. First of all, the timer DPC dispatcher routine has
three call instructions that can call a timer DPC, not all of which are
readily triggerable. Additionally, neither the timer DPC dispatcher or the
DPC dispatcher are exported.
However, while it is not possible to simply ask for the addresses of those two
routines, it is possible to find them programmatically by requesting that a
DPC and a timer DPC be executed through the documented APIs for DPCs and timer
DPCs. From within the DPC or timer DPC routine, it is then possible to locate
the return address via the use of the ReturnAddress() compiler intrinsic.
This works because the return address will be guaranteed to reside within the
DPC or timer DPC dispatcher. Alternatively, an assembly language routine
could be written that simply examines the current pointer at [rsp] at the time
of the call.
This still leaves a problem in the timer DPC dispatcher case, as there are
three call instructions, and it is not easy to observe calls from all three
call sites within the timer DPC dispatcher on-demand, since it is necessary to
programmatically find the return points at runtime. However, once again, the
very same metadata that is critical to x64 SEH support dooms PatchGuard with
respect to this approach, as it is possible to go from an arbitrary
instruction in the middle of any function to the start of that function, by
following chained unwind metadata until an unwind metadata block is reached
that has no parent [4]. This top-level unwind metadata block has a reference
to the first instruction in the function. Now that it is possible to locate
the start of a function from any arbitrary valid instruction location within
that function, it becomes trivial to determine if two addresses reside in the
same function; to do this, one must only follow the unwind metadata chain for
both addresses, and then check to see whether both top-level unwind metadata
blocks refer to the same function. With this technique, combined with the
ability to locate at least one call site within the timer DPC dispatcher, it
again becomes possible to identify the timer DPC dispatcher, as no matter
which call site is used, it will be guaranteed that the call site resides
within the timer DPC dispatcher routine KiTimerExpiration. By comparing
top-level unwind metadata blocks, it becomes possible to authoritatively
discern whether any arbitrary instruction resides within the timer DPC
dispatcher or not.
It is also possible to bypass the alterations to the exception (and
instruction pointer) addresses that KiCustomAccessHandler (the
assembly-language "first chance" exception handler routines for the repurposed
DPC routines) makes by performing a stack trace from the _C_specific_handler
itself instead of relying on the context record or exception handler
information. This is because the call stack is conveyed as if the faulting
instruction in the repurposed DPC call stack was the site of a call to
KiGeneralProtectionFault. As a result, it is possible to substitute the
current context for the context presented to _C_specific_handler for unwind
purposes. This also provides a layer of defense against Microsoft altering
other registers in the exception handler context in future PatchGuard
revisions, which could cause manual unwinds to return incorrect register
values, resulting in system crashes after an unwind intended to effect a hard
return out of the re-purposed DPC routine.
Furthermore, by clever usage of this mechanism for determining whether an
address resides within a particular function, it is also now possible to
determine the real return address for any given re-purposed DPC routine.
Specifically, by checking whether each address in the call stack as of
_C_specific_handler is within either the DPC dispatcher or the
timer DPC dispatcher, one can determine whether a given call frame corresponds
to the call site that called the re-purposed DPC routine or not, irrespective
of any random amount of bogus function calls that may be layered on top of the
re-purposed DPC. This in turn defeats the remaining improvement to the SEH
PatchGuard code path, as it once again becomes possible to cleanly unwind from
any arbitrary point in the PatchGuard exception callstack.
Through the combination of the ability to either circumvent entirely or "see
through" the deception that KiCustomAccessHandler creates over the exception
information passed to _C_specific_handler, and the ability to
recover the correct return address of a repurposed DPC routine, it now becomes
possible to disable the SEH control flow path of PatchGuard 3. This leaves
the remaining problem of locating the non-SEH control flow path of PatchGuard
in non-paged pool memory as the last piece of the puzzle with respect to this
method of disabling PatchGuard. However, locating the trampoline routine that
adapts a DPC routine call to a PatchGuard stage 1 decryption stub call is
trivial, as the adapter trampoline is static and contains a very recognizable
signature in terms of the constants written to the beginning of the decryption
stub. In order to disable the trampoline routine, it is enough to simply
patch it with a "ret" instruction (effectively the same thing as the SEH
bypass technique, but as implemented in code instead of a virtual unwind).
The source code to a working implementation of the hybrid exception
interception and memory searching bypass technique for PatchGuard 3 is
included with the article.
Although this approach is successful in disabling the current iteration of
PatchGuard 3, it is not without its weaknesses. Microsoft could, for
instance, disable this technique via altering the SEH-less PatchGuard
DPC-to-decryption-stub adapter to not be static (i.e. randomization of the
code placed into non-paged pool at runtime). There are also a number of
assumptions of the SEH-based approach that could be invalidated by Microsoft
in a future PatchGuard release. However, in keeping with the fact that it is
possible to gain control flow at a lower level than the exception dispatcher
path itself (i.e. patching KiGeneralProtectionFault), the author feels that it
would be better to focus on removing relevant information before any exception
handlers (assembler or C-language) are called instead of after the defining
moment (in other words, the exception) occurs, as it is the exception that
presents the first easily-accessible interception point to an outside
attacker.
4.2) Timer DPC Dispatcher and DPC Dispatching
Although PatchGuard 3 eliminates SEH as a single point of failure with respect
to executing the system integrity checks, the timer and DPC dispatchers
continue to remain attractive targets. One simple bypass mechanism is to
locate the call sites in both routines (such as by recording the addresses of
both dispatcher routines as described in bypass technique 1, and then
performing disassembly to locate and patch all call sites. At each call site,
it is possible to detect that PatchGuard is being executed by looking for
either a non-canonical DeferredContext parameter value or a DeferredRoutine
that resides within the non-paged pool. (In PatchGuard 3, implementing the
former check alone proves sufficient, as for the ease of the implementation of
PatchGuard 3, both the repurposed DPC routines and the non-SEH-based control
path use compatible calling conventions, which stipulate a non-canonical
obfuscated pointer value as the DeferredContext parameter.)
The main disadvantage of this approach involves inherent difficulties in
performing arbitrary code patching in x64 (specifically, the large size of any
code patch and the large number of now relatively common
instruction-pointer-relative instructions). However, given that this is a
difficulty that impacts any code patching on x64, the author feels that it
should not be considered a significant problem for a determined attacker. In
fact, Microsoft Research's very own Detours implements a code patching system
for x64[5], illustrating that code patching on x64 in general
is not a task that should be considered insurmountable by any means.
Because the timer and DPC dispatchers remain relatively unprotected targets
that have not been involved in public bypass source code that has been
released to date, the author would recommend bolstering the defenses of the
timer and DPC dispatcher for the next PatchGuard release, as the two routines
continue to represent an attractive single point of failure. Adding a third
PatchGuard execution mechanism that does not involve traditional DPCs at all
would be an example of one approach to eliminate the DPC dispatcher related
logic as a single point of failure. It may also be possible to increase the
difficulty of locating all the call sites within the DPC dispatching related
code through a combination of differing static call stack differences for each
of the three call sites of the timer DPC dispatcher (i.e. adding dummy
function calls) combined with call stack randomization on top of static call
stack differences between each of the three timer DPC dispatcher calll sites.
Randomized call stacks alone would not suffice as by examining the call stacks
of many iterations of timer DPC requests, it would become easy to eliminate
the randomized entries (which would not be common to all recorded call stacks)
with a relatively high degree of accuracy given a large sample size. A
disadvantage to taking such an approach is that it would essentially result in
adding deliberately-difficult-to-maintain "spaghetti code" into yet another
critical area of the operating system (timer DPC dispatcher logic). The
author suspects that the maintainer of the timer DPC dispatcher code would
likely not appreciate having to deal with such things.
4.3) Canceling the PatchGuard Timer(s)
As PatchGuard continues to rely upon timer DPCs for the execution of its check
routines, the kernel timer DPC list itself continues to remain a relatively
attractive target for attack. The timer DPC list is common to all control
paths leading to PatchGuard, as timers are always used for the delayed
execution component that periodically calls the check routine.
There are presently two obstacles in the way of the timer DPC list. The first
of which is that altering it relies upon locating non-exported kernel
variables. Although it may be possible to do so via fingerprinting, this does
make the approach slightly less desirable than it might initially appear.
However, fingerprinting can work if done carefully, and there are many short
functions that reference the timer list in a fairly predictable fashion (e.g.
KeCancelTimer). One other possible way to find the DPC list would be to
create and set a timer (thus inserting it into the timer list), and then scan
every 8-byte-aligned value in a non-paged uninitialized data section in
ntoskrnl, treating each valid address as a linked list and searching the first
several entries for the timer that was just linked into the list. While a
rather ugly and bruteforce-based approach (and not entirely safe either as one
would need to be relying heavily on MmIsAddressValid), scanning the ntoskrnl
data sections is one alternative to fingerprinting in terms of finding the
timer list.
The secondary problem with this approach is that starting with PatchGuard 2,
the timer list itself is obfuscated such that the link between a KTIMER object
and its corresponding KDPC is obfuscated. This obfuscation mechanism, as
previously described{backref to 1}, hinges upon two additional non-exported
kernel variables (KiWaitAlways, KiWaitNever) that act as obfuscation keys.
Locating these variables would be likely entail code analysis or
fingerprinting of (possibly exported) routines that need to insert a timer
into the timer list, such as KeSetTimerEx.
Another alternative approach that dispenses with fingerprinting and/or
bruteforce-based approaches altogether, at the expense of requriring added
complexity (a user mode component), would be to postpone the activation of any
driver code that would run afoul of PatchGuard until after Win32 in user mode
has been started. A user mode service could then be created that would
download the symbols for the kernel binary in use, retrieve the addresses of
KiTimerTableListHead (the timer list), KiWaitNever and KiWaitAlways, and pass
these addresses on to the driver via any standard user mode to kernel mode
communication mechanism (such as DeviceIoControl). Because the kernel
debugger relies on the ability to retrieve these variables by name via the PDB
symbols for the !kdexts.timer extension, Microsoft would not be able to block
this approach by removing or renaming the obfuscation key variables without
imparing the functionality of existing debugger binaries.
Once one has located the KiTimerTableListHead, KiWaitAlways, and KiWaitNever,
it is a fairly simple (if perhaps unsafe without synchronization, though one
could always take the "sledgehammer" approach and stop all but one CPU and
raise IRQL to HIGH_LEVEL) to traverse the timer list, deobfuscate
the DPC link on each corresponding timer object, and from there check each
timer to see whether it bears the characteristics of being a PatchGuard timer
(which may include attributes like a timer interval several minutes into the
future, a non-canonical DeferredContext value, and possibly a DPC routine
pointer into non-paged pool). After one has located the timer in question, it
can be easily disabled (either removing it from the list entirely, such as via
KeCancelTimer, or by rewriting the DPC routine to point to an empty function
that simply returns without performing any operation.
Because Microsoft has functionality in the debugger that depends on the
ability to use these variables to access the timer list, they have
unfortunately backed themselves into something of a corner with respect to
current operating system versions, as it is generally Microsoft's policy that
existing debugger binaries continue to function properly after hotfixes or
service pack to a particular already-released operating system version. The
best ways to counteract this approach would be to make it more difficult to
pick out the PatchGuard DPC in-memory with respect to all of the other timer
DPC objects that are in the list at any given time for a typical system, and
to create additional launch vectors for PatchGuard that do not depend so
heavily on the timer list. There exist a number of other ways to execute code
without drawing the attention of someone that does not know what they are
looking, many of which are less obvious than a timer.
4.4) Page-Table Swap
Like all memory accesses in the Windows kernel, PatchGuard's system integrity
check routine operates in protected mode with paging enabled. It may
theoretically be possible to take advantage of this fact to hide kernel
patches from PatchGuard.
The proposed bypass technique would involve patching the first instruction in
the timer and DPC dispatchers to branch to third party code. When a DPCs and
timer DPCs are about to be considered for execution, as signaled by a call to
one of the two dispatcher routines, a shadow copy of the page tables is
created. This shadow copy is configured to be identical to the normal page
table for the current process, except that the page table entries for any
kernel code pages that have been patched are altered to refer to physical
pages that are representative of the original state. The return address of
the DPC or timer DPC dispatcher on the stack is swapped with a pointer into
driver-supplied code, and cr3 is reconfigured to point to the shadow page
table. Then, execution is transferred back to the timer or DPC dispatcher
entrypoint (which no longer shows any signs of patching due to the page table
swap), and DPCs are dispatched. When the dispatcher is finished with its
work, which would include invoking PatchGuard if PatchGuard is to be executed
in any batched timer DPCs, then control is returned to driver-supplied code,
which then mirrors any page table modifications since the shadow copy was made
back to the actual page table for the process, and cr3 is returned to its
original value. Control is then transferred to the normal return point of the
dispatcher.
This approach does not involve disabling PatchGuard at all. Instead, it
describes a potential way to "peacefully coexist" with it, so long as only
kernel code patches are being done. (Data pages, which could be expected to
be modified by a DPC, are considered by the author to be much less practical
to protect from PatchGuard in this fashion.) Because the DPC and timer DPC
dispatcher logic executes at IRQL DISPATCH_LEVEL, thread context
switching is disabled for the current thread, making the cr3 swap approach
relatively feasible.
Because this approach does not involve attacking PatchGuard directly, it
automatically circumvents all of the myriad defensive mechanisms built into
PatchGuard in current releases, making it a fairly attractive potential avenue
of attack. However, there are some downsides. Among other things, the
synchronization required to pull a page tabpe swap off in a multiprocessor
environment are likely to be complex and difficult to safely duplicate if one
allows DPC routines to perform operations that alter PTEs. Additionally,
there would be a performance impact incurred by this approach as it would need
to run continuously in a relatively high-impact path (DPC dispatching)
throughout system lifetime. The performance implications of invalidating TLBs
on every DPC batch may be problematic in some circumstances (swapping cr3
automatically clears out TLBs).
Another disadvantage of this approach is that by virtue of the fact that all
DPCs (and potentially all device hardware interrupts) may run with the shadow
copy of the page table, most hardware-related events will not be subject to
kernel code patches hidden by this mechanism. This may or may not be a
problem depending on what the goal of the desired kernel patching is.
Microsoft could counteract this approach by making a copy of all PTEs that
describe the kernel at PatchGuard initialization time, and then validate all
kernel code PTEs from within the PatchGuard check routine. Additionally, if
Microsoft could make the assumption that PatchGuard always executes in the
system process, another approach could be to require that cr3 take on a known
value.
4.5) DPC Exception Handler Patching
One of the changes introduced in PatchGuard 3 over PatchGuard 2 was a slight
change to the protocol used to invoke the first stage of the decryption
process. Specifically, all callers of an encrypted PatchGuard context now
include a static 8-byte string (of instruction opcodes) that is xor'd with a
value at the start of the PatchGuard context to form the initial decryption
key.
The reasons for making this change over the original behavior are unclear to
the author, but it unfortunately represents an easy target for disabling
PatchGuard, as the string itself (0x8513148113148F0) is fairly unique and
unlikely to appear outside of PatchGuard in terms of kernel code.
Furthermore, all PatchGuard callers, including all ten of the repurposed DPC
routine exception handlers and the non-paged pool memory DPC adapter (if used)
reference the string with no obfuscation to speak of. This presents an
extremely easy, fingerprint-based approach to disabling PatchGuard. By
scanning non-paged pool space for this string, as well as kernel code regions,
it is trivially easy to locate an instruction in the middle of the every
single code path responsible for invoking PatchGuard's check context.
After the instructions referencing the 8-byte string have been located, it is
trivial to patch them to execute an unwind out of the exception handler logic
(or in the case of the non-paged pool memory code, simply return directly).
Such an attack prevents PatchGuard from ever starting, and furthermore has the
advantage of a minimum of additional supporting logic required (when compared
to many of the other bypass techniques outlined in this article).
It would be trivial for Microsoft to disable this technique. The
recommendation of the author would be to get rid of the static 8-byte string
referenced in every PatchGuard caller. Ironically, PatchGuard 2 necessarily
has a similar 4-byte string (which is also still used in PatchGuard 3),
representing the initial instruction of the first stage decryption stub.
Unlike with PatchGuard 3, however, PatchGuard 2 takes care to obfuscate the
process of writing the opcode string out to the PatchGuard context, so that
one cannot simply use a single blanket fingerprint to cover all cases. The
change made in PatchGuard 3 completely blows this work out of the water, so to
speak, and it has the added advantage of being twice as large as a value to
fingerprint as well.
4.6) System Call MSR Swap
A variation on the technique described in {backref:4}, it should theoretically
be possible to swap the system call MSRs (or in fact several other processor
control registers that are protected by PatchGuard) for the duration of DPC or
timer DPC dispatching online, with the "tainted" values being restored after
the dispatcher returns. The system call MSRs are responsible for designating
the address of the system call dispatcher, and are thus an attractive target
for third parties that would like to perform system call hooking.
The same basic concepts would be applied to this technique as previously
described in the cr3 swap technique. If system calls are the only desired
targets to hook, then the cr3 swap can be eliminated as unnecessary for single
processor systems (as it would be safe to make and restore changes to the
actual underlying physical pages before and after a DPC dispatcher call, using
the return address on the stack as a way to return to the altered location
without leaving opcodes patched in the kernel across dispatcher invocations).
For multi-processor systems, some mechanism would need to be developed to
allow the MSR swap to be made across DPC dispatchers while preventing code
patches from becoming visible to a second processor. This is necessary
because there could be more than one PatchGuard context executing
simultaneously with the PatchGuard 3 addition of a probability to initialize a
second check context at system boot time.
In order to block such a technique, Microsoft would likely be best served by
making it difficult to locate all the regions necessary to patch in order to
maintain the deception of an unpatched system across PatchGuard checks. The
principal way to do this would be to create other, alternative launch vectors
for PatchGuard that are unrelated to DPCs and, preferably, do not involve
exported APIs that are easy to intercept from a third party perspective.
5) Conclusion
Although PatchGuard 3 does bring some pointed counter-attacks to many
previously disclosed bypass techniques, version 3, like its predecessors, is
hardly immune to being either disabled completely or simply co-existed with.
It is likely that future revisions to PatchGuard will continue to be
vulnerable to a variety of bypass techniques, though it is certain within
Microsoft's reach to counter many of the publicly disclosed bypass vectors.
It is anticipated by the author that until PatchGuard can be implemented with
hardware support, such as via a combination of trusted boot (TPM) and a
permanent hypervisor, future revisions will continue to be vulnerable to
attack from determined individuals.
On the other hand, Microsoft's efforts with PatchGuard appear to have paid off
so far in terms of preventing a mass-uptake of PatchGuard-violating drivers on
Windows x64. In other words, a case could be made that Microsoft doesn't need
to be perfect with PatchGuard, only "good enough" to give vendors cold feet
about trying to ship products that bypass it. Only time will tell if this
continues to remain the case into the future, however.
References
[1] Skywing. Subverting PatchGuard version 2.
http://www.uninformed.org/?v=6&a=1&t=sumry; accessed September 16, 2007
[2] Skywing. Programming against the x64 exception handling support, part 7: Putting it all together, or building a stack walking routine.
http://www.nynaeve.net/?p=113; accessed September 16, 2007
[3] skape. Improved Automated Analysis of Windows x64 Binaries.
http://uninformed.org/index.cgi?v=4&a=1&t=sumry; accessed September 16, 2007
[4] skape, Skywing. Bypassing PatchGuard on Windows x64.
http://uninformed.org/index.cgi?v=3&a=3&t=sumry; accessed September 16, 2007
[5] Microsoft. Detours.
http://research.microsoft.com/sn/detours/; accessed September 16, 2007