首页
社区
课程
招聘
[原创] 图解远程线程注入技术(新手向)
发表于: 2024-10-24 00:59 3303

[原创] 图解远程线程注入技术(新手向)

2024-10-24 00:59
3303

图解远程线程注入技术

大纲

  • 引言
  • 注入流程
  • 要用到的API
    • CreateRemoteThreadAPI介绍
    • VirtualAllocEx API介绍
    • LoadLibraryAPI介绍
    • ThreadProc线程回调函数介绍
    • WriteProcessMemoryAPI介绍
  • 注入实现
  • 效果展示

0x00 引言

这是新手的第一次在看雪发帖,如有错误请大家及时指出,多多包涵,目前我在科锐学习现在是第二阶段的末尾,最近一直在学习安卓和NDK开发的内容,为了巩固复习以前Windows的内容遂写下这篇文章,同时也记录一下自己的学习历程,随后我会逐一倒着更新我在科锐学习到的一些东西,并且将尽量以足够详尽的语言和图表的形式来解释这些知识点,希望能对想要学习入门的新手提供一些力所能及的帮助,共勉。

0x01 注入流程

远程线程注入(Remote Thread injection)是一种能够将自身的代码注入到目标进程中的技术手段,同DLL劫持,APC注入等其他代码注入技术一样,目的都是将自身代码注入到目标进程中的技术,做到能够改变目标程序的执行流程,或者为该程序添加额外功能,我们知道在Windows系统中,每个程序的进程都是相互隔离互不干扰的。
为了进行代码的注入我们需要巧妙的利用CreateRemoteThread中线程回调函数ThreadProc的函数原型和LoadLibrary的函数原型相同的巧合将自身的DLL注入到目标进程中,并且在DLL的DLL_PROCESS_ATTACH中执行自己的代码达到接管目标程序的效果。

我们首先概览一下注入流程,远程线程注入的流程分为5个步骤:
前置阶段: 在注入之前我们需要对目标程序进行详尽的分析,找到我们需要操作的变量等等,然后根据我们的需求编写DLL用于注入。
注入阶段:

  1. 打开目标进程,获取进程句柄(FindWindowGetWindowProcessId)
  2. 在目标进程中分配内存(VirtualAllocEx)
  3. 在目标进程中申请的内存中写入Dll路径(WriteProcessMemory)
  4. 获取LoadLibrary的地址
  5. 调用CreateRemoteThread, 回调函数填写LoadLibrary地址(目标进程中的),回调参数填入DLL路径
    图片描述

0x02 API介绍

CreateRemoteThread
1
2
3
4
5
6
7
8
9
HANDLE CreateRemoteThread(
  HANDLE hProcess,                          // 目标进程句柄
  LPSECURITY_ATTRIBUTES lpThreadAttributes, // nullptr即可
  SIZE_T dwStackSize,                       // 初始化栈大小
  LPTHREAD_START_ROUTINE lpStartAddress,    // 线程回调函数的提软地址即获取的LoadLibrary地址
  LPVOID lpParameter,                       // 申请的目标进程字符串的地址由VirtualAllocEx获取
  DWORD dwCreationFlags,                    // 0即可
  LPDWORD lpThreadId                        // nullptr即可
);
VirtualAllocEx
1
2
3
4
5
LPVOID VirtualAllocEx(  HANDLE hProcess,          // 进程句柄
  LPVOID lpAddress,         // 指定NULL由函数自己决定在哪里分配
  SIZE_T dwSize,            // 填入要申请的大小
  DWORD flAllocationType,   // MEM_COMMIT类型为COMMIT即实际写入物理内存分页中
  DWORD flProtect           //      PAGE_READWRITE可读可写权限设置);

这里值得注意的是我们可以看到LoadLibrary和ThreadProc他们都只接受一个参数正是因为他们的函数原型一样所以我们才有机会进行远程线程注入

LoadLibrary
1
HINSTANCE LoadLibrary( LPCTSTR lpLibFileName /*dll路径const char* */);
ThreadProc
1
DWORD WINAPI ThreadProc(LPVOID lpParameter /*线程回调函数的参数*/ );
WriteProcessMemory
1
2
3
4
5
6
7
BOOL WriteProcessMemory(
HANDLE hProcess,     // 进程句柄
LPVOID lpBaseAddress, // 地址
LPVOID lpBuffer,    // 要写入的内容指针
DWORD nSize,        // 要写入的大小
LPDWORD lpNumberOfBytesWritten  // 填空即可
);

0x03 注入实现

首先是简单的注入实现这里将会展示我上面提到的流程,值得注意的是我给大家的代码不能直接使用需要自己封装,希望大家把我的代码仅仅作为参考示例,因为你看懂了我的代码,不算懂自己动手敲一遍并且能跑通才能更好的理解这个知识点。

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
std::string strDllPath {"d:\xxx\xxx\xxxxx\cheat.dll"}; // 这里脱下敏
DWORD dwPid = 0// 这里需要根据你自己的需求获取到进程的pid
 
 
    // 1. 打开进程获取句柄
         
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
 
    if (hProc == NULL)
    {
        printf("进程句柄获取失败!");
        return FALSE;
    }
 
    // 2. 在目标进程中分配内存
    BYTE* pMem = (BYTE*)VirtualAllocEx(hProc, 0, 0x1000, MEM_COMMIT, PAGE_READWRITE);
 
    if (pMem == NULL)
    {
        printf("目标进程分配内存失败!");
        return FALSE;
    }
 
    // 3. 写入dll路径
 
    BOOL isWrited = WriteProcessMemory(hProc, pMem, strDllPath.c_str(), strDllPath.size(), NULL);
     
    if (!isWrited)
    {
        printf("写入DLL内存失败!");
        return FALSE;
    }
 
 
    // 4. 获取LoadLibraryA地址
    auto pfnLoadlib = GetProcAddress(GetModuleHandle("kernel32"), "LoadLibraryA");
 
    if (pfnLoadlib == NULL)
    {
 
        printf("获取LoadLibrary 地址失败!");
        return FALSE;
    }
 
 
    // 5. 创建远程线程进行注入
    HANDLE hrt = CreateRemoteThread(hProc, nullptr, 0, (LPTHREAD_START_ROUTINE)pfnLoadlib, pMem, 0, 0);
 
    if (hrt == NULL)
    {
        printf("注入失败!");
        return FALSE;
 
    }

为了更详尽的演示示例我将尝试为一款飞机躲子弹小游戏利用远程线程注入的形式编写一款辅助,辅助功能将会以添加菜单栏的形式添加到游戏窗口中去,辅助功能有自动躲子弹,无敌,修改子弹数量等。
以下为dll中辅助部分的代码,编译完成后用上面注入的代码将其注入到游戏进程中就可以进行使用,具体游戏文件我会po在下面大家自行下载即可。

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
218
219
220
221
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include "resource.h"
#include <cstdint>
#include <string>
 
WNDPROC g_pfnOldWndProc = nullptr;
 
DWORD getPlaneX()
{
    DWORD* pX = (DWORD*)0x00406D6C;
    return *pX;
}
 
 
DWORD getPlaneY()
{
    DWORD* pY = (DWORD*)0x00406D70;
    return *pY;
}
 
void setPlanePosX(DWORD pos)
{
    DWORD* pX = (DWORD*)0x00406D6C;
    *pX = pos;
}
void setPlanePosY(DWORD pos)
{
    DWORD* pY = (DWORD*)0x00406D70;
    *pY = pos;
}
 
DWORD getBulletNumbers()
{
    DWORD* pNum = (DWORD*)0x00406DA8;
    return *pNum;
}
 
 
DWORD translatePos(DWORD dwPos)
{
    DWORD _ret = dwPos;
 
    _ret >>= 6;
    if (_ret < 4)
    {
        return 0;
    }
    _ret -= 4;
    return _ret;
}
 
 
 
BOOL autoHide()
{
    
    DWORD dwBltCount = getBulletNumbers();
    for (unsigned int x = 5; x < 200; x++)
    {
        for (unsigned int y = 5; y < 200; y++)
        {
            dwBltCount = getBulletNumbers();
            BOOL isCrack = FALSE;
            // 遍历子弹数组
            for (unsigned int i = 0; i < dwBltCount; i++)
            {
                uint8_t blt[15];
                memcpy(blt, (void*)((unsigned int)0x00406E10 + i * 15), 15);
                 
                void* tmp = (void*)blt;
                DWORD dwOrgX;
                DWORD dwOrgY;
                memcpy(&dwOrgX, tmp, 4);
                memcpy(&dwOrgY, (void*)((int)tmp + 4), 4);
 
                unsigned int dwPtX = translatePos(dwOrgX); // 点x坐标
                unsigned int dwPtY = translatePos(dwOrgY); // 点y
                 
                // 判断是否碰撞
 
                if (dwPtX >= x - 4 && dwPtX <= x + 16 && dwPtY >= y - 4 && dwPtY <= y + 16)
                {
 
                    isCrack = TRUE;
                    if (x > 200)
                    {
                        x = 0;
                    }
                    x + 10;
                    break;
                }
 
                // 这里如果不输出就会有问题???
                char p[100];
                sprintf_s(p, "[DLL] x: %d, y: %d", x, y);
                OutputDebugString(p);
            }
            if (!isCrack)
            {
                setPlanePosX(x);
                setPlanePosY(y);
            }
        }
    }
    return TRUE;
 
 
}
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    while (true)
    {
        autoHide();
        Sleep(1);
        return 0;
    }
 
}
 
LRESULT CALLBACK NewWindowProc(HWND hwnd,      // handle to window
    UINT uMsg,      // message identifier
    WPARAM wParam,  // first message parameter
    LPARAM lParam   // second message parameter
)
{
    uint8_t * pInvi = (uint8_t *)0x00403616;
    HANDLE tid = nullptr;
    if (uMsg == WM_COMMAND)
    {
        switch (LOWORD(wParam))
        {
        case MN_INVI:
        {
            *pInvi = 0xeb;
            MessageBox(NULL, "开启无敌", "tips", MB_OK);
            break;
        }
        case MN_CLS_INVI:
        {
            *pInvi = 0x74;
            MessageBox(NULL, "关闭无敌", "tips", MB_OK);
            break;
        }
        case MN_AUTO_HD:
        {
            if (tid == nullptr)
            {
                CreateThread(0, 0, ThreadProc, 0, 0, 0);
                MessageBox(NULL, "自动躲子弹", "tips", MB_OK);
                
            }
            break;
        }
        case MN_CLS_AUTO_HD:
        {
            if (tid != nullptr)
            {
                TerminateThread(tid, 0);
                tid = nullptr;
                MessageBox(NULL, "关闭自动躲子弹", "tips", MB_OK);
 
            }
            break;
        }
        default:
            break;
        }
    }
 
    return CallWindowProc(g_pfnOldWndProc, hwnd, uMsg, wParam, lParam);
}
 
BOOL install()
    if (g_pfnOldWndProc == nullptr)
    {
        HWND hWnd = FindWindow("wcTKKN", "摿孭");
        if (hWnd == nullptr)
        {
            OutputDebugString("FindWindow Failed");
            return false;
        }
        //添加菜单
        // HMENU hMain = GetMenu(hWnd);
        HMENU hSubMn = LoadMenu(GetModuleHandle("CheatPlane"), MAKEINTRESOURCE(IDR_MENU1));
     
        //HMENU hFuncMn = GetSubMenu(hSubMn, 0);
     
        SetMenu(hWnd, hSubMn);
        InvalidateRect(hWnd, NULL, TRUE);
        UpdateWindow(hWnd);
 
   
        g_pfnOldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (LONG)NewWindowProc);
    }
    return true;
}
 
 
 
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        install();
        break;
    case DLL_THREAD_ATTACH:
        install();
        break;
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

0x04 注入效果展示

注入之前的游戏

注入之后的游戏

至此我们成功的借助远程线程注入技术将辅助软件注入到游戏进程中

结语

感谢大家看到这里同时也感谢科锐老师们的辛苦付出,如果在这过程中有什么不对的请及时向我纠错我将会第一时间整改,同时文章编写不易希望大家点点赞,有问题也可以发在评论区里,游戏文件我会放在附件里,无毒,请放心使用。
作者介绍: 科锐逆向CR49期二阶段学员,李同学。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-10-24 01:02 被TeddyBe4r编辑 ,原因: 优化导航栏
上传的附件:
收藏
免费 9
支持
分享
最新回复 (3)
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
2024-10-27 19:27
0
雪    币: 227
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
niubi
6天前
0
雪    币: 542
活跃值: (3004)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
4天前
0
游客
登录 | 注册 方可回帖
返回
//