Monday, May 14, 2012

Hardware vs Software Breakpoints


When debugging a program or trying to understand its actual inner working, breakpoints are a very useful tool afforded by the CPU and debugger. Breakpoints return control to an attached debugger when a specified condition is met in the process that is being debugged. These conditions include a memory read, write, or execution at a certain address.


On Intel's x86 architecture, there are 2 types of breakpoints available:
  1. Hardware breakpoint- uses CPU's DR0-DR3 debug registers to store memory addresses. Debug exceptions are generated by the CPU when a memory read, write or execute occurs at or near an address in any of the 4 Debug Registers, useful when we want to see where a memory address was read from or written to
  2. Software breakpoint- a patched instruction in executable code to generate a breakpoint exception. This instruction is "INT 3" or 0xCC in machine code. useful if we want to stop execution at a certain instruction
Hardware Breakpoints
In a Windbg session debugging notepad.exe, one can set a hardware breakpoint as follows:

0:001> ba e1 ntdll!ZwCreateFile

Lets verify that the breakpoint was set:

0:001> bl
 0 e 00000000`773ffc00 e 1 0001 (0001)  0:**** ntdll!ZwCreateFile

Now, let's verify that the address of our hardware breakpoint actually shows up in the CPU's debug registers:

As expected, DR0 contains the virtual address of ntdll!ZwCreateFile. When we try to save a file in notepad, we hit our breakpoint:

Breakpoint 0 hit
ntdll!ZwCreateFile:
00000000`773ffc00 4c8bd1          mov     r10,rcx

Software Breakpoints

Software breakpoints are set in Windbg using a slightly different command:

0:001> bp ntdll!ZwCreateFile

Again, we verify that our breakpoint has been set:

0:001> bl
 0 e 00000000`773ffc00     0001 (0001)  0:**** ntdll!ZwCreateFile

Here's what it looks like when we hit our software breakpoint:

Breakpoint 0 hit
ntdll!ZwCreateFile:
00000000`773ffc00 4c8bd1          mov     r10,rcx


The implementation of a software breakpoint is interesting. The fact that its opcode (0xCC) is exactly 1 byte long was probably driven by the design requirement that it be easy to patch an instruction, which in the x86 architecture is always byte-aligned in location and length. This also makes sense because the smallest granularity of memory access in x86 is 1 byte, making it a byte-accessible machine.


When a software breakpoint is set and the desired instruction in memory is overwritten by the 0xCC byte, the original byte that was overwritten is stored and associated to this software breakpoint in an associative data structure, so that the debugger knows what instruction to execute in order to maintain the original program's logic once it reaches the patched address. Due to the fact that this association is stored in memory and not in a hardware register somewhere in the CPU, we are able to set many more software breakpoints than the maximum of 4 hardware breakpoints.


Finally, it is important to note that software breakpoints patch instructions with the 0xCC byte, and this byte will only halt execution if it is interpreted as an instruction to be executed rather than data that is read or written. The significance of this is that we can only use software breakpoints to break if the CPU executes(not reads or writes) the contents of the breakpoint address. This is why hardware breakpoints can break on a memory read or write, while software breakpoints cannot.

Comparison
Below is a summary of the capabilities of hardware and software breakpoints:


Hardware Breakpoint
Software Breakpoint
Read
X

Write
X

Execute
X
X

Sunday, April 29, 2012

Software Attack Vectors: Format String Vulnerability


Format String Vulnerabilities are a class of vulnerabilities in which the programmer takes a format string specified by the user as input, and uses it in one of the many C Run Time(CRT) functions, such as printf and snprintf. Windows includes a lot of mitigations for this class of vulnerabilities, as we will see shortly.

When we call a function, the parameters are pushed onto the stack starting with the last parameter first. For example, if we call:
function(x, y, z);
The expected order of the push operations in the code emitted by the compiler would be semantically equivalent to the following:
push z
push y
push x

A test program written in C calls _snprintf and receives the format string “%x %x %x” as the only argument on the commandline, which is pointed to by argv[1] in the program. The call to _snprintf in our C code generates the following assembly code:
_snprintf(buffer, BUFFER_SIZE-1, formatString);     //vulnerable function call
004116F9  mov         eax,dword ptr [formatString] 
004116FC  push        eax 
004116FD  push        7Fh 
004116FF  mov         ecx,dword ptr [buffer] 
00411702  push        ecx 
00411703  call        dword ptr [__imp___snprintf (417244h)]

Using dynamic analysis, the values of the eax and ecx registers have been confirmed to be pointers to argv[1] and buffer, respectively. The pattern in the above assembly code is to push the third argument(formatString), followed by the second argument (the constant 7F hex), followed by the first argument(buffer). This is in accordance with our expectations based on the above description of general function call mechanics. 
The information disclosure issue occurs when the attacker-controlled format string (in this case, “%x %x %x”) instructs _snprintf to look for more arguments than we have actually passed to _snprintf. As you can see above, we don’t pass any arguments after the format string to _snprintf. _snprintf does not verify that we have passed the same number of arguments as the format string expects. Any additional arguments that were passed after the formatString parameter would have been pushed onto the call stack before the call to _snprintf. Since the arguments are pushed onto the stack in the reverse order that the function call specifies, the first value to print will be searched for 1 DWORD “under” the address of the format string in the call stack, which is 1 DWORD higher in memory than the address of the format string in the call stack (remember that the x86 call stack grows downwards in memory).
My call stack at the point that we enter _snprintf looks like this:



The next line after the call to _snprintf in the test program was:

printf("buffer: %s\n", buffer);

and it outputted:

buffer: abababab cdcdcdcd efefefef

This output means that we are able to “spy” on the thread’s call stack. Using this technique, we can see other data that might have been stored on the call stack.

An advanced attack of this nature has been documented which would allow the attacker to change the program state rather than just spy on it. This attack involves using the %n sequence in the format string. In the context of format strings, %n means to write the number of bytes written so far, to the address in the parameter corresponding to the %n character sequence in the function call’s arguments. Since the attacker controls how many bytes are written, he controls the value that is written. A carefully crafted malicious format string can also control where this value is written to, depending on the number of %x sequences in the format string before the %n sequence. This ability can be leveraged to overwrite function pointers, returns addresses or other things.
In order to make this highly contrived example, many mitigations had to be bypassed. The mitigations bypassed were as follows:
1.       Call the Windows _set_printf_count_output(); function and pass a parameter of 1
2.       Disable ASLR in the Visual Studio compiler
3.       No Buffer Security Check in the Visual Studio compiler
4.       Disable Run-Time Error Checks (/RTC) in the Visual Studio compiler