简介
有时为了方便对进程中的数据进行修改,我们需要将可执行的shellcode或者dll注入到目标进程中。
注入技术有很多,比如:
-
• 通过createRemoteThread和LoadLibrary进行远线程注入
-
• 通过全局钩子hook
-
• 通过修改注册表进行注入
-
• APC注入
-
• 等等
-
远线程注入是指在另一个进程中创建线程的技术,在另一个进程中创建线程然后注入dll文件。
相关函数介绍
OpenProcess()
打开一个进程,如果是系统进程则需要管理员权限才能打开。
函数声明:
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwProcessId
)
-
• 参数一 : dwDesiredAccess 访问进程对象,是进程访问权限,可以设置 PROCESS_ALL_ACCESS
-
• 参数二 : bInheritHandle 该值为TRUE 则此进程创建的句柄将继承该句柄,否则不会继承
-
• 参数三 : dwProcessId 需要打开的进程 pid
-
• 返回值 : 函数执行成功返回打开进程的句柄,执行失败返回NULL
VirtualAllocEx()
在一个进程中开辟一块空间。
函数声明:
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
)
-
• 参数一 : hProcess 进程的句柄,该句柄必须有PROCESS_VM_OPERATION 权限
-
• 参数二 : lpAddress 指定要分配的页面的起始地址的指针,设置为NULL则自动分配内存
-
• 参数三 : dwSize 要分配的内存的大小
-
• 参数四 : flAllocationType 内存分配类型
-
• 返回值 : 函数执行成功返回分配的页面的基址,失败返回NULL
WriteProcessMemory()
向一个特定的进程中写入数据。
函数声明:
BOOL
WINAPI
WriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
);
-
• 参数一 : hProcess 要修改进程内存的句柄
-
• 参数二 : lpBaseAddress 需要写入的进程中的基地址指针
-
• 参数三 : lpBuffer 要写入的数据的地址指针
-
• 参数四 : nSize 写入数据的字节数
-
• 参数五 : lpNumberOfBytesWritten 一个变量接受传入进程字节数,可以设为 NULL 忽略该参数
-
• 返回值 : 函数执行成功,返回不为0,失败返回0
CreateRemoteThread()
在另一个进程中创建一个新的线程。
函数声明:
HANDLE
WINAPI
CreateRemoteThread(
_In_ HANDLE hProcess,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_opt_ LPDWORD lpThreadId
);
-
• 参数一 : hProcess 需要创建线程的进程句柄
-
• 参数二 : lpThreadAttributes 指向想新线程的安全描述符,可以设置为 NULL 将获得默认的安全描述符
-
• 参数三 : swStackSize 堆栈的初始大小,可以设为0则为默认大小
-
• 参数四 : lpStartAddress 至相关新线程要执行的函数的指针
-
• 参数五 : lpParameter 指向给新线程执行函数的参数
-
• 参数六 : dwCreationFlags 控制线程创建的标志,若为0则在创建线程后立即执行
-
• 参数七 : lpThreadId 一个指向线程标识符的变量的指针,设置为 NULL 不返回线程标识符
-
• 返回值 : 函数执行成功返回新线程的句柄,失败返回 NULL
GetMosuleHandleA()
检索指定模块的模块句柄。
函数声明:
HMODULE
WINAPI
GetModuleHandleA(
_In_opt_ LPCSTR lpModuleName
);
-
• 参数一 : lpModuleName 需要加载的模块的名称
-
• 返回值 : 执行成功返回指定模块的句柄,失败返回 NULL
GetProcAddress()
从指定的动态链接库中找到导出函数或者变量。
函数声明:
WINBASEAPI
FARPROC
WINAPI
GetProcAddress(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);
-
• 参数一 : hModule 指向dll模块的句柄
-
• 参数二 : lpProcName 需要找的函数或变量名字
-
• 返回值 : 执行成功返回函数地址,失败返回 NULL
具体实现
有了上述几个win的api我们就可以实现远线程注入了,在windows中,不同进程加载的ntdll.dll、kernel32.dll这2个最基本的DLL一般地址是一样的,所以我们在当前进程找到这两个dll中的导出函数就可以得到别的进程中的该函数地址。
所以远线程注入的基本思路就是:
-
1. 通过OpenProcess打开一个进程
-
2. 通过GetModuleHandleA和GetProcAddress找到某个dll导出函数的地址,这里我们选择了LoadLibraryA
-
3. 通过VirtualAllocEx在第一步打开的进程中创建一块可读可写可执行的空间
-
4. 使用WriteProessMemory在第三步开辟的空间中写入要注入的dll路径
-
5. 最后使用CreateRemoteThread在第一步打开的进程中创建一个线程,执行LoadLibraryA函数加载恶意的dll文件 具体代码如下,这里的参数一 pid 就是需要注入的进程pid,dllPath就是要注入的dll文件的路径,dllSize就是dll文件的路径大小。
这里createRemoteThread的第四个参数是创建新线程后执行的函数,第五个参数是执行该函数传入的参数,而LoadLibraryA刚好只需要一个参数,所以创建线程后调用该函数然后传入要加载的dll文件的路径,这样就可以加载恶意的dll文件了。
bool remoteThreadInject(DWORD pid, char* dllPath, size_t dllSize) {
// 根据ID打开进程的句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
if (!hProcess) {
std::cout << "OPEN PROCESS ERR..." << std::endl;
return false;
}
// 拿到loadlibary的地址
HMODULE hModule = GetModuleHandleA("kernel32.dll");
if (!hModule) {
std::cout << "GET MODULE ERR..." << std::endl;
CloseHandle(hProcess);
return false;
}
FARPROC funcAddr = GetProcAddress(hModule, "LoadLibraryA");
if (!funcAddr) {
std::cout << "GET FUNCADDR ERR..." << std::endl;
CloseHandle(hProcess);
return false;
}
// 在进程中开辟空间
LPVOID newAddr = VirtualAllocEx(hProcess, 0, dllSize + 1, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!newAddr) {
std::cout << "ALLOC ADDR ERR..." << std::endl;
CloseHandle(hProcess);
return false;
}
// 向进程写数据
bool Ret = WriteProcessMemory(hProcess, newAddr, dllPath, dllSize, 0);
if (!Ret) {
std::cout << "WRITE ERR..." << std::endl;
VirtualFreeEx(hProcess, newAddr, 0, MEM_RELEASE);
CloseHandle(hProcess);
return false;
}
// 在进程中创建线程执行
HANDLE hThread = CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)funcAddr, newAddr, 0, 0);
if (hThread == 0) return false;
WaitForSingleObject(hThread, 4294967295);
VirtualFreeEx(hProcess, newAddr, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return true;
}
如何获得进程的pid
我们可以通过任务管理器来查看相关进城的pid,但是在程序中我们得使用相关API来获得。
这里介绍三个windows的API:
CreateToolhelp32Snapshot
Process32First
Process32Next
先通过 CreateToolhelp32Snapshot 返回一个进程镜像。 然后通过 Process32First 和 Process32Next 遍历进程通过进程名字来匹配到相关pid。
具体代码实现如下:
DWORD getPid(const WCHAR* tmp) {
PROCESSENTRY32 pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE handleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (handleSnap == INVALID_HANDLE_VALUE) {
std::cout << "CreateToolhelp32Snapshot ERROR..." << std::endl;
return 0;
}
BOOL Ret = Process32First(handleSnap, &pe32);
while (Ret) {
if (!wcscmp(pe32.szExeFile, tmp)) {
return pe32.th32ProcessID;
}
Ret = Process32Next(handleSnap, &pe32);
}
CloseHandle(handleSnap);
std::cout << "NO SUCH PROCESS" << std::endl;
return 0;
}
效果展示
首先我实现了一个dll文件,在被加载的时候就弹一个messagebox:
这里我们选择任务管理器进行注入,因为任务管理器是系统进程,所以我们需要使用管理员权限打开
可以看到成功注入dll文件,弹出了messagebox
使用Process Explorer查看,成功加载了dll
-END-
如果本文对您有帮助,来个点赞、在看就是对我们莫大的鼓励。
推荐关注:
团队全员均持CISP-PTE(注册信息安全专业人员-渗透测试工程师)认证,积极参与着各类网络安全赛事并屡获佳绩,同时多次高水准的完成了国家级、省部级攻防演习活动以及相关重报工作,均得到甲方的一致青睐与肯定。
原文始发于微信公众号(弱口令安全实验室):远线程注入
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论