pyd 逆向
what’s pyd
.pyd 文件本质上是一个 Windows 的 DLL 文件, 通过 C/C++ 编译器(如 MSVC)把 C/C++ 代码编译成二进制形式,实现了 Python 扩展模块的接口,可以通过 import xxx 的方式像普通的 .py 文件那样导入使用
preparation
先在 windows 下自己编译一个带有调试信息的 pyd 文件,主要用于后续 bindiff 比较恢复符号
注意编译时 python 的版本尽量与要逆向的文件一致(可以在 ida string 里面搜 dll 看到类似”python312.dll”的字符串确定题目使用的 python 版本)
准备 test_for_pyd.py 文件 和 setup.py 文件
1 | |
1 | |
使用以下命令编译生成 pyd 文件:
1 | |
但是这个 pyd 扔进 ida 之后没有看到 pyx 相关的符号 :sob:
在 linux 下编译了一个类似的 so 文件
创建一个虚拟环境 python3 -m venv myenv
激活虚拟环境 source myenv/bin/activate
在虚拟环境中安装 cython 和 setuptools:
sudo apt install cython3
python setup.py build_ext --inplace
Linux 下的 setup.py :
1 | |
使用以下命令编译生成 so 文件: 1
python setup.py build_ext --inplace --force
test_for_pyd.cpython-312-x86_64-linux-gnu.so
除此之外同时还生成了 test_for_pyd.c 和 test_for_pyd.html
把这个 so 扔进 ida 里面,发现与之前的那个 pyd 文件相比多了不少符号信息

在 ida 的 File->Produce File->Create C Header File 导出结构体
用 ida 打开要逆向的 pyd 文件,在 File->Load File->Parse C Header File 中导入从 so 文件中导出的 .h 文件(如果遇到报错就修复一下)
CTF pyd 逆向分析
ciscn 2024 rand0m
做法一:静态分析
先看看 string,注意到里面有好几个可疑的字符串和数字,x 交叉引用,找到数字所在函数(sub_xxx2BD0),里面通过 PyLong_FromLong() 和 PyLong_FromString() 创造了一系列整数对象,将这些对象存储到一个全局指针数组 off_7FF90BEAB688[29] ~ [49],用于后续查表

rand0m.rand0m:在 sub_xxx12B0 里面,找到关键加密部分





提取出关键部分:
1 | |
整理之后就是
1 | |
接着看 check 函数,注意到其上方有一个 sub_xxx1960

点进去之后发现它创建了一个 python 列表,存入了八个数据

继续往下看到比较函数
PyObject* PyObject_RichCompare(PyObject *v, PyObject *w, int op)
v,w 分别代表的是比较的两个对象,op 是比较操作的枚举值
具体如下:
1 | |
用 z3 爆破求解
1 | |
flag 为:flag{813a97f3d4b34f74802ba12678950880}
做法二:动调+frida
基本思路
用 frida hook(或者 ida 打条件断点)pyd 中的运算和比较相关的函数名来 trace,根据 trace 的结果再结合动调梳理出加密逻辑
由于 pyd 文件不能直接动调,这里采用 attch python 进程的方式
可以在附件给的 challenge.py 中加上
import sys,print(sys.executable)和import os,print(os.getpid()),以便查看运行该文件的 Python 解释器路径和 pid,然后运行 .py 文件之后去 ida,选择 Local Windows debugger 作调试器,在 Debugger -> Attach to Process 中选择对应的 python 进程(| 刚刚解释器的路径下的 python.exe)进行调试
frida 的话可以运行 .py 文件后执行命令 frida-ps | findstr python 找到 pid
先运行 题目给的 challenge.py 文件,得到 pid
然后运行 hook 脚本
1 | |
frida hook 脚本 rand0m.js
1 | |
运行后在 ida 中附加进程开始动调,input 的内容稍微可以长一点 #####
trace 结果 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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131[*] Attached to 35164
[*] Python Crypto Trace Script Started
[*] Script loaded. Monitoring Python operations...
[*] Press Ctrl+C to stop and analyze.
[*] Installing Python operation hooks...
[*] Found Python module: python312.dll at 0x7ff8ab110000
[*] Starting trace...
[*] Successfully hooked 12/12 functions
[0001] BITWISE | PyNumber_Xor
INPUT: 0x100001FFEAB611810000000000000001000000011111111
PARAM: 0x100001FFEAB6118100000000000000020000220E808A7A00000000000000100001FFEAB61181000000000000000100000009E3779B9
OUTPUT: 0x80001FFEAB6118100000000000000010000220E7B902A00000000000000100001FFEAB61181000000000000000100000008F2668A8
[0002] BITWISE | PyNumber_And
INPUT: 0x100001FFEAB6118100000088E81846B00000000459042280000000000000080001FFEAB6118100000088E8184670000000111111110
PARAM: 0x100001FFEAB611810000000000000001000000098D24B3A0000000000000100001FFEAB6118100000000000000020000000FA3AFFFF
OUTPUT: 0x100001FFEAB6118100000088E81846B0000000010101110
[0003] ARITHMETIC | PyNumber_Add
INPUT: 0x100001FFEAB6118100000088E81846B0000000010101110
PARAM: 0x80001FFEAB6118100000000FFFFFFFF000000000000001
OUTPUT: 0x100001FFEAB611810000000000000001000000010101111
[0004] POWER | PyNumber_Power
INPUT: 0x100001FFEAB6118100000000000000010000220C011E4CD
PARAM: 0x80001FFEAB611810000000000000002000000040010001
OUTPUT: 0x0000000016C5E4CD
[0005] OTHER | PyNumber_Remainder
INPUT: 0x0000000016C5E4CD
PARAM: 0x80001FFEAB6118100000000000000010000001111111100000000000000100001FFEAB6118100000088E81846700000000FFFFFFFD
OUTPUT: 0x80001FFEAB6118100000000000000010000220E7B902A00000000000000100001FFEAB6118100000000000000010000000F9704BDF
[0006] COMPARE | PyObject_RichCompare
INPUT: 0x100001FFEAB611810000000000000001000000010101111
PARAM: 0x80001FFEAB611810000000000000002000000052287F38
OUTPUT: 0x0000000000000000
[0007] BITWISE | PyNumber_Xor
INPUT: 0x100001FFEAB611810000000000000001000000022222222
PARAM: 0x100001FFEAB6118100000000000000020000220E808A7A00000000000000100001FFEAB61181000000000000000100000009E3779B9
OUTPUT: 0x10768767A969790B0000000000000000000000000000000000000687675A50000000000000100001FFEAB6118100000088E81846B00000000BC155B9B
[0008] BITWISE | PyNumber_And
INPUT: 0x10768767A969790B000000000000000000000000000000000000222222220
PARAM: 0x100001FFEAB611810000000000000001000000098D24B3A0000000000000100001FFEAB6118100000000000000020000000FA3AFFFF
OUTPUT: 0x0000000022222220
[0009] ARITHMETIC | PyNumber_Add
INPUT: 0x0000000022222220
PARAM: 0x80001FFEAB6118100000000FFFFFFFF000000000000002
OUTPUT: 0x100001FFEAB611810000000000000001000000022222222
[0010] POWER | PyNumber_Power
INPUT: 0x100001FFEAB6118100000088E81846700000001001782AB
PARAM: 0x80001FFEAB611810000000000000002000000040010001
OUTPUT: 0x00000000151B82AB
[0011] OTHER | PyNumber_Remainder
INPUT: 0x00000000151B82AB
PARAM: 0x100001FFEAB6118100000088E81846700000001001782AB0000000000000080001FFEAB6118100000000000000010000000FFFFFFFD
OUTPUT: 0x100001FFEAB6118100000088E81846D00000000113A5AEB
[0012] COMPARE | PyObject_RichCompare
INPUT: 0x100001FFEAB611810000000000000001000000022222222
PARAM: 0x100001FFEAB6118100000000000000010000220E807DCB00000000000000100001FFEAB61181000000000000000100000004A30F74D
OUTPUT: 0x0000000000000000
[0013] BITWISE | PyNumber_Xor
INPUT: 0x100001FFEAB611810000000000000001000000033333333
PARAM: 0x100001FFEAB6118100000000000000020000220E808A7A00000000000000100001FFEAB61181000000000000000100000009E3779B9
OUTPUT: 0x80001FFEAB6118100000000000000010000220E8080B300000000000000100001FFEAB6118100000000000000010000000AD044A8A
[0014] BITWISE | PyNumber_And
INPUT: 0x0000000333333330
PARAM: 0x100001FFEAB611810000000000000001000000098D24B3A0000000000000100001FFEAB6118100000000000000020000000FA3AFFFF
OUTPUT: 0x10768767A969790B000000000000000000000000000000000000032323330
[0015] ARITHMETIC | PyNumber_Add
INPUT: 0x10768767A969790B000000000000000000000000000000000000032323330
PARAM: 0x80001FFEAB6118100000000FFFFFFFF000000000000003
OUTPUT: 0x100001FFEAB611810000000000000001000000032323333
[0016] POWER | PyNumber_Power
INPUT: 0x80001FFEAB61181000000000000000100000010015A089
PARAM: 0x80001FFEAB611810000000000000002000000040010001
OUTPUT: 0x0000000015BDA089
[0017] OTHER | PyNumber_Remainder
INPUT: 0x0000000015BDA089
PARAM: 0x80001FFEAB61181000000000000000100000010015A0890000000000000080001FFEAB6118100000000000000010000000FFFFFFFD
OUTPUT: 0x80001FFEAB6118100000000000000010000220E8080B300000000000000100001FFEAB6118100000000000000010000000592EC804
[0018] COMPARE | PyObject_RichCompare
INPUT: 0x100001FFEAB611810000000000000001000000032323333
PARAM: 0x100001FFEAB6118100000000000000010000000423A1268
OUTPUT: 0x0000000000000000
[0019] BITWISE | PyNumber_Xor
INPUT: 0x100001FFEAB6118100000000000000010000220E80684E00000000000000100001FFEAB611810000000000000001000000044444444
PARAM: 0x100001FFEAB6118100000000000000020000220E808A7A00000000000000100001FFEAB61181000000000000000100000009E3779B9
OUTPUT: 0x10768767A969790B0000000000000000000000000000000000000323233300000000000000080001FFEAB6118100000088E81846B00000000DA733DFD
[0020] BITWISE | PyNumber_And
INPUT: 0x100001FFEAB6118100000088E81846D00000000000000010768767A969790B000000000000000000000000000000000000444444440
PARAM: 0x100001FFEAB611810000000000000001000000098D24B3A0000000000000100001FFEAB6118100000000000000020000000FA3AFFFF
OUTPUT: 0x0000000040004440
[0021] ARITHMETIC | PyNumber_Add
INPUT: 0x0000000040004440
PARAM: 0x80001FFEAB6118100000000FFFFFFFF000000000000004
OUTPUT: 0x3569453C2D9A35111CBA10000000000000180001FFEAB6118100000088E816B5700000B24C1F4C8F8C13326F00000000000000040004444
[0022] POWER | PyNumber_Power
[0023] OTHER | PyNumber_Remainder
INPUT: 0x0000000036634E67
18100000088E81846700000000FFFFFFFD
OUTPUT: 0x10768767A969790B0000000000000000000000000000000000004444444400000000000000100001FFEAB6118100018100000088E81846700000000FFFFFFFD
OUTPUT: 0x10768767A969790B0000000000000000000000000000000000004444444400000000000000100001FFEAB6118100000088E81846D000000007ACF1933
[0024] COMPARE | PyObject_RichCompare
[0024] COMPARE | PyObject_RichCompare
INPUT: 0x87BBEC3CD29C7BC326BE100000000000001800 INPUT: 0x87BBEC3CD29C7BC326BE10000000000000180001FFEAB6118100000088E816B5A00000B24C1F4C8F8C13326F0000001FFEAB6118100000088E816B5A00000B24C1F4C8F8C13326F00000000000000040004444
PARAM: 0x80001FFEAB6118100000000000000020000000400100010000000000000080001FFEAB611810000000000000001000000088108807
OUTPUT: 0x0000000000000000
从 trace 中可以发现一些关键数据,并且观察出输入长度应该是 32,分成了四组,这里的 trace 结果并没有看到左移和右移的操作,可以动调分析去找找




爆破脚本
1 | |