最近,在工作之余学习python逆向工程,找到了一本翻译过来的中文参考书:Python灰帽子——黑客与逆向工程师的Python编程之道。按照书上的顺序,从基础开始构建基于python语言的调试器,目前已经推进到第四章。期间也遇到了一些问题,经过不断地百度和查阅相关书籍,最终都成功解决,同时有了很多收获。这里,想要将这些问题和解决办法记录下来,供以后回头学习。同时期望能够帮助到刚刚入门python,同样在学习的Python灰帽子的同学。
在运行4.1节扩展断点处理例程时,总是得不到预期的效果。
中文版被中断的目标代码如下:
#print_loop.py
from ctypes import * import time
msvcrt = cdll.msvcrt counter = 0
while 1:
msvcrt.printf("Loop iteration %d:\n" %counter)
time.sleep(2)
counter += 1
调试器代码如下:
#print_random.py
from pydbg import *
from pydbg.defines import *
import struct
import random
# This is our user defined callback function
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)
counter = struct.unpack("L",counter)[0]
print "Counter: %d" % int(counter)
# When using read_process_memory, it returns a packed binary
# string, we must first unpack it before we can use it further
# 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)
print "random_counter: %d" %random_counter
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)
return DBG_CONTINUE
# Instantiate the pydbg class
dbg = pydbg()
# Now enter the PID of the printf_loop.py process
pid = raw_input("Enter the printf_loop.py PID: ")
# Attach the debugger to that process
dbg.attach(int(pid))
# Set the breakpoint with the printf_randomizer function
# defined as a callback
printf_address = dbg.func_resolve("msvcrt","printf")
dbg.bp_set(printf_address,description="printf_address",handler=printf_randomizer)
# Resume the process
dbg.run()
运行上述代码,出现如下结果:
发现通过中断获取的counter结果并不对,通过搜索发现有人在运行这段代码时出现一样的错误,并给出了解决的办法和思路,链接如下:https://wenku.baidu.com/view/c70de996a0116c175f0e48f7。
本着求知精神,并没有采用这种办法,继续探索简洁的解决办法。通过深入学习栈帧知识,最终发现问题出在目标代码这一句上:
msvcrt.printf("Loop iteration %d:\n" %counter)
这句调用printf函数输入,实际上只传递了一个实参:”Loop iteration x:\n”,这是一个字符串,其中x代表当前counter值。因此在渗透代码实现printf函数调用前中断时,只有一个实参,且保存在ESP+0x4,这里保存一个指向字符串”Loop iteration x:\n”的指针(如下图所示)。上面链接里的方法就是通过读取ESP+0x4位置处的地址,并进一步读取字符串”Loop iteration x:\n”,然后通过字符串操作实现获取x值,但是这又涉及到一个问题:x的位数?因此,采用这种方法需要进一步考虑x(也即counter)的位数。写入随机数也是同样道理,替换x值即可,但同样有数字位数问题。
但是改成如下形式就不存在这个问题。
msvcrt.printf("Loop iteration %d:\n", counter)
这里实际上传入了两个实参(以下图时刻为例),”Loop iteration %d:\n”字符串和4,其中字符串指针保存在ESP+0x4位置,4保存在ESP+0x8位置(实参从右到左入栈原则)。
至此,问题圆满解决。