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 许可协议。转载请注明出处!