歡迎來到 黑吧安全網 聚焦網絡安全前沿資訊,精華內容,交流技術心得!

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.nkppsz.live)登載此文出于傳遞更多信息之目的,并不代表本站贊同其觀點和對其真實性負責,僅適于網絡安全技術愛好者學習研究使用,學習中請遵循國家相關法律法規。如有問題請聯系我們,聯系郵箱[email protected],我們會在最短的時間內進行處理。
  • 最新更新
    • 相關閱讀
      • 本類熱門
        • 最近下載
        秒速时时彩骗局