TPCTF 2023 maze
TPCTF 2023 maze
- main → sub_403E50 完全是标准 PyInstaller 启动流程,负责读取 _MEIPASS2/_PYI_PROCNAME 环境变量、检查外部归档、并在必要时把内嵌归档解包到临时目录。
- sub_406890 / sub_406A90 / sub_406CD0 等函数做的都是解析 CArchive/PKG、加载 PYZ 模块、并把内嵌的 .pyc 解出来交给 PyEval_EvalCode 执行。
- sub_403C00 明确调用 PyImport_AddModule(“main“)、PyMarshal_ReadObjectFromString 等接口执行内嵌脚本,真正的谜题与校验逻辑全部在打包进去的 Python 代码里,C 端没有额外的校验算法或显式字符串可以利用。
其实就是解pyc了,非exe的还是第一次见
揭开来发现几个pyc没啥东西,maze.so中发现
交叉引用可以主逻辑
这种base64解码init_secret,有很多这种字符串
这种一般找不到逻辑可以对xor或者compare做交叉引用,但是compare太多了,看看xor定位到
其实可以这里下断点然后dump出来,但是非常麻烦,因为我没有ubuntu的ida,好像动调maze.so是需要attach的,比较麻烦
中间有
1 | Attr = (PyObject *)tp_getattro(BuiltinName, _pyx_mstate_global_static.__pyx_n_s_b64decode); else |
对base64解码的操作
长度校验
- 导入base64并取b64decode
- 取模块名UJ9mxXxeoS并b64decode后.decode得到真实模块名(“MazeLang”),这个模块就是下面的一大段base64
- 从该模块取函数cnVuX3RpbGxfb3V0cHV0(即”run_till_output”),这是一个可调用/迭代器,每次调用产出一个整数掩码
- 如果len(输入)!=33,直接返回1(像是反调或迷惑分支)
- 调用aW5pdF9zZWNyZXQ(“init_secret”)初始化全局表c2VjcmV0(“secret”)
1 | 0x1b2c0 函数,run_till_output:初始化 self.time=0,循环检测 self.cars 是否存在并调用 self.step() 推进状态;每轮产出一个数值(在 maze_solve 中被取用参与异或比较)。从代码结构看,该序列仅依赖 self 的内部状态(cars/step/time),与用户输入字符本身无关。 |
1 | s = "IyMgIyMgIyMgIyMgIyMgIyMgIyMKIyMgIyMgIyMgXl4gIyMgXl4gIyMKIyMgIyMgIyMgLi4gIyMgSVogIyMgIyMgIyMgIyMKIyMgJVIgLi4gJUQgIyMgJUQgLi4gLi4gJUwgIyMKIyMgPj4gIyMgLi4gIyMgRUEgKiogUFAgJVUgIyMKIyMgJVUgSUEgVEEgIyMgRUIgKiogUFAgJVUgIyMKIyMgJVUgSUIgVEIgIyMgRUMgKiogUFAgJVUgIyMKIyMgJVUgSUMgVEMgIyMgRUQgKiogUFAgJVUgIyMKIyMgJVUgSUQgVEQgIyMgRUUgKiogUFAgJVUgIyMKIyMgJVUgSUUgVEUgIyMgRUYgKiogUFAgJVUgIyMKIyMgJVUgSUYgVEYgIyMgJVIgKiogSVogJVUgIyMKIyMgJVUgSUcgJUwgIyMgIyMgIyMgIyMgIyMgIyMKIyMgIyMgIyMgIyMgIyMgIyMKClBQIC0+ICs9MQpNTSAtPiAtPTEKSVogLT4gPTAKRUEgLT4gSUYgPT0wIFRIRU4gJVIgRUxTRSAlRApFQiAtPiBJRiA9PTEgVEhFTiAlUiBFTFNFICVECkVDIC0+IElGID09MiBUSEVOICVSIEVMU0UgJUQKRUQgLT4gSUYgPT0zIFRIRU4gJVIgRUxTRSAlRApFRSAtPiBJRiA9PTQgVEhFTiAlUiBFTFNFICVECkVGIC0+IElGID09NSBUSEVOICVSIEVMU0UgJUQKVEEgLT4gSUYgKiogVEhFTiAlTCBFTFNFICVECklBIC0+ID03MgpUQiAtPiBJRiAqKiBUSEVOICVMIEVMU0UgJUQKSUIgLT4gPTczClRDIC0+IElGICoqIFRIRU4gJUwgRUxTRSAlRApJQyAtPiA9ODQKVEQgLT4gSUYgKiogVEhFTiAlTCBFTFNFICVECklEIC0+ID04MApURSAtPiBJRiAqKiBUSEVOICVMIEVMU0UgJUQKSUUgLT4gPTY3ClRGIC0+IElGICoqIFRIRU4gJUwgRUxTRSAlRApJRiAtPiA9ODQKSUcgLT4gPTcwCkxUIC0+IElGID09NiBUSEVOICVEIEVMU0UgJUwK" |
运行输出
1 | ## ## ## ## ## ## ## |
这是GPT的解释
secret 是在模块初始化时构造的。maze.so 的 __pyx_pymod_exec_maze 里,会把字符串 UJ9mxXxeoS 之类的短 base64 段解码成整型,再依次 push 到全局列表 __pyx_n_s_secret。IDA 看伪代码(大约在 maze.so:0xc93b 一带)
ord(S[i]) ^ G[i] == T[perm[i]]
这里可以看到两个数据
1 | secret = [7, 47, 60, 28, 39, 11, 23, 5, 49, 49, 26, 11, 63, 4, 9, 2, 25, 61, 36, 112, 25, 15, 62, 25, 3, 16, 102, 38, 14, 7, 37, 4, 40] |
TPCTF{yOu_@re_m@sT3r_OF_mAZElaN6}
怎么看出来的是位置表?
从值域一眼看出是“索引置换”:maze.EqdU3uQNCi 的列表有 33 个整数,且刚好是 0..32 每个各一次(不重复、不缺失)。这类列表在校验逻辑里只能当“位置索引的置换表”用,比如把第 i 轮要比较的字符位置映射成 perm[i]。
与 T 的数值分布完全不同:打印的 maze.c2VjcmV0 列表里有很多大于 32 的值(如 49、112、102 等),显然不可能是“索引”(索引必须在 0..32 之间)。这类列表才符合“目标常量序列 T(与 G[i] 异或的比较值)”的特征。再加上名字 c2VjcmV0 是 base64(‘secret’),语义上也指向“目标常量”。
校验需要三样东西:G(每轮生成的字节)、T(目标常量)、以及“这一轮用哪个输入字符来比”(是否打乱顺序)。
G 来自运行时序列;T 是固定常量;而“打乱顺序”只能由一个取值范围在 0..32 的排列来提供。你的 EqdU3uQNCi 正好就是这样的排列,因此判断它是“索引置换表”。
这是出题人:TPCTF2023 Maze WP | Yasar’s Blog
记录一下
Py_mod_exec: 指定一个供调用以执行模块的函数。 这造价于执行一个 Python 模块的代码:通常,此函数会向模块添加类和常量。根据文档中的说明,Py_mod_exec会绑定对应的函数,向模块中添加类和常量。可以在
__pyx_pymod_exec_maze
函数中看到非常多的常量定义,同时其中调用了_Pyx_CreateStringTabAndInitStrings
函数。
_Pyx_CreateStringTabAndInitStrings
函数将所有Python代码使用到的字符串插入到__pyx_string_tab
中,并于__pyx_mstate_global_static
中对应的变量绑定。
__pyx_n_s_c29sdmU
对应的_pyx_pw_4maze_3c29sdmU
的逻辑:
- 解析传入的参数,并赋给
__pyx_n_s_SvL6VEBRwx
。 - 调用base64.b64decode解密
__pyx_n_s_UJ9mxXxeoS
。 - 使用解密后的值初始化了一个
__pyx_n_s_TWF6ZUxhbmc
类。 - 调用了
__pyx_n_s_aW5pdF9zZWNyZXQ
函数。 - 将传入参数第i位取
ord
后与__pyx_n_s_TWF6ZUxhbmc
类__pyx_n_s_cnVuX3RpbGxfb3V0cHV0
函数的结果异或,然后与__pyx_n_s_c2VjcmV0
的第i位比较。
分析__pyx_n_s_aW5pdF9zZWNyZXQ
对应的_pyx_pw_4maze_1aW5pdF9zZWNyZXQ
逻辑:
for i in range(33)
- 取出
__pyx_n_s_c2VjcmV0[__pyx_n_s_EqdU3uQNCi[i]]
- 取出
__pyx_n_s_c2VjcmV0[i]
- 将
__pyx_n_s_c2VjcmV0[__pyx_n_s_EqdU3uQNCi[i]]
赋给__pyx_n_s_c2VjcmV0[i]
- 将
__pyx_n_s_c2VjcmV0[i]
赋给__pyx_n_s_c2VjcmV0[__pyx_n_s_EqdU3uQNCi[i]]