欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

Python字节码解混淆之反控制流扁平化

来源:本站整理 作者:佚名 时间:2019-09-06 TAG: 我要投稿

上次打NISCCTF2019留下来的一道题,关于pyc文件逆向,接着这道题把Python Bytecode解混淆相关的知识和工具全部过一遍。同时在已有的基础上进一步创新得到自己的成果,这是下篇,更近一步进行还原混淆过的pyc文件。
 
图表示法
目前可以见到最多的解混淆或者去花指令的手段类似于对于指令的匹配替换,甚至于可以说上一篇中的活跃代码分析也是带执行流分析的简单替换过程。但如果我想构建一个对于某种混淆方式的通用解法,只是工作于指令级别始终不够方便。那么需要一种新的方式去处理这些指令。
在这里选用一种基于有向图的表示法,也即在执行流跳转的位置进行分割,使指令之间成块,相互之间用箭头相关联。如下图:

有了这种表示法之后就可以方便的在独立的块之间进行遍历和处理。
 
Bytecode graph
这个工具来自这里,实现了基本的Python字节码到图表示的转换过程,但是相比起来功能较为单薄。
上一篇中把恶意指令全部NOP掉了,现在想把NOP指令用这个库全部去除,首先尝试调用API画出所有函数的图:
import bytecode_graph
from dis import opmap
import sys
import marshal
pyc_file = open(sys.argv[1], "rb").read()
pyc = marshal.loads(pyc_file[8:])
bcg = bytecode_graph.BytecodeGraph(pyc)
graph = bytecode_graph.Render(bcg, pyc).dot()
graph.write_png('example_graph.png')
结果崩溃了:
Traceback (most recent call last):
  File "./gen_graph.py", line 9, in
    graph = bytecode_graph.Render(bcg, pyc).dot()
  File "/tmp/flare-bytecode_graph/bytecode_gra/render.py", line 112, in dot
    lbl = disassemble(self.co, start=start.addr, stop=stop.addr+1,
AttributeError: 'NoneType' object has no attribute 'addr'
查看源码发现是渲染模块里获取block的函数写的时候没有考虑到一个corner case,修复之后成功生成图像:

首先观察执行流可以很明显的看出是进行过控制流扁平化处理的,然后尝试阅读文档去除多余的NOP分支。
去除NOP的代码:
def remove_nop_inner(co):
    bcg = bytecode_graph.BytecodeGraph(co)
    nodes = [x for x in bcg.nodes()]
    for n in nodes:
        if n.opcode == NOP:
            bcg.delete_node(n)
    return bcg.get_code()
def remove_nop(co):
    #co = remove_nop_inner(co)
    inner = list()
    for i in range(len(co.co_consts)):
        if hasattr(co.co_consts[i], 'co_code'):
            inner.append(remove_nop_inner(co.co_consts[i]))
        else:
            inner.append(co.co_consts[i])
    co.co_consts = tuple(inner)
    return co
再次运行然后再次崩溃:
Traceback (most recent call last):
  File "./de.py", line 163, in
    mode = remove_nop(mode)
  File "./de.py", line 108, in remove_nop
    inner.append(remove_nop_inner(co.co_consts[i]))
  File "./de.py", line 100, in remove_nop_inner
    return bcg.get_code()
  File "/home/fx-ti/ctf/nisc2019/py/deconfus/flare-bytecode_graph/bytecode_gra/bytecode_graph.py", line 225, in get_code
    new_co_lineno = self.calc_lnotab()
  File "/home/fx-ti/ctf/nisc2019/py/deconfus/flare-bytecode_graph/bytecode_gra/bytecode_graph.py", line 173, in calc_lnotab
    new_offset = current.co_lnotab - prev_lineno
TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'
这个错误就很有意思了,和之后成功还原仍然不能使用uncompyle6得到源码有关。都是在对co_lnotab这个部分的解析中出现了错误导致的崩溃。
co_lnotab的文档
Python通过co_lnotab将字节码和源码行数对齐,服务于源码调试。co_lnotab中的数据两字节为一组,分别是字节码的增量偏移和源码的增量偏移。
>>> import marshal
>>> f = open('../../mo.pyc')
>>> f.read(8)
'x03xf3rnLiT\'
>>> c = marshal.load(f)
>>> list(map(ord,c.co_lnotab))
[12, 2, 9, 6, 9, 5, 9, 3, 9, 3, 9, 18, 37, 2, 18, 2]
也即都从0开始计算,首先字节码增加了12条指令,源码就到了第2行,这样不断递增得到的对应关系。
>>> dis.dis(c.co_code)
          0 JUMP_ABSOLUTE     670
          3 NOP
          4 NOP
          5 NOP

[1] [2] [3]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载