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

Tuesday, February 7, 2012

FastIO

In my previous posts about Stuxnet, I talked about the FileSystem Filter functionality of the Rootkit. I covered the idea of an "IO Request Packet" or IRP for short. IRPs are the general way for drivers to pass data to the lower layers in their device stacks. Filesystems often write to relatively slow backing stores (such as hard disks). To get an idea of the difference in timescale we are talking about, CPUs often operate in the microsecond domain, while hard disks operate in the millisecond domain. Due to this time scale difference, the Windows Engineers decided to provide an optimized I/O mechanism to write to such slow storage mediums. This caching mechanism is implemented in the Windows Cache Manager, and the FastIO infrastructure is the way to take advantage of it from a driver.

FastIO can be thought of as logically parallel to the IRP infrastructure in Windows, but with higher performance. Instead of waiting for each IRP to get to the disk, FastIO interacts with the Windows Cache Manager. The windows cache manager stores an in-memory cache of frequently accessed data on the disk (See chapters 9 and 10 of Windows Internals 5th edition by Mark Russinovich and David Solomon). This increases performance because on a cache lookup, the memory is hit (which has no moving parts and is fast) rather than the disk being hit (which has to seek to the correct sector, thereby incurring the cost of mechanical slowness). In addition to the disk not being hit on a read/write, FastIO also allows us to avoid the overhead incurred in synthesizing new IRPs to pass down the device stack.

The FastIO infrastructure is brought together by the FAST_IO_DISPATCH structure, which can be seen in wdm.h in the Windows Driver Development Kit. In the Rootkits book by Hoglund and Butler, they describe this structure as beginning with a size field, and containing all the function pointers for the FastIO functions supported by the driver. When attempting to call a FastIO function, the system first has to figure out whether or not the device in question supports FastIO. As a fallback, if the device does not support FastIO, an IRP is created and sent down the device stack.

I followed the Experiment in Windows Internals 4th Edition:

kd> !drvobj \filesystem\ntfs 2
Driver object (81fd0cc0) is for:
 \FileSystem\Ntfs
DriverEntry:   f847c680   Ntfs!DriverEntry
DriverStartIo: 00000000
DriverUnload:  00000000              
AddDevice:     00000000

Dispatch routines:
[00] IRP_MJ_CREATE                      f841c200              Ntfs!NtfsFsdCreate
[01] IRP_MJ_CREATE_NAMED_PIPE           804f2529          nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE                       f841cda5               Ntfs!NtfsFsdClose
[03] IRP_MJ_READ                        f8402687                Ntfs!NtfsFsdRead
[04] IRP_MJ_WRITE                       f8403428               Ntfs!NtfsFsdWrite
[05] IRP_MJ_QUERY_INFORMATION           f841e53f         Ntfs!NtfsFsdDispatchWait
[06] IRP_MJ_SET_INFORMATION             f84040b1             Ntfs!NtfsFsdSetInformation
[07] IRP_MJ_QUERY_EA                    f841e53f         Ntfs!NtfsFsdDispatchWait
[08] IRP_MJ_SET_EA                      f841e53f              Ntfs!NtfsFsdDispatchWait
[09] IRP_MJ_FLUSH_BUFFERS               f842da23  Ntfs!NtfsFsdFlushBuffers
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    f841d4e2          Ntfs!NtfsFsdDispatch
[0b] IRP_MJ_SET_VOLUME_INFORMATION      f841d4e2               Ntfs!NtfsFsdDispatch
[0c] IRP_MJ_DIRECTORY_CONTROL           f8422595           Ntfs!NtfsFsdDirectoryControl
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         f84218d4        Ntfs!NtfsFsdFileSystemControl
[0e] IRP_MJ_DEVICE_CONTROL              f841d4e2               Ntfs!NtfsFsdDispatch
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     804f2529   nt!IopInvalidDeviceRequest
[10] IRP_MJ_SHUTDOWN                    f8414476      Ntfs!NtfsFsdShutdown
[11] IRP_MJ_LOCK_CONTROL                f84329e3  Ntfs!NtfsFsdLockControl
[12] IRP_MJ_CLEANUP                     f841c4da           Ntfs!NtfsFsdCleanup
[13] IRP_MJ_CREATE_MAILSLOT             804f2529               nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY              f841d4e2                Ntfs!NtfsFsdDispatch
[15] IRP_MJ_SET_SECURITY                f841d4e2     Ntfs!NtfsFsdDispatch
[16] IRP_MJ_POWER                       804f2529             nt!IopInvalidDeviceRequest
[17] IRP_MJ_SYSTEM_CONTROL              804f2529               nt!IopInvalidDeviceRequest
[18] IRP_MJ_DEVICE_CHANGE               804f2529 nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA                 f841e53f  Ntfs!NtfsFsdDispatchWait
[1a] IRP_MJ_SET_QUOTA                   f841e53f       Ntfs!NtfsFsdDispatchWait
[1b] IRP_MJ_PNP                         f846b3fc  Ntfs!NtfsFsdPnp

Fast I/O routines:
FastIoCheckIfPossible                   f8432bbb              Ntfs!NtfsFastIoCheckIfPossible
FastIoRead                              f841f4ce          Ntfs!NtfsCopyReadA
FastIoWrite                             f842f898          Ntfs!NtfsCopyWriteA
FastIoQueryBasicInfo                    f8424db0              Ntfs!NtfsFastQueryBasicInfo
FastIoQueryStandardInfo                 f8424c14          Ntfs!NtfsFastQueryStdInfo
FastIoLock                              f8432e66          Ntfs!NtfsFastLock
FastIoUnlockSingle                      f8432f26  Ntfs!NtfsFastUnlockSingle
FastIoUnlockAll                         f84691b9     Ntfs!NtfsFastUnlockAll
FastIoUnlockAllByKey                    f84692fd              Ntfs!NtfsFastUnlockAllByKey
AcquireFileForNtCreateSection           f841d6f4    Ntfs!NtfsAcquireForCreateSection
ReleaseFileForNtCreateSection           f841d721   Ntfs!NtfsReleaseForCreateSection
FastIoQueryNetworkOpenInfo              f842ffc6   Ntfs!NtfsFastQueryNetworkOpenInfo
AcquireForModWrite                      f846e918            Ntfs!NtfsAcquireFileForModWrite
MdlRead                                 f8430233           Ntfs!NtfsMdlReadA
MdlReadComplete                         8051e58f              nt!FsRtlMdlReadCompleteDev
PrepareMdlWrite                         f842f36d  Ntfs!NtfsPrepareMdlWriteA
MdlWriteComplete                        805f28aa               nt!FsRtlMdlWriteCompleteDev
FastIoQueryOpen                         f8424ec5 Ntfs!NtfsNetworkOpenCreate
AcquireForCcFlush                       f841d3db                Ntfs!NtfsAcquireFileForCcFlush
ReleaseForCcFlush                       f841d39c Ntfs!NtfsReleaseFileForCcFlush

As we can see in the section titled " Fast I/O routines", the NTFS drivers uses most of the FastIO functions. To learn more about the specific FastIO functions, check out:

http://www.osronline.com/article.cfm?id=166