Previously, I had touched some more on the when and why’s as to where hardware breakpoints can be useful.
If you have been following along so far, you should already know the ups and downs of each flavor of breakpoint, and have at least a fair idea as to when you should prefer one to another. There is one other aspect of breakpoint management that I have yet to cover, though, and it is perhaps the most useful feature of breakpoints in WinDbg: conditional breakpoints.
Conditional breakpoints allow you to, as you might imagine, set conditions for breakpoints. That is, the debugger will only actually stop for you to investigate something when both the breakpoint is triggered, and its associated condition is met. These kinds of breakpoints are very useful if you need to stop on a certain function, but only if a certain argument has a certain value (for instance).
However, in WinDbg (and the other DTW/DbgEng debuggers), the support for conditional breakpoints allows you to do much more than that. Specifically, you are permitted to define arbitrary commands that are automatically executed any time a breakpoint is hit. Aside from allowing you to create conditional breakpoints, this also allows you to perform a number of other highly useful tasks in a quick and automated fashion with the debugger. For example, you might want to alter arguments passed to a particular function, or you might want to log arguments (or a stack trace) to a particular function for future analysis.
For example, let’s say that you wanted to see anyone who called CreateFileW, which filename they provided, and what the call stack for each caller might be, what the return value is, and then continue execution. Now, you could do this manually with a “plain” breakpoint and repeating a certain set of commands every time the breakpoint hits, but it would be far superior to automate the whole process.
With DbgEng’s conditional breakpoint support, this is easy. All you need to do in order to have a set of commands that are executed after a breakpoint is to follow the breakpoint statement with a set of commands enclosed in double quotes. (If the commands themselves require the use of double quotes, then you’ll have to escape those, using \”).
From looking at CreateFile on MSDN, we can see that the first argument is a unicode string describing the name of the file that we are to create.
Armed with this information, we can construct a breakpoint that will perform the logging that we are looking for.
Here’s what you might come up with:
0:001> bp kernel32!CreateFileW "du poi(@esp+4);kv;gu;? @eax; g"
Let’s break down this breakpoint a little bit. As usual, the semicolon character (;) is used to separate multiple debugger commands appearing on the same line. The first command is fairly straightforward; it makes use of the du command to display the first argument. The du command simply displays a zero-terminated Unicode string at a given address.
Next, we take a full backtrace (k). After that, we allow execution to continue until we reach the return address of CreateFileW with the gu command (“Go Up one call level”). Finally, we display the eax register (which happens to be the return value register for x86), and continue execution with g.
In action, you might see something like so. In this instance, I am attached to cmd.exe and have executed the command “type c:\config.sys”…
0013fa4c "c:\\CONFIG.SYS" ChildEBP RetAddr 0013e6e4 4ad02f2a kernel32!CreateFileW 0013e730 4ad02e91 cmd!Copen_Work+0x157 0013e744 4ad0dbff cmd!Copen+0x12 0013f7a8 4ad0db62 cmd!TyWork+0x48 0013fc58 4ad0daac cmd!LoopThroughArgs+0x1dd 0013fc6c 4ad05aa2 cmd!eType+0x17 0013fe9c 4ad013eb cmd!FindFixAndRun+0x1f5 0013fee0 4ad0bbba cmd!Dispatch+0x137 0013ff44 4ad05164 cmd!main+0x216 0013ffc0 7c816fd7 cmd!mainCRTStartup+0x125 0013fff0 00000000 kernel32!BaseProcessStart+0x23 Evaluate expression: 132 = 00000084
Essentially, we have turned the debugger into something to inspect function calls and provide us with detailed information about what is happening, in a completely automated fashion.
You can also use this technique for more active intervention in the target as well – for instance, skipping over a function call entirely, modifying what a function does when it is called, or any number of things. Previous articles have, for instance, used conditional breakpoints to alter function behavior (such as making all new virtual memory allocations come from the high end of the address space instead of the low end).
Conditional breakpoints are an invaluable tool in your debugging (and reverse engineering) arsenal; you should absolutely consider them any time that you need any sort of automation to record or alter the behavior of the target, in situations where it is not practical to manually perform the work on every single breakpoint hit.
Next up in this series: Other flow control mechanisms with the debugger, such as stepping and tracing.
[…] Instruction conditionnelle : .if ( $spat( “${MyAlias}”, “*str*” ) != 0 ) { g } Debugger flow control: Using conditional breakpoints (part 3) Plusieurs instructions : bp kernel32!…mais cela se corrige voir la méthode How to use a symbol server with the Visual Studio .NET […]