--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
所谓手动载入就是自己去实现LoadLibrary函数,记得科锐钱老师哪个视频讲过:“他检测或处理了Windows api那咋办啊,自己实现啊!”,高,实在是高,如果自己去实现,就可以更加精确的控制内存读入信息,从理论上讲,这种方法隐藏效果是最棒的,但是实现一个基本的PEloader并不是一件容易的事情,如果还要考虑兼容性完美,就必须还得妥善处理TLS,资源,线程等问题,在这方面处理的话,反正我不会,劝退。。。
这个就比较好说了,我们在注入DLL的时候,通常要选择去调用 LoadLibrary函数,然后再擦除痕迹,这样不用去处理那些TLS,资源,线程等为了解决兼容性产生的问题。但是Windows大家都清楚,表面风平浪静,背地里风起云涌,说不准现在你电脑的XXX安全卫士正在对你的电脑做些什么呢,这些都是你不通过技术手段看不到的东西,实际上没有什么好的方法把痕迹真正的消除。
#include <Windows.h> void Inject(int pID, char* Path) { //获取进程句柄 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID); //申请一块内存给DLL路径 LPVOID pReturnAddress = VirtualAllocEx(hProcess, NULL, strlen(Path) + 1, MEM_COMMIT, PAGE_READWRITE); //写入路径到上一行代码申请的内存中 WriteProcessMemory(hProcess, pReturnAddress, Path, strlen(Path) + 1, NULL); //获取LoadLibraryA函数的地址 HMODULE hModule = LoadLibrary("KERNEL32.DLL"); LPTHREAD_START_ROUTINE lpStartAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryA"); //创建远程线程-并获取线程的句柄 HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, pReturnAddress, 0, NULL); //等待线程事件 WaitForSingleObject(hThread, 2000); //防止内存泄露 CloseHandle(hThread); CloseHandle(hProcess); } int main() { //传dll路径 const char* a = "C:\\Users\\asus\\Desktop\\C++\\FirstDll.dll"; //传入进程ID Inject(12944, (char*)a); return 0; }
根据传入参数,记录我们要传入的数据:
进程ID:3872
路径:C:\Users\86186\Desktop\mydll.dll
修改路径和pid,我们编译生成:
丢到虚拟机测试运行:
成功注入,弹出信息框,通过分析源代码,我们总结了一下注入流程:
1.在别人的程序里开辟内存空间A
2.将 LoadLibrary 函数参数写入A
3.获取LoadLibrary函数地址
4.在别人的程序里远程执行 LoadLibrary实现加载外部DLL
由此可见 LoadLibrary多么重要~这个小玩意儿竟然实现了注入~
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
既然一个东西有安装,那必然就有卸载,凡是都是相对的,不可能只有单方面(我什么时候把哲学搞这么明白了)
话说回来,如果存在DLL注入,能安装进去,那也就必然能从上面卸载下来
安装是LoadLibrary,卸载就是FreeLibrary,为啥是呢,因为我是从MSDN上面看到的:
https://docs.microsoft.com/zh-cn/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya
https://docs.microsoft.com/zh-cn/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary
同样的道理,注入可以远程调用LoadLibrary注射进去,那么我们也可以远程调用FreeLibrary卸载吧,尝试一下:
看一下该函数都有什么参数:
发现需要传一个句柄,那么我们直接放源码吧,可以远程获取进程模块的句柄:
//获取模块句柄 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; } //获取进程id 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; }
简单的修改一下,然后去调用该函数,完整代码如下:
#include <Windows.h>
#include <stdio.h>
#include "tlhelp32.h"
void Inject(int pID, char* Path)
{
//获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID);
//申请一块内存给DLL路径
LPVOID pReturnAddress = VirtualAllocEx(hProcess, NULL, strlen(Path) + 1, MEM_COMMIT, PAGE_READWRITE);
//写入路径到上一行代码申请的内存中
WriteProcessMemory(hProcess, pReturnAddress, Path, strlen(Path) + 1, NULL);
//获取LoadLibraryA函数的地址
HMODULE hModule = LoadLibrary("KERNEL32.DLL");
LPTHREAD_START_ROUTINE lpStartAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryA");
//创建远程线程-并获取线程的句柄
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, pReturnAddress, 0, NULL);
//等待线程事件
WaitForSingleObject(hThread, 2000);
//防止内存泄露
CloseHandle(hThread);
CloseHandle(hProcess);
}
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还在程序内:
下一秒,我们运行Uninject程序,然后刷新一下,它就不见了:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
很多朋友都会这么问,你卸载了,就没了,你还隐藏个鸡儿?
按道理确实是这样,但是也正是如此,真正的隐藏就是卸载掉,让他真正的不存在!
我们因此可以从Freelibrary函数开刀,分析该函数的流程,然后下手
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
先从MSDN给的官方文档下手:
总结一下,大致就是四部分:
1.判断DLL句柄是否有效,有效就说明该DLL存在于进程中
2. 递减模块的引用计数,且判断是否为0
3. 调用模块的DllMain函数响应 DLL_PROCESS_DETACH消息
4.从进程空间撤销对DLL的内存映射
我们不难看出,前三步只是假把式,最后一笔才是点睛之笔,才真正把DLL从内存清出去
因此,如果我们让系统帮我们清楚前面的痕迹,等到清楚内存映射的时候,直接阻止它,这样既清了痕迹,又保了我们的DLL
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
打开OD,附加进程,跳转到函数处:
此时我们可以看到这个函数在里面到底偷偷做了些什么:
我们会发现,其调用了很多来自ntdll的函数,而且最后,是通过调用下面的call来实现最后的内存清除工作:
76021DF7 |. FF15 C0110176 call dword ptr [<&ntdll.NtUnmapViewOfSection>] ; ntdll_12.ZwUnmapViewOfSection
不懂就查: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwunmapviewofsection
看一下这玩意到底是不是清除内存的:
The ZwUnmapViewOfSection routine unmaps a view of a section from the virtual address space of a subject process.
我们发现的确是这样的,那么就专门搞它就可以了~
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
先进函数内部观察:
patch方法也很简单,首地址,利用shellcode技巧直接retn 8
这样的话,我们就把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);
效果:
如此以来,我们便可以再重新整理一下代码和思路了:
先阉割,再FreeLibrary即可!然后再还原,不还原不行,万一别的地方调用有可能会出错,所以你办完事,就不能露痕迹,代码如下:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
①:先注入一个DLL到程序:
啊!进去了,
②编译运行我们自己的程序:
重新点击小电脑附加进程,重新选中,你会发现DLL没有了,我们再去用OD看一下:
发现模块信息的确被擦除了,没有了路径和版本信息
换用CheatEngine工具测试一下:发现完全消失
换用修改版的OD-吾爱破解OD测试:
发现竟然暴露了,说明该方法依然是存在不足的,对某些程序依然会被检测
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1.该方法采用正向分析逻辑,然后逆向分析函数实现流程,通过阉割函数,实现擦除注入痕迹
2.理论上DLL注入痕迹已经完全擦除,但是内存中仍然保留着完整的镜像,擦除的也只是操作系统中数据结构的记录
3.如果要对抗暴力穷搜,还需处理PE文件头特征
最近还要写毕业论文,实在不知道怎么写,我写技术文章可以一直写一直写,写论文几小时扣不出几个字,是我太俗了,不太会写官方的语言
另外有没有2021考研大佬,一起交流进步学习,高数线代概率论冲起来?
最后代码有些乱,贴给大家:
#include <Windows.h>
#include <stdio.h>
#include "tlhelp32.h"
void Inject(int pID, char* Path)
{
//获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID);
//申请一块内存给DLL路径
LPVOID pReturnAddress = VirtualAllocEx(hProcess, NULL, strlen(Path) + 1, MEM_COMMIT, PAGE_READWRITE);
//写入路径到上一行代码申请的内存中
WriteProcessMemory(hProcess, pReturnAddress, Path, strlen(Path) + 1, NULL);
//获取LoadLibraryA函数的地址
HMODULE hModule = LoadLibrary("KERNEL32.DLL");
LPTHREAD_START_ROUTINE lpStartAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryA");
//创建远程线程-并获取线程的句柄
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, pReturnAddress, 0, NULL);
//等待线程事件
WaitForSingleObject(hThread, 2000);
//防止内存泄露
CloseHandle(hThread);
CloseHandle(hProcess);
}
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;
}