windows-syscall篇 @[toc] 学免杀不可避免的学习syscall。主要后面需要用rust重写一个syswhispers3的工具嵌入到一个rust写的c2中。
关于PEB 进程环境信息块,是一个从内核中分配给每个进程的用户模式结构,每一个进程都会有从ring0分配给该进程的进程环境块,后续我们主要需要了解_PEB_LDR_DATA以及其他子结构
这张图是x86系统的结构体 FS段寄存器指向当前的TEB结构,可以看到PEB在TEB的0x30偏移处。在编写代码时PEB的结构体也是需要我们自己定义的,微软官方并没有给出。
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 typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN Spare; HANDLE Mutant; PVOID ImageBase; PPEB_LDR_DATA LoaderData; PVOID ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PVOID FastPebLockRoutine; PVOID FastPebUnlockRoutine; ULONG EnvironmentUpdateCount; PVOID* KernelCallbackTable; PVOID EventLogSection; PVOID EventLog; PVOID FreeList; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[0x2 ]; PVOID ReadOnlySharedMemoryBase; PVOID ReadOnlySharedMemoryHeap; PVOID* ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; BYTE Spare2[0x4 ]; LARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; PVOID** ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; ULONG OSBuildNumber; ULONG OSPlatformId; ULONG ImageSubSystem; ULONG ImageSubSystemMajorVersion; ULONG ImageSubSystemMinorVersion; ULONG GdiHandleBuffer[0x22 ]; ULONG PostProcessInitRoutine; ULONG TlsExpansionBitmap; BYTE TlsExpansionBitmapBits[0x80 ]; ULONG SessionId; } PEB, * PPEB;
在PEB中的0x0c处为一指针,指向PEB_LDR_DATA结构,该结构体包含有关为进程加载的模块的信息(存储着该进程所有模块数据的链表)。
在PEB_LDR_DATA的0x0c,0x14,0x1c中为三个双向链表LIST_ENTRY,在struct _LDR_MODULE的0x00,0x08和0x10处是三个对应的同名称的LIST_ENTRY, PEB_LDR_DATA和struct _LDR_MODULE就是通过这三个LIST_ENTRY对应连接起来的。
三个双向链表分别代表模块加载顺序,模块在内存中的加载顺序以及模块初始化装载的顺序.
typedef struct _PEB_LDR_DATA { ULONG Length; ULONG Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, * PPEB_LDR_DATA;
LIST_ENTRY的结构体是下面这样
typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
每个双向链表都是指向进程装载的模块,结构中的每个指针,指向了一个LDR_DATA_TABLE_ENTRY的结构: 这个结构很重要,提供了内存模块的基址和dll名称
struct _LDR_DATA_TABLE_ENTRY { struct _LIST_ENTRY InLoadOrderLinks; struct _LIST_ENTRY InMemoryOrderLinks; struct _LIST_ENTRY InInitializationOrderLinks; VOID* DllBase; VOID* EntryPoint; ULONG SizeOfImage; struct _UNICODE_STRING FullDllName; struct _UNICODE_STRING BaseDllName; ... };
还有一个很重要的结构体。每个加载的模块都有一个LDR_MODULE结构体,其中的BaseAddress字段是模块在内存当中的基地址,BaseDllName指向一个UNICODE_STRING,其包含模块的名称(kernel32.dll等)。实际上三个链表结构是被PEB_LDR_DATA和LDR_MODULE结构共用的。
typedef struct _LDR_MODULE { LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID BaseAddress; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; SHORT LoadCount; SHORT TlsIndex; LIST_ENTRY HashTableEntry; ULONG TimeDateStamp; } LDR_MODULE, * PLDR_MODULE;
从这张图就可以看到很详细的模块信息以及一些dll基址。
用代码获取相关信息 基本认识了PEB,那肯定还是要回到代码层面,怎么去获取到PEB进程的一些相关信息例如偏移地址等。这里微软给出了一些可以使用的函数。
我们最终检索到PEN进程块的流程大概可以像这样来实现。
先自定义实现一些结构体。
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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 #pragma once #include <Windows.h> typedef struct _LSA_UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } LSA_UNICODE_STRING, * PLSA_UNICODE_STRING, UNICODE_STRING, * PUNICODE_STRING, * PUNICODE_STR;typedef struct _LDR_MODULE { LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID BaseAddress; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; SHORT LoadCount; SHORT TlsIndex; LIST_ENTRY HashTableEntry; ULONG TimeDateStamp; } LDR_MODULE, * PLDR_MODULE;typedef struct _PEB_LDR_DATA { ULONG Length; ULONG Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, * PPEB_LDR_DATA;typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN Spare; HANDLE Mutant; PVOID ImageBase; PPEB_LDR_DATA LoaderData; PVOID ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PVOID FastPebLockRoutine; PVOID FastPebUnlockRoutine; ULONG EnvironmentUpdateCount; PVOID* KernelCallbackTable; PVOID EventLogSection; PVOID EventLog; PVOID FreeList; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[0x2 ]; PVOID ReadOnlySharedMemoryBase; PVOID ReadOnlySharedMemoryHeap; PVOID* ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; BYTE Spare2[0x4 ]; LARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; PVOID** ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; ULONG OSBuildNumber; ULONG OSPlatformId; ULONG ImageSubSystem; ULONG ImageSubSystemMajorVersion; ULONG ImageSubSystemMinorVersion; ULONG GdiHandleBuffer[0x22 ]; ULONG PostProcessInitRoutine; ULONG TlsExpansionBitmap; BYTE TlsExpansionBitmapBits[0x80 ]; ULONG SessionId; } PEB, * PPEB;typedef struct __CLIENT_ID { HANDLE UniqueProcess; HANDLE UniqueThread; } CLIENT_ID, * PCLIENT_ID;typedef struct _TEB_ACTIVE_FRAME_CONTEXT { ULONG Flags; PCHAR FrameName; } TEB_ACTIVE_FRAME_CONTEXT, * PTEB_ACTIVE_FRAME_CONTEXT;typedef struct _TEB_ACTIVE_FRAME { ULONG Flags; struct _TEB_ACTIVE_FRAME * Previous ; PTEB_ACTIVE_FRAME_CONTEXT Context; } TEB_ACTIVE_FRAME, * PTEB_ACTIVE_FRAME;typedef struct _GDI_TEB_BATCH { ULONG Offset; ULONG HDC; ULONG Buffer[310 ]; } GDI_TEB_BATCH, * PGDI_TEB_BATCH;typedef PVOID PACTIVATION_CONTEXT;typedef struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME { struct __RTL_ACTIVATION_CONTEXT_STACK_FRAME * Previous ; PACTIVATION_CONTEXT ActivationContext; ULONG Flags; } RTL_ACTIVATION_CONTEXT_STACK_FRAME, * PRTL_ACTIVATION_CONTEXT_STACK_FRAME;typedef struct _ACTIVATION_CONTEXT_STACK { PRTL_ACTIVATION_CONTEXT_STACK_FRAME ActiveFrame; LIST_ENTRY FrameListCache; ULONG Flags; ULONG NextCookieSequenceNumber; ULONG StackId; } ACTIVATION_CONTEXT_STACK, * PACTIVATION_CONTEXT_STACK;typedef struct _TEB { NT_TIB NtTib; PVOID EnvironmentPointer; CLIENT_ID ClientId; PVOID ActiveRpcHandle; PVOID ThreadLocalStoragePointer; PPEB ProcessEnvironmentBlock; ULONG LastErrorValue; ULONG CountOfOwnedCriticalSections; PVOID CsrClientThread; PVOID Win32ThreadInfo; ULONG User32Reserved[26 ]; ULONG UserReserved[5 ]; PVOID WOW32Reserved; LCID CurrentLocale; ULONG FpSoftwareStatusRegister; PVOID SystemReserved1[54 ]; LONG ExceptionCode;#if (NTDDI_VERSION >= NTDDI_LONGHORN) PACTIVATION_CONTEXT_STACK* ActivationContextStackPointer; UCHAR SpareBytes1[0x30 - 3 * sizeof (PVOID)]; ULONG TxFsContext;#elif (NTDDI_VERSION >= NTDDI_WS03) PACTIVATION_CONTEXT_STACK ActivationContextStackPointer; UCHAR SpareBytes1[0x34 - 3 * sizeof (PVOID)];#else ACTIVATION_CONTEXT_STACK ActivationContextStack; UCHAR SpareBytes1[24 ];#endif GDI_TEB_BATCH GdiTebBatch; CLIENT_ID RealClientId; PVOID GdiCachedProcessHandle; ULONG GdiClientPID; ULONG GdiClientTID; PVOID GdiThreadLocalInfo; PSIZE_T Win32ClientInfo[62 ]; PVOID glDispatchTable[233 ]; PSIZE_T glReserved1[29 ]; PVOID glReserved2; PVOID glSectionInfo; PVOID glSection; PVOID glTable; PVOID glCurrentRC; PVOID glContext; NTSTATUS LastStatusValue; UNICODE_STRING StaticUnicodeString; WCHAR StaticUnicodeBuffer[261 ]; PVOID DeallocationStack; PVOID TlsSlots[64 ]; LIST_ENTRY TlsLinks; PVOID Vdm; PVOID ReservedForNtRpc; PVOID DbgSsReserved[2 ];#if (NTDDI_VERSION >= NTDDI_WS03) ULONG HardErrorMode;#else ULONG HardErrorsAreDisabled;#endif #if (NTDDI_VERSION >= NTDDI_LONGHORN) PVOID Instrumentation[13 - sizeof (GUID) / sizeof (PVOID)]; GUID ActivityId; PVOID SubProcessTag; PVOID EtwLocalData; PVOID EtwTraceData;#elif (NTDDI_VERSION >= NTDDI_WS03) PVOID Instrumentation[14 ]; PVOID SubProcessTag; PVOID EtwLocalData;#else PVOID Instrumentation[16 ];#endif PVOID WinSockData; ULONG GdiBatchCount;#if (NTDDI_VERSION >= NTDDI_LONGHORN) BOOLEAN SpareBool0; BOOLEAN SpareBool1; BOOLEAN SpareBool2;#else BOOLEAN InDbgPrint; BOOLEAN FreeStackOnTermination; BOOLEAN HasFiberData;#endif UCHAR IdealProcessor;#if (NTDDI_VERSION >= NTDDI_WS03) ULONG GuaranteedStackBytes;#else ULONG Spare3;#endif PVOID ReservedForPerf; PVOID ReservedForOle; ULONG WaitingOnLoaderLock;#if (NTDDI_VERSION >= NTDDI_LONGHORN) PVOID SavedPriorityState; ULONG_PTR SoftPatchPtr1; ULONG_PTR ThreadPoolData;#elif (NTDDI_VERSION >= NTDDI_WS03) ULONG_PTR SparePointer1; ULONG_PTR SoftPatchPtr1; ULONG_PTR SoftPatchPtr2;#else Wx86ThreadState Wx86Thread;#endif PVOID* TlsExpansionSlots;#if defined(_WIN64) && !defined(EXPLICIT_32BIT) PVOID DeallocationBStore; PVOID BStoreLimit;#endif ULONG ImpersonationLocale; ULONG IsImpersonating; PVOID NlsCache; PVOID pShimData; ULONG HeapVirtualAffinity; HANDLE CurrentTransactionHandle; PTEB_ACTIVE_FRAME ActiveFrame;#if (NTDDI_VERSION >= NTDDI_WS03) PVOID FlsData;#endif #if (NTDDI_VERSION >= NTDDI_LONGHORN) PVOID PreferredLangauges; PVOID UserPrefLanguages; PVOID MergedPrefLanguages; ULONG MuiImpersonation; union { struct { USHORT SpareCrossTebFlags : 16 ; }; USHORT CrossTebFlags; }; union { struct { USHORT DbgSafeThunkCall : 1 ; USHORT DbgInDebugPrint : 1 ; USHORT DbgHasFiberData : 1 ; USHORT DbgSkipThreadAttach : 1 ; USHORT DbgWerInShipAssertCode : 1 ; USHORT DbgIssuedInitialBp : 1 ; USHORT DbgClonedThread : 1 ; USHORT SpareSameTebBits : 9 ; }; USHORT SameTebFlags; }; PVOID TxnScopeEntercallback; PVOID TxnScopeExitCAllback; PVOID TxnScopeContext; ULONG LockCount; ULONG ProcessRundown; ULONG64 LastSwitchTime; ULONG64 TotalSwitchOutTime; LARGE_INTEGER WaitReasonBitMap;#else BOOLEAN SafeThunkCall; BOOLEAN BooleanSpare[3 ];#endif } TEB, * PTEB;typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; WORD LoadCount; WORD TlsIndex; union { LIST_ENTRY HashLinks; struct { PVOID SectionPointer; ULONG CheckSum; }; }; union { ULONG TimeDateStamp; PVOID LoadedImports; }; PACTIVATION_CONTEXT EntryPointActivationContext; PVOID PatchInformation; LIST_ENTRY ForwarderLinks; LIST_ENTRY ServiceTagLinks; LIST_ENTRY StaticLinks; } LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;typedef struct _OBJECT_ATTRIBUTES { ULONG Length; PVOID RootDirectory; PUNICODE_STRING ObjectName; ULONG Attributes; PVOID SecurityDescriptor; PVOID SecurityQualityOfService; } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;typedef struct _INITIAL_TEB { PVOID StackBase; PVOID StackLimit; PVOID StackCommit; PVOID StackCommitMax; PVOID StackReserved; } INITIAL_TEB, * PINITIAL_TEB;
打印当前进程的FullDllName,也就是模块路径+名称。至于这里为什么要减去0x10字节呢,仔细看上面结构体的布局,因为LDR_MODULE结构体在内存中的首地址是有0x10的偏移的(双向链表连接的是InInitializationOrderModuleList字段而不是LDR_MODULE结构体)
#include <iostream> #include "PEB.h" #include <Windows.h> #include <stdio.h> #include <TLHELP32.H> int main () { PPEB Peb = (PPEB)__readgsqword(0x60 ); PLDR_MODULE pLoadModule; pLoadModule = (PLDR_MODULE)((PBYTE)Peb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 0x10 ); printf ("%ws\r\n" , pLoadModule->FullDllName.Buffer); }
上述代码中,我们首先通过位于0x60的指向GS寄存器的指针检索到当前进程的PEB,我们访问LDR结构体,并且并向前链接到第二个内存顺序模块
这里我们可以用windbg具体查看一下可执行文件里面的结构体到底是怎能排布的。
首先查看当前进程的teb,可以看到在0x60偏移处指向的就是PEB。
找到peb内存起始地址,
查看PEB结构体内容,找到_PEB_LDR_DATA。
上面已经讲到在_PEB_LDR_DATA
中存在三个双向链表,从而找到InMemoryOrderModuleList
这个模块初始化加载顺序链表。
现在就能从InMemoryOrderModuleList
这个链表的Flink
指针找到依次加载到内存中的模块信息。这里显示的是当前PEB进程块也就是正在调试的模块信息,
大多数情况下NTDLL
模块会是第二个内存模块,kernel32dll
将会是第三个内存模块
最后打印的结果就是NTDLL模块FULLNAME.
在这里我们可以通过如下程序(代码参考crispr学长)来遍历进程所有在内存中加载过的模块以及基址:
#include <iostream> #include "peb.h" int main() { PPEB Peb = (PPEB)__readgsqword(0 x60); PLDR_MODULE pLoadModule; pLoadModule = (PLDR_MODULE)((PBYTE)Peb-> LoaderData -> InMemoryOrderModuleList.Flink - 0 x10); PLDR_MODULE pFirstLoadModule = (PLDR_MODULE)((PBYTE)Peb-> LoaderData -> InMemoryOrderModuleList.Flink - 0 x10); do { printf ("Module Name:%ws\r\nModule Base Address:%p\r\n\r\n", pLoadModule-> FullDllName .Buffer,pLoadModule-> BaseAddress); pLoadModule = (PLDR_MODULE)((PBYTE)pLoadModule-> InMemoryOrderModuleList.Flink - 0 x10); } while ((PLDR_MODULE)((PBYTE)pLoadModule-> InMemoryOrderModuleList.Flink -0 x10) != pFirstLoadModule); }
这里给出我用rust写的一个代码,主要为了后续想用rust写一个工具出来,实现的功能是一样的。rust在写windows这块相较于c++难写很多,一些api调用很麻烦这里不建议使用Windows_sys依赖,最好自己定义。
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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 use std::mem::size_of;use std::slice;use ntapi::ntldr::LDR_DATA_TABLE_ENTRY;use ntapi::ntpebteb::{*};use ntapi::ntpsapi::{PPEB_LDR_DATA,GDI_HANDLE_BUFFER};use ntapi::ntrtl::PRTL_USER_PROCESS_PARAMETERS;use winapi::shared::basetsd::{SIZE_T, ULONG_PTR};use winapi::shared::minwindef::{USHORT, PBYTE};use winapi::um::winnt::{PVOID, HANDLE, PRTL_CRITICAL_SECTION, PSLIST_HEADER, ULARGE_INTEGER, FLS_MAXIMUM_AVAILABLE};use winapi::shared::ntdef::{UNICODE_STRING,ULONG, BOOLEAN, CHAR, ULONGLONG};use ntapi::winapi_local::um::winnt::{__readgsqword};#[repr(C)] pub struct LIST_ENTRY { pub Flink: *mut LIST_ENTRY, pub Blink: *mut LIST_ENTRY, }#[repr(C)] pub struct LDR_MODULE { InLoadOrderModuleList: LIST_ENTRY, InMemoryOrderModuleList: LIST_ENTRY, InInitializationOrderModuleList: LIST_ENTRY, BaseAddress: PVOID, EntryPoint: PVOID, SizeOfImage: ULONG, FullDllName: UNICODE_STRING, BaseDllName: UNICODE_STRING, Flags: ULONG, LoadCount: USHORT, TlsIndex: USHORT, HashLinks: LIST_ENTRY, TimeDateStamp: ULONG, }#[repr(C)] pub struct PEB { InheritedAddressSpace: BOOLEAN, ReadImageFileExecOptions: BOOLEAN, BeingDebugged: BOOLEAN, BitField: BOOLEAN, Mutant: HANDLE, ImageBaseAddress: PVOID, Ldr: PPEB_LDR_DATA, ProcessParameters: PRTL_USER_PROCESS_PARAMETERS, SubSystemData: PVOID, ProcessHeap: PVOID, FastPebLock: PRTL_CRITICAL_SECTION, IFEOKey: PVOID, AtlThunkSListPtr: PSLIST_HEADER, CrossProcessFlags: ULONG, u: PEB_u, SystemReserved: [ULONG; 1 ], AtlThunkSListPtr32: ULONG, ApiSetMap: PAPI_SET_NAMESPACE, TlsExpansionCounter: ULONG, TlsBitmap: PVOID, TlsBitmapBits: [ULONG; 2 ], ReadOnlySharedMemoryBase: PVOID, SharedData: PVOID, ReadOnlyStaticServerData: *mut PVOID, AnsiCodePageData: PVOID, OemCodePageData: PVOID, UnicodeCaseTableData: PVOID, NumberOfProcessors: ULONG, NtGlobalFlag: ULONG, CriticalSectionTimeout: ULARGE_INTEGER, HeapSegmentReserve: SIZE_T, HeapSegmentCommit: SIZE_T, HeapDeCommitTotalFreeThreshold: SIZE_T, HeapDeCommitFreeBlockThreshold: SIZE_T, NumberOfHeaps: ULONG, MaximumNumberOfHeaps: ULONG, ProcessHeaps: *mut PVOID, GdiSharedHandleTable: PVOID, ProcessStarterHelper: PVOID, GdiDCAttributeList: ULONG, LoaderLock: PRTL_CRITICAL_SECTION, OSMajorVersion: ULONG, OSMinorVersion: ULONG, OSBuildNumber: USHORT, OSCSDVersion: USHORT, OSPlatformId: ULONG, ImageSubsystem: ULONG, ImageSubsystemMajorVersion: ULONG, ImageSubsystemMinorVersion: ULONG, ActiveProcessAffinityMask: ULONG_PTR, GdiHandleBuffer: GDI_HANDLE_BUFFER, PostProcessInitRoutine: PVOID, TlsExpansionBitmap: PVOID, TlsExpansionBitmapBits: [ULONG; 32 ], SessionId: ULONG, AppCompatFlags: ULARGE_INTEGER, AppCompatFlagsUser: ULARGE_INTEGER, pShimData: PVOID, AppCompatInfo: PVOID, CSDVersion: UNICODE_STRING, ActivationContextData: PVOID, ProcessAssemblyStorageMap: PVOID, SystemDefaultActivationContextData: PVOID, SystemAssemblyStorageMap: PVOID, MinimumStackCommit: SIZE_T, FlsCallback: *mut PVOID, FlsListHead: LIST_ENTRY, FlsBitmap: PVOID, FlsBitmapBits: [ULONG; FLS_MAXIMUM_AVAILABLE as usize / (size_of::<ULONG>() * 8 )], FlsHighIndex: ULONG, WerRegistrationData: PVOID, WerShipAssertPtr: PVOID, pUnused: PVOID, pImageHeaderHash: PVOID, TracingFlags: ULONG, CsrServerReadOnlySharedMemoryBase: ULONGLONG, TppWorkerpListLock: PRTL_CRITICAL_SECTION, TppWorkerpList: LIST_ENTRY, WaitOnAddressHashTable: [PVOID; 128 ], TelemetryCoverageHeader: PVOID, CloudFileFlags: ULONG, CloudFileDiagFlags: ULONG, PlaceholderCompatibilityMode: CHAR, PlaceholderCompatibilityModeReserved: [CHAR; 7 ], LeapSecondData: *mut LEAP_SECOND_DATA, LeapSecondFlags: ULONG, NtGlobalFlag2: ULONG, }fn main () { unsafe { let peb = __readgsqword(0x60 ) as *mut PEB; let p_first_load_module = ((*peb).Ldr.as_ref().unwrap().InMemoryOrderModuleList.Flink as PBYTE).offset(-0x10 ) as *const LDR_MODULE ; let mut p_load_module = ((*peb).Ldr.as_ref().unwrap().InMemoryOrderModuleList.Flink as PBYTE).offset(-0x10 ) as *const LDR_MODULE ; loop { let module_base = p_load_module as *const LDR_DATA_TABLE_ENTRY; let module_name = (*module_base).FullDllName.Buffer as *const UNICODE_STRING; let slice = slice::from_raw_parts((*module_base).FullDllName.Buffer,(*module_base).FullDllName.Length as usize / 2 ); let string = String ::from_utf16_lossy(slice); println! ("Module Name: {:?}" , string.trim()); println! ("Module Base Address: {:?} \r\n" , module_base); p_load_module = ((*p_load_module).InMemoryOrderModuleList.Flink as PBYTE).offset(-0x10 ) as *const LDR_MODULE ; if p_first_load_module as * const LDR_MODULE == ((*p_load_module).InMemoryOrderModuleList.Flink as PBYTE).offset(-0x10 ) as *const LDR_MODULE{ break ; } } } }
掌握这一点后现在我们知道如何获取内存模块的基址,因此我们有能力遍历模块的导出地址表,这就涉及到通过该基址去遍历PE头文件从而获取导出地址表,可以将其分为四个步骤: – 1.获取每个模块的基地址 – 2.获取_IMAGE_DOS_HEADER,并通过检查IMAGE_DOS_SIGNATURE来验证正确性 – 3.遍历_IMAGE_NT_HEADER、_IMAGE_FILE_HEADER、_IMAGE_OPTIONAL_HEADER – 4.在_IMAGE_OPTIONAL_HEADER中找到导出地址表,并将类型转为_IMAGE_EXPORT_DIRECTORY
关于PE文件头的详细数据结构,如下:
typedef struct _IMAGE_NT_HEADERS { DWORD Signature ; //PE 文件头标志 => 4字节 IMAGE_FILE_HEADER FileHeader ; //标准PE 头 => 20字节 IMAGE_OPTIONAL_HEADER32 OptionalHeader ; //扩展PE 头 => 32位下224字节(0xE0) 64位下240字节(0xF0) } IMAGE_NT_HEADERS32 , *PIMAGE_NT_HEADERS32 ;
在_IMAGE_DOS_HEADER
文件头中存在_IMAGE_OPTIONAL_HEADER
。DataDirectory是可选映像头_IMAGE_OPTIONAL_HEADER
的最后128个字节(16项 * 8 bytes),也是IMAGE_NT_HEADERS(PE文件头)的最后一部分数据。 它由16个IMAGE_DATA_DIRECTORY结构组成的数组构成,指向输出表、输入表、资源块、重定位 等数据目录项的RVA(相对虚拟地址)和大小。 IMAGE_DATA_DIRECTORY的结构如下:
// // Directory format.// typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // 数据块的起始RVA DWORD Size; // 数据块的长度 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
可以看到第一个数据就是EXPORT ADDRESS Table
导出地址表在内存中的相对虚拟地址。
然后将其转换为_IMAGE_EXPORT_DIRECTORY
类型,引出目录表IMAGE_EXPORT_DIRECTORY
的结构如下。这样我们就得到了内存中各种函数的地址。
所以总的来讲,当我们得到NT_Header时,由于PE头文件的数据结构已经给出,其前四个字节为一个DWORD类型的Signature,因此加上这四个字节就会得到FileHeader,然后在此基础上加上FileHeader数据结构所占大小最终得到Optional,只需要将其类型转为_IMAGE_EXPORT_DIRECTORY就得到了我们的导出地址表,再通过:
1.AddressOfNames 一个包含函数名称的数组
2.AddressOfNameOrdinals 充当函数寻址数组的索引
3.AddressOfFunctions 一个包含函数地址的数组 这三个数组结构就能获取到每个函数的地址和函数名称。
利用rust实现的代码
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 fn getPeHeader () -> i32 { unsafe { let peb = __readgsqword(0x60 ) as *mut PEB; let mut p_load_module = ((*peb).Ldr.as_ref().unwrap().InMemoryOrderModuleList.Flink as PBYTE).offset(-0x10 ) as *const LDR_MODULE ; p_load_module = ((*p_load_module).InMemoryOrderModuleList.Flink as PBYTE).offset(-0x10 ) as *const LDR_MODULE ; let base = (*p_load_module).BaseAddress as PVOID; let dos_header = transmute::<PVOID, PIMAGE_DOS_HEADER>(base) ; if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE{ return 1 ; } let nt_headers = transmute::<PVOID, PIMAGE_NT_HEADERS>(base.add((*dos_header).e_lfanew as usize )); let file_headers = transmute::<PVOID, PIMAGE_FILE_HEADER>(base.add((*dos_header).e_lfanew as usize + size_of::<u32 >())) ; let optional_headers = transmute::<PVOID, PIMAGE_OPTIONAL_HEADER>(base.add((*dos_header).e_lfanew as usize + size_of::<u32 >() + IMAGE_SIZEOF_FILE_HEADER)) as PIMAGE_OPTIONAL_HEADER; let export_directory = (*nt_headers).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize ]; if export_directory.Size == 0 { println! ("No export table" ); return 1 ; } let export_table = transmute::<PVOID, PIMAGE_EXPORT_DIRECTORY>(base.add(export_directory.VirtualAddress as usize )) ; let AddressOfNames = transmute::<PVOID, *const u32 >(base.add((*export_table).AddressOfNames as usize )) ; let NumberOfFunctions = (*export_table).NumberOfFunctions as usize ; let AddressOfFunctions = transmute::<PVOID, *const u32 >(base.add((*export_table).AddressOfFunctions as usize )); let AddressOfNameOrdinales = transmute::<PVOID, *const u32 >(base.add((*export_table).AddressOfFunctions as usize )); for i in 0 ..NumberOfFunctions-1 { let FunctionName = transmute::<PVOID, *const i8 >(base.add(*AddressOfNames.add(i) as usize )) ; let FunctionRvaName = transmute::<PVOID, *const i8 >(base.add(*AddressOfFunctions.add(i+1 ) as usize )) ; let FunctionAddress = CStr::from_ptr(FunctionRvaName); let FunctionNameString = ConstIntToString(FunctionName); println! ("{}: {:p}" , FunctionNameString, FunctionAddress); } return 0 ; }; }fn main () { getPeHeader(); }
地狱之门项目 这个项目应该算是最古老的syscall利用版本了,但也是学习syscall的必看之路。
原理:通过直接读取进程第二个导入模块即NtDLL,解析结构然后遍历导出表,根据函数名Hash找到函数地址,将这个函数读取出来通过0xb8这个操作码来动态获取对应的系统调用号,从而绕过内存监控,在自己程序中执行了NTDLL的导出函数而不是直接通过LoadLibrary然后GetProcAddress
先来看一个正常的syscall调用的汇编代码,在执行syscall之前,都会执行mov eax,xxx
赋值一个系统调用号(系统调用号被定义为WORD类型(16位无符号整数))给eax,从而直接在内核中执行函数。并且还通过test byte ptr [SharedUserData+0x308 (00000000
7ffe0308)],1 来验证当前的线程执行环境是x64还是x86,如果确定执行环境是基于x64则会通过syscall执行系统调用,否则会执行函数返回。
这是一个被上钩的NTDLL的汇编:ZwMapViewOfSection上的Hook是很明显的(jmp <offset>指令,而不是mov r10, rcx)。而ZwMapViewOfSection的邻居ZwSetInformationFile和NtAccessCheckAndAuditAlarm是干净的,它们的系统调用号分别是0x27和0x29。
项目代码分析 首先是实现了两个重要的结构体。在实现过程中需要定义一个与syscall相关联的数据结构:_VX_TABLE_ENTRY事实上每一个系统调用都需要分配这样一个结构,结构体定义如下:
typedef struct _VX_TABLE_ENTRY { PVOID pAddress; DWORD64 dwHash; WORD wSystemCall; } VX_TABLE_ENTRY , * PVX_TABLE_ENTRY ;
其中包括了指向内存模块的函数地址指针,一个函数哈希(后续通过Hash查找内存模块的函数)以及一个无符号16位的系统调用号wSysemCall 同时还定义了一个更大的数据结构_VX_TABLE用来包含每一个系统调用的函数:
typedef struct _VX_TABLE { VX_TABLE_ENTRY NtAllocateVirtualMemory ; VX_TABLE_ENTRY NtProtectVirtualMemory ; VX_TABLE_ENTRY NtCreateThreadEx ; VX_TABLE_ENTRY NtWaitForSingleObject ; } VX_TABLE , * PVX_TABLE ;
项目最开始还是使用__readgsqword
函数来获取TEB模块,如果是win64系统则从寄存器0x30偏移处获取TEB,否则从0X16.
接下来就是获取导出地址表,和我们获取到的方式是差不多的。
成功获取EAT指针之后现在就需要将之前定义的数据结构填充,通过GetVxTableEntry函数填充_VX_TABLE:
VX_TABLE Table = { 0 };Table .NtAllocateVirtualMemory.dwHash = 0 xf5 bd373480 a6 b89 b;GetVxTableEntry (ImageBase, ExportTable, &Table.NtAllocateVirtualMemory); Table .NtCreateThreadEx.dwHash = 0 x64 dc7 db288 c5015 f;GetVxTableEntry (ImageBase, ExportTable, &Table.NtCreateThreadEx);Table .NtProtectVirtualMemory.dwHash = 0 x858 bcb1046 fb6 a37 ;GetVxTableEntry (ImageBase, ExportTable, &Table.NtProtectVirtualMemory); Table .NtWaitForSingleObject.dwHash = 0 xc6 a2 fa174 e551 bcb;GetVxTableEntry (ImageBase, ExportTable, &Table.NtWaitForSingleObject);
下面就是最核心的操作了,就是定位进行syscall的操作符(0xb8即mov eax)的位置。在代码中,首先遍历导出地址表,通过djb2算法算出函数名的hash值,与我们想要的函数的hash值进行比较,如果相等,则填充函数地址。并且后面还要验证汇编代码0xb8即mov eax是否存在。 最后就是获取到系统调用号,因为系统调用号是一个WORD类型,也就是两个字节并且是小端存储,因此通过高低位转换的方式最终动态获得系统调用号填充到函数结构体pVxTableEntry中。
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 BOOL GetVxTableEntry (PVOID pModuleBase, PIMAGE_EXPORT_DIRECTORY pImageExportDirectory, PVX_TABLE_ENTRY pVxTableEntry) { PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfFunctions); PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfNames); PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfNameOrdinals); for (WORD cx = 0 ; cx < pImageExportDirectory->NumberOfNames; cx++) { PCHAR pczFunctionName = (PCHAR)((PBYTE)pModuleBase + pdwAddressOfNames[cx]); PVOID pFunctionAddress = (PBYTE)pModuleBase + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]]; if (djb2 (pczFunctionName) == pVxTableEntry->dwHash) { pVxTableEntry->pAddress = pFunctionAddress; WORD cw = 0 ; while (TRUE) { if (*((PBYTE)pFunctionAddress + cw) == 0x0f && *((PBYTE)pFunctionAddress + cw + 1 ) == 0x05 ) return FALSE; if (*((PBYTE)pFunctionAddress + cw) == 0xc3 ) return FALSE; if (*((PBYTE)pFunctionAddress + cw) == 0x4c && *((PBYTE)pFunctionAddress + 1 + cw) == 0x8b && *((PBYTE)pFunctionAddress + 2 + cw) == 0xd1 && *((PBYTE)pFunctionAddress + 3 + cw) == 0xb8 && *((PBYTE)pFunctionAddress + 6 + cw) == 0x00 && *((PBYTE)pFunctionAddress + 7 + cw) == 0x00 ) { BYTE high = *((PBYTE)pFunctionAddress + 5 + cw); BYTE low = *((PBYTE)pFunctionAddress + 4 + cw); pVxTableEntry->wSystemCall = (high << 8 ) | low; break ; } cw++; }; } } return TRUE; }
现在每个我们想要调用的函数都获取到了对应的函数hash,系统调用号,函数地址指针,都存放在函数结构体pVxTableEntry中,最后要做的就是生成一段汇编代码来调用这些函数,
最后生成的汇编代码如下。这样我们便能够模拟引入Ntdll调用Nt函数的方式,也就是调用Native API而不是Win API来绕过Hooks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 .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
光环之门 Halo’s Gate 这个项目其实是在解决地狱之门的一些局限性,从分析地狱之门的代码中我们可以看到它所访问内存中的NTDLL也必须是默认的或者说是未经修改的,因为如果本身NTDLL已经被修改过,或者被Hook过则函数汇编操作码就不会是0xb8,对应着mov eax,而可能是0xE9,对应着jmp。因此当我们需要调用的NT函数已经被AV/EDR所Hook,那我们就无法通过地狱之门来动态获取它的系统调用号。
就像上面这张图片一样,ZwMapViewOfSection已经被Hook,而它的邻函数ZwSetInformationFile和NtAccessCheckAndAuditAlarm都没有被Hook,并且邻函数的系统调用号也是临接的。所以说其实我们可以查看被hook函数的邻函数,查看邻函数的系统调用号,然后再相应的加减调整即可。
正常的没有被hook的函数应该是长这个样子。
当出现被Hook的Nt函数时便采取向周围查询的方式:
GetSSN 这里还介绍一种更加方便简单和迅速的方法来发现SSN(syscall number),这种方法不需要unhook,不需要手动从代码存根中读取,也不需要加载NTDLL新副本,可以将它理解成为光环之门的延伸,试想当上下的邻函数都被Hook时,光环之门的做法是继续递归,在不断的寻找没有被Hook的邻函数,而在这里假设一种最坏的情况是所有的邻函数(指Nt*函数)都被Hook时,那最后将会向上递归到SSN=0的Nt函数。
其实可以理解为系统调用的存根重新实现 + 动态 SSN 解析
首先我们需要知道:
1.实际上所有的Zw函数和Nt同名函数实际上是等价的
2.系统调用号实际上是和Zw函数按照地址顺序的排列是一样的
因此我们就只需要遍历所有Zw函数,记录其函数名和函数地址,最后将其按照函数地址升序排列后,每个函数的SSN就是其对应的排列顺序
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 void GetSSN () { std::map<int , string> Nt_Table; PBYTE ImageBase; PIMAGE_DOS_HEADER Dos = NULL ; PIMAGE_NT_HEADERS Nt = NULL ; PIMAGE_FILE_HEADER File = NULL ; PIMAGE_OPTIONAL_HEADER Optional = NULL ; PIMAGE_EXPORT_DIRECTORY ExportTable = NULL ; PPEB Peb = (PPEB)__readgsqword(0x60 ); PLDR_MODULE pLoadModule; pLoadModule = (PLDR_MODULE)((PBYTE)Peb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 0x10 ); ImageBase = (PBYTE)pLoadModule->BaseAddress; Dos = (PIMAGE_DOS_HEADER)ImageBase; if (Dos->e_magic != IMAGE_DOS_SIGNATURE) return 1 ; Nt = (PIMAGE_NT_HEADERS)((PBYTE)Dos + Dos->e_lfanew); File = (PIMAGE_FILE_HEADER)(ImageBase + (Dos->e_lfanew + sizeof (DWORD))); Optional = (PIMAGE_OPTIONAL_HEADER)((PBYTE)File + sizeof (IMAGE_FILE_HEADER)); ExportTable = (PIMAGE_EXPORT_DIRECTORY)(ImageBase + Optional->DataDirectory[0 ].VirtualAddress); PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)(ImageBase + ExportTable->AddressOfFunctions)); PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)ImageBase + ExportTable->AddressOfNames); PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)ImageBase + ExportTable->AddressOfNameOrdinals); for (WORD cx = 0 ; cx < ExportTable->NumberOfNames; cx++) { PCHAR pczFunctionName = (PCHAR)((PBYTE)ImageBase + pdwAddressOfNames[cx]); PVOID pFunctionAddress = (PBYTE)ImageBase + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]]; if (strncmp ((char *)pczFunctionName, "Zw" ,2 ) == 0 ) { Nt_Table[(int )pFunctionAddress] = (string)pczFunctionName; } } int index = 0 ; for (std::map<int , string>::iterator iter = Nt_Table.begin (); iter != Nt_Table.end (); ++iter) { cout << "index:" << index << ' ' << iter->second << endl; index += 1 ; } }
https://blog.csdn.net/weixin_43655282/article/details/104291312 https://bbs.kanxue.com/thread-151456.htm https://www.dailychina.news/showArticle?main_id=a1057a0c93a81ba5960abe906c76377e https://www.anquanke.com/post/id/267345#h3-7 https://tttang.com/archive/1464/#toc_hells-gate https://xz.aliyun.com/t/11496#toc-3 https://www.kn0sky.com/?p=69 https://bbs.kanxue.com/thread-266678.htm