0%

Pydbg学习笔记一 ——调试器基础使用

Pydbg学习笔记一 ——调试器基础使用

前面Ctypes系列的笔记其实出来第一篇以外都应该算是Pydbg模块的一部分,通过对win32进程管理相关API的学习,我们实现了包括断点、读写内存数据、进程控制、附加调试等一系列的功能。

Pydbg库是在上面基础上的封装,有更完善更强大的功能。


安装Pydbg库

由于Pydbg库只支持到python2版本,并且现在已经停止支持了,所以一下的实验只能基于python2来做(注意从头到尾无论是python还是被调试的二进制程序都是32位的)。

在查找资料的时候,我也在github上发现了基于python3pydbg版本。亲测可以安装成功,但是Pydbg所依赖的pydasm库(也是仅支持到python2.x),在我的电脑上一直没有编译成功,所以只能在python2下完成剩下的工作了。

安装步骤:

pydbg依赖pydasmpaimei这两个库,安装步骤比较繁琐,这里找到了一个方便的第三方封装版本,一键安装即可。

首先在这里下载源码压缩包:

解压缩后进入pydbg-pydasm-paimei目录并运行python setup.py install即可编译安装成功。

安装有问题请访问原仓库READEME

pydbg库的使用方法与之前自己实现的调试代码基本一样。


捕获断点后的事件处理函数

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

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

address:断点的地址。

description :可选参数,用来给每个断点设置唯一的名字。

restore:决定了是否要在断点被触发以后重新设置。

handler:指向断点触发时候调用的回调函数。断点回调函数只接收一个参数,就是pydbg()类的实例化对象。所有的上下文数据,线程,进程信息都在回调函数被调用的时候,装填在这个类中。

还是以printf_loop.py作为被调试对象,通过我们自定义的回调函数,试试修改模块中printf()的打印值。

1
2
3
4
5
6
7
8
9
10
11
# printf_loop.py
from ctypes import *
import time

msvcrt = cdll.msvcrt
counter = 0

while 1:
msvcrt.printf(b"Hello %d\n" % counter)
time.sleep(1)
counter += 1

可知printf()函数只接收了一个参数,那么当我们在printf()函数入口下断点之后,esp + 0x4中的地址中存放的值就是输入的Hello %d\n。(注意这里esp+4是栈空间在内存中的位置,其值为parameter1的地址)

首先用x32dbg附加对应模块调试,验证我们的猜想:

image-20200110205356638.png

接下来运行我们的脚本,强行修改内存,改变程序的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# my_test.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 + 0x4 as a DWORD
parameter_addr = dbg.context.Esp + 0x4
# 读取参数在栈空间中的地址(注意返回值是str)
parameter_stack_addr = dbg.read_process_memory(parameter_addr,4)
# 读取栈空间中的参数(counter的值),注意要有一步将str转为int的步骤
counter = dbg.read_process_memory(struct.unpack("L",parameter_stack_addr)[0],8)

# When using 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]
# 这里其实不用unpack也可以
print "Counter: %s" % counter

# Generate a random number and pack it into binary format
# so that it is written correctly back into the process
# 生成我们要替换的字符串
random_counter = "World %d\n" %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(struct.unpack("L",parameter_stack_addr)[0],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 my_test.py
Hello 0
Hello 1
Hello 2
Hello 3
Hello 4
Hello 5
Hello 6
Hello 7
World 42
World 47
World 34
World 66
World 13
World 30
World 78
World 46
World 70
World 97
Enter the printf_loop.py PID: 15620
Counter: Hello 8
Counter: Hello 9
Counter: Hello 10
Counter: Hello 11
Counter: Hello 12
Counter: Hello 13
Counter: Hello 14
Counter: Hello 15
Counter: Hello 16
Counter: Hello 17
Counter: Hello 18
Counter: Hello 19
Counter: Hello 20
Counter: Hello 21
Counter: Hello 22
Counter: Hello 23
Counter: Hello 24

很明显当数字加到8的时候,我们的修改操作起了作用,我们在被调试程序内部修改了输出的内容。