一道cython
配合觅马学rsa
攻击的题目。由于本人稀烂的密码学能力,所以本篇主要分析cython
逻辑。
rsa-factory
提供文件
greet.o
文件,似乎是未连接到库和重定位,从而生成.so
文件的半成品。无法用来调试(虽然我还没研究cython
怎么调试)out.txt
文件,给了rsa
相关数据。与本博客的目标无关。
1 | # out.txt |
定位
扔到IDA7.6
分析
此二进制文件由于没有生成正式.so
文件,没有进行过strip
操作,所以保留了符号表。很大的帮助。
一开始自动进入main
函数。没有用的。
在函数窗口中找到__pyx_pymod_exec_test
函数。此函数代表了原Python文件的主逻辑。
恢复代码
分析起来一步一步的真的很烦,讲解起来更麻烦。直接写点总结性经验吧。
下面先贴一下我翻译后的脚本。和源码已经很接近了,不过双while
循环上面还是用了goto
伪代码。
1 | from typing import Tuple |
上面的goto
伪代码其实是:
1 | while True: |
即实际上外面还有个while
,一共两个。
IDA伪代码里面反编译出来,在开头似乎有个
while(2)
,这个2可能代表了其前驱有2个基本块,再加上IDA里面的goto
语句,暗示了实际上就是个双while
循环。
难点实际在keygen()
函数中。主函数中关于模块导入,函数定义等都是很鲜明的。以前博客也讲过很多次。
经验总结
如何定位__pyx_pymod_exec
函数
此文件似乎是个可执行文件,带有main
函数。但是没什么用。
正确的方法是:
打开导出界面,找到PyInit_test
函数
函数逻辑很小,就这点。
1 | __int64 PyInit_test() |
在_pyx_moduledef
下找到__pyx_moduledef_slots
1 | .data:000000000020ED60 __pyx_moduledef dq 1 ; DATA XREF: PyInit_test↑o |
默认找到第二个地址指针即可。
找到__pyx_pymod_exec
函数指针
1 | .data:000000000020EDE0 __pyx_moduledef_slots dq 1 ; DATA XREF: .data:000000000020EDA8↑o |
第二个函数就是。
解决PyTuple_New()
函数传参判定出问题
修改PyTuple_New()
的函数原型为
1 | _QWORD *PyTuple_New(_QWORD, ...) |
即可。第一个参数是元组的容量。后面三个点代表有可能会传入多余的PyObject*
作为初始化。
向PyTupleObject
传入数据
如上,若PyTuple_New
是只传入了初始化容量,那么后面还需要程序再次传入参数才行。
给一个2参数的元组:
1 | v82 = PyTuple_New((int)v78 + 2); // 一个参数,作为容量 |
只要看到取值从[3]
开始的,就是传参了。[3]
对应元组第一个参数,往后类推。
确定函数调用
全都内联了,又臭又长。
这么长,全是PyObject_Call
的内联形式。
1 | if ( (_UNKNOWN *)v9[1] == &PyMethod_Type ) // 是method函数 |
关于如何确定调用函数时的传参:
- 首先第一个参数通常都是函数指针,交叉引用在上方找到此函数指针,点进去看看这个函数要什么参数。
- 找
(_QWORD *)_Pyx_PyObject_Call_constprop_30(v9, v83);
这类传入一个元组参数的调用函数,然后通过上面确定元组元素的方法来确定。
学会运用交叉引用确定变量
Cython
代码又臭又长,有的变量隔了五六百行才被调用,所以需要学会交叉引用。
在伪代码上交叉引用:按行排序从上到下,寻找距离本行较近,且是数据写入w
的行,且不能处在错误处理行中。
错误处理:
1 | v15 = (__int64 *)_Pyx_PyCFunction_FastCall(v9, &dbit - v78, v81); |
有时由于反汇编生成goto
语句,所以可能出现向上跳转。从而按行排序没法找到正确赋值语句。所以还需要从汇编上看。
从汇编CFG上交叉引用:
比如查:
1 | v14 = (_QWORD *)PyNumber_FloorDivide(v9, w); |
汇编:
1 | mov rsi, [rsp+0D8h+var_C8] |
则交叉引用[rsp+0D8h+var_C8]
,寻找w
写入且距离最近的代码,即可()。
都不行,就看CFG,看前驱块有没有赋值。
比如往上翻翻找到
1 | mov rsi, cs:__pyx_int_1 |
即可确定。
删除冗余代码
IDA里面不给删代码,但是自己可以复制一下伪代码,贴到某个.cpp
文件,用VS Code
打开,然后慢慢删去一些无用代码。比如错误处理,一些函数指针调用等。
比如
1 | else |
其中
1 | v11 = (*v9)-- == 1LL; |
1 | if ( !v15 ) |
都是没用的。
确定传参
在exec
函数中使用PyCFunction_New
声明的函数没法确定传参。
但是点进函数进去,看开头:
1 | _QWORD *__fastcall _pyx_pw_4test_1keygen(__int64 a1, _QWORD *a2, __int64 a3) |
其中PyDict_GetItem_KnownHash
可以作为传参确定点。从a3
取值。
后话
是空白在0x401知识星球上发的题目。已经有wp了。本题据说是有密码大手子找到原题脚本了。否则像我这样慢慢恢复真的很麻烦。
- 本文作者: Taardis
- 本文链接: https://taardisaa.github.io/2022/02/17/rsa-factory-rh/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!