本篇要讲什么?小白看了要问,DLL是啥?为啥要注入?又干嘛隐藏?而大佬直接
我当然是菜鸡,所以我们一起来学习一下这dll到底是神魔东西,他有什么神奇之处。
一、DLL简介
定义:动态链接库英文为DLL,是Dynamic Link Library的缩写。DLL是一个包含可由多个程序,同时使用的代码和数据的库。在Windows中,这种文件被称为应用程序拓展。例如,在 Windows 操作系统中,Comdlg32.dll 执行与对话框有关的常见函数。因此,每个程序都可以使用该 DLL 中包含的功能来实现“打开”对话框。这有助于避免代码重用和促进内存的有效使用。 通过使用 DLL,程序可以实现模块化,由相对独立的组件组成。例如,一个计账程序可以按模块来销售。可以在运行时将各个模块加载到主程序中(如果安装了相应模块)。因为模块是彼此独立的,所以程序的加载速度更快,而且模块只在相应的功能被请求时才加载。
——百度文库
记得某位大佬说过,百度文库是什么,外行人解释内行的事,哈哈,在这了解一下dll还是可以的,但是如果深入理解,欲知此事须躬行啊。
一句话总结一下DLL:使用DLL这种技术可以使得程序模块化,同时让程序加载速度更快。
当然深入理解dll不是这篇文章的重点,这里我们只要能知道dll能干什么就好了。我们来亲自写一个dll试试
#include <Windows.h>
BOOL WINAPI DllMain(HMODULE hDll, DWORD dwReason, LPVOID lpReserved)
{
DisableThreadLibraryCalls(hDll);
if (dwReason == DLL_PROCESS_ATTACH)
{
MessageBoxA(0, "Message Test","Message Title", 0);
}
return 1;
}
这个dll文件是用来干啥的呢,很简单吗,MessageBoxA吗,不就是弹个弹窗吗,那和我直接写一个弹窗程序又有什么区别,真的是。
编译完成,运行,,,运行呢?程序呢?
你这程序写错了,无法启动!!!
啊?真的吗?不可能?我去试试。
没有啊,你就先这样,在那样,然后这样,就好了。
哈哈,一脸懵是不是,因为接下来要进入我们这节课的重点内容,划重点了啊,趴桌子的起来啊。
二、DLL注入
DLL注入技术,一般来讲是向一个正在运行的进程插入/注入代码的过程。我们注入的代码以动态链接库(DLL)的形式存在。DLL文件在运行时将按需加载。
这种技术功能很强大,我们可以对一个程序为所欲为。这就很恐怖了,正所谓技术无好坏,可使用的人不是。我们可以使用这种技术来拓展某一程序的功能。比如,反病毒软件和端点安全解决方案使用这些技术来将其软件的代码嵌入/拦截系统中“所有”正在运行的进程,这使得它们可以在其运行过程中监控每一个进程,从而更好地保护我们。但是有的人不是,他们会用这种技术来拓展一些很过分的功能,比如:
懂了吧。
这也就引出了我们学习网络安全技术的真谛,我们是要成为上面这样的人吗,当然不是,反而我们是要制裁上面那样的人。未知攻焉知防,我们学习技术就是为了抵制破坏网络规则的人!
要掌握这种技术我们要掌握什么基本技能呢?最重要的就是我们熟知的微软Windows API,因为它为我们提供了大量的函数来附加和操纵其他进程。
什么是Windows API?API全称:Application Programming Interface(应用程序编程接口),见名知意,API是一些预先定义函数,目的是用来提供应用程序与开发人员基于某软件或者某硬件得以访问一组例程的能力,并且无需访问源码或无需理解内部工作机制细节。
事实上,微软Windows API中的所有函数都包含于DLL文件之中。其中,最重要的是“Kernel32.dll”(包含管理内存,进程和线程相关的函数),“User32.dll”(大部分是用户接口函数),和“GDI32.dll”(绘制图形和显示文本相关的函数)。
dll注入总共分为四步:
1.附加到目标/远程进程
2.在目标/远程进程内分配内存
3.将DLL文件路径,或者DLL文件,复制到目标/远程进程的内存空间
4.控制进程运行DLL文件
所有这些步骤是通过调用一系列指定的API函数来完成的。其实我更喜欢将dll技术总结为一堆API的罗列,在我们以后接触的很多技术都可以这样说,你只要知道API是个很厉害的东西就完事了。
我们有很多的方法来注入我们的DLL,这里我主要讲解一下最主要的也是最普通的一个方法CreateRemoteThread(),也就是远程线程注入,这也是网上普遍流行的一种入门dll注入,把这个理解了其他的也都是变形。接下来我们根据我们上面说到的四个步骤看一下我们如何实现。
1、附加到目标/远程进程
首先,我们需要获取我们想要交互的进程句柄;为此我们调用“OpenProcess()”API函数。函数原型如下:
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess, //对过程对象的访问。对照该过程的安全描述符检查此访问权限。
_In_ BOOL bInheritHandle, //如果此值为TRUE,则此进程创建的进程将继承该句柄。否则,进程将不会继承此句柄。
_In_ DWORD dwProcessId //要打开的本地进程的标识符。
);
API功能是打开一个现有的本地过程对象。那么这个API要如何使用呢,很简单,填空题,需要什么我们填什么。所以在该程序中我们这么写:
HANDLE hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION |
PROCESS_VM_WRITE,
FALSE, dwProcessId );
很简单就是这样,我们第一个步骤就完成了。
2、 在目标/远程进程空间分配内存
我们调用“VirtualAllocEx()”函数为DLL路径分配内存。先看一下VirtualAllocEx()函数的原型:
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess, //进程的句柄
_In_opt_ LPVOID lpAddress, //该指针为要分配的页面区域指定所需的起始地址。
_In_ SIZE_T dwSize, //要分配的内存区域的大小,以字节为单位。
_In_ DWORD flAllocationType, //内存分配的类型。
_In_ DWORD flProtect //对要分配的页面区域的内存保护。
);
然后进行填空:
// 计算DLL文件路径名称所需的字节数
DWORD dwSize = (lstrlenW(pszLibFile) + 1) * sizeof(wchar_t);
// 在目标/远程进程中为路径名称分配空间
LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
3、复制DLL文件路径(或者DLL文件)到目标远程进程的内存空间中
我们需要调用“WriteProcessMemory()“API函数,来将我们的DLL文件路径,或者整个DLL文件,复制到目标/远程进程中。函数原型如下:
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess, //要修改的过程存储器的句柄。
_In_ LPVOID lpBaseAddress, //指向要写入数据的指定进程中的基地址的指针。
_In_ LPCVOID lpBuffer, //指向缓冲区的指针
_In_ SIZE_T nSize, //要写入指定进程的字节数。
_Out_ SIZE_T *lpNumberOfBytesWritten //指向变量的指针,该变量接收传输到指定进程中的字节数。
);
具体调用:
DWORD n = WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL);
4、控制进程来运行DLL文件
这里我们主要是用到CreateRemoteThread(),函数原型如下:
HANDLE CreateRemoteThread(
HANDLE hProcess, //要在其中创建线程的进程的句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, //该 结构为新线程指定安全描述符,并确定子进程是否可以继承返回的句柄
SIZE_T dwStackSize, //堆栈的初始大小,以字节为单位。
LPTHREAD_START_ROUTINE lpStartAddress, //该指针表示远程进程中线程的起始地址
LPVOID lpParameter, //指向要传递给线程函数的变量的指针。
DWORD dwCreationFlags, //控制线程创建的标志。
LPDWORD lpThreadId //指向接收线程标识符的变量的指针。
);
具体应用:
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL);
可以说,“CreateRemoteThread()”是最传统和最流行,以及最多文档资料介绍的DLL注入技术。
它包括以下几个步骤:
1.使用OpenProcess()函数打开目标进程
2.通过调用GetProAddress()函数找到LoadLibrary()函数的地址
3.通过调用VirtualAllocEx()函数在目标/远程进程地址空间中为DLL文件路径开辟内存空间
4.调用WriteProcessMemory()函数在之前所分配的内存空间中写入DLL文件路径
5.调用CreateRemoteThread()函数创建一个新的线程,新线程以DLL文件路径名称作为参数来调用LoadLibrary()函数
最后总结一下全部步骤:
//附加到目标/远程进程
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessId);
// 在远程进程中为路径名称分配空间
LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
// 将DLL路径名称复制到远程进程地址空间
DWORD n = WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL);
// 获取Kernel32.dll中的LoadLibraryW函数的真正地址
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
// 创建远程线程调用LoadLibraryW(DLLPathname)
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL);
这样我们一个简单的DLL注入就完成了,然后我们就可以为所欲为了,嘿嘿
类似的DLL注入技术还有很多,我在这里再说一个CreateRemoteThread的变种,NtCreateThreadEx()。这是一个未公开的“ntdll.dll”中的函数,未来可能会消失或改变。整体流程上和CreateRemoteThread差不多,但我们需要一个结构体(具体如下所示)来向函数传递参数,以及另一个结构体用于从函数接收数据。
struct NtCreateThreadExBuffer {
ULONG Size;
ULONG Unknown1;
ULONG Unknown2;
PULONG Unknown3;
ULONG Unknown4;
ULONG Unknown5;
ULONG Unknown6;
PULONG Unknown7;
ULONG Unknown8;
};
具体食用方法如下:
PTHREAD_START_ROUTINE ntCreateThreadExAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtCreateThreadEx");
ntbuffer.Size = sizeof(struct NtCreateThreadExBuffer);
ntbuffer.Unknown1 = 0x10003;
ntbuffer.Unknown2 = 0x8;
ntbuffer.Unknown3 = (DWORD*)&dwTmp2;
ntbuffer.Unknown4 = 0;
ntbuffer.Unknown5 = 0x10004;
ntbuffer.Unknown6 = 4;
ntbuffer.Unknown7 = (DWORD*)&dwTmp1;
ntbuffer.Unknown8 = 0;
LPFUN_NtCreateThreadEx funNtCreateThreadEx = (LPFUN_NtCreateThreadEx)ntCreateThreadExAddr;
NTSTATUS status = funNtCreateThreadEx(
&hRemoteThread,
0x1FFFFF,
NULL,
hProcess,
pfnThreadRtn,
(LPVOID)pszLibFileRemote,
FALSE,
NULL,
NULL,
NULL,
&ntbuffer //这里如果是NULL,跑的时候也可以注入
);
除此以外,还有QueueUserAPC()、SetWindowsHookEx()、RtlCreateUserThread()、SetThreadContext()等很多种方法可以注入,如果大家有兴趣可以继续深入研究。
三、DLL隐藏
接下来讲讲比较好玩的东西了,dll隐藏。
这时候有的聪明仔就会问了,dll问什么要隐藏啊,光明正大的不好吗?
不好!当然不好,因为dll注入要做的事情可能不是光明正大的。这里我调用某位大佬的一个例子:
老李和老王是邻居,也就是说老李隔壁是老王,老王也就是所谓的“隔壁老王”。
一天,老李去出差,只剩下老李媳妇儿在家。这天正值越黑风高,老王大半夜嘭嘭嘭敲开了老李家的大门,老李媳妇儿见了面便说:“死鬼儿,这么晚才来”。于是,在今天晚上…
可没料到,老王刚进卧室,老李突然回来了,老王赶忙将自己隐藏到了衣柜里,姿势刚好像DLL的拼写,这个姿势,我画了一下,大致是这样的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bLOvpQoc-1589772800396)(https://bbs.pediy.com/upload/attach/202002/680946_NG7YFDTGWEAE7HH.jpg)]
后面的故事,我不再清楚了,因为,这全都是大佬通过努力编出来的
反正就是,没做坏事,干嘛要隐藏自己呢?所以,隐藏了自己,那有很大的可能性要做坏事
dll隐藏技术大体上可以分成两类:
1.手动载入:
所谓手动载入就是自己去实现LoadLibrary函数,记得科锐钱老师哪个视频讲过:“他检测或处理了Windows api那咋办啊,自己实现啊!”,高,实在是高,如果自己去实现,就可以更加精确的控制内存读入信息,从理论上讲,这种方法隐藏效果是最棒的,但是实现一个基本的PEloader并不是一件容易的事情,如果还要考虑兼容性完美,就必须还得妥善处理TLS,资源,线程等问题,在这方面处理的话,反正我不会,劝退。。。
**
**
2.痕迹消除:
这个就比较好说了,我们在注入DLL的时候,通常要选择去调用 LoadLibrary函数,然后再擦除痕迹,这样不用去处理那些TLS,资源,线程等为了解决兼容性产生的问题。但是Windows大家都清楚,表面风平浪静,背地里风起云涌,说不准现在你电脑的XXX安全卫士正在对你的电脑做些什么呢,这些都是你不通过技术手段看不到的东西,实际上没有什么好的方法把痕迹真正的消除。
以DLL擦除痕迹为例,网上大多数的方法都是通过PEB双向断链,需要很多的硬编码,麻烦不说,通过内存暴力搜索还是会露出尾巴来
攻防无绝对,攻防本身就是一个提升本身技术的过程,只要能够通过一个比较合适的方法,实现相应的功能,便具有可行性
在dll注入中我们知道了加载dll需要LoadLibrary,从哲学的角度来讲凡事都会有对立面(思政课认真听讲的好孩子),与LoadLibrary对立的是FreeLibrary,作用是释放已加载的动态链接库(DLL)模块,并在必要时减少其引用计数。当引用计数达到零时,将从调用进程的地址空间中卸载模块,并且该句柄不再有效。函数原型如下:
BOOL FreeLibrary(
HMODULE hLibModule //加载的库模块的句柄
);
一个具体的卸载dll实例如下:
#include <Windows.h>
#include <stdio.h>
#include "tlhelp32.h"
HMODULE GetProcessModuleHandleByName(DWORD pid, LPCSTR ModuleName)
{
MODULEENTRY32 ModuleInfo;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
if (!hSnapshot)
{
return 0;
}
ZeroMemory(&ModuleInfo, sizeof(MODULEENTRY32));
ModuleInfo.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(hSnapshot, &ModuleInfo))
{
return 0;
}
do
{
if (!lstrcmpi(ModuleInfo.szModule, ModuleName))
{
CloseHandle(hSnapshot);
return ModuleInfo.hModule;
}
} while (Module32Next(hSnapshot, &ModuleInfo));
CloseHandle(hSnapshot);
return 0;
}
DWORD GetProcessIDByName(const char* pName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) {
return NULL;
}
PROCESSENTRY32 pe = { sizeof(pe) };
for (BOOL ret = Process32First(hSnapshot, &pe); ret; ret = Process32Next(hSnapshot, &pe)) {
if (strcmp(pe.szExeFile, pName) == 0) {
CloseHandle(hSnapshot);
return pe.th32ProcessID;
}
//printf("%-6d %s\n", pe.th32ProcessID, pe.szExeFile);
}
CloseHandle(hSnapshot);
return 0;
}
void UnInject(int pID, char* Path)
{
//获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID);
LPVOID pReturnAddress = GetProcessModuleHandleByName(GetProcessIDByName("代码注入器.exe"), "mydll.dll");
//获取LoadLibraryA函数的地址
HMODULE hModule = LoadLibrary("KERNEL32.DLL");
LPTHREAD_START_ROUTINE lpStartAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
//创建远程线程-并获取线程的句柄
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, pReturnAddress, 0, NULL);
//等待线程事件
WaitForSingleObject(hThread, 2000);
//防止内存泄露
CloseHandle(hThread);
CloseHandle(hProcess);
}
int main()
{
const char* a = "C:\\Users\\86186\\Desktop\\mydll.dll";
UnInject(GetProcessIDByName("代码注入器.exe"), (char*)a);
getchar();
return 0;
}
这样我们的DLL就已经卸载下去了。桥豆麻袋!不是说好的隐藏吗,你怎么给我全卸载了!
的确,我们这时已经将dll完全卸载了,但是,真正的隐藏不就是实现看似完全卸载吗?(一本正经胡说八道)
我们来试着了解一下FreeLibrary是如何实现dll卸载的,msdn中如下写到:
当模块的引用计数达到零或进程终止时,系统将从进程的地址空间中卸载模块。在卸载库模块之前,系统通过使用DLL_PROCESS_DETACH值调用模块的DllMain函数(如果有)来使模块从进程中分离 。这样做使库模块有机会清理代表当前进程分配的资源。入口点函数返回后,库模块将从当前进程的地址空间中删除。
总结一下大体如下四部:
1.判断DLL句柄是否有效,有效就说明该DLL存在于进程中
-
递减模块的引用计数,且判断是否为0
-
调用模块的DllMain函数响应 DLL_PROCESS_DETACH消息
4.从进程空间撤销对DLL的内存映射
自己观察一下啊,好好看每一步干了什么
会发现,前三步并没有真正的卸载掉dll,而是在清除痕迹,那我们邪恶的想法就产生了,我把你最后一步搞掉,只进行前三步,那我岂不是即清楚了痕迹又保留了dll。
我们用od附加我们的dll注入器,分析一下FreeLibrary。
跟一下FreeLibrary,我们看看他到底在干什么
我们发现,这里有很多来自ntdll的函数,注意一下ZwUnmapViewOfSection,这里我是分析过的,所以可以快速确定这个函数,如果从头分析怕是要一点点来,我们看一下ZwUnmapViewOfSection是干什么的:所述ZwUnmapViewOfSection例程取消映射一个视图从受试者进程的虚拟地址空间中的部分的。
嘿,别说还真是清楚内存的,那就搞他!
跟进函数看看
吼吼,看着结构清楚我就喜欢
既然我们是要跳过该函数,那我们直接在开头ret8就好了
这样FreeLibrary函数就被我们阉割了(说的好残忍),这样就没有办法将DLL从内存中清出去,且擦出了注入的痕迹,这就好比隔壁老王办完事儿把床铺床单收拾整齐,擦出了痕迹,然后躲在了衣柜里~,咳咳。
思路知道了,我们想想怎么用代码实现
1、获取ZwUnmapViewOfSection函数地址:
DWORD a = GetProcAddress(LoadLibrary("ntdll.dll"), "ZwUnmapViewOfSection");
2、修改该处的内存属性使其可读写:
DWORD dwOldProtect;
VirtualProtectEx(OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcessIDByName("代码注入器.exe")),
addrfun,
6,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);
3、写入内存数据阉割该函数:
BYTE shellcode[] = { 0xc2, 0x08 , 0x00 , 0x90 , 0x90 };
WriteProcessMemory(OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcessIDByName("代码注入器.exe")), addrfun, shellcode, 5, NULL);
4、还原函数:
(做完坏事一定要不留痕迹)
BYTE Oldcode[] = { 0xB8, 0x27 , 0x00 , 0x00 , 0x00 };
WriteProcessMemory(OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcessIDByName("代码注入器.exe")), addrfun, Oldcode, 5, NULL);
ok,这样我们就实现了一个简单的DLL隐藏。
放一下代码:
#include <Windows.h>
#include <stdio.h>
#include "tlhelp32.h"
HMODULE GetProcessModuleHandleByName(DWORD pid, LPCSTR ModuleName)
{
MODULEENTRY32 ModuleInfo;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
if (!hSnapshot)
{
return 0;
}
ZeroMemory(&ModuleInfo, sizeof(MODULEENTRY32));
ModuleInfo.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(hSnapshot, &ModuleInfo))
{
return 0;
}
do
{
if (!lstrcmpi(ModuleInfo.szModule, ModuleName))
{
CloseHandle(hSnapshot);
return ModuleInfo.hModule;
}
} while (Module32Next(hSnapshot, &ModuleInfo));
CloseHandle(hSnapshot);
return 0;
}
DWORD GetProcessIDByName(const char* pName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) {
return NULL;
}
PROCESSENTRY32 pe = { sizeof(pe) };
for (BOOL ret = Process32First(hSnapshot, &pe); ret; ret = Process32Next(hSnapshot, &pe)) {
if (strcmp(pe.szExeFile, pName) == 0) {
CloseHandle(hSnapshot);
return pe.th32ProcessID;
}
//printf("%-6d %s\n", pe.th32ProcessID, pe.szExeFile);
}
CloseHandle(hSnapshot);
return 0;
}
void UnInject(int pID, char* Path)
{
//获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID);
//WriteProcessMemory("")
LPVOID pReturnAddress = GetProcessModuleHandleByName(GetProcessIDByName("代码注入器.exe"), "mydll.dll");
//获取LoadLibraryA函数的地址
HMODULE hModule = LoadLibrary("KERNEL32.DLL");
LPTHREAD_START_ROUTINE lpStartAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
//创建远程线程-并获取线程的句柄
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, pReturnAddress, 0, NULL);
//等待线程事件
WaitForSingleObject(hThread, 2000);
//防止内存泄露
CloseHandle(hThread);
CloseHandle(hProcess);
}
int main()
{
const char* a = "C:\\Users\\86186\\Desktop\\mydll.dll";
HANDLE hToken = NULL;
//打开当前进程的访问令牌
int hRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);
if (hRet)
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
//取得描述权限的LUID
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//调整访问令牌的权限
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
CloseHandle(hToken);
}
//定位函数地址
DWORD addrfun = GetProcAddress(LoadLibrary("ntdll.dll"), "ZwUnmapViewOfSection");
printf("%x \n\n", addrfun);
DWORD dwOldProtect;
//修改内存属性
VirtualProtectEx(OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcessIDByName("代码注入器.exe")), addrfun, 6, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//阉割函数
BYTE shellcode[] = { 0xc2, 0x08 , 0x00 , 0x90 , 0x90 };
WriteProcessMemory(OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcessIDByName("代码注入器.exe")), addrfun, shellcode, 5, NULL);
//调用FreeLibrary实现卸载
UnInject(GetProcessIDByName("代码注入器.exe"), (char*)a);
//还原原函数
//B8 27 00 00 00
BYTE Oldcode[] = { 0xB8, 0x27 , 0x00 , 0x00 , 0x00 };
WriteProcessMemory(OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcessIDByName("代码注入器.exe")), addrfun, Oldcode, 5, NULL);
getchar();
return 0;
}
四、实践出真知
最后我们来试试将dll注入并且实现隐藏:
我们先注入一个dll,这里用的网上传的比较广的一个DLL注入器,为了方便,这里我自己注入我自己。
注入后,dll功能实现,而且留下了痕迹。
接下来,运行我们的卸载隐藏代码:
就会发现,我们的dll不见了,由于实验dll只是一个弹窗无法检测,但其实他还是在的,这时候如果我们用od去打开
就发现他还在,那就放心了。
等一等,这不又被检测出来了吗,没隐藏住啊。的确,我们欺骗了ce等一些的检测,但是如果面对暴力穷搜,这招就不好使了,还需要处理PE文件头特征,那就要更深一步的研究了,本篇在这里只做一个入门介绍,更高深的技术我会另写一篇来与大家共同探讨,哈哈。
最后说出心声。