For this lab, we are given a program and its corresponding source code:
If we run checksec, we can see that NX, PIE and full RELRO are enabled.
The program actually runs as a network service on port 6642 that we must exploit.
If we look at the source code, we can see that there is a vulnerability in the login_prompt() function.
Specifically, the two strncpy() calls are not used correctly.
The manpage for strncpy() tells us the following:
The stpncpy() and strncpy() functions copy at most n characters from src into dst.If src is less than n characters long, the remainder of dst is filled with `\0’ characters.Otherwise, dst is not terminated. […] WarningIf there is no null byte among the first n bytes of src, the string placed in dest will not be null-terminated.
Because of this, if we specify 32 bytes of data or more in the readbuff array, the username and password buffers will be completely filled up without being null terminated.
This is because src will not be less than n characters long, and there will be no null byte encountered in the first 32 bytes of src.
After each strncpy(), what should be done to use each function correctly is the following.
As a side note, other functions that do not guarantee null-termination include snprintf() and wcsncpy().
To verify this vulnerability, we can use gdb.
Look at what happens after a 32 byte username is strncpy()‘d to the stack.
And now look at what happens when a 32 byte password is strncpy()‘d to the stack.
Notice that starting from the beginning of the username, the null termination character isn’t encountered until 0xffffd451 which is far after what is supposed to be the end of the password.
We can also see that the saved EIP, 0x56555f7e is stored at address 0xffffd44c, int result is stored at 0xffffd438, int attempts is stored at 0xffffd43c.
This later gives us a pointer leak when the login fails and the username is printed back out to stdout.
However, before this printf() statement is called, hash_pass() is called, which performs math operations on password and username, transforming the password variable on the stack.
But, because neither the username nor password variable is null-byte terminated, we can cause these operations to transform other addresses on the stack, such as the saved EIP.
Our goal is to transform the saved EIP so that when login_prompt() finishes, it returns to login() instead of back to main().
We can do this in just 2 login attempts.
But first, we need to make a few calculations.
We can see that the saved EIP will exist at an offset of 0x48a from the address of login(), allowing us to dynamically calculate the address of login() at runtime using the leaked pointer..
However, in our final exploit, we also need to account for the fact that the pointer will leak will be the saved EIP xored by 0x3030303.
We get 0x3030303 from 0x41414141^0x42424242.
Additionally, we need to determine the offset of the password buffer that will be xor’d with the transformed saved EIP.
Using gdb, we find that the dword in password will xor the value of the saved EIP.
Therefore, we can get the value of this dword “key” we need by performing a simple calculation, keeping in mind that the initial value of this key will be xor’d with 0x41414141 before the result is used to xor the transformed saved EIP.
Putting everything together, the following exploit will grant us a shell.