远线程注入
远线程注入
OpenProcess 函数
打开现有的本地进程对象。
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwProcessId
);
参数
- dwDesiredAccess [in]
访问进程对象。此访问权限针对进程的安全描述符进行检查。此参数可以是一个或多个进程访问权限。如果调用该函数的进程启用了SeDebugPrivilege权限,则无论安全描述符的内容如何,都会授予所请求的访问权限。 - bInheritHandle [in]
若此值为TRUE,则此进程创建的进程将继承该句柄。否则,进程不会继承此句柄。 - dwProcessId [in]
要打开的本地进程的标识符。
如果指定的进程是系统进程(0x00000000),则该函数失败,最后一个错误代码为ERROR_INVALID_PARAMETER。如果指定的进程是空闲进程或CSRSS进程之一,则此功能将失败,并且最后一个错误代码为ERROR_ACCESS_DENIED,因为它们的访问限制会阻止用户级代码打开它们。
如果您使用GetCurrentProcessId作为此函数的参数,请考虑使用GetCurrentProcess而不是OpenProcess,以提高性能。
返回值
- 如果函数成功,则返回值是指定进程的打开句柄。
- 如果函数失败,返回值为NULL。 要获取扩展错误信息,请调用GetLastError。
VirtualAllocEx 函数
在指定进程的虚拟地址空间内保留,提交或更改内存区域的状态。 该函数初始化其分配给零的内存。
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
参数
- hProcess [in]
过程的句柄。该函数在该进程的虚拟地址空间内分配内存。
句柄必须具有PROCESS_VM_OPERATION权限。有关更多信息,请参阅流程安全和访问权限。 - lpAddress [in]
指定要分配的页面的所需起始地址的指针。
如果您正在保留内存,则该函数会将该地址舍入到分配粒度的最接近的倍数。
如果您提交已经保留的内存,该功能会将该地址舍入到最接近的页面边界。要确定页面的大小和主机上的分配粒度,请使用GetSystemInfo函数。
如果lpAddress为NULL,则该函数确定在哪里分配该区域。 - dwSize [in]
要分配的内存大小,以字节为单位。
如果lpAddress为NULL,则函数将dwSize循环到下一个页面边界。
如果lpAddress不为NULL,则该函数将从lpAddress到lpAddress + dwSize的范围内分配包含一个或多个字节的所有页面。这意味着,例如,跨越页面边界的2字节范围会导致功能分配两个页面。 - flAllocationType [in]
内存分配类型。此参数必须包含以下值之一:
VALUE | MEANING |
---|---|
MEM_COMMIT | 为指定的预留内存页分配内存费用(从磁盘上的内存和分页文件的总体大小)。 该函数还保证当调用者稍后初次访问存储器时,内容将为零。 除非/直到虚拟地址被实际访问,实际的物理页面才被分配 |
MEM_RESERVE | 保留进程的虚拟地址空间的范围,而不会在内存或磁盘上的分页文件中分配任何实际物理存储 |
MEM_RESET | 表示由lpAddress和dwSize指定的内存范围内的数据不再受关注。 页面不应从页面文件中读取或写入页面文件。 然而,内存块将在以后再次被使用,所以不应该被分解。 该值不能与任何其他值一起使用 |
MEM_RESET_UNDO | 只能在早期成功应用了MEM_RESET的地址范围上调用MEM_RESET_UNDO。 它指示由lpAddress和dwSize指定的指定内存范围内的数据对呼叫者感兴趣,并尝试反转MEM_RESET的影响。 如果功能成功,则表示指定地址范围内的所有数据都是完整的。 如果功能失败,地址范围中的至少一些数据已被替换为零 |
- flProtect [in]
要分配的页面区域的内存保护。 如果页面被提交,您可以指定任何一个内存保护常量。
如果lpAddress指定了一个地址,flProtect不能是以下值之一:
PAGE_NOACCESS
PAGE_GUARD
PAGE_NOCACHE
PAGE_WRITECOMBINE
返回值
- 如果函数成功,则返回值是分配的页面区域的基址。
- 如果函数失败,返回值为NULL。 要获取扩展错误信息,请调用GetLastError。
WriteProcessMemory 函数
指定的进程中将数据写入内存区域。 要写入的整个区域必须可访问或操作失败。
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_ LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_ SIZE_T *lpNumberOfBytesWritten
);
参数
- hProcess [in]
要修改的进程内存的句柄。 句柄必须具有PROCESS_VM_WRITE和PROCESS_VM_OPERATION访问进程。 - lpBaseAddress [in]
指向写入数据的指定进程中的基地址的指针。 在数据传输发生之前,系统会验证指定大小的基地址和内存中的所有数据是否可以进行写入访问,如果不可访问,则该函数将失败。 - lpBuffer [in]
指向缓冲区的指针,其中包含要写入指定进程的地址空间的数据。 - nSize [in]
要写入指定进程的字节数。 - lpNumberOfBytesWritten [out]
指向变量的指针,该变量接收传输到指定进程的字节数。 此参数是可选的。 如果lpNumberOfBytesWritten为NULL,则忽略该参数。
返回值
- 如果函数成功,则返回值不为零。
- 如果函数失败,返回值为0(零)。 要获取扩展错误信息,请调用GetLastError。
CreateRemoteThread 函数
创建在另一个进程的虚拟地址空间中运行的线程。
使用CreateRemoteThreadEx函数创建在另一个进程的虚拟地址空间中运行的线程,并可选地指定扩展属性。
HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess,
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_ LPDWORD lpThreadId
);
参数
- hProcess [in]
要创建线程的进程的句柄。 句柄必须具有PROCESS_CREATE_THREAD,PROCESS_QUERY_INFORMATION,PROCESS_VM_OPERATION,PROCESS_VM_WRITE和PROCESS_VM_READ访问权限,如果某些平台上没有这些权限,可能会失败。 有关更多信息,请参阅流程安全和访问权限。 - lpThreadAttributes [in]
指向SECURITY_ATTRIBUTES结构的指针,该结构指定新线程的安全描述符,并确定子进程是否可以继承返回的句柄。 如果lpThreadAttributes为NULL,则线程将获得默认安全描述符,并且该句柄不能被继承。 线程的默认安全描述符中的访问控制列表(ACL)来自创建者的主令牌。dwStackSize [in]
堆栈的初始大小,以字节为单位。 系统将此值循环到最近的页面。 如果此参数为0(零),则新线程使用可执行文件的默认大小。 有关更多信息,请参阅线程堆栈大小。 - lpStartAddress [in]
指向由线程执行的类型为LPTHREAD_START_ROUTINE的应用程序定义函数的指针,并表示远程进程中线程的起始地址。 该功能必须存在于远程进程中。 有关更多信息,请参阅ThreadProc。 - lpParameter [in]
指向要传递给线程函数的变量的指针。 - dwCreationFlags [in]
控制线程创建的标志。若是 0,则表示线程在创建后立即运行。 - lpThreadId [out]
指向接收线程标识符的变量的指针。
如果此参数为NULL,则不返回线程标识符。
返回值
- 如果函数成功,则返回值是新线程的句柄。
- 如果函数失败,返回值为NULL。 要获取扩展错误信息,请调用GetLastError。
执行流程
-
通过OpenProcess获取目标进程句柄。
-
通过VirtualAllocEx在目标进程空间中申请内存,通过WriteProcessMemory放入需要载入的dll的路径
-
通过GetModuleHandleA获取诸如kernel32.dll这类系统dll的模块句柄,进而获取LoadLibraryA这类载入动态链接库的函数地址
-
通过
CreateRemoteThread
的参数传入目标进程句柄、写入到目标进程空间的dll路径、LoadLibraryA函数地址,实现中目标中创建多线程加载dll。
向进程空间写入数据
我们可以直接调用 VirtualAllocEx
函数在目标进程空间中申请一块内存,然后再调用WriteProcessMemory
函数将指定的 DLL 路径写入到目标进程空间中。
加载DLL实现
我们能够获取目标进程的 LoadLibrary 函数的地址,而且还能够获取目标进程空间中某个 DLL 路径的字符串地址,那么,将LoadLibrary 函数的地址作为多线程函数的地址,某个 DLL 路径字符串作为多线程函数的参数,传递给 CreateRemoteThread 函数在目标进程空间创建一个多线程,这个多线程就会调用 LoadLibrary 函数加载 DLL。
代码实现
DLL文件:
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
::MessageBox(NULL, "This Is From Dll!\nInject Success!", "OK", MB_OK);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
cpp代码:
#include <Windows.h>
#include<stdio.h>
VOID InjectDLL(DWORD PID, const char* Path) {
HANDLE hProcess;
DWORD dwSize = 0;
LPVOID PDllAddr;
FARPROC func;
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess==NULL) {
printf("OpenProcess Error");
return;
}
dwSize = lstrlen(Path) + 1;
PDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (PDllAddr==NULL) {
printf("VirtualAllocEx Error");
return;
}
BOOL pWrite = WriteProcessMemory(hProcess, PDllAddr, Path, dwSize, NULL);
if (!pWrite) {
printf("WriteProcessMemory Error");
return;
}
HMODULE hModule = GetModuleHandleA("kernel32.dll");
func = GetProcAddress(hModule, "LoadLibraryA");
if (func == NULL) {
printf("GetProcAddress Error");
return;
}
HANDLE pRemoteThread= CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)func, PDllAddr,0,NULL);
if (pRemoteThread == NULL) {
printf("CreateRemoteThread Error");
return;
}
CloseHandle(hProcess);
}
int main() {
const char* path ;
path = "hookdll.dll";
InjectDLL(5020, path);
system("pause");
return 0;
}
补充
获取进程句柄需要用OpenProcess,而服务进程和系统进程有session0隔离,我们正常运行程序的权限是用户权限,explorer和cmd打开的进程权限默认没有sedebug权限,而想要获取系统进程和服务进程的句柄,进行指定写相关的访问权的OpenProcess操作,需要SeDebug权限。Administrator默认拥有,但是未开启,所以可以通过代码开启,或者通过管理员启动powershell执行,这样自带sedebug权限。
坑点记录
注入的DLL文件需要与注入对应进程位数对应,否则可能会注入失败。当然也有可能是是因为权限不够问题。但该方法进行系统进程注入时,并不能成功注入到系统进程中。这是因为系统存在SEESION 0 隔离的安全机制。传统的DLL注入方式,并不能突破SESSION 0 隔离。