概括
这篇博文介绍了夺旗挑战赛,该挑战赛是 2024 年 picoCTF 活动的一部分,该活动持续到 2024 年 3 月 26 日星期二。我们与 NVISO 的团队一起决定参加并应对尽可能多的挑战,最终在全球记分牌上获得了第 130 名的好成绩。我决定尝试专注于二进制利用挑战。虽然我学习了Corelan 的Windows 课程中的 Stack & Heap 利用,但 Linux 二进制利用对我来说相当新,这在试图填补知识空白的同时提供了一个很好的挑战。
挑战涉及格式字符串漏洞。这是一种漏洞,其中输入字符串的提交数据被评估为printf()
应用程序不安全使用(例如函数)的参数,从而导致能够读取和/或写入内存。format string 3
挑战提供了 4 个文件:
-
存在漏洞的二进制文件
format-string-3
(下载链接) -
存在漏洞的二进制源代码
format-string-3.c
(下载链接) -
AC标准库
libc.so.6
(下载链接) -
动态链接器作为解释器
ld-linux-x86-64.so.2
(下载链接)
提供这些文件是为了在本地分析漏洞,但目的是编写漏洞来攻击运行易受攻击的二进制文件的远程目标。
最终漏洞利用步骤如下:
-
setvbuf
获取函数的地址libc
。这实际上是由易受攻击的二进制文件本身通过一个puts()
函数提供的,用于模拟打印到 stdout 的信息泄露, -
动态计算库的基地址
libc
, -
利用格式字符串漏洞,用函数地址覆盖
puts
全局偏移表(GOT)中的函数地址。system
对于步骤 2,动态计算地址(相对于静态/硬编码)很重要,因为我们可以验证远程目标每次运行时是否在不同地址加载模块。我们可以通过多次运行二进制文件来验证这一点,每次运行时都会提供不同的内存地址。这是由于地址空间布局随机化 (ASLR) 和位置独立可执行文件 (PIE) 编译器标志的组合。后者可以通过readelf
在我们的二进制文件上使用来验证,因为二进制文件是作为挑战的一部分提供的。
了解这些缓解措施之间的区别的有趣资源:ASLR/PIE – Nightmare (guyinatuxedo.github.io)
然后,通过生成一个shell,我们可以读取并提交标志文件内容来解决挑战。
漏洞详细信息
字符串格式化的背景
这项挑战涉及一个格式字符串漏洞,正如其名称和描述所示。当用户输入直接传递并用作 C 库printf()
及其变体等函数的参数时,就会出现此漏洞:
int printf(const char *format, ...)
int fprintf(FILE *stream, const char *format, ...)
int sprintf(char *str, const char *format, ...)
int vprintf(const char *format, va_list arg)
int vsprintf(char *str, const char *format, va_list arg)
碳
即使进行了输入验证,也printf(input)
应避免将输入直接传递给这些函数之一(例如:)。建议改用占位符和字符串格式printf("%s", input)
。
格式字符串漏洞的影响可以分为几类:
-
能够读取堆栈上的值
-
任意内存读取
-
任意内存写入
在可以进行任意内存写入的情况下,攻击者可以完全控制程序的执行流程,甚至可能实现远程代码执行。
全球抵消表的背景
过程链接表 (PLT) 和全局偏移表 (GOT) 在程序的执行中都起着至关重要的作用,特别是使用共享库编译的程序——几乎任何在现代系统上运行的二进制文件。
GOT 是存储全局变量和函数地址的中央存储库。在当前 CTF 挑战中,格式字符串漏洞尤为突出,因此了解 GOT 至关重要。利用此漏洞需要操纵存储在 GOT 中的地址来重定向程序流。
当一个可执行文件用 C 编写成要调用的函数function
并被编译为 ELF 可执行文件时,该函数将被编译为function@plt
。执行该程序时,它将跳转到和的 PLT 条目function
:
-
如果有 GOT 条目
function
,它会跳转到存储在那里的地址; -
如果没有 GOT 条目,它将解析地址并跳转到那里。
第一个选项的示例,其中有一个GOT 条目function
,如下图所示: