OverTheWire.org - Maze - Level 5 Writeup

Let's run the executable and see how it works

maze5@maze:~$ /maze/maze5
X----------------
 Username: user
      Key: pass
Wrong length you!

It asks for username and password that must have a proper length.
The section that checks the lenght of the passw rd is the following:

   0x0804863a <+83>:    call   0x80483f0 <strlen@plt>
   0x0804863f <+88>:    add    $0x4,%esp
   0x08048642 <+91>:    cmp    $0x8,%eax
   0x08048645 <+94>:    jne    0x8048658 <main+113>
   0x0804864b <+100>:   call   0x80483f0 <strlen@plt>
   0x08048650 <+105>:   add    $0x4,%esp
   0x08048653 <+108>:   cmp    $0x8,%eax
   0x08048656 <+111>:   je     0x804866c <main+133>


So the required length is 8 bytes for both username and password.
If we input 8 byte username and password and trace it with ltrace:

maze5@maze:~$ ltrace /maze/maze5
__libc_start_main(0x80485e7, 1, 0xffffd7a4, 0x80486e0 <unfinished ...>
puts("X----------------"X----------------
)                                                                        = 18
printf(" Username: ")                                                                            = 11
__isoc99_scanf(0x804877e, 0xffffd6ff, 0x804821c, 0x80486e9 Username: username
)                                      = 1
printf("      Key: ")                                                                            = 11
__isoc99_scanf(0x804877e, 0xffffd6f6, 0x804821c, 0x80486e9      Key: password
)                                      = 1
strlen("username")                                                                               = 8
strlen("password")                                                                               = 8
ptrace(0, 0, 0, 0)                                                                               = 0xffffffff
puts("\nnahnah..."


The interesting code in main section is the following:

   0x0804866c <+133>:   push   $0x0
   0x0804866e <+135>:   push   $0x0
   0x08048670 <+137>:   push   $0x0
   0x08048672 <+139>:   push   $0x0
   0x08048674 <+141>:   call   0x8048420 <ptrace@plt>
   0x08048679 <+146>:   add    $0x10,%esp
   0x0804867c <+149>:   test   %eax,%eax
   0x0804867e <+151>:   je     0x8048694 <main+173>
   0x08048680 <+153>:   push   $0x80487a0
   0x08048685 <+158>:   call   0x80483c0 <puts@plt>
   0x0804868a <+163>:   add    $0x4,%esp
   0x0804868d <+166>:   mov    $0x0,%eax
   0x08048692 <+171>:   jmp    0x80486d6 <main+239>
   0x08048694 <+173>:   lea    -0x12(%ebp),%eax
   0x08048697 <+176>:   push   %eax
   0x08048698 <+177>:   lea    -0x9(%ebp),%eax
   0x0804869b <+180>:   push   %eax
   0x0804869c <+181>:   call   0x804853b <foo>

The bad part is that call to ptrace(0,0,0,0) which don't help when it comes to analyzing the behavior in gdb.
Well, not that bad...

We can get past that ptrace with a combination of break and jump.

Let's place a break on the ptrace call when we start gdb, at run time, when the flow stops at the break we jump after the puts call:

# at gdb start
(gdb) break *0x08048674

# during execution
(gdb) jump *0x08048694

This helps to get past the ptrace call in gdb.

After the ptrace() and a call to puts() that shows "nahnah", we have other code that pushes username and password address then calls the function foo().

Let's see the foo() code:

   0x0804853b <+0>:     push   %ebp
   0x0804853c <+1>:     mov    %esp,%ebp
   0x0804853e <+3>:     sub    $0x14,%esp
   0x08048541 <+6>:     movl   $0x6e697270,-0x11(%ebp)
   0x08048548 <+13>:    movl   $0x6c6f6c74,-0xd(%ebp)
   0x0804854f <+20>:    movb   $0x0,-0x9(%ebp)
   0x08048553 <+24>:    movl   $0x0,-0x4(%ebp)
   0x0804855a <+31>:    jmp    0x804859c <foo+97>
   0x0804855c <+33>:    mov    -0x4(%ebp),%edx
   0x0804855f <+36>:    mov    0x8(%ebp),%eax
   0x08048562 <+39>:    add    %edx,%eax
   0x08048564 <+41>:    movzbl (%eax),%eax
   0x08048567 <+44>:    movsbl %al,%eax
   0x0804856a <+47>:    sub    $0x41,%eax
   0x0804856d <+50>:    mov    %eax,-0x8(%ebp)
   0x08048570 <+53>:    lea    -0x11(%ebp),%edx
   0x08048573 <+56>:    mov    -0x4(%ebp),%eax
   0x08048576 <+59>:    add    %edx,%eax
   0x08048578 <+61>:    movzbl (%eax),%eax
   0x0804857b <+64>:    mov    %eax,%ecx
   0x0804857d <+66>:    mov    -0x4(%ebp),%eax
   0x08048580 <+69>:    lea    (%eax,%eax,1),%edx
   0x08048583 <+72>:    mov    -0x8(%ebp),%eax
   0x08048586 <+75>:    add    %edx,%eax
   0x08048588 <+77>:    sub    %eax,%ecx
   0x0804858a <+79>:    mov    %ecx,%eax
   0x0804858c <+81>:    mov    %eax,%ecx
   0x0804858e <+83>:    lea    -0x11(%ebp),%edx
   0x08048591 <+86>:    mov    -0x4(%ebp),%eax
   0x08048594 <+89>:    add    %edx,%eax
   0x08048596 <+91>:    mov    %cl,(%eax)
   0x08048598 <+93>:    addl   $0x1,-0x4(%ebp)
   0x0804859c <+97>:    pushl  0x8(%ebp)
   0x0804859f <+100>:   call   0x80483f0 <strlen@plt>
   0x080485a4 <+105>:   add    $0x4,%esp
   0x080485a7 <+108>:   mov    %eax,%edx
   0x080485a9 <+110>:   mov    -0x4(%ebp),%eax
   0x080485ac <+113>:   cmp    %eax,%edx
   0x080485ae <+115>:   ja     0x804855c <foo+33>
   0x080485b0 <+117>:   jmp    0x80485d3 <foo+152>
   0x080485b2 <+119>:   lea    -0x11(%ebp),%edx
   0x080485b5 <+122>:   mov    -0x4(%ebp),%eax
   0x080485b8 <+125>:   add    %edx,%eax
   0x080485ba <+127>:   movzbl (%eax),%edx
   0x080485bd <+130>:   mov    -0x4(%ebp),%ecx
   0x080485c0 <+133>:   mov    0xc(%ebp),%eax
   0x080485c3 <+136>:   add    %ecx,%eax
   0x080485c5 <+138>:   movzbl (%eax),%eax
   0x080485c8 <+141>:   cmp    %al,%dl
   0x080485ca <+143>:   je     0x80485d3 <foo+152>
   0x080485cc <+145>:   mov    $0x0,%eax
   0x080485d1 <+150>:   jmp    0x80485e5 <foo+170>
   0x080485d3 <+152>:   mov    -0x4(%ebp),%eax
   0x080485d6 <+155>:   lea    -0x1(%eax),%edx
   0x080485d9 <+158>:   mov    %edx,-0x4(%ebp)
   0x080485dc <+161>:   test   %eax,%eax
   0x080485de <+163>:   jne    0x80485b2 <foo+119>
   0x080485e0 <+165>:   mov    $0x1,%eax
   0x080485e5 <+170>:   leave
   0x080485e6 <+171>:   ret

Quite long and complex with some jumps.
The interesting part is at line 0x080485c8.
It compares byte values in eax and edx.
If they are not equal, it puts 0 in eax and then exit.

Otherwise it jumps to another section, it loads data from memory location and jumps back to the beginning of foo().
It looks like it is checking something between the username and password byte-by-byte.

We can put a break at the line of the comparison and see what happens.
So now let's summarize the approach:
  • choose a standard username: AAAAAAAA
  • choose a standard password: BCDEFGHI
  • open gdb
  • put a break at the ptrace() call
  • put a break at the comparison in foo()
  • use jump to reach foo() and the break inside it
  • verify register eax and edx that needs to be compared

maze5@maze:~$ gdb /maze/maze5
...
(gdb) break *0x08048674
Breakpoint 1 at 0x8048674: file maze5.c, line 48.
(gdb) break *0x080485c8
Breakpoint 2 at 0x80485c8: file maze5.c, line 32.
(gdb) r
Starting program: /maze/maze5
X----------------
 Username: AAAAAAAA
      Key: BCDEFGHI

Breakpoint 1, 0x08048674 in main () at maze5.c:48
48      maze5.c: No such file or directory.
(gdb) jump *0x08048694
Continuing at 0x8048694.

Breakpoint 2, 0x080485c8 in foo (s=0xffffd6ef "AAAAAAAA",
    a=0xffffd6e6 "BCDEFGHI") at maze5.c:32
32      in maze5.c
(gdb) info reg eax edx
eax            0x49     73
edx            0x5e     94
(gdb)

It seems that eax contains the last byte of the password (0x49 is hex for letter I)

Edx has another value, so comparison will fail.

Let's run again with a different username, let's say we change last byte of the username to AAAAAAAB

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /maze/maze5
X----------------
 Username: AAAAAAAB
      Key: BCDEFGHI

Breakpoint 1, 0x08048674 in main () at maze5.c:48
48      in maze5.c
(gdb) jump *0x08048694
Continuing at 0x8048694.

Breakpoint 2, 0x080485c8 in foo (s=0xffffd6ef "AAAAAAAB", a=0xffffd6e6 "BCDEFGHI") at maze5.c:32
32      in maze5.c
(gdb) info reg eax edx
eax            0x49     73
edx            0x5d     93

Interesting... the edx value is decreased by 1, while the 8th byte of username was change from A to B (se increased by 1)

Let's then adjust the last byte of username to V and retry

Explanation: A gives 0x5e, B gives 0x5d ... so V should give 0x49


(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /maze/maze5
X----------------
 Username: AAAAAAAV
      Key: BCDEFGHI

Breakpoint 1, 0x08048674 in main () at maze5.c:48
48      in maze5.c
(gdb) jump *0x08048694
Continuing at 0x8048694.

Breakpoint 2, 0x080485c8 in foo (s=0xffffd6ef "AAAAAAAV", a=0xffffd6e6 "BCDEFGHI") at maze5.c:32
32      in maze5.c
(gdb) info reg eax edx
eax            0x49     73
edx            0x49     73

Yes... eax and edx are equal...
If we continue the execution we should reach the break again

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /maze/maze5
X----------------
 Username: AAAAAAAV
      Key: BCDEFGHI

Breakpoint 1, 0x08048674 in main () at maze5.c:48
48      in maze5.c
(gdb) jump *0x08048694
Continuing at 0x8048694.

Breakpoint 2, 0x080485c8 in foo (s=0xffffd6ef "AAAAAAAV", a=0xffffd6e6 "BCDEFGHI") at maze5.c:32
32      in maze5.c
(gdb) info reg eax edx
eax            0x49     73
edx            0x49     73
(gdb) c
Continuing.

Breakpoint 2, 0x080485c8 in foo (s=0xffffd6ef "AAAAAAAV", a=0xffffd6e6 "BCDEFGHI") at maze5.c:32
32      in maze5.c
(gdb) info reg eax edx
eax            0x48     72
edx            0x63     99

OK, we got past the first jump and then the code is now comparing the 7th byte of the password (H which is 0x48) with another value 0x63

Let's use the same approach: with some tries, we discover that changing 7th byte of the username to \ will give eax = edx = 0x48


(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /maze/maze5
X----------------
 Username: AAAAAA\V
      Key: BCDEFGHI

Breakpoint 1, 0x08048674 in main () at maze5.c:48
48      in maze5.c
(gdb) jump *0x08048694
Continuing at 0x8048694.

Breakpoint 2, 0x080485c8 in foo (s=0xffffd6ef "AAAAAA\\V", a=0xffffd6e6 "BCDEFGHI") at maze5.c:32
32      in maze5.c
(gdb) info reg eax edx
eax            0x49     73
edx            0x49     73
(gdb) c
Continuing.

Breakpoint 2, 0x080485c8 in foo (s=0xffffd6ef "AAAAAA\\V", a=0xffffd6e6 "BCDEFGHI") at maze5.c:32
32      in maze5.c
(gdb) info reg eax edx
eax            0x48     72
edx            0x48     72
(gdb) c
Continuing.

Breakpoint 2, 0x080485c8 in foo (s=0xffffd6ef "AAAAAA\\V", a=0xffffd6e6 "BCDEFGHI") at maze5.c:32
32      in maze5.c
(gdb) info reg eax edx
eax            0x47     71
edx            0x62     98

We got past the second iteration and now we have to guess the 6th byte...

If we use the same approach for the 6th byte, etc.... we discover that a possibile combination of username and password are: onbdg\\V and BCDEFGHI

Note that with a different password, also username must be different: the base fact is that foo() function compares the password byte by byte with a shifted value of the correspondent byte of username.

Finally we can use the username and password to get our privileged shell

maze5@maze:~$  /maze/maze5
X----------------
 Username: onbdg\\V
      Key: BCDEFGHI

Yeh, here's your shell
$ id
uid=15005(maze5) gid=15005(maze5) euid=15006(maze6) groups=15005(maze5)
$ cat /etc/maze_pass/maze5
cat: /etc/maze_pass/maze5: Permission denied
$ cat /etc/maze_pass/maze6
e*******i
$

No comments:

Post a Comment

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