Tuesday, August 25, 2009

Gray Hat Python Chapter 6.1 Minor Stuff

In Chapter 6.1, "Soft Hooking with PyDbg", I had some minor issues getting Immunity Debugger to connect to Firefox. First, I had to quit and reload the debugger. Dunno why this was, but until then, no attachment attempts worked. Secondly, since I'm not use to Immunity Debugger, the following line from the chapter wasn't helpful:

Once you have accepted the site's SSL certificate and the page has loaded, attach Immunity Debugger to the firefox.exe process and set a breakpoint on nspr4.PR_Write.


Immunity Debugger help on Breakpoints (Ordinary):
You place this breakpoint by selecting the command in Disassembler and pressing F2, or over pop-up menu
.

Well, I had no idea where the assembly was for the nspr4.PR_Write routine. After digging around, I came across the "Executable modules" window. I did the following:
  • Sorted by name
  • Found the 'nspr4' module
  • Right-clicked and chose 'View names' in the drop down
  • In the 'Names in nspr4' window, typed in PR_Write' and selected the instance
  • Hit F2 (which caused the address to become red and an entry to appear in the 'Breakpoints' window


From there, I was able to complete the exercise with no problem.

Wednesday, August 19, 2009

Gray Hat Python Chapter 5 Mucho Love

Continuing on with my ramblings on Gray Hat Python, I'm now onto Chapter 5 and have slowed a bit down. The best advice I can give is what Justin gave at the end of Chapter 4.3.2: get a program with a known vulnerability and start loading it up in the examples. I didn't do this at first, which made Chapter 5 pretty difficult to follow.

I am using a milw0rm exploit with a different shellcode payload. This at least has helped in exercise 5.3.1 and 5.3.2. By the way, you will need to modify the code samples for both examples to get them working correctly.

Example 5.3.1 findinstruction.py diff

--- findinstruction-orig.py 2009-08-19 14:37:13.000000000 -0500
+++ findinstruction.py 2009-08-19 14:37:35.000000000 -0500
@@ -16,7 +16,7 @@
access = code_page.getAccess( human = True )

if "execute" in access.lower():
- imm.log("[*] Found: %s (0x%08x)" % ( search_code, hit ), address = hit )
+ imm.Log("[*] Found: %s (0x%08x)" % ( search_code, hit ), address = hit )


return "[*] Finished searching for instructions, check the Log window."

This threw me off. According to the Immunity Debugger documentation, both immlib.Debugger.Log() and immlib.Debugger.log() have the same prototype:

Log(self, msg, address=0, highlight=False, gray=False, focus=0)
Adds a single line of ASCII text to the log window. source code

log(self, msg, address=0, highlight=False, gray=False, focus=0)
Adds a single line of ASCII text to the log window.

But, running w/ imm.log() kept on throwing an error that 'address' was an unexpected keyword argument. This is more of a bug w/ the debugger than the program, methinks. At this time, though, I was not able to load up the Immunity forums, so it's hard to say.




Example 5.3.2 badchar.py diff

--- badchar-orig.py 2009-08-19 14:26:15.000000000 -0500
+++ badchar.py 2009-08-19 14:32:37.000000000 -0500
@@ -1,4 +1,5 @@
from immlib import *
+import binascii

def main(args):

@@ -12,15 +13,16 @@
# Shellcode to verify
shellcode = "<>"
shellcode_length = len(shellcode)
+ shellcode = binascii.b2a_hex(shellcode)

debug_shellcode = imm.readMemory( address, shellcode_length )
debug_shellcode = debug_shellcode.encode("HEX")

imm.log("Address: 0x%08x" % address)
- imm.log("Shellcode Length : %d" % length)
+ imm.log("Shellcode Length : %d" % shellcode_length)

- imm.log("Attack Shellcode: %s" % canvas_shellcode[:512])
- imm.log("In Memory Shellcode: %s" % id_shellcode[:512])
+ imm.log("Attack Shellcode: %s" % shellcode[:512])
+ imm.log("In Memory shellcode: %s" % debug_shellcode[:512])

# Begin a byte-by-byte comparison of the two shellcode buffers
count = 0

For the above, once the changes are made, then modify the script to include your shellcode. I was having problems getting the lists to contain the same type of characters. shellcode[] was containing actual numbers / letters (e.g. \x41 or 'A') whilst debug_shellcode[] contained strings of the numbers (e.g. '41'). The variables just needed to be renamed to the ones used in the program. (I can't think for the life of me why Justin used 'canvas' ;-)

Friday, August 14, 2009

Gray Hat Python Chapter 4.1 Love

So, I gave up on Chapter 3.4.2 and went to Chapter 4. The first exercise is 4.1 Extending Breakpoint Handlers. The idea was to modify the process memory to replace the counter number with another random number between 1-100. Instead of this, the test harness printed out a static number (versus the counter) and was not able to modify the printed output. I found out I wasn't the only one with this issue. (And btw, thanks to Brad at stacksmash.org for his prior work with this book and blog posts. He has been very helpful!)

The first thing I wanted to see was what the context data looked like. PyDbg has a nice method called pydbg.pydbg.dump_context() which helped in troubleshooting. I added it here:

def printf_randomizer(dbg):

# Read in the value of the counter at ESP + 0x4 as a DWORD
parameter_addr = dbg.context.Esp + 0x4
print dbg.dump_context()



I got the following context data:

CONTEXT DUMP
EIP: 77c4186a push byte 0×10
EAX: 0021fb88 ( 2227080) -> 0! (stack)
EBX: 00000000 ( 0) -> N/A
ECX: 0021fc98 ( 2227352) -> ..!…………………`….R…R……..!…….!………..!……….G..j..w………………..x…………R….!……………..x…….j..w………….h…….R…….R……`….R………………`…….`…..!…..< U..p……….. ………………..`….R…R……..!…….!………..!……….G..j..w………………..x…………R….!……………..x…….j..w………….h…….R…….R……`….R………………`…….`…..!…..< U..p……….. ..!…………………`….R…R……..!…….!………..!……….G..j..w………………..x…………R….!……………..x…….j..w………….h…….R…….R……`….R………………`…….`…..!…..< U..p……….. 0! (stack)
EBP: 0021fb8c ( 2227084) -> ..!………..!………..!.j..w..!.0.!…!…!.p.!..w..0.!.j..w..!…!…!…!…..d………….!.x…..!………..!.d…….%.1ld.!.p%………………JY..,_…………!…………. Y..p%…………!..V….!……………!………j..w..!…!….. (stack)
ESP: 0021fb80 ( 2227072) -> :…….0.!…!………..!………..!.j..w..!.0.!…!…!.p.!..w..0.!.j..w..!…!…!…!…..d………….!.x…..!………..!.d…….%.1ld.!.p%………………JY..,_…………!…………. Y..p%…………!..V….!……………!………j..w (stack)
+00: 1d1aba3a ( 488290874) -> N/A
+04: 00a9c994 ( 11127188) -> Loop iteration 4! (heap)
+08: 0021fc30 ( 2227248) -> ……….!…………. Y..p%…………!..V….!……………!………j..w..!…!…….!………..!…………………`….R…R……..!…….!………..!……….G..j..w………………..x…………R….!……………..x…….j..w…. (stack)
+0c: 0021fbbc ( 2227132) -> p! (stack)
+10: 1d1aaa9a ( 488286874) -> N/A
+14: 1d1aa8e0 ( 488286432) -> N/A

Counter: 2227248


The test harness was peeking at ESP+0x8 for the counter variable. On my Windows XP Pro SP3 system (running under Parallels on Mac), though, no counter variable I could tell was at ESP+0x8. Looking at ESP+0x4, though, existed what seemed to be an address (0x00a9c994) that PyDbg was hinted pointed to the heap, which contains the string. So, instead of a counter variable on the stack, the whole string is in the heap.

To read the counter variable, I changed the printf_randomizer from this:

def printf_randomizer(dbg):

# Read in the value of the counter at ESP + 0x8 as a DWORD
parameter_addr = dbg.context.Esp + 0x8
counter = dbg.read_process_memory(parameter_addr, 4)

# When we use read_process_memory, it returns a packed binary
# string. We must first unpack it before we can use it further.
counter = struct.unpack("L", counter)[0]
print "Counter: %d" % int(counter)


To this:

def printf_randomizer(dbg):

# Read in the value of the counter at ESP + 0x4 as a DWORD
parameter_addr = dbg.context.Esp + 0x4
#print dbg.dump_context()
counter = dbg.read_process_memory(parameter_addr,4)

# When using read_process_memory, it returns a packed binary
# string, we must first unpack it before we can use it further

# Hack time. Our real parameter address is different, since it's
# referenced. This is the base. We'll need to go into it to find the
# offset
parameter_addr_base = struct.unpack("L",counter)[0]

# If using this string, "Loop iteration ", the length to number = 15
# Add in the number itself (assume counter doesn't go beyond XXXX
# And then "!\n", two more bytes
string_len = 15 + 4 + 2
counter_string = dbg.read_process_memory(parameter_addr_base, int(string_len))
counter_string = struct.unpack(str(string_len) + "s",counter_string)[0]

# cleanup string
counter_string = counter_string.split("!\n")[0]
# And grab number
counter = counter_string[15:]
print "Counter: %d" % int(counter)


Now, here's the thing. We can read the variable, whoopy. But, we're going to have to modify the heap to possibly insert a variable between one to three bytes into an existing heap that might not have room. So, to play nice, I'm only adding back into the heap the amount possible. (We don't want to add a vulnerability to our testing ;-)

Also, from testing, the data does not need to be packed, since it's a string. So, the random_number is going back into the heap as a string and not an int.

Prior code:

# Generate a random number and pack it into binary format
# so that it is written correctly back into the process
random_counter = random.randint(1,100)
random_counter = struct.pack("L",random_counter)[0]

# Now swap in our random number and resume the process
dbg.write_process_memory(parameter_addr, random_counter)


New code:

# Generate a random number and pack it into binary format
# so that it is written correctly back into the process
random_counter = int(random.randint(1,100))

# Pack in only what will fit, though.
if (len(counter) > 1):
random_counter = str(random_counter)[0:len(counter)-1]
else:
random_counter = str(random_counter)[0]
#random_counter = struct.pack("L",random_counter)[0]

# Change our parameter address to point to the right
# location, 15 characters in
parameter_addr = parameter_addr_base + 15

# Now swap in our random number and resume the process
dbg.write_process_memory(parameter_addr,random_counter)


And, it gives this output:

Enter the printf_loop.py PID: 408
Counter: 2
Counter: 3
Counter: 4
Counter: 5
Counter: 6
Counter: 7
Counter: 8
Counter: 9
Counter: 10
Counter: 11
Counter: 12
Counter: 13
Counter: 14
[...]

C:\Documents and Settings\Administrator\workspace\Grey Hat Python\src>python pri
ntf_loop.py
Loop iteration 0!
Loop iteration 1!
Loop iteration 5!
Loop iteration 6!
Loop iteration 7!
Loop iteration 5!
Loop iteration 4!
Loop iteration 6!
Loop iteration 8!
Loop iteration 2!
Loop iteration 10!
Loop iteration 81!
Loop iteration 52!
Loop iteration 53!
Loop iteration 74!
[...]


Fun!

Thursday, August 06, 2009

Gray Hat Python Chapter 3.4.2 Love, Part 1 (and some 3.4.1 pillow talk)

So, yeah, 3.4.2 didn't go well either. Going back to my write-up on Chapter 3.4.1, I realize I missed an error and as well propagated it:
It looks like self.context.Eip is causing some grief. Looking ahead, it looks like it's covered on page 48. Comment out the following:

self.context = self.get_thread_context(h_thread=self.h_thread)
self.context.Eip -= 1

kernel32.SetThreadContext(self.h_thread,byref(self.context))


self.context.Eip should have been able to be set to the CONTEXT() structure from my_debugger_defines. Since it's not being set, then something is amiss.

Here's the code for get_thread_context from the book:

def get_thread_context(self, thread_id=None, h_thread=None):

context = CONTEXT()
context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS

# Obtain a handle to the thread
h_thread = self.open_thread(thread_id)
if kernel32.GetThreadContext(h_thread, byref(context)):
kernel32.CloseHandle(h_thread)
return context
else:
return False

Here's the code for get_thread_context from the src files:

def get_thread_context (self, thread_id=None,h_thread=None):

context = CONTEXT()
context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS

# Obtain a handle to the thread
if h_thread is None:
self.h_thread = self.open_thread(thread_id)

if kernel32.GetThreadContext(self.h_thread, byref(context)):

return context
else:
return False


So, the book example blindly sets the local h_thread versus the revised code. Funny enough, the revised code I am assuming is assuming that the passed h_thread is also self.h_thread, since no latter assignment is made. The other difference is kernel32.CloseHandle() is called in the book example whilst not in the src code example.

There are two times that h_thread is already set for us: one in get_debug_event() and the other in exception_handler_breakpoint(). There are two times it is not set: bp_set_hw() and bp_del_hw(). So, we want to still test to see if h_thread is None and if so, set. If it isn't, well, then use it :-)

def get_thread_context (self, thread_id=None,h_thread=None):

context = CONTEXT()
context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS

# Obtain a handle to the thread
if h_thread is None:
h_thread = self.open_thread(thread_id)
self.h_thread = h_thread

if kernel32.GetThreadContext(h_thread, byref(context)):

return context
else:
return False

And, I'll remove the CloseHandle call for now. Looking at the functions where the handle is passed to us, exception_handler_breakpoint() would be very unhappy with us if we closed the handle prematurely. And, we'll set self.h_thread, so it's in the instance itself.

And, here's the run:

Enter the PID of the process to attach to: 3000
[*] Address of printf: 0x77c4186a
Event Code: 3 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 6 Thread ID: 3008
Event Code: 2 Thread ID: 1444
Event Code: 1 Thread ID: 1444
[*] Exception address: 0x7c90120e
[*] Hit the first breakpoint.
Event Code: 4 Thread ID: 1444
Event Code: 1 Thread ID: 3008
Traceback (most recent call last):
File "C:\Documents and Settings\Administrator\workspace\Grey Hat Python\src\my_test.py", line 21, in
debugger.run()
File "C:\Documents and Settings\Administrator\workspace\Grey Hat Python\src\my_debugger.py", line 90, in run
self.get_debug_event()
File "C:\Documents and Settings\Administrator\workspace\Grey Hat Python\src\my_debugger.py", line 125, in get_debug_event
self.exception_handler_single_step()
File "C:\Documents and Settings\Administrator\workspace\Grey Hat Python\src\my_debugger.py", line 343, in exception_handler_single_step
continue_status = DBG_EXCEPTION_NOT_HANDLED
NameError: global name 'DBG_EXCEPTION_NOT_HANDLED' is not defined

DBG_EXCEPTION_NOT_HANDLED is not defined in my_debugger_defines. Put this below DBG_CONTINUE:

DBG_EXCEPTION_NOT_HANDLED = 0x80010001


Oh, also since you see my path, I'm running this in a VM, hence Administrator running all of this. Why not?! :-)

Next run:

Enter the PID of the process to attach to: 3068
[*] Address of printf: 0x77c4186a
Event Code: 3 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 6 Thread ID: 3076
Event Code: 2 Thread ID: 3140
Event Code: 1 Thread ID: 3140
[*] Exception address: 0x7c90120e
[*] Hit the first breakpoint.
Event Code: 4 Thread ID: 3140
Event Code: 1 Thread ID: 3076
Traceback (most recent call last):
File "C:\Documents and Settings\Administrator\workspace\Grey Hat Python\src\my_test.py", line 21, in
debugger.run()
File "C:\Documents and Settings\Administrator\workspace\Grey Hat Python\src\my_debugger.py", line 90, in run
self.get_debug_event()
File "C:\Documents and Settings\Administrator\workspace\Grey Hat Python\src\my_debugger.py", line 125, in get_debug_event
self.exception_handler_single_step()
File "C:\Documents and Settings\Administrator\workspace\Grey Hat Python\src\my_debugger.py", line 346, in exception_handler_single_step
if self.bp_del_hw(slot):
UnboundLocalError: local variable 'slot' referenced before assignment


Here's the code snippet:

if self.context.Dr6 & 0x1 and self.hardware_breakpoints.has_key(0):
slot = 0
elif self.context.Dr6 & 0x2 and self.hardware_breakpoints.has_key(1):
slot = 1
elif self.context.Dr6 & 0x4 and self.hardware_breakpoints.has_key(2):
slot = 2
elif self.context.Dr6 & 0x8 and self.hardware_breakpoints.has_key(3):
slot = 3
else:
# This wasn't an INT1 generated by a hw breakpoint
continue_status = DBG_EXCEPTION_NOT_HANDLED

# now let's remove the breakpoint from the list
if self.bp_del_hw(slot):
continue_status = DBG_CONTINUE


I'm troubleshooting this. Throwing a print in there shows Dr6 as 4294905840 / 0xFFFF0FF0, which is the power-up state [1]. So, we're at 0, which seems to me that the register is not being set. Also, this shows a coding error. The conditional does not set slot and assumes 'slot' it's set. I'll ignore that issue. But, now, I gotta take off...


[1] Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 3A: System Programming Guide, Table 9-1

Blog Archive