远程线程注入

问题引入


如果有一个程序,我们想在他里面执行我们自己的东西有什么办法呢?

  • 方法一:

QQ_1727865179500
我们可以让程序执行到这个虚线的地方,然后让他跳到这个function(),执行完function后再跳回原来的下一条指令。这种方式执行的就叫做HOOK

  • 方法二:

    我们可以直接开一个线程,这个线程就用来跑外面自己的代码。
    QQ_1727867497096

但是不管是哪种方法,都需要将外面自己的代码贴到进程A中才行

分为模块注入和代码注入


代码注入:

 没有全局变量和没有使用IAT表的硬编码,放在任何位置都能执行
  • 优点:很难被发现
  • 缺点:写起来很麻烦(因为有全局变量地址和IAT函数地址)

模块注入:

将模块一整个注入进去,这个模块可以是dll、exe等等
  • 优点:写起来非常简单
  • 缺点:非常容易被查杀

自己的进程加载DLL


1
2
3
4
// 1、自己的进程加载dll
HINSTANCE hModule = LoadLibrary("InjectDll.dll");
// 2、释放dll
FreeLibrary(hModule);

远程线程注入


原理:

  • B 往A注入需要提供线程函数的地址(需要在A中),和线程函数参数(也要在A中)
    asda

  • 线程函数是有格式要求的,类型是DWORD,调用约定是WINAPI,参数是void* 的指针:

    1
    2
    3
    DWORD WINAPI Function(LPVOID lParameter){
    return ExitCode;
    }
  • 有个偶然的东西就是,在程序中加载模块时用的函数是:LoadLoadLibrary而这个函数是这样的

    1
    HMODULE LoadLibraryA( [in] LPCSTR lpLibFileName );

    根据以往的学到的知识,我们可以知道这个HMODULE其实就是一个DWORD,然后参数是个LPCSTR,可以转成void* 用。

  • 由于LoadLibrary所属模块是每个程序一定要加载的,所以他这个函数的地址都一样(关闭地址随机化),所以我们创建线程时的线程函数直接将这个LoadLibrary的函数指针扔过去就行了

  • 此外这个函数还需要一个函数参数,即模块名称,这时候就要从进程B,将这个模块名写道进程A中了(WriteProcessMemory)先申请空间

  • 最后通过GetExitCodeThread获取线程的退出码,而LoadLibrary的返回值就是模块首地址,所以我们获取的就是模块句柄

所需函数


平常我们在一个进程中创建线程时用的是CreateThread:

1
2
3
4
5
6
7
8
HANDLE WINAPI CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
PDWORD lpThreadId
);

而我们要在进程B中让进程A创建一个线程时用的是CreateRemoteThread:

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

这两个唯一的不同点就是CreateRemoteThread多了一个hProcess

注入流程


  1. 打开要被注入的进程
  2. 远程进程中申请空间VirtualAllocEx
  3. 向进程中写入数据(dll名)WriteProcessMemory
  4. 在远程进程中创建线程CreateRemoteThread
  5. 等待线程结束返回 WaitForSingleObject
  6. 释放空间VirtualFreeEx

代码实现


Dll代码:

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
#include <windows.h>

void Init() {
MessageBox(NULL, TEXT("注入成功,Dll初始化"), TEXT("Init"), MB_OK);
}

void Destory() {
MessageBox(NULL, TEXT("程序结束,销毁"), TEXT("Destory"), MB_OK);
}

BOOL APIENTRY DllMain
(HANDLE hModule,
DWORD u1_reason_for_call,
LPVOID lpReserved)
{
switch (u1_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Init();
break;
case DLL_PROCESS_DETACH:
Destory();
break;
}
return TRUE;
}

这里的dll模块就不写那么花里胡哨了,只要能体现出来注入成功就行了:在dll初始化时弹出一个对话框,还有被销毁时弹出一个对话框。

进程B代码:

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
73
74
75
76
77
78
#include <windows.h>
#include <TlHelp32.h>
#include <assert.h>
#include <wchar.h>
#include <stdio.h>
// 获取相关PID
DWORD GetPid(LPWSTR name)
{
// 获取进程快照
HANDLE hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
// 判断是否打开成功
assert(hProcSnap != INVALID_HANDLE_VALUE);

PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);

// 获取进程列表中的第一个进程
BOOL flag = Process32First(hProcSnap, &pe32);
while (flag)
{
if (wcscmp(pe32.szExeFile, name) == 0)
{
CloseHandle(hProcSnap);
return pe32.th32ProcessID;
}
// 获取下一个进程
flag = Process32Next(hProcSnap, &pe32);
}

CloseHandle(hProcSnap);
MessageBox(NULL, TEXT("没有获取到相关进程"), TEXT("错误"), MB_OK);
return 0;

}


int main()
{
printf("请输入要注入的进程名称(.exe): ");
TCHAR Filename[MAX_PATH] = {0};
wscanf_s(L"%ls", Filename, MAX_PATH);
printf("请输入模块名称(.exe): ");
CHAR DllName[MAX_PATH] = { 0 };
scanf_s("%s", DllName, MAX_PATH);


DWORD Pid = GetPid(Filename);
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);

// 在进程A中分配放置dllname的空间
LPVOID Address = VirtualAllocEx(hProcess, NULL, 256, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!Address)
{
printf("进程内存空间申请失败\n");
return 0;
}


// 将dll名写入进程A中
DWORD DllNameAddr = WriteProcessMemory(hProcess, Address, DllName, 256, NULL);

// 创建远程线程
HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, Address, NULL, NULL);
if (!hRemoteThread)
{
printf("远程线程创建失败\n");
return 0;
}


// 等待线程结束
WaitForSingleObject(hRemoteThread, INFINITE);
DWORD ExitCode = 0;
GetExitCodeThread(hRemoteThread, &ExitCode);

printf("注入的dll地址是: 0x%X\n", ExitCode);

}

我们先打开要注入的进程A(32位),这里我就用之前写的Pe查看器来测试,先把他重命名一下test1.exe。不知道为什么用之前的进程名搜不出pid。打开后直接运行注入程序。
QQ_1727953734975
这里的模块(.exe)写多了,不用管。然后查看”pe查看器”
QQ_1727953784422
已经注入成功了,dll贴到4GB空间了。当我们关掉”test1.exe”时还会弹出销毁窗口,并且进程B中会输出dll注入到的地址:
213
asdaw

问题提出


  1. 如何启动我们想在A进程中执行的代码?
  2. 如何卸载我们远程注入的DLL?

1. 卸载我们远程注入的DLL

用FreeLibrary为线程函数,远程卸载就行了:

1
2
3
4
5
6
7
8
hRemoteThread = ::CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)FreeLibrary, (LPVOID)ExitCode, NULL, NULL);
if (!hRemoteThread)
{
printf("卸载dll远程线程创建失败\n");
return 0;
}
WaitForSingleObject(hRemoteThread, INFINITE);
GetExitCodeThread(hRemoteThread, &ExitCode);

2. 如何启动我们想在A进程中执行的代码?

答: 需要用到进程通信