Pwnhub之奇妙的巨蟒 Writeup
本周的Pwnhub延迟到了周一,所以周一中午就看了下这题,是一道Python 的pyc逆向题,思路到挺简单的,但是很花精力
本题是一道pyc逆向题,用uncompyle6没法跑出来,所以猜测又是考python的opcode
之前做过相关研究,也写过一篇blog:http://0x48.pw/2017/03/20/0x2f/
主要是两个参考文档:
1 | import dis, marshal |
我们主要关心上面这三个集合,分别是co_consts
, co_varnames
, co_names
其中:
- co_consts集合中包含的是该模块中定义的各种数据类型,具体的值,比如定义的对象,赋值的字符串/int型数据
- co_varnames表示的是当前作用域的局部变量的变量名
接下来就是看具体的opcode:
1 | dis.disassemble_string(code.co_code) |
但是发现报错,跑不了,我的思路是一句一句看,从opcode.h头文件中可以看出opcode为1字节,再加上操作数的话就是3字节,所以一句指令的长度是1字节或者3字节,所以:
1 | 3]) dis.disassemble_string(code.co_code[: |
我使用上面的方面进行简单的测试,发现有一大堆的JUMP_ABSOLUTE
和JUMP_FORWARD
指令,这时就知道这里有混淆了。
参考文档,我们可以知道JUMP_ABSOLUTE
是跳到绝对地址,JUMP_FORWARD
是下一个地址加上操作数,比如0 JUMP_FORWARD 24 (to 27)
,当前地址0,当前指令是3字节,下一个地址是3,加上24是27,所以执行完这句指令是跳到27,这里我只是举例,在本题中,地址0是从code.co_code[0]开始
该模块的最外层很麻烦,追了一会指令流就看不住了,然后就看定义的函数:
1 | func_list = [] |
随便看了下各个函数的相关信息,发现u函数中有flag相关信息,然后开始逆u函数,首先收集下u函数的相关变量信息:
1 | 1] u = func_list[- |
写了个脚本,自动追踪指令并输出,但是跳过两个JUMP
指令的输出,然后又发现了一个控制流平坦化混淆…….简单的举例下:
1 | 175: |
解释下各个指令的含义:
- LOAD_CONST 10 (10) ==> push co_consts[10]
- STORE_FAST 2 (2) ==> pop co_varnames[2]
- LOAD_FAST 2 (2) ==> push co_varnames[2]
- COMPARE_OP 2 (==) ==> pop x1; pop x2; if x1 == x2: push 1 else: push 0 (该指令的操作数2表示栈上的两个数进行比较)
- POP_JUMP_IF_TRUE 74 ==> pop x1; if x1: jmp 74
翻译成伪代码就是:
1 | DIVIDER = co_consts[10] |
这个就是控制流平坦化混淆,中间有一堆垃圾代码,因为我怕时间来不及就没有写全自动换脚本,是半自动半手工做题,用脚本去掉JUMP混淆,把结果输出到文件中,然后用ctrl+f,去掉控制流平坦化混淆(之后会在我博客中放全自动脚本)
去掉混淆后的代码:
1 | 283: |
第一个分支我们可以翻译出上面的代码,然后把指令调到253,在继续跑脚本:
1 | 682: |
继续跟踪到709:
1 | 324: |
根据到262:
1 | 24: |
根据上面追踪翻译出来的代码,成功还原出u函数:
1 | def u(OOO000OOOOOO00OOO): |
如果输出Good job!
则表示得到flag
所以下面就是取逆向q
, r
, p
三个函数,原理和上面逆向u
函数一样:
1 | def q(O0OOO0O0O00O00OOO): |
q
和r
两个函数,一个是进行decode操作,一个是判断长度,所以判断flag是否正确就在p
函数中,而p
函数是手工最难逆的函数,我从下午6点,逆到了8点,/(ㄒoㄒ)/~~,我应该是采取了最笨的方法,前面提到了,我现在有个自动化的思路,之后会放到我blog中。
1 | def p(hhh): |
然后写个脚本爆破出flag(现在想想,应该可以用z3):
1 | #! /usr/bin/env python |
傻逼的半自动化脚本:
1 | #!/usr/bin/env python2.7 |
自动跑p函数,使用z3跑出flag自动化脚本:
1 | #!/usr/bin/env python2.7 |
思路挺简单的,相当于自己实现一个解释器,实现一个stack,因为我代码中的opcode不全,所以只能针对本题,还有几种思路,比如魔改dis,目前的dis是线性的翻译opcode,可以按照我脚本的思路,当遇到JUMP类指令时,也跟随跳转,但是这个不能去除混淆,混淆还是需要自己写代码去,而我上面自动跑flag的脚本思路是来源于Triton,传入的参数是未知的,就设置为符号变量,当分支判断的时候进行响应的处理,进行动态分析,这样就不需要去混淆。
等我把Triton研究清楚了,说不定能用Triton调试pyc?
Pwnhub之奇妙的巨蟒 Writeup