Not a complete writeup, just my thoughts while working on it.
At first glance, it looks like it’s nontrivial to locate
main() in a
Via this SO question.
__libc_start_main has the following prototype:
int __libc_start_main(int (*main) (int, char**, char**), int argc, char *__unbounded *__unbounded ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (*__unbounded stack_end));
So, we can look at the top address of the stack in order to get a pointer
to main, set a breakpoint on it, and
continue till we get there.
The entry point of the binary is
This is distinct from the address of
main — it corresponds to
tools like Binja. (can also be found with
readelf -h ./load | grep Entry)
x64 Linux calling convention is these registers for args
RDI, RSI, RDX,
RCX, R8, R9, XMM0–7.
So, the address of
main on my local machine on first run is
0x7ffff7ffe168. Setting that as a breakpoint and continuing doesn’t work.
So, I pull the address of main out of Binja.
I then go reverse the file, annotating it.
Looks like a -1 for the size causes a crash, or at least, a silent exit w/o a successful file read.
The read appears to read into a stack-allocated variable that doesn’t look very big. I’m guessing that if I read a particular file (maybe something in /proc that I control, offset to wherever in the binary) then I’ll be able to overwrite the return address with that of libc or something.
__printf_chk should still be usable, so I bet I can get arbitrary read.
But I can’t write things to the stack, so that’s a pain in the ass.
/dev/stdin is a file I control 100%, solving that problem.
I’m not sure if ASLR is on or not, but I’m guessing no. I can probably
catting a file.
I think the proper approach is ROP, by loading the stack with some addresses. But, I need addresses. I have things in the code section so I can search for ROP gadgets there.
I’m curious if a shellcode-level execve can do pipes. I think the answer is yes?
Sebastian pointed me to
reopen, and “try to open /dev/console or something as fd 1”.
Misc other stuff:
printfwith stack checking to prevent
/proc/self/memgives us arbitrary memory, but we don’t actually see the output ever so that’s nothing.
- .got.plt and .plt.got are two separate things. .plt.got has AX, .got.plt has WA.
readelf -Sfor sections.
Post-competition, looks like one solution is reading a character of the flag at a time and terminating on match, hanging on mismatch. Thus you leak the entire flag. Interesting!