杂
所谓远程线程,并非指跨主机,而是指跨进程。也即一个进程对另一个进程的注入。
被注入进程
最好还是自己手写一个,不然很多软件的进程应该是有防护机制,注进去的线程会被杀掉,dll也会被卸载,不方便观察。
delete.exe源码
#include <stdio.h>
#include <windows.h>
int main(void){
while(1){
Sleep(100);
}
return 0;
}
很简单,就是一段死循环
编写DLL
library.h
#ifndef DLL_GENERATE_LIBRARY_H
#define DLL_GENERATE_LIBRARY_H
// extern "C":以C方式导出
// __declspec(dllexport) 声明一个导出函数
extern "C" __declspec(dllexport) void MyAdd(int a, int b);
#endif //DLL_GENERATE_LIBRARY_H
library.cpp
#include "library.h"
#include <windows.h>
#include <iostream>
// 实现
void MyAdd(int a, int b) {
std::cout << a + b << std::endl;
}
extern "C" BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){
switch(fdwReason) {
case DLL_PROCESS_ATTACH:
MessageBox(NULL, "It works!", "Status", MB_OK);
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
default:
break;
}
return TRUE;
}
主函数DllMain的作用是首次被load时,弹窗提示。
注入流程
首先使用OpenProcess
打开指定PID的进程,权限要足够,不然可能出现无法创建线程的情况。
第二,获得dll文件的绝对路径的字符串长度,并使用VirtualAllocEx
在被注入进程中申请相应大小的内存。
第三,使用WriteProcessMemory
将dll文件的绝对路径字符串写入被注入进程。
第四,以dll文件的句柄、要查找的函数名作为GetProcAddress
的参数,查找本进程中加载的Kernel32.dll中LoadLibrary
函数的始址,根据是否采用UNICODE有不同的函数W/ A
。因为这个dll在所有进程中加载的位置都相同,所以本进程中的函数位置就是被注入进程中函数的位置。
第五,在被注入进程中使用CreateRemoteThread
创建线程,参数为线程函数(LoadLibrary)地址,线程函数参数(dll文件的绝对路径)
第六,若成功注入,因为dll是首次被load,所以调用其DllMain
时,fdwReason
参数的值为DLL_PROCESS_ATTACH
,判断一下,会弹窗提示成功。
枚举流程
注入完成后,枚举一下被注入进程加载了哪些DLL
Windows开发:进程、线程及DLL的枚举
注入及枚举源码
#include <iostream>
#include <windows.h>
#include <tchar.h>
#include <tlhelp32.h>
// 参数:dll路径,被注入进程的PID
bool ModuleInject(LPCTSTR szModule, DWORD dwPid){
// 参数:权限、是否有子进程继承权限、PID
HANDLE hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE,
FALSE,
dwPid
);
if (hProcess){
// 成功获取到进程句柄
// dll路径字符串的大小
int cByte = (_tcslen(szModule) + 1) * sizeof(TCHAR);
// 在被注入进程中申请分配内存,返回首地址
// 参数:进程句柄、要开始分配内存的位置、分配大小、分配内存类型、内存管理方式
LPVOID pAddr = VirtualAllocEx(hProcess, NULL, cByte, MEM_COMMIT, PAGE_READWRITE);
if (pAddr){
// 分配成功则尝试在被注入进程中写入dll路径
// 参数:进程句柄、分配好的内存首址、数据内容指针、数据长度、传出(写入了多少字节)
if (WriteProcessMemory(hProcess, pAddr, szModule, cByte, NULL)){
// 如果定义了UNICODE
#ifdef _UNICODE
// 使用GMH获得Kernel32.dll的句柄,用GPA在其中找到宽字节的LoadLibraryW函数的地址,此地址在所有进程中相同
// 强转为PSR类型指针,作为CreateRemoteThread的参数
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32")), "LoadLibraryW");
#else
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32")), "LoadLibraryA");
#endif
if (pfnStartAddr){
// 远程线程ID
DWORD dwThreadID = 0;
// 远程线程句柄
// 参数:进程句柄、安全属性指针、堆栈大小(0默认)、线程函数(LoadLibrary)地址、线程函数参数(dll路径)、创建标志、传出远程线程ID
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pfnStartAddr, pAddr, 0, &dwThreadID);
if (hRemoteThread){
// 若成功注入,则dll被load到进程中,其DllMain被执行
std::cout << "inject success." << std::endl;
// 等待远程线程退出再退出
WaitForSingleObject(hRemoteThread, INFINITE);
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
}
else{
std::cout << "create remote thread failed." << std::endl;
}
}
else{
std::cout << "cannot find func LoadLibrary" << std::endl;
}
}
else{
std::cout << "write mem failed." << std::endl;
}
}
else{
std::cout << "alloc mem failed." << std::endl;
return FALSE;
}
}
else{
std::cout << "failed to open process." << std::endl;
return FALSE;
}
}
VOID EnumProcessModule(int myPid) {
MODULEENTRY32 me32 = {0};
me32.dwSize = sizeof(MODULEENTRY32);
// 创建DLL型快照,参数为进程ID
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, myPid);
if (hSnap == INVALID_HANDLE_VALUE){
std::cout << "failed to create module snapshot." << std::endl;
}
else{
BOOL enumStatus = Module32First(hSnap, &me32);
int i = 0;
while (enumStatus){
std::cout << me32.szModule << ", " << me32.szExePath << std::endl;
i++;
enumStatus = Module32Next(hSnap, &me32);
}
}
CloseHandle(hSnap);
}
int main() {
int myPid = 0;
std::cin >> myPid;
ModuleInject(_T("D:\\CLion_workspace\\win32_programing\\dll_programing\\dll_inject\\lib\\libdll_generate.dll"), myPid);
Sleep(5000);
EnumProcessModule(myPid);
return 0;
}
演示
首先打开delete.exe,使用IDE运行或直接运行编译出的exe都可以,查看其PID
确认dll位置正确,运行注入程序,输入PID,杀软放行
弹窗提示
查看被注入进程加载的dll,注入成功。