pyd-re
pyd文件逆向
具体是看l1nk师傅的博客:https://tttang.com/archive/1641/#toc_pyd_1。**本博客就是按照他的,一步一步来复现。**
pyd是python的链接库文件。
0x00 环境准备
首先写test_for_pyd.py
:
1 | def test_hello(a): |
之后写setup.py
:
1 | from setuptools import setup |
输入python .\setup.py build_ext --inplace
,即可生成test_for_pyd.cp38-win_amd64.pyd
与test_for_pyd.c
。test_for_pyd.c
可以理解为test_for_pyd.py
对应的C代码,而test_for_pyd.cp38-win_amd64.pyd
是对应的库文件。
0x01 变量分析
变量区分为变量名区与变量定义区。
以下是变量名区:
可以看到,这个变量名,真鸡儿全。
以下是变量定义区:
可以看到,Python使用PyObject*
来描述变量。
注:在python中,即使是数字也是一个对象。如果在我们的代码中显式的写了某个数字,并且这个数字不仅仅用于赋值,那这个数字就会被作为一个对象存储。例如,test_hello
与int
函数中数字一共有0,2,4,16
这4个数字,2,16
在表达式中使用了,0
赋值的num
也用在了表达式中,但是4
没有,就是单纯的赋值,因此在变量定义区就只有:
1 | static PyObject *__pyx_int_0; |
而没有static PyObject *__pyx_int_4;
。
最终,这些对象将在全局表中进行关联,如下所示:
在实际逆向工作中,找到这个全局表的位置非常重要,通过此表可以还原出大部分的符号信息。当然,可作为对象的数字也会被初始化,如下:
0x02 函数分析
在pyd中,不同类型的函数有不同的实现形式。所有以__pyx_pf
开头的变量都是python中函数的定义。
以test_hello
为例,首先,此函数会被声明为:
可以看到,test_hello
函数并不会被直接调用,而是会先定义一个proto
(原型)。此原型声明了函数的调用模式,其中第一个参数是__pyx_self
,其实就是描述python这个运行的对象(相当于C++中的self指针)。然后第二个参数表述了要引用的参数。
接着,又用PyMethodDef
定义了函数的其他信息,PyMethodDef
是一个结构体,在逆向过程中,可以通过这个PyMethodDef
所在的表找到当前代码中实现的相关函数。
最后,实现了此函数的wrapper形式(啥是wrapper不必多言)。
下面进入test_hello
的函数体部分:
(1)增加__pyx_int_2
对象的引用计数,表示此处用了这个数字。
(2)将这个对象赋值给__pyx_v_test
,也就是python中定义的test对象。
(3)通过使用方法__Pyx_PyInt_AddObjC
进行整数对象的相加。
(4)完成加法后,其增加了一个临时的引用对象__pyx_t_1
的引用计数。
(5)将答案赋值给了__pyx_v_result
,之后将这个值重新值为了0,相当于完成了result = test + 2
这个流程。
(6)对__pyx_v_t
变量进行赋值。
(7)将__pyx_v_a
对象传入__Pyx_PrintOne
这个函数中,实现print
的调用。
再来看int
函数的调用流程,与test_hello
类型,不同的是在这个函数中存在一些内置的函数(reserverd)以及常见的迭代对象的使用。直接来看不同的部分:
(1)使用__Pyx_PyObject_CallOneArg
调用reversed(string)
,并将结果保存在__pyx_t_1
中。
(2)检查一个字符串是否为List
或者Tuple
。如果是的话,直接将其赋值给__pyx_t_1
,之后会以迭代器的方式调用,并且用__pyx_t_2
标记此时的循环下标;否则,调用PyObject_GetIter
,获取当前对象的可迭代对象,赋值给__pyx_t_1
,此时__pyx_t_3
会记录当前的带对象的下一个迭代对象。并且__pyx_t_2
会被赋值为-1作为标记。
(3)进入一个循环,每次循环的时候首先检查__pyx_t_3
是否为空,如果不为空的话进行迭代逻辑处理,并且将迭代对象交给__pyx_t_4
,否则进行迭代对象的尝试获取。
(4)假设为空,此时确认__pyx_t_1
是否为List
或者Tuple
,若满足其中一项,则通过PyList_GET_SIZE
取出其大小,并且如果大小大于__pyx_t_2
,则条数循环。显然,我们这个时候的__pyx_t_2
作为下标,小于我们的size,于是调用Py*_GET_ITEM
(或者PySequence_ITEM
)将其中元素赋值给__pyx_t_4
,同时自增作为下标的__pyx_t_2
。
(5)调用了一个内置函数find
。可以看到其首先通过函数__Pyx_PyObject_GetAttrStr
,指定需要在__pyx_v_table
找到__pyx_n_s_find
,并且这个值会临时存放在__pyx_t_5
。
(6)通过__Pyx_PyObject_Call2Args
对函数进行了调用。注意,第一个参数是通过调用__Pyx_PyObject_GetAttrStr
找到了方法,然后第二个参数为上一段,找到被迭代对象后,通过__Pyx_XDECREF_SET(__pyx_v_each, __pyx_t_4);
赋值得到的,真正的迭代对象的值。
(7)将运算结果得到的值赋值给__pyx_v_index
。
(8)将__pyx_v_index
与前文的__pyx_v_num
进行相加,完成最后的运算,并且将最终运算结果__pyx_v_num
作为返回值。
补充:函数名会放到一个全局的dict
对象中,从而保证可以从module
中取出对应对象。
0x03 技巧总结
需要关注的数据结构:
__pyx_string_tab
(用于初始化Python符号表)。__Pyx_InitCachedBuiltins
(初始化各类函数)。__Pyx_InitGlobals
(初始化数字)。
留言
- 文章链接: https://wd-2711.tech/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处!