简单的 hell’s gate
Hell’s Gate 除了 Windows 10 中的 minimal process 和 pico process 以外,用户模式的所有进程都会隐式链接 NTDLL.dll,其中包含了 Image Loader,Image Loader 包含了用于加载/卸载其他隐式链接或者延迟加载的模块的功能。除此之外,NTDLL.dll 还包含了 API 转发的最终目标,用户模式的 API 调用会通过 syscall 转入内核模式。也就是说,NTDLL.dll 中的函数是 syscall 的包装。
由于 NTDLL 几乎链接到所有加载到内存中的 PE image,我们可以利用这个模块进行系统调用。如果 PEB 的 InMemoryModuleList
没有被杀软修改的话,第一个元素应该是 PE image 自身,第二个是 NTDLL.dll。
1 2 3 4 5 6 7 8 9 10 11 12 13 > ?? ((ntdll!_LDR_DATA_TABLE_ENTRY*)(((int64)((ntdll!_PEB*)@@(@$peb))->Ldr->InMemoryOrderModuleList.Flink) - 0x10 )) -> BaseDllName struct _UNICODE_STRING "Calculator.exe" +0x000 Length : 0x1c +0x002 MaximumLength : 0x1e +0x008 Buffer : 0x000002ae `678048fe "Calculator.exe" > ?? ((ntdll!_LDR_DATA_TABLE_ENTRY*)(((int64)((ntdll!_PEB*)@@(@$peb))->Ldr->InMemoryOrderModuleList.Flink->Flink) - 0x10 )) -> BaseDllName struct _UNICODE_STRING "ntdll.dll" +0x000 Length : 0x12 +0x002 MaximumLength : 0x14 +0x008 Buffer : 0x00007ffe `0e 920b30 "ntdll.dll"
关于 PEB 具体结构的资料比较多,这里不再介绍。PEB 的位置取决于进程空间是 32 位还是 64 位。在 32 位进程空间中,PEB 在 FS 寄存器偏移 48 字节处(fs:[0x30]
);64 位的地址空间中 PEB 在 GS 寄存器偏移 96 字节处(gs:[0x60]
)。
1 2 3 4 5 6 7 8 9 10 // asm, x86mov eax, fs:[0 x30] // asm, x64mov rax, qs:[0 x60] // C/C++ intrinsic function PPEB peb = (PPEB)__readfsdword(0 x30) PPEB peb = (PPEB)__readgsqword(0 x60)
微软没有定义 PEB 结构,而且 PEB 结构的定义经常随系统版本变化,因此必须自己定义 PEB 和相关的结构。PEB 结构相当稳定,其中的成员不仅对 Windows 系统来说很有价值,对我们聚合系统调用来说也是一样。具体来说,我们对 Ldr
成员感兴趣。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 > dt ntdll!_PEB +0 x000 InheritedAddressSpace : UChar +0 x001 ReadImageFileExecOptions : UChar +0 x002 BeingDebugged : UChar +0 x003 BitField : UChar +0 x003 ImageUsesLargePages : Pos 0 , 1 Bit +0 x003 IsProtectedProcess : Pos 1 , 1 Bit +0 x003 IsImageDynamicallyRelocated : Pos 2 , 1 Bit +0 x003 SkipPatchingUser32Forwarders : Pos 3 , 1 Bit +0 x003 IsPackagedProcess : Pos 4 , 1 Bit +0 x003 IsAppContainer : Pos 5 , 1 Bit +0 x003 IsProtectedProcessLight : Pos 6 , 1 Bit +0 x003 IsLongPathAwareProcess : Pos 7 , 1 Bit +0 x004 Padding0 : [4] UChar +0 x008 Mutant : Ptr64 Void +0 x010 ImageBaseAddress : Ptr64 Void +0 x018 Ldr : Ptr64 _PEB_LDR_DATA +0 x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS +0 x028 SubSystemData : Ptr64 Void +0 x030 ProcessHeap : Ptr64 Void +0 x038 FastPebLock : Ptr64 _RTL_CRITICAL_SECTION +0 x040 AtlThunkSListPtr : Ptr64 _SLIST_HEADER +0 x048 IFEOKey : Ptr64 Void +0 x050 CrossProcessFlags : Uint4B +0 x050 ProcessInJob : Pos 0 , 1 Bit +0 x050 ProcessInitializing : Pos 1 , 1 Bit +0 x050 ProcessUsingVEH : Pos 2 , 1 Bit +0 x050 ProcessUsingVCH : Pos 3 , 1 Bit +0 x050 ProcessUsingFTH : Pos 4 , 1 Bit +0 x050 ProcessPreviouslyThrottled : Pos 5 , 1 Bit +0 x050 ProcessCurrentlyThrottled : Pos 6 , 1 Bit +0 x050 ProcessImagesHotPatched : Pos 7 , 1 Bit +0 x050 ReservedBits0 : Pos 8 , 24 Bits +0 x054 Padding1 : [4] UChar +0 x058 KernelCallbackTable : Ptr64 Void +0 x058 UserSharedInfoPtr : Ptr64 Void +0 x060 SystemReserved : Uint4B +0 x064 AtlThunkSListPtr32 : Uint4B +0 x068 ApiSetMap : Ptr64 Void +0 x070 TlsExpansionCounter : Uint4B +0 x074 Padding2 : [4] UChar +0 x078 TlsBitmap : Ptr64 Void +0 x080 TlsBitmapBits : [2] Uint4B +0 x088 ReadOnlySharedMemoryBase : Ptr64 Void +0 x090 SharedData : Ptr64 Void +0 x098 ReadOnlyStaticServerData : Ptr64 Ptr64 Void +0 x0a0 AnsiCodePageData : Ptr64 Void +0 x0a8 OemCodePageData : Ptr64 Void +0 x0b0 UnicodeCaseTableData : Ptr64 Void +0 x0b8 NumberOfProcessors : Uint4B +0 x0bc NtGlobalFlag : Uint4B +0 x0c0 CriticalSectionTimeout : _LARGE_INTEGER +0 x0c8 HeapSegmentReserve : Uint8B +0 x0d0 HeapSegmentCommit : Uint8B +0 x0d8 HeapDeCommitTotalFreeThreshold : Uint8B +0 x0e0 HeapDeCommitFreeBlockThreshold : Uint8B +0 x0e8 NumberOfHeaps : Uint4B +0 x0ec MaximumNumberOfHeaps : Uint4B +0 x0f0 ProcessHeaps : Ptr64 Ptr64 Void +0 x0f8 GdiSharedHandleTable : Ptr64 Void +0 x100 ProcessStarterHelper : Ptr64 Void +0 x108 GdiDCAttributeList : Uint4B +0 x10c Padding3 : [4] UChar +0 x110 LoaderLock : Ptr64 _RTL_CRITICAL_SECTION +0 x118 OSMajorVersion : Uint4B +0 x11c OSMinorVersion : Uint4B +0 x120 OSBuildNumber : Uint2B +0 x122 OSCSDVersion : Uint2B +0 x124 OSPlatformId : Uint4B +0 x128 ImageSubsystem : Uint4B +0 x12c ImageSubsystemMajorVersion : Uint4B +0 x130 ImageSubsystemMinorVersion : Uint4B +0 x134 Padding4 : [4] UChar +0 x138 ActiveProcessAffinityMask : Uint8B +0 x140 GdiHandleBuffer : [60] Uint4B +0 x230 PostProcessInitRoutine : Ptr64 void +0 x238 TlsExpansionBitmap : Ptr64 Void +0 x240 TlsExpansionBitmapBits : [32] Uint4B +0 x2c0 SessionId : Uint4B +0 x2c4 Padding5 : [4] UChar +0 x2c8 AppCompatFlags : _ULARGE_INTEGER +0 x2d0 AppCompatFlagsUser : _ULARGE_INTEGER +0 x2d8 pShimData : Ptr64 Void +0 x2e0 AppCompatInfo : Ptr64 Void +0 x2e8 CSDVersion : _UNICODE_STRING +0 x2f8 ActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA +0 x300 ProcessAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP +0 x308 SystemDefaultActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA +0 x310 SystemAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP +0 x318 MinimumStackCommit : Uint8B +0 x320 SparePointers : [4] Ptr64 Void +0 x340 SpareUlongs : [5] Uint4B +0 x358 WerRegistrationData : Ptr64 Void +0 x360 WerShipAssertPtr : Ptr64 Void +0 x368 pUnused : Ptr64 Void +0 x370 pImageHeaderHash : Ptr64 Void +0 x378 TracingFlags : Uint4B +0 x378 HeapTracingEnabled : Pos 0 , 1 Bit +0 x378 CritSecTracingEnabled : Pos 1 , 1 Bit +0 x378 LibLoaderTracingEnabled : Pos 2 , 1 Bit +0 x378 SpareTracingBits : Pos 3 , 29 Bits +0 x37c Padding6 : [4] UChar +0 x380 CsrServerReadOnlySharedMemoryBase : Uint8B +0 x388 TppWorkerpListLock : Uint8B +0 x390 TppWorkerpList : _LIST_ENTRY +0 x3a0 WaitOnAddressHashTable : [128] Ptr64 Void +0 x7a0 TelemetryCoverageHeader : Ptr64 Void +0 x7a8 CloudFileFlags : Uint4B +0 x7ac CloudFileDiagFlags : Uint4B +0 x7b0 PlaceholderCompatibilityMode : Char +0 x7b1 PlaceholderCompatibilityModeReserved : [7] Char +0 x7b8 LeapSecondData : Ptr64 _LEAP_SECOND_DATA +0 x7c0 LeapSecondFlags : Uint4B +0 x7c0 SixtySecondEnabled : Pos 0 , 1 Bit +0 x7c0 Reserved : Pos 1 , 31 Bits +0 x7c4 NtGlobalFlag2 : Uint4B
Ldr
是一个指向 _PEB_LDR_DATA
结构的指针,其中InLoadOrderModuleList
,InMemoryOrderModuleList
,InInitializationOrderModuleList
三个都是_LIST_ENTRY
类型,即环形双向链表,可以通过Flink
和Blink
两个成员遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 > dt ntdll!_PEB_LDR_DATA +0 x000 Length : Uint4B +0 x004 Initialized : UChar +0 x008 SsHandle : Ptr64 Void +0 x010 InLoadOrderModuleList : _LIST_ENTRY +0 x020 InMemoryOrderModuleList : _LIST_ENTRY +0 x030 InInitializationOrderModuleList : _LIST_ENTRY +0 x040 EntryInProgress : Ptr64 Void +0 x048 ShutdownInProgress : UChar +0 x050 ShutdownThreadId : Ptr64 Void > dt ntdll!_LIST_ENTRY +0 x000 Flink : Ptr64 _LIST_ENTRY +0 x008 Blink : Ptr64 _LIST_ENTRY
这个双向链表每一个元素连接了一个_LDR_DATA_TABLE_ENTRY
类型,代表一个加载的 module,InMemoryOrderLinks
在这个结构中的偏移是0x10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 > dt ntdll!_LDR_DATA_TABLE_ENTRY +0 x000 InLoadOrderLinks : _LIST_ENTRY +0 x010 InMemoryOrderLinks : _LIST_ENTRY +0 x020 InInitializationOrderLinks : _LIST_ENTRY +0 x030 DllBase : Ptr64 Void +0 x038 EntryPoint : Ptr64 Void +0 x040 SizeOfImage : Uint4B +0 x048 FullDllName : _UNICODE_STRING +0 x058 BaseDllName : _UNICODE_STRING +0 x068 FlagGroup : [4] UChar +0 x068 Flags : Uint4B +0 x068 PackagedBinary : Pos 0 , 1 Bit +0 x068 MarkedForRemoval : Pos 1 , 1 Bit +0 x068 ImageDll : Pos 2 , 1 Bit +0 x068 LoadNotificationsSent : Pos 3 , 1 Bit +0 x068 TelemetryEntryProcessed : Pos 4 , 1 Bit +0 x068 ProcessStaticImport : Pos 5 , 1 Bit +0 x068 InLegacyLists : Pos 6 , 1 Bit +0 x068 InIndexes : Pos 7 , 1 Bit +0 x068 ShimDll : Pos 8 , 1 Bit +0 x068 InExceptionTable : Pos 9 , 1 Bit +0 x068 ReservedFlags1 : Pos 10 , 2 Bits +0 x068 LoadInProgress : Pos 12 , 1 Bit +0 x068 LoadConfigProcessed : Pos 13 , 1 Bit +0 x068 EntryProcessed : Pos 14 , 1 Bit +0 x068 ProtectDelayLoad : Pos 15 , 1 Bit +0 x068 ReservedFlags3 : Pos 16 , 2 Bits +0 x068 DontCallForThreads : Pos 18 , 1 Bit +0 x068 ProcessAttachCalled : Pos 19 , 1 Bit +0 x068 ProcessAttachFailed : Pos 20 , 1 Bit +0 x068 CorDeferredValidate : Pos 21 , 1 Bit +0 x068 CorImage : Pos 22 , 1 Bit +0 x068 DontRelocate : Pos 23 , 1 Bit +0 x068 CorILOnly : Pos 24 , 1 Bit +0 x068 ChpeImage : Pos 25 , 1 Bit +0 x068 ReservedFlags5 : Pos 26 , 2 Bits +0 x068 Redirected : Pos 28 , 1 Bit +0 x068 ReservedFlags6 : Pos 29 , 2 Bits +0 x068 CompatDatabaseProcessed : Pos 31 , 1 Bit +0 x06c ObsoleteLoadCount : Uint2B +0 x06e TlsIndex : Uint2B +0 x070 HashLinks : _LIST_ENTRY +0 x080 TimeDateStamp : Uint4B +0 x088 EntryPointActivationContext : Ptr64 _ACTIVATION_CONTEXT +0 x090 Lock : Ptr64 Void +0 x098 DdagNode : Ptr64 _LDR_DDAG_NODE +0 x0a0 NodeModuleLink : _LIST_ENTRY +0 x0b0 LoadContext : Ptr64 _LDRP_LOAD_CONTEXT +0 x0b8 ParentDllBase : Ptr64 Void +0 x0c0 SwitchBackContext : Ptr64 Void +0 x0c8 BaseAddressIndexNode : _RTL_BALANCED_NODE +0 x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE +0 x0f8 OriginalBase : Uint8B +0 x100 LoadTime : _LARGE_INTEGER +0 x108 BaseNameHashValue : Uint4B +0 x10c LoadReason : _LDR_DLL_LOAD_REASON +0 x110 ImplicitPathOptions : Uint4B +0 x114 ReferenceCount : Uint4B +0 x118 DependentLoadFlags : Uint4B +0 x11c SigningLevel : UChar
根据以上讨论,代表 NTDLL.dll 的_LDR_DATA_TABLE_ENTRY
应该是InMemoryOrderModuleList
中的第二个。综合以上内容,就是寻找 NTDLL.dll 的方式:
1 2 3 4 5 6 0 :025 > ?? ((ntdll!_LDR_DATA_TABLE_ENTRY*)(((int64)((ntdll!_PEB*)@@(@$peb))->Ldr->InMemoryOrderModuleList.Flink->Flink) - 0x10 )) -> BaseDllNamestruct _UNICODE_STRING "ntdll.dll" +0x000 Length : 0x12 +0x002 MaximumLength : 0x14 +0x008 Buffer : 0x00007ffe `0e 920b30 "ntdll.dll"
但是某些杀软会修改 InMemoryOrderModuleList,所以获取后需要验证获取到的是否是 NTDLL.dll。
接下来我们获取到了 NTDLL.dll,我们就可以遍历它的导出表并且动态解析 system calls。我们的目的是获取导出地址表,步骤如下:
获取基地址
获取_IMAGE_DOS_HEADER
并通过检查 signature 来验证
遍历IMAGE_NT_HEADER
,IMAGE_FILE_HEADER
,IMAGE_OPTIONALHEADER
定位 Optional Header 中 Data Directory 的 导出地址表,类型为_IMAGE_EXPORT_DIRECTORY
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 > ?? ((ntdll!_LDR_DATA_TABLE_ENTRY*)(((int64)((ntdll!_PEB*)@@(@$peb))->Ldr->InMemoryOrderModuleList.Flink->Flink) - 0x10 )) -> DllBase void * 0x00007ffe `0e 7f0000> ?? ((ntdll!_IMAGE_NT_HEADERS64*)(((ntdll!_IMAGE_DOS_HEADER*)@@(0x00007ffe `0e 7f0000))->e_lfanew + 0x00007ffe0e7f0000 )) -> OptionalHeader.DataDirectory struct _IMAGE_DATA_DIRECTORY [16 ] 0x00007ffe `0e 7f0170 +0x000 VirtualAddress : 0x151180 +0x004 Size : 0x12e71 > ?? (OLE32!_IMAGE_EXPORT_DIRECTORY*)@@(0x00007ffe0e7f0000 + 0x151180 ) struct _IMAGE_EXPORT_DIRECTORY * 0x00007ffe `0e 941180 +0x000 Characteristics : 0 +0x004 TimeDateStamp : 0xa280d1d6 +0x008 MajorVersion : 0 +0x00a MinorVersion : 0 +0x00c Name : 0x157098 +0x010 Base : 8 +0x014 NumberOfFunctions : 0x97f +0x018 NumberOfNames : 0x97e +0x01c AddressOfFunctions : 0x1511a8 +0x020 AddressOfNames : 0x1537a4 +0x024 AddressOfNameOrdinals : 0x155d9c
NTDLL.dll 的函数都有类似的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 > uf ntdll!NtCreateMutant ntdll!NtCreateMutant : 00007 ffe`0e88 e3 b0 4 c 8 bd1 mov r10 , rcx00007 ffe`0e88 e3 b3 b8 b3000000 mov eax, 0 B3 h00007 ffe`0e88 e3 b8 f604250803 fe7 f01 test byte ptr [SharedUserData+0x308 (00000000 `7 ffe0308 )], 1 00007 ffe`0e88 e3 c 0 7503 jne ntdll!NtCreateMutant +0x15 (00007 ffe`0e88 e3 c 5 ) Branchntdll!NtCreateMutant +0x12 : 00007 ffe`0e88 e3 c 2 0 f05 syscall00007 ffe`0e88 e3 c 4 c 3 ret ntdll!NtCreateMutant +0x15 : 00007 ffe`0e88 e3 c 5 cd2 e int 2 Eh00007 ffe`0e88 e3 c 7 c 3 ret > uf ntdll!NtCreateFile ntdll!NtCreateFile : 00007 ffe`0e88 d800 4 c 8 bd1 mov r10 , rcx00007 ffe`0e88 d803 b855000000 mov eax, 55 h00007 ffe`0e88 d808 f604250803 fe7 f01 test byte ptr [SharedUserData+0x308 (00000000 `7 ffe0308 )], 1 00007 ffe`0e88 d810 7503 jne ntdll!NtCreateFile +0x15 (00007 ffe`0e88 d815 ) Branchntdll!NtCreateFile +0x12 : 00007 ffe`0e88 d812 0 f05 syscall00007 ffe`0e88 d814 c 3 ret ntdll!NtCreateFile +0x15 : 00007 ffe`0e88 d815 cd2 e int 2 Eh00007 ffe`0e88 d817 c 3 ret > uf ntdll!NtCreateProcess ntdll!NtCreateProcess : 00007 ffe`0e88 e470 4 c 8 bd1 mov r10 , rcx00007 ffe`0e88 e473 b8 b9000000 mov eax, 0 B9 h00007 ffe`0e88 e478 f604250803 fe7 f01 test byte ptr [SharedUserData+0x308 (00000000 `7 ffe0308 )], 1 00007 ffe`0e88 e480 7503 jne ntdll!NtCreateProcess +0x15 (00007 ffe`0e88 e485 ) Branchntdll!NtCreateProcess +0x12 : 00007 ffe`0e88 e482 0 f05 syscall00007 ffe`0e88 e484 c 3 ret ntdll!NtCreateProcess +0x15 : 00007 ffe`0e88 e485 cd2 e int 2 Eh00007 ffe`0e88 e487 c 3 ret
观察上面几个函数,都是mov r10, rcx
,mov eax, <syscall>
,随后 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
用来检查当前线程的运行环境是 x86 或是 x64,如果是 x64 则执行 System call,不是则返回。System calls 类型为 WORD,存储在 EAX 中。mov r10, rcx
机器码为0x4c8bd1
,3字节,mov eax, <syscall>
第一个字节是0xb8
,后两字节为 system call,小端序,再后两字节为0x0000
,可以用如下方式获取函数中的 System call。
1 2 3 4 5 6 7 8 9 10 11 > db (ntdll!NtCreateMutant + 0x4 ) L 2 00007ffe `0e88e3b4 b3 00 > ? (0x00 << 8 ) | 0xb3 Evaluate expression: 179 = 00000000 `000000b3 > db (ntdll!NtPlugPlayControl + 0x4 ) L2 00007ffe `0e88f394 32 01 > ? (0x01 << 8 ) | 0x32 Evaluate expression: 306 = 00000000 `00000132
现在我们有了可以动态获取的 system call,接下来需要动态执行系统调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .data wSystemCall DWORD 000h .code HellsGate PROC mov wSystemCall, 000h mov wSystemCall, ecx ret HellsGate ENDP HellDescent PROC mov r10 , rcx mov eax , wSystemCall syscall ret HellDescent ENDP End
HellsGate 用于设置执行的 syscal,HellDescent 用来执行 syscall,实际调用时如下:
1 2 3 4 5 6 WORD syscall = 0x00b3 ; HellsGate(syscall ) ;HANDLE hMutant = INVALID_HANDLE_VALUE; NTSTATUS st = HellDescent(&hMutant , MUTANT_ALL_ACCESS, NULL, TRUE) ;
实现:am0nsec/HellsGate: Original C Implementation of the Hell’s Gate VX Technique (github.com) ,相比与 Paper 中有一些改动。
Improvement EDR 会经常做一些 API Hook,如果我们使用的 API 被 Hook,有可能获取不到 syscall,因此我们可以从磁盘上获取 NTDLL.dll。
这类方法可以避免 EDR 的用户模式 API Hook,但对于内核模式的检测没有效果,因为在从 R3 进入 R0 时,地狱之门与普通的 API 调用时一样的。
Reference Hell’s Gate ,原 Paper
am0nsec/HellsGate: Original C Implementation of the Hell’s Gate VX Technique (github.com) ,原 Paper 中的实现
Kara-4search/HellgateLoader_CSharp: Load shellcode via HELLGATE, Rewrite hellgate with .net framework for learning purpose. (github.com) ,C# 实现
Retrieving ntdll Syscall Stubs from Disk at Run-time - Red Teaming Experiments (ired.team) ,从磁盘读取 NTDLL.dll
Implementing Direct Syscalls Using Hell’s Gate – Team Hydra ,从磁盘读取 NTDLL.dll
Malware Mitigation when Direct System Calls are Used - Cyberbit ,对于直接系统调用的检测
syscall的前世今生 - 跳跳糖 (tttang.com)