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

时间:2023-03-08 19:09:26

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);
}

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

可以看到我们的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);
}
}

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

上面写到__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的实现+远线程注入

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

2.计算函数的位置

都有个偏移

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

可以看到偏移是11122

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

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

可以看到20000+11122 = 31122

也可以写个def

LIBRARY
EXPORTS
Test

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

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

最后再调用就可以了

lp();

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

动态加载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:\mytools\TestDll\jiazai\Debug\TestDLL.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
);

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

可以看到注入成功了

完整代码

// 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:\mytools\TestDll\TestDLL\Debug\TestDLL.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的实现+远线程注入

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

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