一次基于ast的python反混淆尝试
0x00 前言
之前看到一个python混淆的网站,感觉很不错,就拿过来研究了一下,当时霸王硬上弓(指扔给idea美化代码之后然后插print)硬是给撅出来了
当时硬print的时候就在想,能否以python解释器的方法来处理分析这些内容,然后就接触到一个东西”AST”。
最近刚好看到他们家换了新的混淆方法,就拿过来好好研究怎么用AST分析了。
0x01 前置内容
Python 源码编译过程
Python 源码到机器码的过程,以 CPython 为例,编译过程如下:
将源代码解析为解析树(Parser Tree)
将解析树转换为抽象语法树(Abstract Syntax Tree)
将抽象语法树转换到控制流图(Control Flow Graph)
根据流图将字节码(bytecode)发送给虚拟机(ceval)
可以使用以下模块进行操作:
ast 模块可以控制抽象语法树的生成和编译
py-compile 模块能够将源码换成字节码(编译),保存在 __pycache__ 文件夹,以 .pyc 结尾(不可读)
dis 模块通过反汇编支持对字节码的分析(可读)
一些简单代码的ast分析
这里先上一些简单的例子,为后面的分析铺垫。
1 | import ast |
得到的输出是这样的:
1 | python .\test.py |
大致看一看,不难发现,ast处理后的python其实已经和bytecode都差不多了,相对应的位置都一样,只不过可读性比字节码好上了不少,看起来还特别的直观。
Python ast的抽象文法:
这里就摘了一部分,是这次反混淆需要用到的
Abstract Grammar
mod | Module Interactive Expression FunctionType |
stmt | FunctionDef<brReturn Deleate Assign … |
expr | BllOp NamedExpr Lambda … |
… | … |
在我们一个python代码里,出现的主要还是 stmt 里的内容,定义函数FunctionDef
,赋值Assign
,在这些stmt里面是各种expr
结合上面的语句大致对照一下:
1 | Import(names=[alias(name='requests')]), --> import requests |
0x02 开淦
这是一个print(1+1)
被加密&混淆后的结果
这里先剧透一下,上面的obfuscate
在第一层用不上,这里就先看前面的加密部分。
大致扫了一眼,整个代码里面,全是变量定义外加lambda
,最后执行的表达式(expr)也被混在了这一行里面,这里仿照ast.py
中注释给出的代码样例,搓出Assign
和Expr
的抽象文法
1 | class AssignExtractor(ast.NodeVisitor): |
开始分析:
先来一个print(1+1)
1 | #pip install pycryptodome , It works only v3.11 Above. |
乍一看,用了大量的lambda把代码写成了一行,想提取里面的代码很难,我第一次破的时候是直接扔到pycharm里面格式化到处插print
看变量的值,为了对准lambda
表达式后面的;
挺痛苦的,上AST看一下。
因为里面的代码主要是赋值(Assign)语句,最后执行的代码(Expr)跟在了lambda
的分号后面了,先大致拉出来看看:
1 | from extractors import AssignExtractor,ExprExtractor |
代码的结构就能很轻松地找出来,之后便可以通过在代码里插print
的方法来确定我被混淆的代码最后哪里运行的
1 | ____(''.join((chr(int(OO00O0OO00O0O0OO00 / 2)) for OO00O0OO00O0O0OO00 in [202, 240, 202, 198] if _____ != ______)))(f"""____("".join(chr(i) for i in [101,120,101,99]))({____(base64.b64decode(codecs.decode(zlib.decompress(base64.b64decode(b'eJw9kN1ygjAUhF8JIkzlMo6mEnIcHVIM3AGtoPIT2wSSPH2p7fTu252d2T3n3MkyK896dLvrSMIeaGxEGn0l/rpiLu3hlXm5yxDmO8tQZIDoeUQLr4oWePxk8VZfBpr9af8mXdzLTk8swRbP25bNzPvP8qwWJDRA8RX4vhLkfvuk0QRl3DOUekDC9xHZVnBcyUnXY7mtBrIOBDEKXNRl3KiBBor25l5MN7U5qSA/HsJiVpfsVIQ/Hj4dgoSYOndx+7tZLZ2m3qA4AFpUD6RDsbLXB2m0dPuPZa8GblvoGm/gthdI+8PxyYtnXqRLl9uiJi+xBbqtCmKm8/K3b7hsbmQ=')).decode(), ''.join((chr(int(i / 8)) for i in [912, 888, 928, 392, 408]))).encode()))})""") |
把这段代码单独拎出来,看看里面长啥样:
这里用graphviz
库来做个可视化 Pythonの抽象構文木をGraphvizで可視化する
pip install graphviz
1 | from graphviz import Digraph |
当这张图拉出来了之后,一目了然,把这个搜索改成广搜,来一层一层拨开
1 | import random ,base64,codecs,zlib;pyobfuscate="" |
这里想到用eval
直接打印出来变量,直接看
我在整理笔记到这里的时候,突发奇想,既然我都能把整个逻辑弄成图,为啥不直接把对应的值也弄上去,到时候分析起来就更方便了,也就不需要到处print看值
1 | def bfs_visit(root): |
这不比命令行里一条条对数据方便多了
仔细看看,左边NoneType
上方是exec
,右边是个JoinedStr
,结合这里面一堆call
,不难看出最后执行的是exec(JoinedStr)
但是右边又套娃套了好几层
用随机数特定的种子来生成特定的文字,玩的挺脏的,得到反混淆后的代码:
1 |
|
简单一看,又想骂娘https://pyobfuscate.com/public/pyd2/aes.txt
代码还是那一些,同样的思路继续干,这里把最后一个Expr
拿出来吧,原代码太长了
1 | _______.___________________(_______.________________(_______.________________________())[:______._ * ______._] + _______.________________(_______._______________())[______.___] + _______.________________(_______._________________([]))[______.___] + _______.________________(_______.________________________())[______._ ** ______._ + ______.__])(____, _______._______________________(), _______.________________(_______.________________________())[______._ ** ______._ + ______.__] + _______.________________(_______.______________())[______._] + _______.________________(_______.________________________())[______._ ** ______._ + ______.__] + _______.________________(_______.________________________())[______.___]) |
左边exec
右边compile
看到这里,compile
上加载了一个前面代码的变量,这里再改进一下代码,同时更换输出格式为svg
,避免因为图片过大报错
1 | import ast |
这里放张svg图,建议新建窗口打开
找到了,因为变量类型是ast.Moudle
,就用ast.unpares
还原代码
print(ast.unparse(____))
1 | try: |
仔细审审的话,发现上面的try必定出错,不需要管,最后得到解密
代码是:
1 | import base64, os, hashlib, random |
嗯哼,这么强的混淆只是为了保护一个解密器的代码。
0x03 复盘
其实只是破解这个混淆的话,根本不需要上ast
来大炮打蚊子,直接把以下内容送进去混淆然后运行:
1 | import pdb |
dis一下
ast分析确实有很多好处的,首先是整个代码的就变得非常直观了,比较适合研究这个混淆的原理,能一眼看出来我这个运行的代码是如何被一步一步拼接出来的,比如这个compile
最后生成分析图像的代码:
1 | from queue import Queue |