4.1扩展断点处理

在前面的章节中我们讲解了用事件处理函数处理调试事件的方法。用 PyDbg 可以很容 易的扩展这种功能,只需要构建一个用户模式的回调函数。当收到一个调试事件的时候,回 调函数执行我们定义的操作。比如读取特定地址的数据,设置更更多的断点,操作内存。操 作完成后,再将权限交还给调试器,恢复被调试的进程。

PyDbg 设置函数的断点原型如下:

bp_set(address, description="",restore=True,handler=None)

address 是要设置的断点的地址,description 参数可选,用来给每个断点设置唯一的名字。 restore 决定了是否要在断点被触发以后重新设置, handler 指向断点触发时候调用的回调函 数。断点回调函数只接收一个参数,就是 pydbg()类的实例化对象。所有的上下文数据,线 程,进程信息都在回调函数被调用的时候,装填在这个类中。

以 printf_loop.py 为测试目标,让我们实现一个自定义的回调函数。这次我们在 printf() 函数上下断点,以便读取 printf()输出时用到的参数 counter 变量,之后用一个 1 到 100 的随 机数替换这个变量的值,最后再打印出来。记住,我们是在目标进程内处理,拷贝,操作这 些实时的断点信息。这非常的强大!新建一个 printf_random.py 文件,键入下面的代码。

#printf_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)
    # 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)
    # 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)
    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()

现在运行 printf_loop.py 和 printf_random.py 两个文件。输出结果将和表 4-1 相似。

Table 4-1:调试器和进程的输出

Output from Debugger Output from Debugged Process
Enter the printf_loop.py PID: 3466 Loop iteration 0!
Loop iteration 1!
Loop iteration 2!
Loop iteration 3!
Counter: 4 Loop iteration 32!
Counter: 5 Loop iteration 39!
Counter: 6 Loop iteration 86!
Counter: 7 Loop iteration 22!
Counter: 8 Loop iteration 70!
Counter: 9 Loop iteration 95!
Counter: 10 Loop iteration 60!

为了不把你搞混,让我们看看 printf_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

先搞明白一点,printf()接受的这个 counter 是主函数里 counter 的拷贝,就是说在 printf 函数内部,无论怎么修改都不会影响到外面的这个 counter(C 语言所说的只有传递指针才能真 正的改变值)。

你应该看到,调试器在 printf 循环到第 counter 变量为 4 的时候才设置了断点。这是 因 为被 counter 被捕捉到的时候已经为 4 了(这是为了让大家看到对比结果,不要认为调试器 傻了)。同样你会看到 printf_loop.py 的输出结果一直到 3 都是正常的。到 4 的时候,printf() 被中断,内部的 counter 被随即修改为 32!这个例子很简单且强大,它告诉了你在调试事件 发生的时候如何构建回调函数完成自定义的操作。现在让我们看一看 PyDbg 是如何处理应 用程序崩溃的。