DLL注入_远程线程注入

 

什么是线程注入?

线程注入,是通过开启远程线程的方式,将DLL加载到目标宿主进程中的常用方式。

 

什么是动态链接库?

首先Windows中链接库分为两种:动态链接库DLL、静态链接库LIB。

① 静态链接库:在运行的时候就直接把代码全部加载到程序中,调用方式比如:

#prama comment (lib,"Psapi.lib")

② 动态链接库:在需要的时候加载,加载方式为:

——使用LoadLibrary动态加载DLL

——使用GetProcAddress获取DLL中导出函数的指针

——最后用FreeLibrary卸载指定的DLL

动态链接库设计的目的是为了动态加载功能,动态地释放内存,节约内存,方便每一个程序的4GB内存的管理,4GB有2GB用来放系统内核,即系统大量的DLL。其本质上也是一个可以被加载的程序。同时系统对于DLL只会保存一份。

 

在Visual Studio编译环境下,DLL又分为三类:

① 非MFC的DLL——使用SDK API进行编程,能被所有语言调用

② MFC规则DLL——使用MFC进行编程,能被所有语言调用

③ MFC扩展DLL——使用MFC进行编程,只能被MFC编写的程序调用

(下面使用第一种)

 

MFC——Microsoft Foundation Class-Library :微软用C++对API进行的封装,全部封装成了类,方便使

 

需要注意:

DLL的导出函数使用

extern "C" _declspec(dllexport)

而导入函数使用

extern "C" _declspec(dllimport)

extern "C" 的作用是作为一种编译约定

 

那么进程是如何调用DLL的呢?

①使用LoadLibrary加载进所需DLL

HMODULE hMod = LoadLibrary(DLL路径)

②定义导入函数指针

typedef int(*ADD_IMPORT) (int a,int b); // 定义一个指向返回值为int类型的函数指针

③使用GetProcAddress获得函数入口点

ADD_IMPORT add_proc = (ADD_IMPORT) GetProcAddress(hMod,"Add");

④然后就可以使用了:比如 int result = add_proc(1,2);

 

如下使用VS2019进行编写编译:

1、新建Win32项目,选择DLL

DLL注入_远程线程注入_第1张图片

DLL注入_远程线程注入_第2张图片

关于DLL入口主函数第二个参数ul_reason_for_call,即DLL四种当前的状态:

DLL注入_远程线程注入_第3张图片

2、新建如下头文件

DLL注入_远程线程注入_第4张图片

3、在dajiDLL.cpp中

DLL注入_远程线程注入_第5张图片

然后同一解决方案下新建MFC项目

DLL注入_远程线程注入_第6张图片

 

这里需要说一下注入的可行性:

DLL注入_远程线程注入_第7张图片

①kernel32和user32是两个在大部分程序上都会调用的DLL

②同一个DLL,在不同的进程中,不一定被映射(加载)到同一个内存地址

③但是kernel32和user32除外,他们总是被映射到进程的内存首选地址

④因此在所有使用这两个DLL的进程中,这两个DLL的内存地址是相同的

⑤所以在本进程获取的kernel32.dll中的函数的地址,在目标进程中也是一样的

流程:目标进程-传入DLL地址-开启远程线程-加载DLL-实现DLL注入

 

具体使用函数如下:

OpenProcess    // 获取已知进程的句柄

VirtualAllocEx    // 在远程进程中申请内存空间

WriteProcessMemory    // 向进程中写入东西

GetProcAddress    // 取得函数在DLL中的地址

CreateRemoteThreadEx    // 创建远程线程——即在其他进程中创建新的线程

CloseHandle    // 关闭句柄

利用以上函数即可完成线程的注入

 

上面讲了这么多下面开始编写远程线程注入的代码:
① 在资源视图中找到主Dialog文件新建如下组件并命名,其中Inject按钮设为默认按钮,两个Button的ID分别为IDC_INJECT和IDC_OPEN

DLL注入_远程线程注入_第8张图片

② 双击Open按钮编写如下代码,需要提前将编辑框1和2的ID分别命名为IDC_DLLPATH、IDC_EXENAME

// 获取DLL路径名,打开DLL文件
void CdajiInjecterDlg::OnBnClickedOpen()
{
	// 定义一个filedialog对象,通过它来获取dll的路径
	CFileDialog filedialog(TRUE,0,0,6UL,_T("DLL Files|*.dll|"));
	// 如果弹出了对话框并且选择了DLL,那么将获得的路径填充显示到DLLPath
	if (filedialog.DoModal() == IDOK)
	{
		CString DLLpath;
		DLLpath = filedialog.GetPathName();
		// 将路径填充到IDC_DLLPATH的对话框
		SetDlgItemText(IDC_DLLPATH, DLLpath);
	}
}

③ 双击Inject按钮,首先需要在inject按钮函数的前面添加注入功能的函数Inject

// 注入函数,同时返回值为是否注入成功,默认返回成功
BOOL Inject(LPCTSTR DLLPath,DWORD ProcessID) 
{
	// 定义一个句柄获取目标进程的所有权限,包括子进程
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, ProcessID);
	// 判读是否获取到
	if (!hProcess)
	{
		return FALSE;
	}
	// 计算一下获取到的DLLpath究竟有多长,要在内存空间中申请内存来存放它,注意+1,为\0
	SIZE_T PathSize = (_tcslen(DLLPath) + 1) * sizeof(TCHAR); // 宽字节
	// 在晋城中申请内存存放此DLLpath
	LPVOID StartAddress = VirtualAllocEx(hProcess, NULL, PathSize, MEM_COMMIT, PAGE_READWRITE);
	// 如果未能获取到地址,返回失败
	if (!StartAddress)
	{
		return FALSE;
	}
	// 获取到地址则将DLLPath写入此地址
	if (!WriteProcessMemory(hProcess, StartAddress, DLLPath, PathSize, NULL))
	{
		return FALSE;
	}
	// 获取LoadLibrary的入口点地址,需要进行强制类型转换
	PTHREAD_START_ROUTINE pfnStartAddress = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "LoadLibraryW");
	// 在这里获取到的地址,在目标进程中也一样,因此直接传递过去即可
	// 先判断是否获取到
	if (!pfnStartAddress)
	{
		return FALSE;
	}
	// 最重要的一步,创建远程线程,首先获取句柄,createremotethreadex函数只在win7以上系统有
	HANDLE hThread = CreateRemoteThreadEx(hProcess,NULL,NULL,pfnStartAddress,StartAddress,NULL,NULL,NULL);
	// 判断线程是否创建成功
	if (!hThread)
	{
		return FALSE;
	}
	// 最后等待线程结束,然后清理线程和进程
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);
	CloseHandle(hProcess);
	return TRUE; 
}

④ 在上面的注入函数之前还需要添加一个进程查找函数,如下

// 获取进程,其参数为进程名——在进程列表中获取我们想要的参数
DWORD ProcessFind(LPCTSTR Exename) 
{
	// 第一个参数只获取进程名
	HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
	// 对获取的进程进行判断
	if(!hProcess)
	{
		return FALSE;
	}
	// 获取成功
	PROCESSENTRY32 info;
	info.dwSize = sizeof(PROCESSENTRY32);
	if (!Process32First(hProcess,&info)) 
	{
		return FALSE;
	}
	while (true) // 遍历进程中的所有项
	{
		// 判断是否与所给进程名相符,就是输入的进程名
		if(_tcscmp(info.szExeFile,Exename)==0)
		{
			// 相符的话返回该进程的PID
			return info.th32ProcessID;
		}
		// 如果遍历了整个进程列表都没有找到,那么返回FALSE
		if (!Process32Next(hProcess, &info))
		{
			return FALSE;
		}
	}
	// 函数默认返回FALSE
	return FALSE;
}

⑤ 最后是注入按钮Inject的函数代码:


// 点击按钮执行注入
void CdajiInjecterDlg::OnBnClickedInject()
{
	CString Dllpath;
	CString Exename;
	GetDlgItemText(IDC_EXENAME, Exename);
	GetDlgItemText(IDC_DLLPATH, Dllpath);

	// 如果用户什么进程名都没输入,那么提示
	if (Exename.GetLength() == 0)
	{
		MessageBox(_T("请输入进程名!"));
		return;  // 未填写进程名,直接返回,不让此函数继续执行
	}
	// DWORD类型变量用于存储PID
	DWORD ProcessID = ProcessFind(Exename);
	// 如果在进程列表中没找到此程序,提示
	if (!ProcessID)
	{
		MessageBox(_T("未找到该程序!"));
		return;  // 不再执行下面的
	}
	// 执行注入,并且对注入结果进行获取
	BOOL IsInjected = Inject(Dllpath, ProcessID);
	// 注入成功与失败,提示
	if (IsInjected)
	{
		MessageBox(_T("注入成功!"));
	}
	else
	{
		MessageBox(_T("注入失败!"));
	}
}

最后在dllmain.cpp中添加一个清楚的注入成功提醒标识:

// dllmain.cpp : 定义 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:
		MessageBox(NULL,L"Hello daji!",L"Welcome!",NULL);
		break;
	// 注意到此只能注入32位的进程,如果想要注入64位的进程
	// 需要在配置管理器中进行修改


    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

 

如下进行测试,选择x64版本DLL(注意编译的时候选择编译的版本):

DLL注入_远程线程注入_第9张图片

目标进程为Everything.exe

DLL注入_远程线程注入_第10张图片

DLL注入_远程线程注入_第11张图片

可以在任务栏的Evething中发现DLL的线程,二者此时属于同一个进程。

 

 

你可能感兴趣的:(Windows编程)