So the core vuln in this program was a logic error in the STOR functionality. The program was a home brew ftp server with limited functionality that allowed you to login, retrieve, list, pwd, cwd, and store files on the server (although storing never wrote to disc). The first part of this challenge was logging in, which required finding a collision with the password hash, but that's outside the scope of this writeup... After logging in, calling PWD indicated that the default working direct was /home/ctf. Calling PASV to instruct the server to operate in passive mode I was able to LIST the contents of the working directory and saw flag.txt. Hooray! Trying to RETR the flag told me I'd entered an illegal character.
Looking inside the RETR function revealed that it was checking each character in my input against this character in .bss, which I called illegal_char. Spoiler: the prohibited character was 'f'.
Let's see where this is loaded into memory though. From solving the first challenge, I was already familiar with the password validation function and noticed that after validating the password, it sets the is_logged_in global to 1 and also moves the character 'f' into illegal_char.
Back in the main loop, is_logged_in is checked before allowing any commands other than USER PASS or HELP. So either I had to log in without logging in, or I had to find a way to overwrite that 'f' in memory. On to the STOR function!
So what's going on here? Up at the top, you can see that 10 is loaded into rbp+n, which is later passed as the size argument to _recv. Up to 10 characters are received into buffer_512, which happens to exist before illegal_char in memory.
Looking ahead a bit, you can see that a 0 is moved into buffer_512 (in order to null terminate the string) at the index I called increment_me. There's no bounds checking in this block, so as long as it doesn't happen elsewhere, we can just send enough data such that the 0 overwrites our illegal_char.
Great! Now comes the logic error... While recv only takes 10 characters at a time, it never checks if it has received more than n characters and instead loops back around an receives another 10. It's important to note that only 10 characters ever get written into the buffer, but the increment_me count gets incremented with every call to recv.
So if you STOR 520 characters, the 'f' gets wiped out and you can go back and RETR flag.txt
Before I drop the code, I want to give a shout out to Gallopsled. I'm a new convert to pwntools, and it really streamlines the exploit development process.
Solution code here: