Preamble
This post is part of a series on common software vulnerabilities. The exploits target binaries from Rainfall, a CTF project by 42. Prior knowledge of x86 assembly and C is required !
Reconstructed C code from assembly
|
|
Exploit
In this program, a new child process is spawn using fork()
. The parent then proceed to wait
the child. The use ptrace(PTRACE_ME)
, which, according to the manual :
PTRACE_TRACEME Indicates that this process is to be traced by its parent. Any signal (except SIGKILL) delivered to this process will cause it to stop and its parent to be notified via wait(2). Also, all subsequent calls to execve(2) by this process will cause a SIGTRAP to be sent to it, giving the parent a chance to gain control before the new program begins execution. A process probably shouldn’t make this request if its parent isn’t expecting to trace it. (pid, addr, and data are ignored.)
The child has an obvious buffer-overflow vulnerability because of the usage of gets
, granting us saved-eip overwrite. The NX bit of the executable is disabled, which mean we can execute arbitrary code in the stack.
Whenever the child will try to execve
, a SIGTRAP
will wake up the parent. Right after, the parent proceed to get the value of the eax
register in the child process, and if it contains the value 11
, which correspond to the sysno for execve
on x86 Linux, the parent immediately send a SIGKILL
to the child. This prevent us from launching a shellcode from the stack that try to use the execve
syscall.
My initial approach was to craft a shellcode that use execveat
syscall instead. Unfortunatly, the Linux kernel that the host has is too old and does not implement the syscall, returning errno ENOSYS
in the eax
register after the int 0x80
.
ret2libc
Instead of crafting a shellcode, we can take advantage of the fact that the libc
is loaded into memory and has already some really usefull fuction like system
for attackers.
We can overwrite the saved-eip with the address of the system
function, and set-up the stack so the first argument is a pointer to a /bin/sh
string. With GDB, we can find these easily. Also ASLR is disabled so the libc
will be loaded in memory at the exact same address for each run.
|
|
The address of system
is at 0xf7e6aed0
and the address of /bin/sh
is at 0xf7f897ec
. How convenient that the libc even has such string !
Payload
After deducing how much garbage bytes we need to write before overwriting the saved-eip, we overwrite it to the address of system
and set-up the call stack so that system
first argument is the pointer to /bin/sh
:
|
|
Success !