基于AST的Python混淆
字数 3070 2025-10-01 14:05:45

基于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节点:ImportImportFrom

ImportFrom节点处理(from xx import yy, zz, ...)

  1. 记录xx模块的所有导入项
  2. 对之后出现的变量(不在内置、全局、局部变量中),如果在xx中,使用:
    yy = getattr(xx, yy)  # 进行导入
    

2.4 全局变量与局部变量处理

通过是否进入FunctionDef节点判断是否处于函数定义中:

  • 设置变量时

    • 如果不在导入变量、内置变量之列,考虑全局或局部变量
    • 处于函数定义中:设置局部变量,否则为全局变量
  • 读取变量时

    • 依次检查是否为:导入变量 → 局部变量 → 全局变量 → 内置变量 → ImportFrom导入项

2.5 特殊变量处理

For循环中的临时变量(i, k, v等)需要特殊处理,使用处理函数中局部变量的思路处理以下节点:

  • ListComp
  • DictComp
  • SetComp
  • For

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类型采取不同策略:

  1. Name节点

    • 模块是__builtins__:以getattr形式混淆
    • 属性是__dict__:以getattr形式混淆
    • 模块是导入变量:以__dict__形式混淆
    • 类是内置变量:以get_referents形式混淆
    • 其他情况:无法确定属性所属类(未做变量类型确定)
  2. Constant/List/Dict节点

    • 对应Python常见变量类型,可直接确定属性对应类
    • get_referents形式混淆
  3. Call节点

    • 如果调用的函数未做返回值类型定义,无法确定属性所属类

4. 控制流混淆

4.1 基本思路

基于OLLVM控制流混淆思路,在py2cfg和AST基础上实现:

  • 混淆单位为函数
  • 通过py2cfg获取每个函数的控制流图
  • 修改控制流结构,增加分析难度

4.2 基本块处理

对每个基本块进行以下操作:

  1. 为每个块生成唯一ID(自定义随机数生成器),作为OLLVM中的switchVar
  2. 对循环节点的基本块进行特殊处理

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节点)

  1. 生成If节点,条件与While节点相同
  2. 复制While节点的跳转关系
  3. 增加跳转:If节点 → 循环结束的下一个节点

4.4.2 无条件While循环(判断为永真式)

  1. 分析前驱节点:

    • 在循环体中:循环体中的最后一个节点
    • 不在循环体中:执行循环体前的节点
  2. 更新节点连接:

    • 删除:执行循环体前的节点 → While节点
    • 增加:执行循环体前的节点 → 循环体中第一个节点
    • 删除:While节点 → 循环体中第一个节点
    • 删除:循环体中的最后一个节点 → While节点
    • 增加:循环体中的最后一个节点 → 循环体中的第一个基本块

4.5 For循环处理

将for循环改为iter + next + if + break的形式:

  1. 分析前驱节点:

    • 在循环体中:循环体中的最后一个节点
    • 不在循环体中:执行循环体前的节点
  2. 生成新节点:

    • iter节点:iter_var = iter(for_iter)
    • next节点:step_var = next(iter_var, None)
    • if节点:if step_var is not None
    • assign节点:for_target = step_var
  3. 更新节点连接:

    • 删除:循环体中的最后一个节点 → 循环头节点
    • 增加:循环体中的最后一个节点 → iter节点
    • 增加:iter节点 → next节点
    • 增加:next节点 → if节点
    • 增加:if节点 → assign节点(跳转条件为if节点自身)
    • 增加:if节点 → 循环结束的下一个节点(跳转条件与if节点自身相反)
    • 增加:assign节点 → 循环体中第一个节点
    • 删除:循环体中的最后一个节点 → 循环头节点
    • 增加:循环体中的最后一个节点 → next节点

4.6 控制流混淆实现

  1. 生成Assign语句,为switchVar赋初始值

  2. 生成结束循环的if-break语句,作为逻辑结束点

  3. 构建无限循环体:

    • 遍历函数的每一个基本块
    • 根据后继节点数处理不同情况:
      • 后继节点数为0:...
      • 后继节点数为1:...
      • 后继节点数为2:if的两个分支跳转条件与节点条件跳转关系相同
  4. 将新的If节点加入到循环体中

  5. Assign语句和无限循环体构成函数的新逻辑

  6. 直接在FunctionDef的AST节点中替换新逻辑

5. 总结

本文详细介绍了基于AST的Python代码混淆技术,包括:

  1. 变量名混淆:通过识别不同类型变量并采用相应混淆策略
  2. 属性名混淆:区分模块属性和类属性,根据value类型采取不同混淆方法
  3. 控制流混淆:基于OLLVM思路,重构函数控制流,增加分析复杂度

这些技术通过操作AST节点实现,能有效提高代码的逆向工程难度,保护知识产权。实际应用中需根据具体需求选择合适的混淆策略和强度。

基于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__) 判断,混淆方式: 后两种方法将变量以字符串形式表示,便于后续字符串混淆。 2.3 导入变量混淆 导入变量有两种AST节点: Import 和 ImportFrom ImportFrom节点处理(from xx import yy, zz, ...) 记录xx模块的所有导入项 对之后出现的变量(不在内置、全局、局部变量中),如果在xx中,使用: 2.4 全局变量与局部变量处理 通过是否进入 FunctionDef 节点判断是否处于函数定义中: 设置变量时 : 如果不在导入变量、内置变量之列,考虑全局或局部变量 处于函数定义中:设置局部变量,否则为全局变量 读取变量时 : 依次检查是否为:导入变量 → 局部变量 → 全局变量 → 内置变量 → ImportFrom导入项 2.5 特殊变量处理 For循环中的临时变量(i, k, v等)需要特殊处理,使用处理函数中局部变量的思路处理以下节点: ListComp DictComp SetComp For 3. 属性名混淆 3.1 基本混淆方法 对于属性名,可通过以下方式混淆: 需要区分模块属性和类属性: 模块属性混淆 : 类属性混淆 : 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 None assign 节点: 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节点实现,能有效提高代码的逆向工程难度,保护知识产权。实际应用中需根据具体需求选择合适的混淆策略和强度。