用ghidra中的列表修补程序中的bug
2023-7-16 12:11:0 Author: xz.aliyun.com(查看原文) 阅读量:6 收藏

有了完整的多架构反编译器,很容易理解为什么我的许多学生会直接跳过列表视图,使用反编译器插件。反编译器中的类 C 代码对大多数人来说比列表视图中更神秘的汇编指令更加熟悉。然而,一名强大的逆向工程师必须在两个领域都有信心。这就是为什么在本文中,我将仅使用 Ghidra 中的汇编列表来分析和修补漏洞。

这次的示例应用程序是一个用于计算阶乘的简单程序。已知该程序在处理某些无效输入时会进入一个不返回的循环,我们的任务是找出原因,并更新程序以优雅地处理这些输入。因为本文的缘故,我将尽量详细地解释汇编代码,但为了节省时间,可能会忽略一些用户界面方面的细节。鼓励想要更深入了解如何使用 Ghidra 的读者查看我在 Black Hat USA 2023 上的课程。本文底部包含一些额外的细节。您可以从以下网址下载示例程序并跟随操作:https://secur3.us/GhidraFiles/factorial

导入factorial 程序到 Ghidra 后,第一步是找到入口点。我们可以查看 Ghidra 的导入结果摘要,以了解应该在哪里查找。

摘要显示需要 libc.so.6 库。使用 libc 的程序通常使用名为 libc_start_main 的函数启动 main 函数。我们通过在列表视图中按“G”键并键入 libc_start_main 跳转到该符号。

此地址是 libc 函数的外部引用点。XREF[2] 字段告诉我们,自动分析找到了两个交叉引用到此地址的引用。一个是调用 (c),另一个是指针解引用 (*)。我们要跳转到调用引用,该引用位于 FUN_00101070 的地址范围内的地址 0010108b 处。单击此 XREF 将我们带到 CALL 指令的地址。在此之前的一条指令中,我们可以看到 main 的地址:

这条指令将 FUN_001011a1 的有效地址 (LEA) 加载到寄存器 RDI 中,该寄存器将用作传递给 __libc_start_main 的第一个参数。跳转到该地址将显示程序原始 main 函数的反汇编代码。

列表显示了main函数堆栈帧的 Ghidra 表示形式。Ghidra 已经识别出两个堆栈变量。其中一个 (local_10) 在堆栈下方 0x10 字节处,另一个 (local_14) 在下方 0x14 字节处。变量和函数的交叉引用在右侧注明,并指示内存如何被引用 (R/W/*/c/j)。这里的前三条指令是所谓的函数开头,用于在堆栈上为我们的变量腾出空间。真正的程序逻辑从地址 001011a9 开始,如下所示:

要更好地理解此代码,我们可以将鼠标悬停在 printf 引用上,以获取显示已知参数的弹出窗口:

这些指令可以理解为:
1.将定义字符串的地址加载到 RAX 中
2.将地址复制到 RDI (__format 参数)
3.将 EAX 设置为 0x0,表示没有可变参数
4.通过 thunked 引用调用 printf
这些指令的结果是将字符串“Enter a non-negative integer:”打印到控制台。
继续浏览列表,我们有以下指令:

这些指令可以理解为:
1.将本地变量的地址加载到 RAX 中 (local_14 在存储基指针 RBP 下方 14 或 0xc 字节处)
2.将地址复制到寄存器 RSI 中,作为 scanf 的参数
3.将程序内存中定义的字符串的地址加载到 RAX 中
4.将地址复制到 RDI 中作为 scanf 的参数
5.EAX 清除为 0x0
6.从链接的 libc 库中调用 scanf
虽然 Ghidra 未能将两个字节识别为定义字符串,但跳转到 DAT_00102027 显示格式字符串为 %d:

调用 scanf 后,local_14 处的变量将存储从用户输入解析出的数字,或者如果输入无法解析为数字,则为 0。程序后面的几行展示了 scanf 解析出的数字如何处理:

在地址001011d8处,解析用户输入的结果存储在local_14中,对此地址进行解引用的结果存储在EAX中。TEST指令对EAX本身进行按位AND运算。这会设置由跳转指令if-signed(JS)在001011dd处读取的符号标志,如果该值为负,则会将控制重定向到0010120c以显示错误消息。

如果控制没有跳转到错误消息,则程序流程会继续,通过将local_14中的值加载并通过EAX作为FUN_00101159函数的参数移动到EDI中。累加器寄存器(RAX)似乎包含从函数返回的返回值,该值在001011e9处移动到local_10中。

00101ed和00101f0处的指令将本地变量local_14和local_10加载到EAX和RDX中。然后将来自local_14的值从EAX复制到ESI,然后在001011f6处重新使用累加器寄存器来布置格式字符串的地址,然后在00101205处将其加载到RDI作为printf的参数。
生成的消息是“The factorial of %d is %lu.\n”,其中第一个数字是用户输入,第二个数字是我们未知函数的返回值。
然后,代码将无条件跳转到LAB_0010121b,其中包含退出函数。

此时,让我们回顾一下main函数所做的高级别描述。
1.通过printf提示用户输入数字
2.通过scanf将用户输入解析为数字
3.如果该值为负,则打印错误
4.将用户输入数字传递给函数
5.打印用户输入和返回值

我们可以将本地变量重新标记为input和 factorial_value 或类似变量,以使列表更易读。选择变量标签按下“L”键即可完成此操作。

使用光标文本突出显示可以让我们快速查看变量的使用情况:

单击FUN_00101159将使列表视图跳转到未知函数,根据格式字符串,该函数将计算输入数字的阶乘。我们可以从重命名函数开始识别:

下面显示了此 factorial函数的完整列表:

该函数从EDI(请参见001011e2)将用户输入复制到local_1c中,然后将该值与0x0进行比较(请参见00101160)。如果不是零,则使用跳转指令JNZ将控制发送到LAB_0010116d,如果该值为零,则将EAX设置为立即值0x1,然后进行无条件跳转到函数结尾语句LAB_0010119f。
如果该值不为零,则分别在0010116d和00101175处初始化local_10和local_14为0x1。接下来的指令(在0010117c处)是无条件跳转到LAB_00101193,如下所示,其中焦点放在分支指令上,以显示控制流程的更多详细信息:

跳转后的第一条指令将local_14中的值加载到EAX中,然后将其与local_1c中的值进行比较。如果local_14中的值小于或等于local_1c中的值,则指令(JLE)在程序中返回。否则,程序将继续执行0010119b,在该处将local_10中的值复制到累加器RAX中,这是我们预期的返回值所在的位置。
如果该值较小,则控制将继续在0010117e处,其中将local_14中的值复制到寄存器EAX中,然后在00101181处执行CDQE指令。此指令将使用符号扩展将双字扩展为四字。换句话说,在保留原始符号的情况下,EAX中的32位值将扩展为RAX中的64位值。
在00101183处,将local_10中的值(最近初始化为1)复制到RDX中,然后在00101187处与RAX进行整数乘法(IMUL)。得到的值存储在RAX中,并复制到地址0010118b处的local_10中。在返回LAB_00101193之前,最后一条指令是将local_14的值加1(在0010118f处)。

我们可以从这个结构推断出 local_14 是一个循环索引计数器,local_1c(用户输入)是循环的上限,local_10 用于在计算阶乘时存储中间值。
将变量重命名为 i,input 和 factorial_subtotal 可以使列表视图更容易理解,我们可以加载函数图以获得更好的流程可视化:

这个示例应该非常清楚地说明了在哪里查找非返回循环。LAB_00101193 和 LAB_0010117e 的块将持续运行,除非循环索引小于或等于用户输入值。
你可能会想知道这是怎么发生的,因为main函数包含了一个检查负数的条件,而factorial 函数还检查了该值是否为非零。然而问题来自于 00101199 处的 JLE 指令。这是一种有符号比较。这意味着如果索引值 i 变得足够大,它将翻转成一个负数的数字,这将被解释为小于输入数字。当用户输入一个恰好是有符号 32 位整数的最大值(例如 2 ** 31-1,等于 2147483647)时,这种边缘情况将会发生。有符号计数器值永远不会表示大于此数字的值,并且循环将继续直到被中止。

修复此错误的一种方法是将 CMP/JLE 重写为 CMP/JNC。如果未设置进位标志,跳转如果不进位(JNC)指令执行无符号比较并跳转。
这样做的第一步是修补 CMP 指令。CMP 将从另一个操作数中减去一个操作数,并根据结果设置标志。我们可以右键单击指令并选择“修补指令”以加载汇编程序:

我们只需要重新排列此指令上的操作数,同时确保保持 3 字节指令即可。

最后,我们需要将有问题的有符号 JLE 调用替换为无符号 JNC 调用,以便将数字视为无符号值,并仅在适当时跳回循环中。

在 Ghidra 中保存程序后,可以将修改后的程序导出为新的 ELF 可执行文件:

运行原始版本与修补版本显示了此更改的影响:

逻辑仍然有些错误,因为缺少识别累积值包装的检查,但已实现修复无限循环的目标。
希望你喜欢阅读并考虑自己尝试这个挑战。如果遇到问题,请通过本帖子的评论或通过 Twitter @CraigTweets 联系我。
From:https://medium.com/@cy1337/patching-a-bug-from-a-ghidra-listing-8496e529224a


文章来源: https://xz.aliyun.com/t/12699
如有侵权请联系:admin#unsafe.sh