首页 远程DLL注入
文章
取消

远程DLL注入

0X00 前言

书接上回,本文介绍DLL远程注入

0x01 技术实现

我们这里创建一个notepad进程为例,并打印其PID和句柄。

1
2
3
4
5
6
7
8
if (CreateProcessA(NULL, (LPSTR)"notepad", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) == NULL){
        printf("[!] 进程创建失败\n");
        return;
}else{
        printf("[+] 进程创建成功!\n");
        printf("[+] 进程 PID: %d\n", pi.dwProcessId);
        printf("[+] 进程句柄: %p\n", pi.hProcess);
 }

当我们已成功获取到目标进程的进程句柄时,下一步就是将 DLL 注入到目标进程,这将需要使用几个Windows API。

VirtualAllocEx类似于 VirtualAlloc ,允许在远程进程中分配内存
WriteProcessMemory将数据写入远程进程
CreateRemoteThread在远程进程中创建一个线程

LoadLibraryA 用于在调用它的进程中加载 DLL。由于是在远程进程而不是本地进程中加载 DLL,因此不能直接调用它。所以必须检索LoadLibraryA 的地址 并将其传递给远程进程中创建的线程。可以使用 GetProcAddress GetModuleHandle 来确定地址。

1
2
// 通过kernel32.dll使用 LoadLibraryA
pLoadLibraryA = GetProcAddress(GetModuleHandleA("Kernel32"), "LoadLibraryA");

当在远程进程中创建新线程时,存储的地址 pLoadLibraryA 将用作线程入口。

下一步是在远程进程中给DLL分配内存。

1
pAddress = VirtualAllocEx(pi.hProcess, NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

在远程进程中成功分配内存后,可以使用 WriteProcessMemory 写入分配的内存。

根据其文档,WriteProcessMemory 函数如下所示

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

根据 WriteProcessMemory上面显示的 的参数,它将按如下方式调用,将申请的内存写入分配的地址,由之前调用的 VirtualAllocEx 函数返回。

1
WriteProcessMemory(pi.hProcess, pAddress, (LPVOID)path, len, NULL);

将 DLL 的路径成功写入分配的内存后,然后使用CreateRemoteThread在远程进程中创建一个新线程。并将pLoadLibraryA作为线程的起始地址传递,然后 pAddress 作为参数传递给调用 。

1
HANDLE hThread = CreateRemoteThread(pi.hProcess, NULL, 0, pLoadLibraryA, pAddress, 0, NULL);

完整代码

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

void DLLInject(LPCSTR path)
{
    STARTUPINFOA si = { sizeof(STARTUPINFOA) };
    PROCESS_INFORMATION pi = {};

    PTHREAD_START_ROUTINE pLoadLibraryA = NULL;
    LPVOID pAddress = NULL;
    HANDLE hThread = NULL;

    // 创建一个notepad的进程
    if (CreateProcessA(NULL, (LPSTR)"notepad", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) == NULL)
    {
        printf("[!] 进程创建失败");
        return;
    }
    else
    {
        printf("[+] 进程创建成功!\n");
        printf("[+] 进程 PID: %d\n", pi.dwProcessId);
        printf("[+] 进程句柄: %p\n", pi.hProcess);

        int len = strlen(path);

        // 通过kernel32.dll使用 LoadLibraryA
        pLoadLibraryA = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("Kernel32"), "LoadLibraryA");
        if (pLoadLibraryA == NULL) {
            printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
        }

        pAddress = VirtualAllocEx(pi.hProcess, NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
        if (pAddress == NULL) {
            printf("[!] VirtualAllocEx Failed With Error : %d \n", GetLastError());
        }

        WriteProcessMemory(pi.hProcess, pAddress, (LPVOID)path, len, NULL);
        printf("[*] 正在注入进程...\n");
        HANDLE hThread = CreateRemoteThread(pi.hProcess, NULL, 0, pLoadLibraryA, pAddress, 0, NULL);
        if (hThread == NULL) {
            printf("[!] CreateRemoteThread Failed With Error : %d \n", GetLastError());
        }
        printf("[+] 进程注入成功\n");

        if (pi.hProcess)CloseHandle(pi.hProcess);
        if (pi.hThread)CloseHandle(pi.hThread);
        if (hThread)CloseHandle(hThread);
    }
}

int main()
{

    DLLInject(R"(demo.dll)");

    return 0;
}

0x02 结果验证

下面请出我们上节课的老朋友demo.dll 来演示。

可见成功注入

0x03 小结

本文介绍了本地远程DLL注入的方法,并通过探讨了技术细节,以及具体的实现方法。当然,读者还可以通过一些其他办法,比如使用CreateToolhelp32Snapshot 去遍历进程然后选择已有进程注入,而不是通过创建进程去注入,这样可以更好的提升隐蔽性。

注:本文仅用于安全研究和学习之用

本文由作者按照 CC BY 4.0 进行授权