A Paimei tutorial - simple heap-traceing - part 2
Abstract
The following tutorial builds on the basics which are documented in the first part. For vulnerability discovery, debugging and fuzzing are essential. Furthermore the proper knowledge of memory management, assembly instructions and Python will still be needed. You cannot just scratch the surface while trying to explore these techniques. I wrote this tutorial to inspire people, or to help people coping with Windows and its restrictions. But most people in the fields will laugh at this and call it primitive. It is.
For whom this tutorial maybe of interest:
- people developing Windows exploits and searching for vulnerabilities
- anyone who spent more than a couple of hours using IDA, Python, or pyDBG
- Reverse engineering folks
- deeply security interested individuals, the common addicted seeker - again I link to good documentations
Recommended and used sources for this tutorial were:
- the Offensive Security 101 lab exercises
- Pedram's demo at Redhive
- The Black Hat 2006 presentation for Windows Vista Heap management enhancements by Adrian Marinescu
- Shellcoders Handbook SE and "The Art Of Exploitation 2nd"
- this Win32 API tutorial
Review: Keep things easy
Have a look at the following C# example of exception handling:
- #993333;">void inPutter#009900;">(Komplex zahl1#339933;">, Komplex zahl2#009900;">)#009900;">{
- try #009900;">{
- zahl1.#202020;">Re #339933;">= #993333;">float.#202020;">Parse#009900;">(Zahl1_r.#202020;">Text#009900;">)#339933;">; #009900;">} catch #009900;">(Exception ex#009900;">) #009900;">{
- MessageBox.#202020;">Show#009900;">(ex.#202020;">StackTrace#009900;">)#339933;">; #666666; font-style: italic;">// for dbg
- #009900;">} finally #009900;">{
- #666666; font-style: italic;">// something
- #009900;">}
- #009900;">} #666666; font-style: italic;">// end of inPutter()
It's a very primitive example of exception handling. Alternatively maybe tryParse may have been kewler... but that's not the point. The implementation of the exception objects allows us to output a Strack-trace. So in case of handled crashes we know the reason why. Easily said: with debugging in the first part of this tutorial we did the same thing with a closed-source Crackme binary.
Tracing the Heap in the wild world of Windows
A script waits to be used: heap_trace.py. Again we start the uDraw server. We start our Crackme and note the PID somewhere.
- python heap_trace.py #ff4500;">1234
That's it. Maybe you need to elevate this process, depending on your Windows policy setups. The script will log the trace to the console and process the graph into uDraw on the fly. - As you saw on the demo by Pendam. To gain a better understanding of this "linked-list" here's some background from the Shellcoders Handbook (second edition), because in Vista a new approach to manage the Heap has been made. Simply said: instead of using the syscalls in C - like you do in *nix, for Windows you have to use the DLLs. This has got a couple of reasons. It's simply convenient, because changes to the internal Windows structure don't affect your programs. MS developers can separate their work from yours. The relocatable file-format is used in Windows, gets loaded at runtime to provide the functionalities of shared libraries. Their format is PE-COFF; and the Windows PE-loader accepts them. Most times ;).
When a DLL gets loaded, it calls an initialization function. This function often sets up its own heap using HeapCreate() and stores a global variable as a pointer to that heap so that future allocation operations can use it instead of the default heap. Most DLLs have a .data section in memory for storing global variables, and you will often find useful function pointers or data structures stored in that area. Because many DLLs are loaded, there are many heaps. With so many heaps to keep track of, heap corruption attacks can become quite confusing. In Linux, there is typically a single heap that can get corrupted, but in Windows, several heaps may get corrupted at once, which makes analyzing the situation much more complex. When a user calls malloc() in Win32, he or she is actually using a function exported by msvcrt.dll, which then calls HeapAllocate() with msvcrt.dll’s private heap. You may be tempted to try to use the HeapValidate()function to analyze a heap corruption situation, but this function does not do anything useful.
(Chapter 6, Shellcoders Handbook 2nd)
In the script's default list-output it's difficult to gain an overview which blocks are freed, or which of these are able to receive data. So have a look at the blue boxes in the graph. Attach the process to ollyDBG and have a look at the first address from the graph.
7E3A5A06 8BF0 MOV ESI#339933;">,EAX
Set a conditional breakpoint and log for RtlAllocateHeap calls. We get:
7E3A5A00 FF15 9C13397E CALL DWORD PTR DS#339933;">:#009900;">[#339933;"><#339933;">;&#339933;">;KERNEL32.#202020;">HeapAlloc#339933;">>#339933;">;>#339933;">;; ntdll.#202020;">RtlAllocateHeap 7E3A5A06 8BF0 MOV ESI#339933;">,EAX

It could look like this, where there's a breakpoint log at [ESP +8] - where the heap size is stored in hex. If you run the programm you see that the sizes of the created blocks are logged. Of course these are the same sizes heap_trace.py showed. Each time the process runs, the RtlAllocateHeap will run, allocating different spaces. We just know the sizes for sure. So the condition of our breakpoint is: [ebp +8] == 0b8. We trigger for that size.
heap_trace.py does what?
I guess the imports are understood jet, because I mentioned utils in part 1 as part of pyDBG. I'll skip trivial parts as long as they're not related with our heap-tracing tasks.
- USAGE = #483d8b;">"USAGE: heap_trace.py"#66cc66;">;
- error = #ff7700;font-weight:bold;">lambda msg: #dc143c;">sys.stderr.write(#483d8b;">"ERROR" + msg + #483d8b;">"#000099; font-weight: bold;">\n") #ff7700;font-weight:bold;">or #dc143c;">sys.exit(#ff4500;">1)
- #ff7700;font-weight:bold;">if #008000;">len(#dc143c;">sys.argv) #66cc66;">!= #ff4500;">2:
- error(USAGE)
- #ff7700;font-weight:bold;">try:
- pid = #008000;">int(#dc143c;">sys.argv[#ff4500;">1])
- #ff7700;font-weight:bold;">except:
- error(USAGE)
I think the lambda error here is a kind of elegant solution, because it'll show whether any errors occurred and use sys.stderr to store these.
- dbg.set_callback(LOAD_DLL_DEBUG_EVENT, dll_load_handler)
- #808080; font-style: italic;"># refers to part 1
- addrRtlAllocateHeap = dbg.func_resolve(#483d8b;">"ntdll", #483d8b;">"RtlAllocateHeap")
- addrRtlFreeHeap = dbg.func_resolve(#483d8b;">"ntdll", #483d8b;">"RtlFreeHeap")
- addrRtlReAllocateHeap = dbg.func_resolve(#483d8b;">"ntdll", #483d8b;">"RtlReAllocateHeap"
- #808080; font-style: italic;"># we also had this in part 1
- hooks = utils.hook_container()
- hooks.add(dbg, addrRtlAllocateHeap, #ff4500;">3, #008000;">None, RtlAllocateHeap)
- hooks.add(dbg, addrRtlFreeHeap, #ff4500;">3, #008000;">None, RtlFreeHeap)
- hooks.add(dbg, addrRtlReAllocateHeap, #ff4500;">4, #008000;">None, RtlReAllocateHeap)
- #808080; font-style: italic;"># and this
It's very similar what we see here. Throgh dbg.set_callback we create a hook for each DLL calling event, enter the function dll_load_handler, which is defined in Pendam's script at this point:
- #ff7700;font-weight:bold;">def dll_load_handler (dbg):
- last_dll = dbg.get_system_dll(-#ff4500;">1)
- #ff7700;font-weight:bold;">print #483d8b;">"Loading 0x%08x %s" #66cc66;">% (last_dll.base, last_dll.path)
- #ff7700;font-weight:bold;">return DBG_CONTINUE
The hook instances we created get to find the addresses of the APIs RtlAllocateHeap, RtlFreeHeap, RtlReAllocateHeap. If we use Wing (or any capable Python script debugger) and set a breakpoint to halt after this function has been called we are able to print out the addresses:
#008000;">hex(addrRtlFreeGeap)
In Wing we could continue the execution, or just do something else (in the same function context!). You can have a look at Ricardo's tutorials at this point. They are in Spain. Or at Chuck's translations, but these are in robo-english. - But dumping the heap isn't a problem any more. So far for this journey. If you're fuzzing some processes at some time, maybe in MacOS, Windows 7 or XP... think of this as an opportunity to get good information.
So long,
wishi

Post new comment