注入技术-远程线程注入

本文最后更新于:2021-08-09 晚上

远程线程注入

远程线程注入是指一个进程在另一个进程中创建线程的技术。

函数介绍

OpenProcess

打开现有的本地进程对象

1
2
3
4
5
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);

dwDesiredAccess:访问进程对象。此访问权限为针对进程的安全描述符进行检查,此参数可以是一个或者多个进程访问权限。如果调用了该函数的进程启用了SeDebugPrivilege权限,则无论安全描述符的内容是什么,它都会授予所请求的访问权限。

bInheritHandle:若此值为TRUE,则此进程创建的进程将继承该句柄。否则,进程不会进程此句柄。

dwProcessId:要打开的本地进程PID。

返回值:如果函数成功,则返回值是指定进程的打开句柄;如果失败,则返回值为NULL。

VirtualAllocEx

在指定进程的虚拟地址空间内保留、提交或更改内存的状态。

1
2
3
4
5
6
7
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

hProcess:进程的句柄。此函数在该进程的虚拟地址空间内分配内存,句柄必须具有PROCESS_VM_OPERATION访问权限。

lpAddress:指定要分配页面所需起始地址的指针。如果为NULL,则该函数自动分配内存。

dwSize:要分配的内存大小,以字节为单位。

flAllocationType:内存分配类型。此参数必须为以下值之一。

含义
MEM_COMMIT
0x00001000
在磁盘的分页文件和整体内存中,为指定的预留内存页分配内存
MEM_RESERVE
0x00002000
保留进程中虚拟地址空间的范围,但不会在内存或磁盘上的分页文件中分配任何实际物理存储位置
MEM_RESET
0x00080000
表示不再关注由lpAddress和dwSize指定的内存范围内的数据,页面不应从页面文件中读取或写入。
MEM_RESET_UNDO
0x1000000
只能在早期成功应用了MEM_RESET的地址范围内调用MEM_RESET_UNDO

flProtect:要分配的页面区域的内存保护。如果页面已提交,则可以指定任何一个内存保护常量。如果lpAddress指定了一个地址,则flProtect不能是以下任何值:

  • PAGE_NOACCESS
  • PAGE_GUARD
  • PAGE_NOCACHE
  • PAGE_WRITECOMBINE

返回值:如果函数成功,则返回值是分配页面的基址;如果失败,则返回为NULL。

WriteProcessMemory

在指定的进程中将数据写入内存区域,要写入的整个区域必须可访问,否则操作失败。

1
2
3
4
5
6
7
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);

hProcess:要修改的进程内存的句柄。句柄必须具有PROCESS_VM_WRITE和PROCESS_VM_OPERATION访问权限。

lpBaseAddress:指向指定进程中写入数据的基地址指针。在数据传输发生之前,系统会验证指定大小的基地址和内存中的所有数据是否可以进行写入访问,如果不可以访问,则该函数将失败。

lpBuffer:指向缓冲区的指针,其中包含要写入指定进程的地址空间中的数据。

nSize:要写入指定进程的字节数。

lpNumberOfBytesWritten:指向变量的指针,该变量接受传输到指定进程的字节数。如果为NULL,则忽略该参数。

返回值:如果函数成功,则返回值不为0;如果失败,则为0;

CreateRemoteThread

创建一个在另一个进程的虚拟地址空间中运行的线程。

1
2
3
4
5
6
7
8
9
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

hProcess:要创建线程的进程的句柄。句柄必须具有PROCESS_CREATE_THREAD、PROCESS_QUERY_INFORMATION、PROCESS_VM_OPERATION、PROCESS_VM_WRITE和PROCESS_VM_READ访问权限。

lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构指定新线程的安全描述符,并确定子进程是否可以继承返回的句柄。如果为NULL,则线程将会获得默认的安全描述符,并且不能继承该句柄。

dwStackSize:堆栈的初始大小,以字节为单位。如果参数为0,则新线程使用可执行文件的默认大小。

lpStartAddress:指向由线程执行类型为LPTHREAD_START_ROUTINE的应用程序定义的函数指针,并表示远程进程中线程的起始地址,该函数必须存在于远程进程中。

lpParameter:指向要传递给线程函数的变量的指针。

dwCreationFlags:控制线程创建的标志。

含义
0 线程在创建后立即运行
CREATE_SUSPENDED

该线程在挂起状态下创建,并且在调用ResumeThread函数之前不会运行
STACK_SIZE_PARAM_IS_A_RESERVATION

所述dwStackSize参数指定堆栈的初始保留大小。如果未指定此标志,则dwStackSize指定提交大小。

lpThreadId:指向接受线程标识符的变量的指针。如果此参数为NULL,则不返回线程标识符。

返回值:如果成功,则返回值是新线程的句柄;如果失败,则返回NULL。

实现过程

RemoteThreadInject.cpp代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include "pch.h"
#include <Windows.h>
#include <processthreadsapi.h>
#include <stdio.h>
BOOL CreateRemoteThreadInject(DWORD dwProcessId, WCHAR* pszDllFileName) {
HANDLE hProcess = NULL;
DWORD dwSize = 0;
LPVOID pDllAddr = NULL;
FARPROC pFuncProcAddr = NULL;
//获取注入进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess == NULL) {
printf("打开进程失败\n");
return FALSE;
}
dwSize = lstrlen(pszDllFileName) + 1;
//申请内存
pDllAddr = VirtualAllocEx(hProcess, NULL, 0x100, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (pDllAddr == NULL) {
printf("申请内存失败\n");
return FALSE;
}
//向申请的内存写入数据
BOOL WriteFlag = WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize * 2, NULL);
if (WriteFlag == NULL) {
printf("写入内存失败\n");
return FALSE;
}
//获取loadlibrary
pFuncProcAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
if (pFuncProcAddr == NULL) {
printf("获取loadlibrary地址失败\n");
return FALSE;
}
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
if (hRemoteThread == NULL) {
printf("创建线程失败\n");
return FALSE;
}
WaitForSingleObject(hRemoteThread, -1);
DWORD code;
GetExitCodeThread(hRemoteThread, &code);
code = GetLastError();
VirtualFreeEx(hProcess, pDllAddr, 0, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hRemoteThread);
return TRUE;
}
int main() {
printf("按下回车开始注入\n");
getchar();
HWND hNotepadWindow = FindWindow(L"Notepad",L"无标题 - 记事本");
if (hNotepadWindow == NULL) {
printf("打开进程失败\n");
exit(-1);
}
DWORD dwPID = 0;
GetWindowThreadProcessId(hNotepadWindow, &dwPID);
if (dwPID == 0) {
printf("获取PID失败\n");
exit(-1);
}
bool flag = CreateRemoteThreadInject(dwPID, L"自己的文件路径");
if (flag == FALSE) {
printf("注入失败\n");
}
else
printf("注入成功\n");
getchar();
return 0;
}

dllmain.cpp代码如下,主要实现一个弹窗功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"注入成功!", L"ok", MB_OK);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

效果查看

先打开notepad再打开我们编写的程序,将DLL文件放在指定路径下。

开始注入,就可以看到已经成功弹出了窗口。

参考

参考《Windows黑客编程技术详解》一书