OSRLogo
OSRLogoOSRLogoOSRLogo x Subscribe to The NT Insider
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Mon, 22 Oct 2018     118021 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
Downloads
ListServer / Forum
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

Turning a Breakpoint into a Busypoint

During dynamic analysis, I often want to prevent a code path from continuing to execute beyond a certain point. For example, maybe I suspect a race between the read and write paths in a driver. In this case, I may want to allow the write path to proceed up to a point before executing the read path.

This type of analysis often requires modifying the code to check for a special condition that causes the code to spin or wait until some other condition is met. However, with a little bit of understanding of x86/x64 assembly language and some WinDbg commands we can dynamically stop any code path dead in its tracks and then resume it when we see fit.

The basic idea is that we will set a breakpoint in the location that we are interested in. Once we hit that breakpoint, we overwrite the current instruction with an infinite loop. Any thread that hits this instruction will loop forever, of course chewing up CPU time but never actually doing anything. Other paths through our code are then free to execute as we see fit. When we are done, we can then go back and restore the original instruction and allow the threads to execute.

For our busy loop, we use the standard x86/x64 JMP instruction and specify the start of the JMP instruction as the target of the jump. In other words, we will jump to the jump instruction, thus creating our infinite loop. Due to the fact that we are jumping such a short distance, an 8-bit relative jump is sufficient for our needs. Referring to the Intel reference manual, we can see what the opcode for this instruction would be:

Opcode

Mnemonic

Description

EB cb

JMP rel8

Jump short, RIP = RIP + 8-bit displacement sign extended to 64-bits

 

From this we know that we need a two byte instruction, where the first byte is 0xEB and the second byte is the sign extended displacement. For our instruction, we want the jump target to be the jump instruction, thus we want negative two (0xFE) as our displacement. This causes the processor to jump to the next instruction minus two, which is the start of our jump!

The first thing we want to do is choose the location of our busypoint. For this example, we have chosen the write processing routine of NTFS:

1: kd> bp ntfs!ntfscommonwrite

1: kd> g

Breakpoint 0 hit

Ntfs!NtfsCommonWrite:

fffff880`016c2c00 4c8bdc mov r11,rsp

0: kd> u @$ip

Ntfs!NtfsCommonWrite:

fffff880`016c2c00 4c8bdc mov r11,rsp

fffff880`016c2c03 49895b18 mov qword ptr [r11+18h],rbx

fffff880`016c2c07 49897320 mov qword ptr [r11+20h],rsi

fffff880`016c2c0b 57 push rdi

fffff880`016c2c0c 4154 push r12

fffff880`016c2c0e 4155 push r13

fffff880`016c2c10 4156 push r14

fffff880`016c2c12 4157 push r15

Note that in the output we use the $ip pseudo register. This is a WinDbg pseudo register that will always give the current instruction pointer regardless of target architecture. This alleviates us from having to use either @eip or @rip depending on the target machine, instead we can always simply refer to @$ip.

The next step is to save the two bytes that we are going to overwrite so that we can restore them later. A user defined pseudo register is a convenient place to save this data, so we will use $t0:

0: kd> r @$t0 = low(poi(@$ip))

0: kd> r @$t0

$t0=0000000000008b4c

 

To break the expression down:

1.       poi($ip) - Dereference the current instruction pointer, returning a pointer sized result

2.       low(value) - Return the low word of the supplied value

Therefore we are simply dereferencing the current instruction pointer and masking off everything but the low word of the result.

Given this, we can now overwrite our instruction pointer with our magic sequence of 0xFEEB (x86/x64 are little endian!):

0: kd> ew @$ip 0xFEEB

0: kd> u @$ip

Ntfs!NtfsCommonWrite:

fffff880`016c2c00 ebfe jmp Ntfs!NtfsCommonWrite

 

You can now be sure that no subsequent threads will execute beyond this point. They will, however, continue to chew up CPU time. This either will or will not be an issue in your analysis, depending on the priority of the threads involved. Of course, dynamically changing the priority of threads or putting threads to sleep are topics for another day :)

 

To release the threads from this state, we can simply use our stored value in the pseudo register to put the original bytes back:

 

0: kd> ew @$ip @$t0

0: kd> u @$ip

Ntfs!NtfsCommonWrite:

fffff880`016c2c00 4c8bdc mov r11,rsp

fffff880`016c2c03 49895b18 mov qword ptr [r11+18h],rbx

fffff880`016c2c07 49897320 mov qword ptr [r11+20h],rsi

fffff880`016c2c0b 57 push rdi

fffff880`016c2c0c 4154 push r12

fffff880`016c2c0e 4155 push r13

fffff880`016c2c10 4156 push r14

fffff880`016c2c12 4157 push r15

 

OSR is Hiring! Click here to find out more.

 

Related Articles
Enabling Debugging on the Local Machine for Windows XP®
More on Kernel Debugging - KMODE_EXCEPTION_NOT_HANDLED
Making WinDbg Your Friend - Creating Debugger Extensions
Life Support for WinDbg - New Windows NT Support Tools
Special Win2K PnP Tracing and Checks
Choose Your Weapon: Kernel Mode Debuggers - a Choice at Last
Wild Speculation -- Debugging Another Crash Dump
Resolving Symbol Problems in WinDBG
Getting DbgPrint Output To Appear In Vista and Later
I Hooked Up The Debugger Using 1394, and NOW...

bottom nav links