win32笔记十八 远程线程注入与进程通信
本帖最后由 huchen 于 2024-5-24 00:34 编辑之前的笔记索引
(https://www.52pojie.cn/thread-1914443-1-1.html)
# 远程线程
在说之前,先来简单的回顾下线程
**线程是附属在进程上的执行实体,是代码的执行流程**,以前说过,一个进程至少要有一个线程,这个取决于你想同时干多少件事,那就要有多少个线程。
一个main函数就算一个线程
**代码必须通过线程才能执行**,就比如说你定义了一个新函数,但是这代码并没有执行起来,换句话说就是没有在主函数调用,所以代码必须通过线程才能执行
当然,也可以通过创建一个线程来让这段代码执行
```c
#include<stdio.h>
#include<Windows.h>
void Fun()
{
for (int i = 1 ; i <= 10; i++)
{
Sleep(1000);
printf("------Fun*(%d)-------\n",i);
}
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
Fun();
return 0;
}
int main()
{
CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
getchar();
return 0;
}
```
这样也是可以运行的
所以,代码只有放进线程里才能被调用,至于这个线程,是main还是自己起的线程都可以
回顾完毕,接下要说一个万恶之源,也是微软提供的一个邪恶的API
## CreateRemoteThread
```c
HANDLE CreateRemoteThread(
HANDLE hProcess, // handle to process
LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
DWORD dwStackSize, // initial stack size
LPTHREAD_START_ROUTINE lpStartAddress, // thread function
LPVOID lpParameter, // thread argument
DWORD dwCreationFlags, // creation option
LPDWORD lpThreadId // thread identifier
);
```
这个函数的作用是可以在别的进程创建一个线程
看这个参数也很熟悉,只是多了个进程的句柄
接下来先说说我们的目的是什么,这里要借用上面的代码,生成的exe文件,放置到桌面(好方便的目的,不放到桌面也行)我们写代码,让这个exe在继续执行一次,首先让这个exe一直执行
```c
// 远程线程.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
//自定义函数,第一个参数是PID,第二个参数是函数地址
BOOL MyRemoteThread(DWORD ProcId,DWORD ProcAddr)
{
HANDLE hProc;
HANDLE hThread;
DWORD ThreadID;
//1.获取进程的句柄
hProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcId);
if(hProc == NULL)
{
OutputDebugString("获取失败\n");
return FALSE;
}
//2.创建远程线程
hThread = CreateRemoteThread(hProc,NULL,0,(LPTHREAD_START_ROUTINE)ProcAddr,NULL,0,&ThreadID);
if(hThread == NULL)
{
OutputDebugString("创建失败\n");
CloseHandle(hProc);
return FALSE;
}
//3.关闭资源
CloseHandle(hProc);
CloseHandle(hThread);
return TRUE;
}
int main()
{
//用工具获取窗口句柄
HWND hwn = (HWND)0x000201BA;
DWORD ProcID;
GetWindowThreadProcessId(hwn,&ProcID);
MyRemoteThread(ProcID,0x00401118 );
return 0;
}
```
我相信这些API已经构不成什么大威胁了,所以就简单带过
第一步我要获取这个进程的句柄,怎么获得这个进程的句柄呢
想到OpenProcess这个API,可以得到句柄,看这个函数的参数,是需要PID的,也就是进程ID,这个就简单了,根据以前学过的知识很容易,我这里就用了窗口来获取PID,GetWindowThreadProcessId这个函数就是通过窗口句柄来获得PID的
当然也可以用创建父子进程来获取,或者直接看资源管理器,这些都行
既然要创建线程,那么肯定要执行一段代码或者函数啊,那么就要这个函数的地址,也就是线程函数
这里由于我还没学到那么深,所以这里就直接在汇编中看CreateThead函数地址了
(还请大佬们告诉我怎么用代码来获取别的进程的API函数地址(不是像LoadLibrary那种地址一样的),我知道在kernel32.dll里,但是不知道怎么用代码实现,如果有相关的文章,还烦请推给我,感谢了)
然后万事具备,只欠CreateRemoteThread
然后执行,发现成功
# 远程线程注入
## 什么是注入
所谓注入就是在第三方进程不知道或者不允许的情况下将模块或代码写入对方进程空间,并设法执行的技术
已知的注入方式
远程线程注入、APC注入、消息钩子注入、注册表注入、导入表注入、输入法注入等等
了解了什么是远程线程,这里就说一下思路是什么
在上一章,我们使用了远程线程让程序在执行了一次,我们又知道了怎么加载一个dll,那么我能不能在别的程序加载这个dll呢?
这里就要就要去看看CreateRemoteThread函数里的线程函数了
> - *lpStartAddress*
>
> Pointer to the application-defined function of type**LPTHREAD_START_ROUTINE** to be executed by the thread and represents thestarting address of the thread in the remote process. The function must exist inthe remote process. For more information on the thread function, see [**ThreadProc**](prothred_9wkj.htm).
>
> - ` lpStartAddress`:这是一个指向应用程序定义函数的指针,该函数的类型是 `LPTHREAD_START_ROUTINE`。当线程启动时,这个函数将被执行,并代表远程进程中线程的起始地址。这个函数必须在远程进程中存在。有关线程函数的更多信息,请参阅 `ThreadProc`。
这里说明只要按照ThreadProc这个格式就可以了,格式就自行搜索了
所以我就可以把LoadLibrary来当成线程函数,来完成注入的操作
```c
// 远程线程注入.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
HANDLE hProc;
HMODULE hModule;
DWORD lpExitCode;
BOOL LoadDll(DWORD ProcId,char *DllPath)
{
HANDLE hThread;
DWORD PathLen;
LPVOID DllAlloc;
BOOL Ret;
DWORD LoadAddr;
Ret = 0;
hProc = 0;
LoadAddr = 0;
//1.获取进程的句柄
hProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcId);
if(hProc == NULL)
{
OutputDebugString("获取失败\n");
return FALSE;
}
//2.计算dll路径长度
PathLen = strlen(DllPath)+1;
//3.为dll分配空间
DllAlloc = VirtualAllocEx(hProc,NULL,PathLen,MEM_COMMIT,PAGE_READWRITE);
if(DllAlloc == NULL)
{
OutputDebugString("分配失败\n");
CloseHandle(hProc);
return FALSE;
}
//4.写入dll路径进目标进程
Ret = WriteProcessMemory(hProc,DllAlloc,DllPath,PathLen,NULL);
if(!Ret)
{
OutputDebugString("写入失败\n");
CloseHandle(hProc);
return FALSE;
}
//5.获取模块句柄
hModule = GetModuleHandle("kernel32.dll");
if(!hModule)
{
OutputDebugString("获取句柄失败\n");
CloseHandle(hProc);
return FALSE;
}
//6.获取LoadLibraryA函数地址
LoadAddr = (DWORD)GetProcAddress(hModule,"LoadLibraryA");
if(!LoadAddr)
{
OutputDebugString("获取地址失败\n");
CloseHandle(hProc);
CloseHandle(hModule);
return FALSE;
}
//7.创建远程线程
hThread = CreateRemoteThread(hProc,NULL,0,(LPTHREAD_START_ROUTINE)LoadAddr,DllAlloc,0,NULL);
if(hThread == NULL)
{
OutputDebugString("创建失败\n");
CloseHandle(hProc);
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, &lpExitCode);
//8.关闭资源
CloseHandle(hThread);
return TRUE;
}
//9.卸载dll
BOOL FreeDll()
{
HANDLE hFreeThread;
DWORD FreeAddr = (DWORD)GetProcAddress(hModule,"FreeLibrary");
hFreeThread = CreateRemoteThread(hProc,NULL,0,(LPTHREAD_START_ROUTINE)FreeAddr,(LPVOID)lpExitCode,0,NULL);
if(hFreeThread == NULL)
{
OutputDebugString("卸载失败\n");
CloseHandle(hProc);
return FALSE;
}
WaitForSingleObject(hFreeThread, INFINITE);
CloseHandle(hFreeThread);
CloseHandle(hProc);
return TRUE;
}
int main()
{
LoadDll(10096,"C:\\Documents and Settings\\Administrator\\桌面\\Test\\B.dll");
if(getchar() == 'q')
{
FreeDll();
}
return 0;
}
```
这里我就直接看管理器的方式来获取PID了
这里我来解释一下每一步是干什么的
0.先从定义LoadDll函数说起,有两个参数,一个PID,一个是dll的绝对路径,字符串的形式
1.获取句柄,这个不多说
2.计算路径的长度,为后面的分配空间作准备
3.为dll分配空间,VirtualAllocEx这个参数在以前的章节讲过他的兄弟,这个只是在其它的进程分配空间。
可以在分配空间下个断点,运行,然后附加到OD,查看DllAlloc的内容
再输入命令,db 0x003a0000,可以看到什么都没有,这是因为还没有写进去
写进去就可以看到了
5.获取模块句柄,LoadLibrary在kernel32.dll这个模块里,要查找指定的API,要先获取相对应的模块句柄
6.获取LoadLibraryA函数地址
7.创建远程线程
8.关闭资源
9.卸载dll,都差不多的操作
dll还是用的以前的加减法,然后就是DllMain的处理
```c
// B.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
HANDLE hdDll;
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
printf("DLL注入中……\n");
return 0;
}
/*DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
printf("卸载已完成\n");
return 0;
}*/
BOOL APIENTRY DllMain( HANDLE hModule,
DWORDul_reason_for_call,
LPVOID lpReserved
)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hdDll = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL);
break;
case DLL_PROCESS_DETACH:
//hdDll = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc1,NULL,0,NULL);
MessageBox(0,"卸载完成","",0);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
```
这是用来处理的方式,有个问题,当执行注释的时候,就会闪退,两个控制台都没了,还请大佬们解答一下
运行结果
我这个进程管理工具不显示中文,但是下面会有,这是对的,可以看到注入进去了
然后我在卸载
点了确定后就卸载成功了
# 进程间通信
如果两个进程处于同一台机器的话,可以通过管道、消息队列、共享内存等来实现通信,但大部分还是采用的是共享内存来进行通信的
接下就以共享内存来实现通信
首先写个小游戏
```c
#include<stdio.h>
#include<windows.h>
void Attack()
{
printf("********攻击*********\n");
return;
}
void Evade()
{
printf("********闪避*********\n");
return;
}
void Blood()
{
printf("********回血*********\n");
return;
}
int main()
{
char cmd;
printf("********游戏开始*********\n");
while(1)
{
cmd = getchar();
switch(cmd)
{
case 'A':
Attack();
break;
case 'E':
Evade();
break;
case 'B':
Blood();
break;
}
}
system("pause");
return 0;
}
```
就是个根据输入来显示如何操作的游戏
这个例子需要把我们之前学的给串起来,实现的功能就是让这个程序自己玩
所以我又写了个dll
```c
// GameDll.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#define _MAP_ "共享内存"
#define _ATTACK_ 0x00401030
#define _EVADE_ 0x00401080
#define _BLOOD_ 0x004010D0
HANDLE hModule;
HANDLE hMapFile;
LPTSTR Buff;
DWORD Type;
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
Type = 0;
//共享内存
hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,_MAP_);
if(hMapFile == NULL)
{
printf("打开共享内存失败:%d\n",GetLastError());
return 0;
}
//映射内存
Buff = (LPTSTR)MapViewOfFile(hMapFile,FILE_MAP_ALL_ACCESS,0,0,BUFSIZ);
for(;;)
{
if(Buff!=NULL)
{
//读取数据
CopyMemory(&Type,Buff,4);
}
if(Type == 1)
{
//攻击
_asm
{
mov eax,_ATTACK_
call eax
}
Type = 0;
CopyMemory(Buff,&Type,4);
}
if(Type == 2)
{
//闪避
_asm
{
mov eax,_EVADE_
call eax
}
Type = 0;
CopyMemory(Buff,&Type,4);
}
if(Type == 3)
{
//回血
_asm
{
mov eax,_BLOOD_
call eax
}
Type = 0;
CopyMemory(Buff,&Type,4);
}
if(Type == 4)
{
//卸载dll并退出
FreeLibraryAndExitThread((HMODULE)hModule,0);
}
Sleep(500);
}
return 0;
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORDul_reason_for_call,
LPVOID lpReserved
)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
```
首先我获取了游戏里关键函数的地址,这个就不演示了,太基础了
这是个dll,所以需要注入,注入的话就需要真正的操控者
```c
// 进程间通信.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#define _MAP_ "共享内存"
HANDLE hProc;
HMODULE hModule;
DWORD lpExitCode;
HANDLE hMap;
LPTSTR Buff;
BOOL LoadDll(DWORD ProcId,char *DllPath)
{
HANDLE hThread;
DWORD PathLen;
LPVOID DllAlloc;
BOOL Ret;
DWORD LoadAddr;
Ret = 0;
hProc = 0;
LoadAddr = 0;
//1.获取进程的句柄
hProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcId);
if(hProc == NULL)
{
OutputDebugString("获取失败\n");
return FALSE;
}
//2.计算dll路径长度
PathLen = strlen(DllPath)+1;
//3.为dll分配空间
DllAlloc = VirtualAllocEx(hProc,NULL,PathLen,MEM_COMMIT,PAGE_READWRITE);
if(DllAlloc == NULL)
{
OutputDebugString("分配失败\n");
CloseHandle(hProc);
return FALSE;
}
//4.写入dll路径进目标进程
Ret = WriteProcessMemory(hProc,DllAlloc,DllPath,PathLen,NULL);
if(!Ret)
{
OutputDebugString("写入失败\n");
CloseHandle(hProc);
return FALSE;
}
//5.获取模块句柄
hModule = GetModuleHandle("kernel32.dll");
if(!hModule)
{
OutputDebugString("获取句柄失败\n");
CloseHandle(hProc);
return FALSE;
}
//6.获取LoadLibraryA函数地址
LoadAddr = (DWORD)GetProcAddress(hModule,"LoadLibraryA");
if(!LoadAddr)
{
OutputDebugString("获取地址失败\n");
CloseHandle(hProc);
CloseHandle(hModule);
return FALSE;
}
//printf("%x\n",LoadAddr);
//7.创建远程线程
hThread = CreateRemoteThread(hProc,NULL,0,(LPTHREAD_START_ROUTINE)LoadAddr,DllAlloc,0,NULL);
if(hThread == NULL)
{
OutputDebugString("创建失败\n");
CloseHandle(hProc);
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, &lpExitCode);
//8.关闭资源
//CloseHandle(hProc);
CloseHandle(hThread);
return TRUE;
}
BOOL CreatShareMemory()
{
//创建共享内存
hMap = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,0x1000,_MAP_);
if(hMap == NULL)
{
printf("CreateFileMapping ERROR:%d\n",GetLastError);
return FALSE;
}
//映射内存
Buff = (LPTSTR)MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0,0,BUFSIZ);
if(Buff == NULL)
{
printf("MapViewOfFile ERROR:%d\n",GetLastError);
return FALSE;
}
return TRUE;
}
BOOL FreeDll()
{
HANDLE hFreeThread;
DWORD FreeAddr = (DWORD)GetProcAddress(hModule,"FreeLibrary");
hFreeThread = CreateRemoteThread(hProc,NULL,0,(LPTHREAD_START_ROUTINE)FreeAddr,(LPVOID)lpExitCode,0,NULL);
if(hFreeThread == NULL)
{
OutputDebugString("卸载失败\n");
CloseHandle(hProc);
return FALSE;
}
WaitForSingleObject(hFreeThread, INFINITE);
CloseHandle(hFreeThread);
CloseHandle(hProc);
return TRUE;
}
DWORD GetProcID(char* ProcName)
{
HANDLE hProcSnap = NULL;
PROCESSENTRY32 lppe = {0};
hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(hProcSnap == INVALID_HANDLE_VALUE)
{
return 0;
}
lppe.dwSize = sizeof(PROCESSENTRY32);
if(Process32First(hProcSnap,&lppe))
{
do
{
if(!strcmp(ProcName,lppe.szExeFile))
return (DWORD)lppe.th32ProcessID;
}
while(Process32Next(hProcSnap,&lppe));
}
else
{
CloseHandle(hProcSnap);
return 0;
}
}
int main()
{
DWORD CmdList = {2,3,3,3,3,3,3,3,1,4};
DWORD CmdCode;
if(CreatShareMemory())
{
LoadDll(GetProcID("Game.exe"),"C:\\Documents and Settings\\Administrator\\桌面\\Test1\\GameDll.dll");
}
for(int i = 0;i<10;i++)
{
CmdCode = CmdList;
CopyMemory(Buff,&CmdCode,4);
Sleep(2000);
}
if(getchar() == 'q')
{
FreeDll();
}
getchar();
return 0;
}
```
DWORD CmdList = {2,3,3,3,3,3,3,3,1,4};这个就是命令,先前定义好的,不用再一个一个输入,在注入之前,我还创建了共享内存,内存名一样的话,那dll与这控制的就是用的同一个内存,所以才能传入命令,反馈出相应的结果
这里再说一下,我的主函数第一个参数传的是进程名,通过CreateToolhelp32Snapshot函数可以遍历进程名,来获得想要的PID,这个可以自行查阅,在进程相关API中也提到过
还有个问题,在dll中输入4是退出并卸载dll,但是我的并没有成功,仍能在进程管理器中查找到该dll,所以我自己实现了卸载dll的功能,具体看代码
接下来没有什么大问题了,其它的查一查就知道了,看看效果
## 运行结果
看看dll加载
接下来就是卸载了
可以看到卸载成功 学习了,非常感谢!! 感谢大佬{:1_919:},离深度修改逃跑吧少年又更进一步:loveliness: 写的不错! 可以啊,如果进程通信回来的话,可以进入内核玩玩了
页:
[1]