基于AST的Python代码混淆技术详解
1. 概述
本文档详细介绍了基于抽象语法树(AST)的Python代码混淆技术,涵盖变量名混淆、属性名混淆和控制流混淆三个主要方面。通过操作AST节点实现代码保护,提高逆向工程难度。
2. 变量名混淆
2.1 变量分类与识别
Python变量分为以下几类:
- 内置变量(
__builtins__包含的) - 全局变量
- 局部变量
- 导入模块、方法
AST节点表示:
- 读取变量:
Name(id=xx, ctx=Load()) - 设置变量:
Name(id=xx, ctx=Store())
2.2 内置变量混淆方法
内置变量可通过xx in dir(__builtins__)判断,混淆方式:
# 方法1:直接重命名
obfname = xx
# 方法2:使用getattr
obfname = getattr(__builtins__, xx)
# 方法3:使用__dict__
__builtins__.__dict__[obfname] = __builtins__.__dict__[xx]
后两种方法将变量以字符串形式表示,便于后续字符串混淆。
2.3 导入变量混淆
导入变量有两种AST节点:Import和ImportFrom
ImportFrom节点处理(from xx import yy, zz, ...)
- 记录xx模块的所有导入项
- 对之后出现的变量(不在内置、全局、局部变量中),如果在xx中,使用:
yy = getattr(xx, yy) # 进行导入
2.4 全局变量与局部变量处理
通过是否进入FunctionDef节点判断是否处于函数定义中:
-
设置变量时:
- 如果不在导入变量、内置变量之列,考虑全局或局部变量
- 处于函数定义中:设置局部变量,否则为全局变量
-
读取变量时:
- 依次检查是否为:导入变量 → 局部变量 → 全局变量 → 内置变量 → ImportFrom导入项
2.5 特殊变量处理
For循环中的临时变量(i, k, v等)需要特殊处理,使用处理函数中局部变量的思路处理以下节点:
ListCompDictCompSetCompFor
3. 属性名混淆
3.1 基本混淆方法
对于属性名,可通过以下方式混淆:
obfname = xx.yy
# 或
xx.obfname = xx.yy
需要区分模块属性和类属性:
-
模块属性混淆:
xx.__dict__[obfname] = xx.__dict__[yy] -
类属性混淆:
gc.get_referents(xx.__dict__)[0][obfname] = gc.get_referents(xx.__dict__)[0][yy]
3.2 Attribute节点处理
属性名混淆出现在访问Attribute的AST节点,混淆定义需要出现在该节点之前。设置三个存储新AST节点的列表:
builtin_attribute_nodes:存储__builtins__属性混淆的新节点class_attribute_nodes:存储类属性混淆的新节点module_attribute_nodes:存储模块属性混淆的新节点
3.3 不同value类型的处理
根据Attribute节点的value类型采取不同策略:
-
Name节点:
- 模块是
__builtins__:以getattr形式混淆 - 属性是
__dict__:以getattr形式混淆 - 模块是导入变量:以
__dict__形式混淆 - 类是内置变量:以
get_referents形式混淆 - 其他情况:无法确定属性所属类(未做变量类型确定)
- 模块是
-
Constant/List/Dict节点:
- 对应Python常见变量类型,可直接确定属性对应类
- 以
get_referents形式混淆
-
Call节点:
- 如果调用的函数未做返回值类型定义,无法确定属性所属类
4. 控制流混淆
4.1 基本思路
基于OLLVM控制流混淆思路,在py2cfg和AST基础上实现:
- 混淆单位为函数
- 通过py2cfg获取每个函数的控制流图
- 修改控制流结构,增加分析难度
4.2 基本块处理
对每个基本块进行以下操作:
- 为每个块生成唯一ID(自定义随机数生成器),作为OLLVM中的
switchVar - 对循环节点的基本块进行特殊处理
4.3 循环节点处理
4.3.1 循环头节点分析
判断循环头(While或For)的后继节点数量:
- 一个后继节点:循环结束后无其他语句,生成含
Return None的后继基本块 - 两个后继节点:第一个为循环体中第一个节点,第二个为循环结束的下一个节点
4.3.2 循环体分析
使用BFS搜索循环体中所有基本块:
- 加入队列条件:非循环头节点、非循环结束的下一个节点、非遍历过的节点
- 对于遍历到的节点:
- 循环体后仍有节点:后继节点为循环结束的下一个节点,则为break节点
- 循环体后无节点:后继节点数为0且含Break语句,则为break节点
4.3.3 break节点处理
- 含多条语句:删去break语句
- 只有一条语句:删去该节点,将前驱节点连接到后续节点(保留条件跳转关系)
4.3.4 continue节点处理
- 识别:所有不在循环体中且为循环头节点的前驱节点
- 处理方法与break节点相同
4.4 While循环处理
4.4.1 有条件While循环(判断为Compare节点)
- 生成If节点,条件与While节点相同
- 复制While节点的跳转关系
- 增加跳转:If节点 → 循环结束的下一个节点
4.4.2 无条件While循环(判断为永真式)
-
分析前驱节点:
- 在循环体中:循环体中的最后一个节点
- 不在循环体中:执行循环体前的节点
-
更新节点连接:
- 删除:执行循环体前的节点 → While节点
- 增加:执行循环体前的节点 → 循环体中第一个节点
- 删除:While节点 → 循环体中第一个节点
- 删除:循环体中的最后一个节点 → While节点
- 增加:循环体中的最后一个节点 → 循环体中的第一个基本块
4.5 For循环处理
将for循环改为iter + next + if + break的形式:
-
分析前驱节点:
- 在循环体中:循环体中的最后一个节点
- 不在循环体中:执行循环体前的节点
-
生成新节点:
iter节点:iter_var = iter(for_iter)next节点:step_var = next(iter_var, None)if节点:if step_var is not Noneassign节点:for_target = step_var
-
更新节点连接:
- 删除:循环体中的最后一个节点 → 循环头节点
- 增加:循环体中的最后一个节点 → iter节点
- 增加:iter节点 → next节点
- 增加:next节点 → if节点
- 增加:if节点 → assign节点(跳转条件为if节点自身)
- 增加:if节点 → 循环结束的下一个节点(跳转条件与if节点自身相反)
- 增加:assign节点 → 循环体中第一个节点
- 删除:循环体中的最后一个节点 → 循环头节点
- 增加:循环体中的最后一个节点 → next节点
4.6 控制流混淆实现
-
生成Assign语句,为switchVar赋初始值
-
生成结束循环的if-break语句,作为逻辑结束点
-
构建无限循环体:
- 遍历函数的每一个基本块
- 根据后继节点数处理不同情况:
- 后继节点数为0:...
- 后继节点数为1:...
- 后继节点数为2:if的两个分支跳转条件与节点条件跳转关系相同
-
将新的If节点加入到循环体中
-
Assign语句和无限循环体构成函数的新逻辑
-
直接在FunctionDef的AST节点中替换新逻辑
5. 总结
本文详细介绍了基于AST的Python代码混淆技术,包括:
- 变量名混淆:通过识别不同类型变量并采用相应混淆策略
- 属性名混淆:区分模块属性和类属性,根据value类型采取不同混淆方法
- 控制流混淆:基于OLLVM思路,重构函数控制流,增加分析复杂度
这些技术通过操作AST节点实现,能有效提高代码的逆向工程难度,保护知识产权。实际应用中需根据具体需求选择合适的混淆策略和强度。