动态加载dll的实现+远线程注入

1.在目标进程中申请内存
2.向目标进程内存中写入shellcode(没有特征,编码比较麻烦)
3.创建远线程执行shellcode

之前可以看到shellcode很难编写还要去依赖库,去字符串区等等很麻烦,为了让被注入代码更容易编写,最好的方法就是通过dll来编写

dll加载:
1.静态调用:通过在我们的程序中添加头文件,以及lib文件来完成调用,前提就是获取dll然后还有头文件
2.动态调用:仅仅只需要一个dll即可完成调用

先写个试一下

#include <Windows.h>

__declspec(dllexport) void Test(){
	MessageBox(NULL, NULL, NULL, NULL);
}

可以看到我们的Test,但是这种方式会对Test进行名称粉碎,由c++编译器添加的,需要告诉编译器使用c的方式来命名函数,所以我们这么写,然后还需要指明函数参数的调用约定
1.__stdcall 标准 栈传参,函数内部(被调用者)平栈
2. __cdecl c 栈传参,函数外部(调用者)平栈
3. __fastcall 快速 寄存器传参
4. __thiscall 类的thiscall调用约定,使用ecx寄存器来传递this指针

extern "C"{
	__declspec(dllexport) void __stdcall Test(){
		MessageBox(NULL, NULL, NULL, NULL);
	}
}

上面写到__stdcall是函数内部平参这里来看看

void __stdcall test(int n1, int n2){
	
	return;
}

int main()
{
	test(1, 2);

	return 0;
}

两个返回8一个返回4

void __stdcall test(int n1, int n2){
002013C0  push        ebp  
002013C1  mov         ebp,esp  
002013C3  sub         esp,0C0h  
002013C9  push        ebx  
002013CA  push        esi  
002013CB  push        edi  
002013CC  lea         edi,[ebp-0C0h]  
002013D2  mov         ecx,30h  
002013D7  mov         eax,0CCCCCCCCh  
002013DC  rep stos    dword ptr es:[edi]  
	
	return;
}
002013DE  pop         edi  
002013DF  pop         esi  
002013E0  pop         ebx  
002013E1  mov         esp,ebp  
002013E3  pop         ebp  
002013E4  ret         8  

很明显改变了参数返回的时候值就会变化而且是函数内部的变化所以是函数内部平栈,绝大部分的windows api都是stdcall,但是也有特例,比如wsprintf

char szBuf[256] = {0};
wsprintfA(szBuf,"%s","1234");

这里很明显因为他是由外界传入的参数来决定多少,所以是函数外部平参

继续回到动态加载dll
1.将目标dll加载到我们进程中

HMODULE hDll = LoadLibraryA("./TestDLL.dll");

返回值是模块句柄

这里就可以看到我们的dll完全被加载到我们的内存里面来了,所以说返回的模块句柄也就是当前dll在当前进程中的首地址,加载过程是由我们的操作系统来完成的(包括各节的扩展分配内存,重定位等等),有时候也可以自己写loadlibraby因为用这个函数太官方了,自己写就叫做内存加载,这是很多病毒的手法

2.计算函数的位置
都有个偏移

可以看到偏移是11122

LPVOID lp = GetProcAddress(hDll, "Test");

可以看到20000+11122 = 31122

也可以写个def

LIBRARY
EXPORTS
Test

这里得到的就是个函数指针

typedef void(*PFN_FPP)();
PFN_FPP lp = (PFN_FPP)GetProcAddress(hDll, "Test");

最后再调用就可以了

lp();

但是如何要求目标进程调用Loadlibrary来加载我们的dll,最后运行我们的dll中的导出函数

可以看到Loadlibrary是存在于Kernel32.dll中的,所以先去找目标进程中的Kernel32.dll的位置,一般程序中都有Kernel32.dll这个,因为他是个很基本的dll
然后再找到该dll导出的loadlibraryA或W函数的位置

因为我们都是用的同一个kernel32.dll所以我们可以通过一个公式堆出来目标进程中的loadlibrary的地址
公式:目标的loadlibrary - 目标的kernel32地址 = 本地的loadlibrary - 本地的kernel32地址

	//获取进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	HANDLE hDestModule = NULL;
	//接下来找到该进程中kernel32.dll的基址
	hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
	MODULEENTRY32 mo32 = { 0 };
	mo32.dwSize = sizeof(MODULEENTRY32);
	bRet = Module32First(hSnap, &mo32);
	while (bRet)
	{
		bRet = Module32Next(hSnap, &mo32);
		wprintf(mo32.szExePath);
		std::wstring wstr = mo32.szExePath;
		if (wstr.find(L"KERNEL32.DLL") != std::string::npos){
			hDestModule = mo32.modBaseAddr;
			break;
		}
		
	}

	LPVOID lpDestAddr = NULL;
	if (hDestModule != NULL){
		//获取本进程的kernel32地址
		HMODULE hkernel32 = GetModuleHandleA("KERNEL32.DLL");

		//计算函数的位置
		LPVOID lploadlibrary = GetProcAddress(hkernel32, "LoadLibraryA");

		//获取了目标进程中的loadlibrary的地址
		lpDestAddr = (char*)lploadlibrary - (char*)hkernel32 + (char*)hDestModule;
	}

我们获取到了loadlibrary的地址后就通过CreateRemoteThread加载dll地址和函数地址来调用

	//1.在目标进程开辟空间
	LPVOID lpAddr = VirtualAllocEx(
		hProcess,	//在目标进程中开辟空间
		NULL,	//表示任意地址,随机分配
		1,	//内存通常是以分页为单位来给空间 1页=4k 4096字节
		MEM_COMMIT,	//告诉操作系统给分配一块内存
		PAGE_EXECUTE_READWRITE
		);

	if (lpAddr == NULL){
		printf("Alloc error!");
		return 0;
	}

	DWORD dwWritesBytes = 0;

	char* pDestDllPath = R"(G:mytoolsTestDlljiazaiDebugTestDLL.dll)";

	//2.在目标进程中写入目标dll的路径
	bRet = WriteProcessMemory(
		hProcess,	//目标进程
		lpAddr,	//目标地址	目标进程中
		pDestDllPath,	//源数据	当前进程中
		strlen(pDestDllPath)+1,	//写多大
		&dwWritesBytes //成功写入的字节数
		);
	if (!bRet){
		VirtualFreeEx(hProcess, lpAddr, 1, MEM_DECOMMIT);
		return 0;
	}

	//3.向目标程序调用一个线程 创建远程线程 执行写入代码
	HANDLE hRemoteThread = CreateRemoteThread(hProcess,	//目标进程
		NULL,
		0,
		(LPTHREAD_START_ROUTINE)LoadLibraryA,	//目标进程的回调函数
		lpAddr,	//回调参数
		0,
		NULL
		);

可以看到注入成功了

完整代码

// shellcode.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <Windows.h>
#include <TlHelp32.h>
#include <string>

typedef void(*PFN_FOO)();

int main()
{
	//获取快照
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	PROCESSENTRY32 pe32;
	DWORD pid = 0;
	pe32.dwSize = sizeof(PROCESSENTRY32);
	//查看第一个进程
	BOOL bRet = Process32First(hSnap, &pe32);

	while (bRet)
	{
		bRet = Process32Next(hSnap, &pe32);
		if (wcscmp(pe32.szExeFile, L"procexp.exe") == 0){
			pid = pe32.th32ProcessID;
			break;
		}
	}
	//获取进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	HANDLE hDestModule = NULL;
	//接下来找到该进程中kernel32.dll的基址
	hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
	MODULEENTRY32 mo32 = { 0 };
	mo32.dwSize = sizeof(MODULEENTRY32);
	bRet = Module32First(hSnap, &mo32);
	while (bRet)
	{
		bRet = Module32Next(hSnap, &mo32);
		wprintf(mo32.szExePath);
		std::wstring wstr = mo32.szExePath;
		if (wstr.find(L"KERNEL32.DLL") != std::string::npos){
			hDestModule = mo32.modBaseAddr;
			break;
		}
		
	}

	LPVOID lpDestAddr = NULL;
	if (hDestModule != NULL){
		//获取本进程的kernel32地址
		HMODULE hkernel32 = GetModuleHandleA("KERNEL32.DLL");

		//计算函数的位置
		LPVOID lploadlibrary = GetProcAddress(hkernel32, "LoadLibraryA");

		//获取了目标进程中的loadlibrary的地址
		lpDestAddr = (char*)lploadlibrary - (char*)hkernel32 + (char*)hDestModule;
	}
	




	//1.在目标进程开辟空间
	LPVOID lpAddr = VirtualAllocEx(
		hProcess,	//在目标进程中开辟空间
		NULL,	//表示任意地址,随机分配
		1,	//内存通常是以分页为单位来给空间 1页=4k 4096字节
		MEM_COMMIT,	//告诉操作系统给分配一块内存
		PAGE_EXECUTE_READWRITE
		);

	if (lpAddr == NULL){
		printf("Alloc error!");
		return 0;
	}

	DWORD dwWritesBytes = 0;

	char* pDestDllPath = R"(G:mytoolsTestDllTestDLLDebugTestDLL.dll)";

	//2.在目标进程中写入目标dll的路径
	bRet = WriteProcessMemory(
		hProcess,	//目标进程
		lpAddr,	//目标地址	目标进程中
		pDestDllPath,	//源数据	当前进程中
		strlen(pDestDllPath)+1,	//写多大
		&dwWritesBytes //成功写入的字节数
		);
	if (!bRet){
		VirtualFreeEx(hProcess, lpAddr, 1, MEM_DECOMMIT);
		return 0;
	}

	//3.向目标程序调用一个线程 创建远程线程 执行写入代码
	HANDLE hRemoteThread = CreateRemoteThread(hProcess,	//目标进程
		NULL,
		0,
		(LPTHREAD_START_ROUTINE)lpDestAddr,	//目标进程的回调函数
		lpAddr,	//回调参数
		0,
		NULL
		);


	return 0;
}

所以我们直接把代码写入dll里面就可以为所欲为了,但是不要写同步的代码,不然加载dll会卡死,然后如果我们想要获取到我们注入模块的地址,其实就是需要lpDestAddr的返回值,这里怎么获取,就有一个小技巧,hRemoteThread 这个是线程的模块而lpDestAddr才是我们注入的dll的地址,这里其实只需要获取线程的退出码,就可以获得loadlibrarya的返回基地址

	DWORD dwRetCode;
	//获取远线程的退出值
	GetExitCodeThread(hRemoteThread, &dwRetCode);

同一个文件允许同一个dll加载多次而且地址是不变的只是引用次数会+1

原文地址:https://www.cnblogs.com/yicunyiye/p/13696721.html