04 Apr 2020
The program ask us to provide two file name or file descriptor and compares their differences. After spend some hours to search complicates things in the program, the function securityCheck get my attention:
char* securityCheck(char* arg, char* s)
{
if(strstr(arg, ".pass"))
return "<<<For security reasons, your filename has been blocked>>>";
return s;
}
If a file name contains the string “.pass”, the string “«<For security reasons, your filename has been blocked»>” will be compared with the other but the file content will be loaded in the program.
To verify this, I created a file named “.pass” with a string inside. If I placed a breakpoint at 0xXXXXXedf and I run the program with these arguments: -fn=.pass -fd=20
(any number can be entered for the second argument it is not matter), you can see that the content of the file in the stack:
But in the server, it is not possible to open the .pass file because gdb drop the setuid rights so we need to find another way.
The first number for the file descriptor corresponds to STDIN,STDOUT and STDERR. So the next new file opened get the file descriptor number 3. To obtain the content of .pass, these arguments need to be entered -fn=/home/lab8B/.pass -fd=3
:
The program allows creating two vectors with some integer parameters in different data types (car, short int, unsigned long, etc). It is possible to sum the two vectors in a third vector and save this vector like favourites. I can save 10 favourites vectors.
After some tests, I saw some strange behaviour when I print the list of favourites. It seems the program is bugged here. After checking the code, I found the problem in the function fave when the memcpy function is called:
memcpy(faves[i], (int*)(&v3)+i, sizeof(struct vector));
The pointer of the destination is increased by one. It means that the next source does not point to the next vector structure but to the next parameter (the next “integer”).
Let’s check with gdb what happens. I placed a breakpoint to the memcpy call and we can see below the arguments for memcpy before the call:
The src argument points to the beginning of the vector structure and it is corresponding to the address of the printf function. It is the default value for all vectors like you can see below:
int main(int argc, char** argv)
{
char sel;
printMenu();
v1.printFunc = printf;
v2.printFunc = printf;
v3.printFunc = printf;
Below, the values of the first saved vector:
The function pointer point to the printf address. Let’s check if we save a second time the same vector.
The src argument points to the next “integer”. 0x40062 corresponds to the char value (b in ASCII) and 4 to the value of the short int 4. The size of a char is equals to one byte and an address is equals to 4 bytes. It is the reason why we see the value of the short int and the value of the char in the pointer function.
A function named thisIsASecret call the system function:
/*
* Bonus points if you don't use this function.
*/
void thisIsASecret()
{
system("/bin/sh");
}
So if I enter in the good integer the value of the thisIsASecret address in decimal format and save enough vectors, I will be able to overwrite the function pointer and jump to the function thisIsASecret. To do this, I will use a Python script that you can download here.
The program allows you to read some “books” named A, B and C and that’s all. I check the security enable in the program:
The code contains two obtain obvious vulnerabilities:
/* Our Apologies,the interface is currently under developement */
char buf_secure[512];
scanf("%s", buf_secure);
printf(buf_secure); <= basic format string vulnerability
/* We specialize in words of wisdom */
char buf[24];
// to avoid the null
global_addr = (&buf+0x1);
// have to make sure no one is stealing the librarians cookies (they get angry)
global_addr_check = global_addr-0x2;
char lolz[4];
printf("\n..I like to read ^_^ <== ");
read(STDIN, buf, 2048); // >> read a lot every day ! <= buffer overflow here
To overwrite the RET address we need to bypass this condition:
if(((*( global_addr))^(*(global_addr_check))) != ((*( global_addr))^(0xdeadbeef))){
printf("\n\nWoah There\n");
// why are you trying to break my program q-q
exit(EXIT_FAILURE);
}
// protected by my CUSTOM cookie - so soooo safe now
return;
Thanks to the buffer overflow, it is possible with the following code:
p.recvuntil("[+] Enter Your Favorite Author's Last Name:")
p.sendline("A")
p.recvuntil("..I like to read ^_^ <== ")
p.sendline("A"*16+"\xEF\xBE\xAD\xDE"
If we try to overflow the RET address, we will see this error:
*** stack smashing detected ***: ./lab8A terminated
It is because of the canary is enabled. With Ghidra, we can see where the canary is checked:
To bypass this protection, we can get the random value through the format string. If we check the content of the stack before calling the vulnerable printf, we can see where the canary is located:
It is easy to calculate the offset:
512/4 = 128 | 128 + 2 =130 |
Now, we can place the value of canary and a classic ropchain in the right place in the buffer overflow in order to pop the shell.
You can download the solution here.