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!
5 comments:
Thanks for posting this! I'm going through Grey Hat Python right now and am using Python 2.6 on Win XP SP3 and I seem to have it working! I definitely appreciate it. Now I get to step through this once again and try to understand everything.
Cool! I know in some of the later postings I focused on the error codes versus getting the stuff to work. So, take some of that stuff with a grain of salt or two :)
Big thanks.
I moved "print" command to be more clear:
# Now swap in our random number and resume the process
print "Counter: %d, randomCounter: %d" % (int(counter), int(random_counter)) dbg.write_process_memory(parameter_addr,random_counter)
return DBG_CONTINUE
Nice work. :-)
The reason that the counter was on the heap is that python was doing the string formatting in the example printf_loop.py code (using the % operator) before passing the string to the printf function - whereas printf_random.py assumes that counter was the second variable on the stack.
The example should work if you use counter as the second variable. ie
printf_loop.py:
msvcrt.printf("Loop iteration %d!\n", counter)
JohnU,
You'are absolutely right!
the code of book is wrong, but sample code (on the website) is written with ','
using operator '%' and using ',' have a big diffrence.
Post a Comment