Windows程序设计学习笔记——线程(三)线程注入

1.C语言创建线程:

头文件process.h

uintptr_t _beginthread( 
   void( *start_address )( void * ),    //回调函数地址
   unsigned stack_size,    //栈大小
   void *arglist     //用户自定义参数
);
uintptr_t _beginthreadex( 
   void *security,    //安全属性
   unsigned stack_size,
   unsigned ( *start_address )( void * ),
   void *arglist,
   unsigned initflag,    //线程初始状态,0为就绪,CREATE_SUSPENDED表示暂停
   unsigned *thrdaddr    //线程id指针,可为NULL
);

使用:

#include 
#include
#include 
int g_val = 0;
HANDLE hd[4] = {};
using std::cout;
using std::endl;
unsigned callback(void* param)
{
    for (int i = 0; i < 1000000; i++)
    {
        g_val++;
    }
    cout << "值:" << g_val <<"线程id:"<

运行结果:

Windows程序设计学习笔记——线程(三)线程注入_第1张图片

 2.C++11线程

C++11将多线程引入标准库中,不需要再使用CreateThread函数创建线程

参考thread::thread - C++ Reference

thread构造函数如下:

template 
explicit thread (Fn&& fn, Args&&... args);
//Fn为函数指针,Args是变长参数

等待线程结束:

std::thread::join()         类型void join()

注意:不能直接调system("pause")因为thread对象是一个与线程相关的对象,存储线程相关信息,所以析构时会认为绑定的线程没有结束而抛出异常

将thread对象与创建的线程解绑:

std::thread::detach()        类型void detach()

thread头文件的this_thread命名空间提供了其他一些访问和操作线程的相关函数,参考this_thread - C++ Reference

获取线程id:

thread::id get_id() noexcept;

sleep

template 
  void sleep_for (const chrono::duration& rel_time);

template 
  void sleep_until (const chrono::time_point& abs_time);

chrono是C++的事件库,duration是与时间有关的对象,参考 - C++ Reference

使用示例:

this_thread::sleep_for(std::chrono::seconds(5));    //线程休眠5s

其他时间度量如下:

Windows程序设计学习笔记——线程(三)线程注入_第2张图片可以将全局变量和回调函数封装进类,如下

#include 
#include
#include
#include
using namespace std;
class _mythread
{
public:
    void callback(int param)
    {
        for (int i = 0; i < 100; i++)
        {
            g_val++;
        }
        this_thread::sleep_for(chrono::seconds(1));
        cout << "值:" << g_val << "线程id:" << this_thread::get_id() <<"自定义参数为:"<

使用std::thread的好处是可以跨平台,这样代码在Linux上也跑的起来

3.远程线程注入

远程线程注入就是在其他进程中创建我们的线程,我们的线程会把我们的dll载入到注入进程中

创建远程线程api:

HANDLE WINAPI CreateRemoteThread(
  __in   HANDLE hProcess,
  __in   LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in   SIZE_T dwStackSize,
  __in   LPTHREAD_START_ROUTINE lpStartAddress,
  __in   LPVOID lpParameter,
  __in   DWORD dwCreationFlags,
  __out  LPDWORD lpThreadId
);

与CreateThread函数相比只是多了第一个参数进程句柄,比较如下两个函数

DWORD WINAPI ThreadProc(
  __in  LPVOID lpParameter
);

HMODULE WINAPI LoadLibrary(
  __in  LPCTSTR lpFileName
);

可以发现两个函数形式基本相同,所以我们可以将LoadLibrary作为线程回调函数,但是我们不能直接将LoadLibrary作为参数传入CreateThread(参加PE文件结构),不过由于系统dll(如kernel32.dll)在所有进程中地址是相同的(重启后可能变化),我们可以使用如下函数获取LoadLibrary函数地址

FARPROC WINAPI GetProcAddress(
//获取dll模块中函数地址
  __in  HMODULE hModule,    //dll模块句柄
  __in  LPCSTR lpProcName    //(导出)函数名
);

HMODULE WINAPI GetModuleHandle(
//获取模块句柄
  __in_opt  LPCTSTR lpModuleName    //模块名
);

注意:LoadLibrary实际上是LoadLibraryA和LoadLibraryW,分别对于ANSI和Unicode编码,获取函数地址时需要根据dll文件名是哪种编码形式保存的来选择获取哪个LoadLibrary函数

还有一个问题是指向dll文件路径的指针是在本地进程而非远程进程中,我们把它作为参数传给远程线程,远程线程再传给loadlibrary,由于每个进程都有自己独立的虚拟内存空间,所以loadlibrary在从远程线程传入的参数的地址中找不到dll文件路径,并且可能会导致远程进程因违规访问而崩溃,所以我们需要把dll路径字符串存入远程进程的地址空间中,windows提供了如下函数来在另一个进程开辟内存空间:

//开辟内存

LPVOID WINAPI VirtualAllocEx(
  __in      HANDLE hProcess,    //进程句柄
  __in_opt  LPVOID lpAddress,    //指向要开辟内存的起始地址的指针,填NULL由api自己找
  __in      SIZE_T dwSize,    //大小,与分页对齐
  __in      DWORD flAllocationType,    //分配类型,一般填MEM_COMMIT,指明开辟的虚拟内存要映射进物理内存
  __in      DWORD flProtect    //内存分页权限,填PAGE_READWRITE表示可读可写
);

//释放内存
BOOL WINAPI VirtualFreeEx(
  __in  HANDLE hProcess,
  __in  LPVOID lpAddress,
  __in  SIZE_T dwSize,
  __in  DWORD dwFreeType
);

//读取内存
BOOL WINAPI ReadProcessMemory(
  __in   HANDLE hProcess,
  __in   LPCVOID lpBaseAddress,    //远程进程中的地址
  __out  LPVOID lpBuffer,    //本地进程中的缓冲区地址
  __in   SIZE_T nSize,    //要传输的字节数
  __out  SIZE_T* lpNumberOfBytesRead    //实际传输的字节数
);

//写入内存
BOOL WINAPI WriteProcessMemory(
  __in   HANDLE hProcess,
  __in   LPVOID lpBaseAddress,
  __in   LPCVOID lpBuffer,
  __in   SIZE_T nSize,
  __out  SIZE_T* lpNumberOfBytesWritten
);


由上可以看出我们使用远程线程注入dll分为以下几步:

1.在远程进程的地址空间中开辟一段内存空间

2.写入dll的路径到开辟的内存中

3.获取LoadLibraryW或LoadLibraryW函数地址(通过Kernel32.dll)

4.创建远程线程,传入参数

5.释放内存

6.使用GetProcAddress来获取FreeLibrary函数在Kernel32.dll的地址

7.创建远程线程调用FreeLibrary释放我们的dll

示例:

cpp:

#include 
#include
using namespace std;

int main()
{
	HWND hw = FindWindow(NULL, "AntConc");	//获取窗体句柄
	DWORD wid = 0;
	GetWindowThreadProcessId(hw, &wid);		//获取窗体线程id
	HANDLE hd=OpenProcess(PROCESS_ALL_ACCESS, FALSE, wid);	//获取进程句柄
	
	//在远程进程的地址空间中开辟一段内存空间
	LPVOID pPath=VirtualAllocEx(hd, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	
	//写入dll的路径到开辟的内存中
	char buffer[] = { "C:\\Users\\MSI-NB\\source\\repos\\Dlltest\\x64\\Debug\\Dlltest.dll" };
	WriteProcessMemory(hd, pPath, buffer, sizeof(buffer), NULL);

	//获取LoadLibraryW或LoadLibraryW函数地址(通过Kernel32.dll)
	LPTHREAD_START_ROUTINE pload = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32.dll"), "LoadLibraryA");	//这里用的是多字节字符集

	//创建远程线程,传入参数
	HANDLE thread=CreateRemoteThread(hd, NULL, 0, pload, pPath, 0, NULL);
	WaitForSingleObject(thread, INFINITE);

	//释放内存
	VirtualFreeEx(hd, pPath, 0x1000, MEM_DECOMMIT);

	//使用GetProcAddress来获取FreeLibrary函数在Kernel32.dll的地址
	LPTHREAD_START_ROUTINE pfree = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32.dll"), "FreeLibrary");

	//创建远程线程调用FreeLibrary释放我们的dll
	thread = CreateRemoteThread(hd, NULL, 0, pfree, NULL, 0, NULL);
	WaitForSingleObject(thread, INFINITE);
	system("pause");
}	

dll:

#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        HWND hw = FindWindow(NULL, "AntConc");
        MessageBox(hw, "hello", "hi", MB_OK);
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

示例中将dll注入到antconc中,注入成功dllmain收到DLL_PROCESS_ATTACH后将会执行

运行结果:

 

你可能感兴趣的:(windows程序设计,学习,c++,windows,系统安全,安全)