The code is quite small. It has the basic accept - fork pattern at the beginning and runs this function:
What stands out is the call eax instruction. It looks like the code actually executes some memory from the data segment. Let's try to find out what that memory is because the call is slightly unusual. We go to analyze the first function call within this routine - sub_80487E1.
In the function we notice a call to mmap to allocate the strange memory buffer:
There are several interesting things about this call. First, it requests the location to be at 0x0BADC0DE. Second, the permissions are set to READ|WRITE but not EXECUTE. This is interesting because just above we see this mmapped buffer being executed. Fear not, the signal handler will catch the error.
Next we see that the user sends in a user name into a buffer on the stack. The recv call is requested to read 44 bytes from the socket:
The peculiar thing is that the sub instruction places the destination buffer at ebp-18h which is only 8 bytes away from the next thing on the buffer. The portion of the stack looks like this:
So, we are going to go ahead and overwrite that base pointer and return address with a buffer that is bigger than 24 bytes long. There are, however, some trivial restrictions on what our username input can be. The reader should follow to address 0x080488C8 in the binary. It will show that the username is checked to contain ASCII alpha characters and NULL or NL characters. So, to get around that we will simply provide capital letters to the username.
Next, the function will request a password which is also written on the stack at ebp-94h the 's' variable:
The 's' buffer is actually 128 bytes long and the recv call reads 128 bytes. So, there is no overflow here. However, the following statements will write to the previously mmapped buffer (dest buffer). They will use strcpy to copy both the username and the password to the dest buffer. The password will be offset by 8 bytes presumably because the username is expected to be at most 8 bytes long. For this reason we will need to make sure that first 8 bytes of the username are actually executable, but not too damaging, code.
To recap, we've seen that we can overwrite the return address, much of the stack and we can control the contents of 0x0BADC0DE. However, 0x0BADC0DE is no executable but we'd like it to be!
We notice that the binary imports the mprotect function which can change permissions on memory pages:
We shall implement the classic return-to-libc style attack. The return address of our bad function is set to 0x08048580 with 0x0BADC0DE for first parameter, 0x400 (arbitrary, really) for the length and 4 for RWX permission set. Then to run our shell code we set the return address to 0x0BADC0DE again which will let us exec a shell. The exploit code looks like this:
buf += 'PPPP'
buf += "PPPPAAADAAAEAAA\x00"
buf += uint(0x0BADC000) # EBP
buf += uint(0x08048580) # EIP
buf += uint(0x0BADC000) # return EIP
buf += uint(0x0BADC000) # mprotect return address
buf += uint(0x400)
buf += uint(0x00000004)
buf += "AAAF"
buf += "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f"
buf += "\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
buf += "A"*110
sys.stdout.write( buf )
sys.stdout.write( ";\nls;cat flag\n" )
We just netcat that to the listener and read the flag. Simples! The reason we can use the exec /bin/sh shellcode is because the function we exploited conveniently dup2's STDIN/OUT to the socket.