Python字节码逆向还原源码(CTF挑战):工具适配与opcode解析求助
手动解析Python字节码还原
get_flag函数:参数计数与代码对应逻辑 你遇到的核心问题是字节码操作数(参数计数)与手写代码的栈/操作逻辑不匹配——Python字节码基于栈式虚拟机运行,每个opcode的操作数数量、栈操作行为是固定的,一旦手写代码的逻辑和目标字节码的栈操作顺序、操作数索引对应不上,dis输出就会出现差异。
关键分析与解决步骤
要对齐目标字节码和还原代码,你需要逐行拆解目标get_flag的字节码,重点关注以下几点:
1. 明确opcode的参数计数规则
每个Python opcode的操作数数量是固定的,举例:
LOAD_GLOBAL n:1个参数(n是全局名字表的索引,对应要加载的变量名)CALL_FUNCTION n:1个参数(n是传入的位置参数数量)BINARY_XOR:0个参数(直接弹出栈顶两个元素,计算异或后压栈)LOAD_CONST n:1个参数(n是函数常量池的索引,对应常量值)
你可以对照Python官方dis库文档,或直接用dis.opname/dis.opmap查看opcode定义,确认每个指令的参数数量和栈行为。
2. 对比目标字节码与手写代码的dis输出差异
针对你当前的get_flag尝试代码,常见差异点可能有:
- 变量来源的操作数索引:如果目标字节码中
f/k是通过LOAD_CONST加载(函数内部常量),而你的代码用了全局变量(LOAD_GLOBAL),操作数索引(参数n)会完全不同; - 循环逻辑的栈操作:你写的
for i in range(len(f))会生成len(f)、range()两次CALL_FUNCTION 1指令,但如果目标字节码是直接遍历f的元素(GET_ITER+FOR_ITER),就不会有这些调用,参数计数自然不匹配; - 字节拼接的指令序列:你的
bytes([ord(f[i]) ^ ord(k[i % len(k)])])会生成BUILD_LIST 1+CALL_FUNCTION 1,如果目标字节码用BYTEARRAY_APPEND直接追加,指令和参数计数也会不一样。
3. 按栈操作逻辑逆向推导代码
比如假设目标get_flag的字节码片段如下(示例):
2 0 LOAD_CONST 1 (b'') 2 STORE_FAST 0 (out) 3 4 LOAD_GLOBAL 0 (f) 6 GET_ITER >> 8 FOR_ITER 28 (to 38) 10 STORE_FAST 1 (i) 12 LOAD_FAST 0 (out) 14 LOAD_GLOBAL 1 (f) 16 LOAD_FAST 1 (i) 18 BINARY_SUBSCR 20 LOAD_GLOBAL 2 (ord) 22 CALL_FUNCTION 1 24 LOAD_GLOBAL 3 (k) 26 LOAD_FAST 1 (i) 28 LOAD_GLOBAL 2 (len) 30 LOAD_GLOBAL 3 (k) 32 CALL_FUNCTION 1 34 BINARY_MODULO 36 BINARY_SUBSCR 38 LOAD_GLOBAL 2 (ord) 40 CALL_FUNCTION 1 42 BINARY_XOR 44 BUILD_LIST 1 46 CALL_FUNCTION 1 48 BINARY_ADD 50 STORE_FAST 0 (out) 52 JUMP_ABSOLUTE 8 >> 54 LOAD_FAST 0 (out) 56 RETURN_VALUE
逐行拆解后可对应到代码:
def get_flag(): out = b"" for i in range(len(f)): out += bytes([ord(f[i]) ^ ord(k[i % len(k)])]) return out
如果目标字节码没有range(len(f))的调用,而是直接遍历f的字符,那循环逻辑就要改成for char in f:,同时调整后续的索引逻辑。
核心总结
要解决参数计数不匹配的问题,你需要:
- 把目标字节码的每一条指令的操作数索引、栈操作行为拆解清楚;
- 对比手写代码的
dis输出,找到指令序列、参数索引、栈深度的差异; - 调整代码逻辑,让生成的字节码的操作数数量、栈行为完全对齐目标。
内容的提问来源于stack exchange,提问作者Ricardo Uqueio
相关产品推荐
相关产品推荐

