Magic with Manticore - The Trail of Bits Blog
Manticore是一款下一代二进制分析工具,具有简单而强大的API,支持符号执行、污点分析和插桩。使用Manticore可以识别“有趣”的代码位置,并推导出到达这些位置的输入。这可以生成输入以提高测试覆盖率,或快速引导执行到漏洞位置。
我利用Manticore的能力解决了Magic,这是今年DEFCON CTF资格赛中的一个挑战,包含200个独特的二进制文件,每个文件都有一个单独的密钥。当每个二进制文件中输入正确的密钥时,它会打印出一个总和:
输入代码: ==== 药物有帮助 总和是12
逆向工程200个可执行文件以逐个提取字符串需要大量时间。这个挑战需要自动化。随着CTF中这类挑战的增多,现代工具将成为保持竞争力的必要条件。
我们将结合两种工具——Binary Ninja和Manticore——的力量,通过三种不同的解决方案展示如何将它们应用到您的工作中。
挑战结构
Magic二进制文件具有简单的结构。有一个主函数提示输入密钥,从stdin读取,运行检查器函数,然后打印出总和。检查器函数逐个加载输入字符串的字节,并调用一个函数来检查每个字符。字符检查函数与固定字符值进行比较。如果匹配,函数返回一个要累加的值;如果不匹配,程序退出。
主函数、检查器函数和单个字符检查函数
Manticore的API非常直接。我们将使用钩子在到达指令时调用函数,使用CPU类访问寄存器,以及求解器。工作流程包括通过提供路径加载二进制文件,并在该二进制文件中的指令上添加分析钩子。之后,运行Manticore。当到达地址时,您的钩子被执行,您可以推理程序的状态。
定义为钩子的函数接受一个参数:state。state包含创建符号值或缓冲区、求解符号值和放弃路径的功能。它还包含一个成员cpu,用于保存寄存器的状态,并允许读写内存和寄存器。
策略
解决Magic的方法有很多。我们将展示三种方法,以演示Manticore的灵活性。
- 一种符号解决方案,钩住每条指令以发现字符检查函数的位置。当Manticore处于字符检查函数时,它设置钩子以求解必要的值。
- 一种具体解决方案,钩住每个字符检查函数的地址,并简单地从操作码中读取值。
- 一种符号解决方案,钩住每个字符检查函数的地址并求解值。
这不是您可以使用Manticore采取的方法的详尽列表。有句俗话说,“有很多方法剥猫皮”;Manticore是一个剥猫皮机器。
函数地址将使用Binary Ninja提取。所有策略都需要一个终止钩子的地址来打印解决方案。后两种策略需要字符检查函数的地址。
使用Binary Ninja API提取地址
为了提取字符检查函数的地址以及end_hook()地址,我们将使用Binary Ninja。Binary Ninja是一个为快节奏CTF环境设计的逆向工程平台。它用户友好,具有强大的分析功能。我们将使用其API定位我们想要的地址。在Binary Ninja API中加载文件非常直接。
|
|
为了到达检查器函数,我们首先需要可执行文件的主函数。我们首先检索程序入口函数的入口块。我们知道主函数的地址在LLIL的第11条指令中加载。从该指令中,我们进行健全性检查,确保它是一个加载到RDI中的常量,然后提取常量(主函数的地址)。使用主函数的地址调用get_function_at()返回主函数。
|
|
get_checker()函数类似于get_main()。它定位从主函数调用的检查器函数的地址。然后它加载该地址的函数并返回它。
1. 通过操作码识别的符号解决方案
每个字符检查函数具有相同的指令。这意味着我们可以检查操作码,并将它们用作到达目标函数的指示。我们喜欢这种解决方案,适用于我们可能不一定知道需要设置钩子的位置,但可以识别何时到达的情况。
- 设置每条指令的钩子。
- 检查操作码是否匹配检查函数的前几条指令。
- 设置正分支的钩子以求解寄存器值RDI并存储值。
- 设置负分支的钩子以放弃该状态。
- 设置预分支(当前指令)的钩子以检查是否知道求解的值。如果我们知道该值,设置RDI,这样我们就不需要再次求解它。
- 设置终止指令的钩子。
负分支上的state.abandon()调用至关重要。这停止了Manticore对该分支的推理,这在更复杂的代码中可能需要一段时间。没有放弃,您可能需要3小时解决;有了它,只需1分钟。
|
|
我们在这里使用Manticore的上下文来存储值。上下文字典实际上是多进程管理器的字典。当您开始使用多个工作器时,您将需要使用上下文在它们之间共享数据。
函数set_hooks()将在策略3中重用:通过地址钩子的符号解决方案。它设置预分支、正分支和负分支钩子。
|
|
注意,values字典和target_order数组有一个奇怪的更新模式。它们需要重新分配给上下文字典,以通知多进程管理器它们已更改。
end_hook()函数在所有三种策略中用于声明终止点。它在所有检查字符函数之后声明一个钩子。该钩子打印出发现的字符,然后终止Manticore。
|
|
2. 通过地址钩子的具体解决方案
由于这个挑战对每个字符执行简单的相等性检查,很容易提取值。静态解决会更高效。事实上,可以用一行丑陋的bash解决。
|
|
然而,在这种情况下,我们可以利用具体化。当一个值被写入寄存器时,它不再是符号的。这导致分支明确并跳过求解。这也意味着负分支上的放弃钩子不再必要,因为由于具体值,它将始终采取正分支。
- 设置每个字符检查函数的钩子。
- 从操作码中提取目标值。
- 将该目标值写入寄存器RDI。
- 设置终止指令的钩子。
|
|
3. 通过地址钩子的符号解决方案
从每个函数静态提取值很容易。然而,如果每个字符检查函数在比较结果之前进行了一些任意的位运算,我们就不想为静态提取重新实现所有这些指令。这时混合方法会有用。我们静态识别目标函数,然后在每个函数中求解值。
- 设置每个字符检查函数的钩子。
- 设置正分支的钩子以求解寄存器值RDI并存储值。
- 设置负分支的钩子以放弃该状态。
- 设置预分支(当前指令)的钩子以检查是否知道求解的值。
- 如果我们知道该值,将其写入RDI,这样我们就不需要再次求解它。
- 设置终止指令的钩子。
|
|
将所有内容整合在一起
通过这三个函数,我们拥有了所需的目标地址。将所有内容整合到main()中,我们有了挑战Magic的动态求解器。您可以在此处找到完整的代码列表。
|
|
启用调试打印语句的运行将有助于显示此脚本的执行。第一次击中正分支时,我们看到“Reached target [addr]. Current Key:”语句和到此为止的密钥。有时会采取负分支,状态将被放弃。当我们使用先前求解的值具体化分支时,我们看到“Writing [chr] at [addr]…”。最后,当击中end_hook()时,我们看到“GOAL:”和我们的最终密钥。
开始更智能地使用Manticore
Manticore在编译代码的较小部分提供符号执行。它可以非常快速地发现到达特定路径所需的输入。将符号执行的机械效率与人类直觉相结合,增强您的能力。凭借直接的API和强大的功能,Manticore是任何从事二进制分析工作的人的必备工具。
接受Manticore挑战
您何不试一试?我们创建了一个与Magic非常相似的挑战,但设计成您不能简单地grep解决方案。安装Manticore,编译挑战,并步入二进制分析的未来。立即尝试!第一个在5分钟内执行的挑战解决方案将从Manticore团队获得奖金。(提示:使用多个工作器并进行优化。)
感谢@saelo贡献了使用Manticore运行Magic所需的功能。
如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News
页面内容 挑战结构 策略 使用Binary Ninja API提取地址
- 通过操作码识别的符号解决方案
- 通过地址钩子的具体解决方案
- 通过地址钩子的符号解决方案 将所有内容整合在一起 开始更智能地使用Manticore 接受Manticore挑战 最近帖子 Trail of Bits的Buttercup在AIxCC挑战中获得第二名 Buttercup现已开源! AIxCC决赛:记录 攻击者的提示注入工程:利用GitHub Copilot 发现NVIDIA Triton中的内存损坏(作为新员工) © 2025 Trail of Bits。 使用Hugo和Mainroad主题生成。