Analysis of the bug =================== The `child()` function uses `read()` to input some bytes into the `cmd` buffer. Later, the function uses the buffer as if it contained a string (lines 70, 75, ...). However, there is no guarantee that the string is properly terminated, and the function itself only adds a terminating null byte if the read bytes contain a newline (lines 23--25). An attacker can then easily cause `child()` to use a non-terminated string. In particular, the `printf()` at line 87 will display the bytes supplied by the attacker, and then continue with the bytes that follow them in memory, up to the first null byte. Attack plan =========== We want to make sure that the bytes that follow the attacker's payload contain the password. This is easy to achieve, since both `pw` (line 19) and `cmd` (line 53) are obtained through a `malloc()` of the same size, and `cmd` is `free()`d before `pw` (lines 80--81). This means that, whenever we send a `PWD whatever\n` command, the chunk that contains the password will be at the head of its fastbin list, and the next `cmd = malloc(MALLOC_SIZE)` will reuse the same chunk. If we now send a non-terminated string which does not start with either `PWD `, nor `QUIT`, the program will reach line 87 and send us more bytes from the chunk, possibily reaveling the password that was stored there before. Note that the first bytes of the chunk will have been overwritten by `free(pw)` (to store the list pointers). This is not a problem for us, since the first 16 bytes of the chunk contain the username, and we are only looking for the password. We do want to make sure, however, that these bytes do not contain any null bytes that may stop the `printf()` before reaveling the password. We can easily achieve this if we send exactly 16 bytes. Attack implementation ===================== The attack is very simple and can be performed directly from the shell. Assuming that the server is running at 10.0.0.1, port 10000: ``` sh nc 10.0.0.1 10000 PWD whatever # press enter 1234567890123456 # press ctrl+d ``` The server should now print `Unrecognized command: 123456789123456` followed by the secret password. Note: when a process calls a blocking `read()` on a terminal, the kernel normally waits until it has assembled a full line (or filled the process buffer) before letting the `read()` return. A full line is triggered by a newline character, but we want to avoid it when we send the 16 bytes that trigger the bug. We can terminate the input without a newline if we type ctrl+d, since this key combination asks the kernel to immediatly let the `read()` return. The reading process will receive whatever bytes had been collected up to that point, even if no newline had been typed. In our case, the `nc` process will wake up from the `read()` and send the 16 bytes to the server. You may have thought that ctrl+d means EOF, but this is only a special case: when you type ctrl+d on an *empty line* the kernel will just let the `read()` return, as before. However, since no bytes have been collected, the `read()` will return zero and the reading process will interpret this as an EOF. Of course, you can also write a program or script that connects to the server with a socket and then sends only the required bytes. Suggested fix ============= Before using `cmd` as a string we must make sure that it is properly terminated. A simple fix is to just return an error if we can find no `\n` at line 68. Note, however, that the real problem is in the way we used `read()`, since there is no guarantee that we will always receive a complete line in a single `read()` call, even if the client is not doing anything wrong. We should either use the standard I/O library to receive a line (`fgets`), or assemble one by ourselves by repeatedly calling `read()` until we see a newline (or cross some maximum line lenght). As a preventive measure against other possible bugs, it is a also good idea to overwrite the password with zeroes before freeing it.