Hell's Gate

简单的 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`0e920b30 "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, x86
mov eax, fs:[0x30]

// asm, x64
mov rax, qs:[0x60]

// C/C++ intrinsic function
PPEB peb = (PPEB)__readfsdword(0x30)

PPEB peb = (PPEB)__readgsqword(0x60)

微软没有定义 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
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
+0x004 Padding0 : [4] UChar
+0x008 Mutant : Ptr64 Void
+0x010 ImageBaseAddress : Ptr64 Void
+0x018 Ldr : Ptr64 _PEB_LDR_DATA
+0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS
+0x028 SubSystemData : Ptr64 Void
+0x030 ProcessHeap : Ptr64 Void
+0x038 FastPebLock : Ptr64 _RTL_CRITICAL_SECTION
+0x040 AtlThunkSListPtr : Ptr64 _SLIST_HEADER
+0x048 IFEOKey : Ptr64 Void
+0x050 CrossProcessFlags : Uint4B
+0x050 ProcessInJob : Pos 0, 1 Bit
+0x050 ProcessInitializing : Pos 1, 1 Bit
+0x050 ProcessUsingVEH : Pos 2, 1 Bit
+0x050 ProcessUsingVCH : Pos 3, 1 Bit
+0x050 ProcessUsingFTH : Pos 4, 1 Bit
+0x050 ProcessPreviouslyThrottled : Pos 5, 1 Bit
+0x050 ProcessCurrentlyThrottled : Pos 6, 1 Bit
+0x050 ProcessImagesHotPatched : Pos 7, 1 Bit
+0x050 ReservedBits0 : Pos 8, 24 Bits
+0x054 Padding1 : [4] UChar
+0x058 KernelCallbackTable : Ptr64 Void
+0x058 UserSharedInfoPtr : Ptr64 Void
+0x060 SystemReserved : Uint4B
+0x064 AtlThunkSListPtr32 : Uint4B
+0x068 ApiSetMap : Ptr64 Void
+0x070 TlsExpansionCounter : Uint4B
+0x074 Padding2 : [4] UChar
+0x078 TlsBitmap : Ptr64 Void
+0x080 TlsBitmapBits : [2] Uint4B
+0x088 ReadOnlySharedMemoryBase : Ptr64 Void
+0x090 SharedData : Ptr64 Void
+0x098 ReadOnlyStaticServerData : Ptr64 Ptr64 Void
+0x0a0 AnsiCodePageData : Ptr64 Void
+0x0a8 OemCodePageData : Ptr64 Void
+0x0b0 UnicodeCaseTableData : Ptr64 Void
+0x0b8 NumberOfProcessors : Uint4B
+0x0bc NtGlobalFlag : Uint4B
+0x0c0 CriticalSectionTimeout : _LARGE_INTEGER
+0x0c8 HeapSegmentReserve : Uint8B
+0x0d0 HeapSegmentCommit : Uint8B
+0x0d8 HeapDeCommitTotalFreeThreshold : Uint8B
+0x0e0 HeapDeCommitFreeBlockThreshold : Uint8B
+0x0e8 NumberOfHeaps : Uint4B
+0x0ec MaximumNumberOfHeaps : Uint4B
+0x0f0 ProcessHeaps : Ptr64 Ptr64 Void
+0x0f8 GdiSharedHandleTable : Ptr64 Void
+0x100 ProcessStarterHelper : Ptr64 Void
+0x108 GdiDCAttributeList : Uint4B
+0x10c Padding3 : [4] UChar
+0x110 LoaderLock : Ptr64 _RTL_CRITICAL_SECTION
+0x118 OSMajorVersion : Uint4B
+0x11c OSMinorVersion : Uint4B
+0x120 OSBuildNumber : Uint2B
+0x122 OSCSDVersion : Uint2B
+0x124 OSPlatformId : Uint4B
+0x128 ImageSubsystem : Uint4B
+0x12c ImageSubsystemMajorVersion : Uint4B
+0x130 ImageSubsystemMinorVersion : Uint4B
+0x134 Padding4 : [4] UChar
+0x138 ActiveProcessAffinityMask : Uint8B
+0x140 GdiHandleBuffer : [60] Uint4B
+0x230 PostProcessInitRoutine : Ptr64 void
+0x238 TlsExpansionBitmap : Ptr64 Void
+0x240 TlsExpansionBitmapBits : [32] Uint4B
+0x2c0 SessionId : Uint4B
+0x2c4 Padding5 : [4] UChar
+0x2c8 AppCompatFlags : _ULARGE_INTEGER
+0x2d0 AppCompatFlagsUser : _ULARGE_INTEGER
+0x2d8 pShimData : Ptr64 Void
+0x2e0 AppCompatInfo : Ptr64 Void
+0x2e8 CSDVersion : _UNICODE_STRING
+0x2f8 ActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA
+0x300 ProcessAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP
+0x308 SystemDefaultActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA
+0x310 SystemAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP
+0x318 MinimumStackCommit : Uint8B
+0x320 SparePointers : [4] Ptr64 Void
+0x340 SpareUlongs : [5] Uint4B
+0x358 WerRegistrationData : Ptr64 Void
+0x360 WerShipAssertPtr : Ptr64 Void
+0x368 pUnused : Ptr64 Void
+0x370 pImageHeaderHash : Ptr64 Void
+0x378 TracingFlags : Uint4B
+0x378 HeapTracingEnabled : Pos 0, 1 Bit
+0x378 CritSecTracingEnabled : Pos 1, 1 Bit
+0x378 LibLoaderTracingEnabled : Pos 2, 1 Bit
+0x378 SpareTracingBits : Pos 3, 29 Bits
+0x37c Padding6 : [4] UChar
+0x380 CsrServerReadOnlySharedMemoryBase : Uint8B
+0x388 TppWorkerpListLock : Uint8B
+0x390 TppWorkerpList : _LIST_ENTRY
+0x3a0 WaitOnAddressHashTable : [128] Ptr64 Void
+0x7a0 TelemetryCoverageHeader : Ptr64 Void
+0x7a8 CloudFileFlags : Uint4B
+0x7ac CloudFileDiagFlags : Uint4B
+0x7b0 PlaceholderCompatibilityMode : Char
+0x7b1 PlaceholderCompatibilityModeReserved : [7] Char
+0x7b8 LeapSecondData : Ptr64 _LEAP_SECOND_DATA
+0x7c0 LeapSecondFlags : Uint4B
+0x7c0 SixtySecondEnabled : Pos 0, 1 Bit
+0x7c0 Reserved : Pos 1, 31 Bits
+0x7c4 NtGlobalFlag2 : Uint4B

Ldr 是一个指向 _PEB_LDR_DATA 结构的指针,其中InLoadOrderModuleListInMemoryOrderModuleListInInitializationOrderModuleList 三个都是_LIST_ENTRY类型,即环形双向链表,可以通过FlinkBlink两个成员遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> dt ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr64 Void
+0x010 InLoadOrderModuleList : _LIST_ENTRY
+0x020 InMemoryOrderModuleList : _LIST_ENTRY
+0x030 InInitializationOrderModuleList : _LIST_ENTRY
+0x040 EntryInProgress : Ptr64 Void
+0x048 ShutdownInProgress : UChar
+0x050 ShutdownThreadId : Ptr64 Void

> dt ntdll!_LIST_ENTRY
+0x000 Flink : Ptr64 _LIST_ENTRY
+0x008 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
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x010 InMemoryOrderLinks : _LIST_ENTRY
+0x020 InInitializationOrderLinks : _LIST_ENTRY
+0x030 DllBase : Ptr64 Void
+0x038 EntryPoint : Ptr64 Void
+0x040 SizeOfImage : Uint4B
+0x048 FullDllName : _UNICODE_STRING
+0x058 BaseDllName : _UNICODE_STRING
+0x068 FlagGroup : [4] UChar
+0x068 Flags : Uint4B
+0x068 PackagedBinary : Pos 0, 1 Bit
+0x068 MarkedForRemoval : Pos 1, 1 Bit
+0x068 ImageDll : Pos 2, 1 Bit
+0x068 LoadNotificationsSent : Pos 3, 1 Bit
+0x068 TelemetryEntryProcessed : Pos 4, 1 Bit
+0x068 ProcessStaticImport : Pos 5, 1 Bit
+0x068 InLegacyLists : Pos 6, 1 Bit
+0x068 InIndexes : Pos 7, 1 Bit
+0x068 ShimDll : Pos 8, 1 Bit
+0x068 InExceptionTable : Pos 9, 1 Bit
+0x068 ReservedFlags1 : Pos 10, 2 Bits
+0x068 LoadInProgress : Pos 12, 1 Bit
+0x068 LoadConfigProcessed : Pos 13, 1 Bit
+0x068 EntryProcessed : Pos 14, 1 Bit
+0x068 ProtectDelayLoad : Pos 15, 1 Bit
+0x068 ReservedFlags3 : Pos 16, 2 Bits
+0x068 DontCallForThreads : Pos 18, 1 Bit
+0x068 ProcessAttachCalled : Pos 19, 1 Bit
+0x068 ProcessAttachFailed : Pos 20, 1 Bit
+0x068 CorDeferredValidate : Pos 21, 1 Bit
+0x068 CorImage : Pos 22, 1 Bit
+0x068 DontRelocate : Pos 23, 1 Bit
+0x068 CorILOnly : Pos 24, 1 Bit
+0x068 ChpeImage : Pos 25, 1 Bit
+0x068 ReservedFlags5 : Pos 26, 2 Bits
+0x068 Redirected : Pos 28, 1 Bit
+0x068 ReservedFlags6 : Pos 29, 2 Bits
+0x068 CompatDatabaseProcessed : Pos 31, 1 Bit
+0x06c ObsoleteLoadCount : Uint2B
+0x06e TlsIndex : Uint2B
+0x070 HashLinks : _LIST_ENTRY
+0x080 TimeDateStamp : Uint4B
+0x088 EntryPointActivationContext : Ptr64 _ACTIVATION_CONTEXT
+0x090 Lock : Ptr64 Void
+0x098 DdagNode : Ptr64 _LDR_DDAG_NODE
+0x0a0 NodeModuleLink : _LIST_ENTRY
+0x0b0 LoadContext : Ptr64 _LDRP_LOAD_CONTEXT
+0x0b8 ParentDllBase : Ptr64 Void
+0x0c0 SwitchBackContext : Ptr64 Void
+0x0c8 BaseAddressIndexNode : _RTL_BALANCED_NODE
+0x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE
+0x0f8 OriginalBase : Uint8B
+0x100 LoadTime : _LARGE_INTEGER
+0x108 BaseNameHashValue : Uint4B
+0x10c LoadReason : _LDR_DLL_LOAD_REASON
+0x110 ImplicitPathOptions : Uint4B
+0x114 ReferenceCount : Uint4B
+0x118 DependentLoadFlags : Uint4B
+0x11c 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))->BaseDllName
struct _UNICODE_STRING
"ntdll.dll"
+0x000 Length : 0x12
+0x002 MaximumLength : 0x14
+0x008 Buffer : 0x00007ffe`0e920b30 "ntdll.dll"

但是某些杀软会修改 InMemoryOrderModuleList,所以获取后需要验证获取到的是否是 NTDLL.dll。

接下来我们获取到了 NTDLL.dll,我们就可以遍历它的导出表并且动态解析 system calls。我们的目的是获取导出地址表,步骤如下:

  1. 获取基地址
  2. 获取_IMAGE_DOS_HEADER并通过检查 signature 来验证
  3. 遍历IMAGE_NT_HEADERIMAGE_FILE_HEADERIMAGE_OPTIONALHEADER
  4. 定位 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`0e7f0000

> ?? ((ntdll!_IMAGE_NT_HEADERS64*)(((ntdll!_IMAGE_DOS_HEADER*)@@(0x00007ffe`0e7f0000))->e_lfanew + 0x00007ffe0e7f0000))->OptionalHeader.DataDirectory
struct _IMAGE_DATA_DIRECTORY [16] 0x00007ffe`0e7f0170
+0x000 VirtualAddress : 0x151180
+0x004 Size : 0x12e71

> ?? (OLE32!_IMAGE_EXPORT_DIRECTORY*)@@(0x00007ffe0e7f0000 + 0x151180)
struct _IMAGE_EXPORT_DIRECTORY * 0x00007ffe`0e941180
+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:
00007ffe`0e88e3b0 4c8bd1 mov r10,rcx
00007ffe`0e88e3b3 b8b3000000 mov eax,0B3h
00007ffe`0e88e3b8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffe`0e88e3c0 7503 jne ntdll!NtCreateMutant+0x15 (00007ffe`0e88e3c5) Branch

ntdll!NtCreateMutant+0x12:
00007ffe`0e88e3c2 0f05 syscall
00007ffe`0e88e3c4 c3 ret

ntdll!NtCreateMutant+0x15:
00007ffe`0e88e3c5 cd2e int 2Eh
00007ffe`0e88e3c7 c3 ret


> uf ntdll!NtCreateFile
ntdll!NtCreateFile:
00007ffe`0e88d800 4c8bd1 mov r10,rcx
00007ffe`0e88d803 b855000000 mov eax,55h
00007ffe`0e88d808 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffe`0e88d810 7503 jne ntdll!NtCreateFile+0x15 (00007ffe`0e88d815) Branch

ntdll!NtCreateFile+0x12:
00007ffe`0e88d812 0f05 syscall
00007ffe`0e88d814 c3 ret

ntdll!NtCreateFile+0x15:
00007ffe`0e88d815 cd2e int 2Eh
00007ffe`0e88d817 c3 ret


> uf ntdll!NtCreateProcess
ntdll!NtCreateProcess:
00007ffe`0e88e470 4c8bd1 mov r10,rcx
00007ffe`0e88e473 b8b9000000 mov eax,0B9h
00007ffe`0e88e478 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffe`0e88e480 7503 jne ntdll!NtCreateProcess+0x15 (00007ffe`0e88e485) Branch

ntdll!NtCreateProcess+0x12:
00007ffe`0e88e482 0f05 syscall
00007ffe`0e88e484 c3 ret

ntdll!NtCreateProcess+0x15:
00007ffe`0e88e485 cd2e int 2Eh
00007ffe`0e88e487 c3 ret

观察上面几个函数,都是mov r10, rcxmov 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
// NtCreateMutant
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)

作者

lll

发布于

2021-12-25

更新于

2023-03-24

许可协议