点击蓝字
关注我们
声明
本文作者:Gality
本文字数:3500
阅读时长:20~30分钟
附件/链接:点击查看原文下载
本文属于【狼组安全社区】原创奖励计划,未经许可禁止转载
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,狼组安全团队以及文章作者不为此承担任何责任。
狼组安全团队有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经狼组安全团队允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
前言
本篇文章为进程注入系列的第二篇文章,同样是在process-inject项目的基础上进行进一步的扩展延伸,进而掌握进程注入的各种方式,本系列预计至少会有10篇文章,涉及7种进程注入方式及一些发散扩展。
原项目地址:https://github.com/suvllian/process-inject
题外话
首先,先说点题外话,我们都知道现代操作系统都是多任务的,也就是说同一时间会执行多个进程,而每个进程都有自己独立的进程空间,这就保证了所谓的进程的安全性,每个进程对自己所属的空间有控制权,而其他进程无权干涉别的进程的空间。
那么为什么windows提供了API使得一个进程可以获取别的进程内存的读写权限呢?这就相当于微软官方在设计了进程内存独立的机制后,又专门为访问别的进程的内存提供了方法,这并不是自己打自己脸,二者其实都有自己的意义
拿CreateRemoteThread来说,虽然这是一个经典的木马使用的api,但是如果你想在进程运行中增加功能或者对进程进行修改,就必须用到CreateRemoteThread,或者你想要为另一个进程创建的窗口建立子类,又或者当你想挂接别的进程时,都会用到这个API,所以我们看待问题时不能太过极端,技术无好坏,但我们有!
远程线程注入概论
远程线程注入是最经典的进程注入的手段,当然,也是被各大杀软厂商盯的死死的,但作为进程注入系列的开篇,又不得不从这个最古老的注入手段说起,虽然后续的各种手段都有了各种各样免杀的方式,但是身上都或多或少的有远程线程注入这种方式的影子,所以,作为学习来说,肯定还是从他开始。
前面说到过,windows提供的API使得我们可以对别的进程的内存进行访问,同时还提供了可以让别的API执行指定函数的API:CreateRemoteThread,这个API是这种注入方式的核心,即先将想要加载的DLL的绝对路径字符串写入到指定空间,然后用CreateRemoteThread强迫目标进程执行LoadLibrary将我们指定的DLL加载进进程,此时DLLMain中的内容就会直接执行。
所以远程线程注入的基本流程为:
-
获取debug权限
-
用OpenProcess获取进程句柄
-
通过进程句柄申请进程的内存VirtualAddress
-
用WriteProcessMemory进程内存进行写操作
-
用CreateRemoteThread让被注入exe加载任意dll
大体流程如上所述,不过还有些涉及到的知识点和细节需要说下
前置知识
DLL
因为本专题是对进程注入的研究,故只是简单介绍下Dll,dll和exe其实共享同一种文件格式,都是PE文件格式,我们可以简单把exe和dll认为成两种类型的应用程序:exe是独立执行的应用程序,就像机器人的主干,实现一些主体功能,dll是动态的修补应用程序,就像机器人的各种配件,机器人可以通过各种配件增加功能,同时部分功能的实现也需要以来一些基础配件(系统dll),dll本身没法直接执行
dll的入口函数叫做DllMain,DllMain中的代码会在dll被装载时自动执行,一般是为了dll做一些准备工作,不过既然是我们自己编写的dll,就可以将恶意代码写在DllMain中,这样只需要加载dll,我们的恶意代码就会自动执行了
seDebug权限
这个在上一篇文章<进程注入之进程提权>中讲的非常清楚了,不明白的话请参考上一篇文章
CreateRemoteThread
使用 Windows 远程线程机制,在本地进程中通过 CreateRemoteThread 函数在其他进程中开启并运行一个线程。CreateRemoteThread 函数原型如下:
HANDLE WINAPI CreateRemoteThread (
HANDLE hProcess, // 远程进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性
SIZE_T dwStackSize, // 线程栈的大小
LPTHREAD_START_ROUTINE lpStartAddress, // 线程入口函数的起始地址
LPVOID lpParameter, // 传递给线程函数的参数
DWORD dwCreationFlags, // 线程是否立即启动
LPDWORD lpThreadId // 用于保存内核分配给线程的ID
);
主要关注三个参数:hProcess、lpStartAddress 和 lpParameter。
-
hProcess 是要执行线程的目标进程句柄;
-
lpStartAddress 是线程函数的起始地址,且该函数必须位于目标进程;
-
lpParameter 是传递给线程函数的参数。
为了使远程进程加载 DLL,把 LoadLibrary 函数作为 CreateRemoteThread 的线程函数,要加载的 DLL 路径作为线程函数的参数即可。
让远程进程执行 LoadLibrary 函数加载 DLL 文件,需解决两个问题:
1)获得远程进程中 LoadLibrary 函数的地址:Kernel32.dll 是系统基本库,且 Windows 系统中,所有进程加载 Kernel32.dll 模块基址是固定且一致的,所以只需获取本地进程中 LoadLibrary 地址写入即可(通过GetProcAddress获得)。
2)向远程进程传递需加载 DLL 的路径:通过 Windows API 函数把路径写入远程进程中,使用以下API:OpenProcess、VirtualAllocEx、WriteProcessMemory、VirtualFreeEx。
还有个小细节,在于需要做一步强制类型转换,也就是将LoadLibrary转换成指定的LPTHREAD_START_ROUTINE类型才能正常使用,这是微软规定的。
注入过程
获取sedebug权限
直接贴代码了,上一章中详细讲过
BOOL EnableDebugPrivilege() {
HANDLE TokenHandle = NULL;
TOKEN_PRIVILEGES TokenPrivilege;
LUID uID;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) {
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID)) {
TokenPrivilege.PrivilegeCount = 1;
TokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
TokenPrivilege.Privileges[0].Luid = uID;
if (AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
CloseHandle(TokenHandle);
TokenHandle = INVALID_HANDLE_VALUE;
return TRUE;
}
else
goto Fail;
}
else
goto Fail;
}
else
goto Fail;
Fail:
CloseHandle(TokenHandle);
TokenHandle = INVALID_HANDLE_VALUE;
return FALSE;
}
获取目标进程句柄
使用 OpenProcess 函数打开远程进程的句柄。访问权限 dwDesiredAccess 需要设置为 PROCESS_ALL_ACCESS。
HANDLE WINAPI OpenProcess (
DWORD dwDesiredAccess, // 指定所得句柄具有的访问权限
BOOL bInheritHandle, // 是否可被继承
DWORD dwProcessId // 指定要打开的进程ID
);
OpenProcess(PROCESS_ALL_ACCESS, FALSE, ulTargetProcessID);
在目标进程分配内存空间
使用 VirtualAllocEx 在目标进程中分配内存空间,用来保存要加载 DLL 的路径。
LPVOID WINAPI VirtualAllocEx (
HANDLE hProcess, // 目标进程句柄
LPVOID lpAddress, // 期望的起始地址,通常置为NULL
SIZE_T dwSize, // 需分配的内存大小
DWORD flAllocationType, // 分配内存空间的类型,取 MEM_COMMIT
DWORD flProtect // 内存访问权限,指定为可读可写:PAGE_READWRITE
);
VirtualAddress = (WCHAR*)VirtualAllocEx(TargetProcessHandle, NULL,ulDllLength * sizeof(ULONG32), MEM_COMMIT, PAGE_READWRITE);
将 DLL 路径写至目标进程
用 WriteProcessMemory 函数把需加载的 DLL 的绝对路径写入到远程进程分配的内存空间中。
BOOL WINAPI WriteProcessMemory (
HANDLE hProcess, // 目标进程句柄
LPVOID lpBaseAddress, // 目标进程内存空间地址,也就是待写入字符串的地址(目的地址)
LPCVOID lpBuffer, // 原存放字符串的地址(源地址)
SIZE_T nSize, // 需写入数据字节数
SIZE_T *lpNumberOfBytesWritten // 实际写入的字节数,设置为 NULL
);
WriteProcessMemory(TargetProcessHandle, VirtualAddress, (LPVOID)wzDllFullPath, ulDllLength * sizeof(WCHAR), NULL)
获取 LoadLibraryW 地址
Windows 系统中,LoadLibraryW 函数位于 kernel32.dll 中,并且系统核心 DLL 会加载到固定地址,所以系统中所有进程的 LoadLibraryW 函数地址是相同的。用 GetProcAddress 函数获取本地进程 LoadLibraryW 地址即可。
WINAPI GetProcAddress (
MODULE hModule, // 模块句柄
LPCSTR lpProcName // 函数名
);
FunctionAddress = (LPTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(_T("kernel32")), "LoadLibraryW"); // ::表示调用API
在目标进程中运行远程线程
使用 CreateRemoteThread 函数使得目标进程调用 LoadLibraryW 函数加载指定 DLL
HANDLE WINAPI CreateRemoteThread (
HANDLE hProcess, // 远程进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性
SIZE_T dwStackSize, // 线程栈的大小
LPTHREAD_START_ROUTINE lpStartAddress, // 线程入口函数的起始地址
LPVOID lpParameter, // 传递给线程函数的参数
DWORD dwCreationFlags, // 线程是否立即启动
LPDWORD lpThreadId // 用于保存内核分配给线程的ID
);
ThreadHandle = CreateRemoteThread(TargetProcessHandle, NULL, 0, FunctionAddress, VirtualAddress, 0, NULL);
等待远程线程结束并清理
在远程线程结束后及时释放内存并关闭句柄
WaitForSingleObject(ThreadHandle, INFINITE); //等待线程结束
//释放申请的内存
VirtualFreeEx(TargetProcessHandle, VirtualAddress, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(ThreadHandle); //关闭句柄
CloseHandle(TargetProcessHandle);
最终代码及效果
// RemoteProcessInjection.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
using namespace std;
BOOL EnableDebugPrivilege();
BOOL InjectDllByRemoteThread(ULONG32 ulTargetProcessID, WCHAR* wzDllFullPath);
int _tmain(int argc, _TCHAR* argv[])
{
if (EnableDebugPrivilege() == FALSE) {
return 0;
}
ULONG32 ulProcessID = 0;
printf("Input the Process ID:");
cin >> ulProcessID;
WCHAR wzDllFullPath[MAX_PATH] = { 0 };
wcsncat_s(wzDllFullPath, L"D:\project\TestDll\Release\TestDll.dll", 60);
wcsncat_s(wzDllFullPath, L"D:\project\TestDll\x64\Release\TestDll.dll", 60);
InjectDllByRemoteThread(ulProcessID, wzDllFullPath);
return 0;
}
BOOL EnableDebugPrivilege() {
HANDLE TokenHandle = NULL;
TOKEN_PRIVILEGES TokenPrivilege;
LUID uID;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) {
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID)) {
TokenPrivilege.PrivilegeCount = 1;
TokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
TokenPrivilege.Privileges[0].Luid = uID;
if (AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
CloseHandle(TokenHandle);
TokenHandle = INVALID_HANDLE_VALUE;
return TRUE;
}
else
goto Fail;
}
else
goto Fail;
}
else
goto Fail;
Fail:
CloseHandle(TokenHandle);
TokenHandle = INVALID_HANDLE_VALUE;
return FALSE;
}
BOOL InjectDllByRemoteThread(ULONG32 ulTargetProcessID, WCHAR* wzDllFullPath) {
HANDLE TargetProcessHandle = NULL;
TargetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ulTargetProcessID);
//open handle
if (TargetProcessHandle == NULL) {
printf("failed to open dll");
return FALSE;
}
//get address of remote process
WCHAR* VirtualAddress = NULL;
ULONG32 ulDllLength = (ULONG32)_tcslen(wzDllFullPath) + 1;
VirtualAddress = (WCHAR*)VirtualAllocEx(TargetProcessHandle, NULL, ulDllLength * sizeof(ULONG32), MEM_COMMIT, PAGE_READWRITE);
if (VirtualAddress == NULL) {
printf("failed to Alloc!");
CloseHandle(TargetProcessHandle);
return FALSE;
}
//write
if (FALSE == WriteProcessMemory(TargetProcessHandle, VirtualAddress, (LPVOID)wzDllFullPath, ulDllLength * sizeof(WCHAR), NULL)) {
printf("failed to writen");
VirtualFreeEx(TargetProcessHandle, VirtualAddress, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(TargetProcessHandle);
return FALSE;
}
LPTHREAD_START_ROUTINE FunctionAddress = NULL;
FunctionAddress = (LPTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(_T("kernel32")), "LoadLibraryW");
HANDLE ThreadHandle = INVALID_HANDLE_VALUE;
//start
ThreadHandle = CreateRemoteThread(TargetProcessHandle, NULL, 0, FunctionAddress, VirtualAddress, 0, NULL);
if (ThreadHandle == NULL) {
VirtualFreeEx(TargetProcessHandle, VirtualAddress, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(TargetProcessHandle);
return FALSE;
}
//wait for single object
WaitForSingleObject(ThreadHandle, INFINITE);
VirtualFreeEx(TargetProcessHandle, VirtualAddress, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(ThreadHandle);
CloseHandle(TargetProcessHandle);
return TRUE;
}
后记
作者
Gality
过去的很久没更新了,未来会补上
扫描关注公众号回复加群
和师傅们一起讨论研究~
长
按
关
注
WgpSec狼组安全团队
微信号:wgpsec
Twitter:@wgpsec
本文始发于微信公众号(WgpSec狼组安全团队):进程注入之远程线程注入
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论