旧文迁移,TLS、SEH、ETC等静态与动态反调试~
基础
TLS
即线程局部存储,是个线程的独立的数据存储空间,使用TLS技术可在线程内部独立使用或修改进程的全局数据或静态数据,就像是对待自身的局部变量一样,而TLS回调函数会在线程创建或终止时被自动调用执行,且开始时先于EP代码执行。一个程序可以注册多个TLS回调函数,他们在PE文件头中被索引:
转到RVA9310->RAW7910处:
其中重要的是AddressOfCallBacks,指向回调函数数组VA408114->RAW6714:
这里只有一个回调函数咯,它的定义为:
1 2 3 4 5 6
| typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK) ( PVOID DllHandle, DWORD Reason, PVOID Reserved );
|
它和DllMain()
差不多,都是系统调用,参数什么的都差不多:
1 2 3 4
| #define DLL_PROCESS_ATTACH 1 #define DLL_THREAD_ATTACH 2 #define DLL_THREAD_DETACH 3 #define DLL_PROCESS_DETACH 0
|
回调函数写好后,需要向链接器指定此为回调函数,即创建TLS目录与TLS段!
调试程序
为了实现期望的效果需要去掉StrongOD插件的第一个功能:
然后开始调试:
。。。(无语)效果,然后设置下调试选项,将它设置为第一次暂停在系统断点处:
Go!………………………………….这些插件太厉害了,懵逼了半天,在使用主模块入口点时,就停在了回调函数位置:
我也不知道该说什么好了,那么套路就是从上面的PE文件头可以看出回调函数的地址为401000H,那么在这里下一个断点就好了,从这里开始调试!
手动添加回调函数:
找位置:
增加最后一个节区大小
添加到节区末尾的空白区域
添加新的节区
明显的第二种方法最简单,but作者为了让我们练习其他方法,这里使用第一种方法,
必须佩服作者了,最后一个节区刚好占满,那么改吧添加200HByte上去,然后添加执行属性:
这里虚拟大小没有变,因为增加后还是不足对齐的1000,至于添加写属性,是为了等下在od里面写代码。然后改文件大小,使用填零512(200H)字节:
接着在TLS数据目录中写出TLS表的偏移与大小,接着创建一个IMAGE_TLS_DIRCTORY结构体,在那里面写出TLS回调函数数组首地址,再在那个数组里面写回调函数的地址,即可,最后要写回调函数,就在od里面写,然后保存即可:
SEH
SEH(Structured Exception Handling)是操作系统默认的异常处理机制,在软件漏洞和反调试里等都占据着十分重要的地位,下面先简单介绍这个鬼:
就是异常处理,try-catch这种,只是他和语言或者说编译器封装的异常处理还是有点不一样的,回忆一下刚学的Java,当异常发生时,会检查当前是否可以有处理语句,要是不能处理就往上抛出,直到抛出到虚拟机还没被处理就会报错,此处的这种异常处理也是类似的,当异常发生时,会挨个检查异常处理函数(等价于catch语句块内容),直到异常被处理为止!具体内容请戳我,写的很详细,我这里就不多记录了,放几个结构体与逻辑图:
Next|Handler结构:
1 2 3 4
| 0:000> dt _exception_registration_record ntdll!_EXCEPTION_REGISTRATION_RECORD + 0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD + 0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION
|
异常处理函数定义:
1 2 3 4 5 6 7 8 9 10 11
| EXCEPTION_DISPOSITION __cdecl _except_handler(struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD;
|
SEH是基于线程的,故通过上图的方式可以找到它,另一个角度,它是基于堆栈的,所以安装和卸载也是使用堆栈:
1 2 3 4 5 6
| push @myhandler push FS:[0] mov FS:[0],ESP mov eax,[ESP] mov FS:[0], EAX add esp, 8
|
这里可能有点难以理解,其实画个图会好很多
至于在SEH中,还记得这个异常处理回调函数的第三个参数是指向CONTEXT结构体的指针,他能传入异常处寄存器的信息,我们可以对其进行修改,当然,我们还能做更多的事,具体在反调试部分记录,现在先来调试下随书给的软件:
它是C写的,很容易到main函数,这里先安装了一个异常处理函数,然后对0H处进行赋值,这是空指针赋值分区,没有被分配不能直接使用,于是会抛出异常,所以第一次调试会很奇怪怎么突然就跳到了陌生的区域(说突然是因为od插件导致没有任何提示。。。),继续往下看,本来按照逻辑软件应该弹出Debbugger detected :(,但是正常执行趋势弹出Hello说明异常处理函数改变了EIP的值!
进入异常处理函数进行分析:
手动跳转到异常处理函数去看看吧:
根据上面的代码,可以分析下此时的堆栈数据:
SS:[ESP+C]为[12FCA8]即CONTEXT起始位置,FS:[30]为PEB的起始地址,再加2指向Bedebugged这个变量,然后将之与1比较判断是否处于调试状态,接着将会有两个分支,分析一下,ESI是CONTEXT起始,回顾CONTEXT结构体,DS:[ESI+B8]就是EIP,那么这里就是根据是否处于调试状态将EIP修改为了不同的值,接着XOR EAX,EAX 将之置为0,它作为返回代表继续执行异常代码,于是调试与不调试结果就会不同!最后,终于到了反调试部分了,这里将会详细记录它在反调试的用途。
TEB
线程环境块,这个结构体非常复杂,而且在不同的系统,包括不同位数,内容都不一样!目前只需要关注前两个结构:
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
| 0:000> dt teb ole32!TEB +0x000 NtTib : _NT_TIB +0x038 EnvironmentPointer : Ptr64 Void +0x040 ClientId : _CLIENT_ID +0x050 ActiveRpcHandle : Ptr64 Void +0x058 ThreadLocalStoragePointer : Ptr64 Void +0x060 ProcessEnvironmentBlock : Ptr64 _PEB +0x068 LastErrorValue : Uint4B +0x06c CountOfOwnedCriticalSections : Uint4B +0x070 CsrClientThread : Ptr64 Void +0x078 Win32ThreadInfo : Ptr64 Void +0x080 User32Reserved : [26] Uint4B +0x0e8 UserReserved : [5] Uint4B +0x100 WOW32Reserved : Ptr64 Void +0x108 CurrentLocale : Uint4B +0x10c FpSoftwareStatusRegister : Uint4B +0x110 SystemReserved1 : [54] Ptr64 Void +0x2c0 ExceptionCode : Int4B +0x2c8 ActivationContextStackPointer : Ptr64 _ACTIVATION_CONTEXT_STACK +0x2d0 SpareBytes : [24] UChar +0x2e8 TxFsContext : Uint4B +0x2f0 GdiTebBatch : _GDI_TEB_BATCH +0x7d8 RealClientId : _CLIENT_ID +0x7e8 GdiCachedProcessHandle : Ptr64 Void +0x7f0 GdiClientPID : Uint4B +0x7f4 GdiClientTID : Uint4B +0x7f8 GdiThreadLocalInfo : Ptr64 Void +0x800 Win32ClientInfo : [62] Uint8B +0x9f0 glDispatchTable : [233] Ptr64 Void +0x1138 glReserved1 : [29] Uint8B +0x1220 glReserved2 : Ptr64 Void +0x1228 glSectionInfo : Ptr64 Void +0x1230 glSection : Ptr64 Void +0x1238 glTable : Ptr64 Void +0x1240 glCurrentRC : Ptr64 Void +0x1248 glContext : Ptr64 Void +0x1250 LastStatusValue : Uint4B +0x1258 StaticUnicodeString : _UNICODE_STRING +0x1268 StaticUnicodeBuffer : [261] Wchar +0x1478 DeallocationStack : Ptr64 Void +0x1480 TlsSlots : [64] Ptr64 Void +0x1680 TlsLinks : _LIST_ENTRY +0x1690 Vdm : Ptr64 Void +0x1698 ReservedForNtRpc : Ptr64 Void +0x16a0 DbgSsReserved : [2] Ptr64 Void +0x16b0 HardErrorMode : Uint4B +0x16b8 Instrumentation : [11] Ptr64 Void +0x1710 ActivityId : _GUID +0x1720 SubProcessTag : Ptr64 Void +0x1728 EtwLocalData : Ptr64 Void +0x1730 EtwTraceData : Ptr64 Void +0x1738 WinSockData : Ptr64 Void +0x1740 GdiBatchCount : Uint4B +0x1744 CurrentIdealProcessor : _PROCESSOR_NUMBER +0x1744 IdealProcessorValue : Uint4B +0x1744 ReservedPad0 : UChar +0x1745 ReservedPad1 : UChar +0x1746 ReservedPad2 : UChar +0x1747 IdealProcessor : UChar +0x1748 GuaranteedStackBytes : Uint4B +0x1750 ReservedForPerf : Ptr64 Void +0x1758 ReservedForOle : Ptr64 Void +0x1760 WaitingOnLoaderLock : Uint4B +0x1768 SavedPriorityState : Ptr64 Void +0x1770 SoftPatchPtr1 : Uint8B +0x1778 ThreadPoolData : Ptr64 Void +0x1780 TlsExpansionSlots : Ptr64 Ptr64 Void +0x1788 DeallocationBStore : Ptr64 Void +0x1790 BStoreLimit : Ptr64 Void +0x1798 MuiGeneration : Uint4B +0x179c IsImpersonating : Uint4B +0x17a0 NlsCache : Ptr64 Void +0x17a8 pShimData : Ptr64 Void +0x17b0 HeapVirtualAffinity : Uint4B +0x17b8 CurrentTransactionHandle : Ptr64 Void +0x17c0 ActiveFrame : Ptr64 _TEB_ACTIVE_FRAME +0x17c8 FlsData : Ptr64 Void +0x17d0 PreferredLanguages : Ptr64 Void +0x17d8 UserPrefLanguages : Ptr64 Void +0x17e0 MergedPrefLanguages : Ptr64 Void +0x17e8 MuiImpersonation : Uint4B +0x17ec CrossTebFlags : Uint2B +0x17ec SpareCrossTebBits : Pos 0, 16 Bits +0x17ee SameTebFlags : Uint2B +0x17ee SafeThunkCall : Pos 0, 1 Bit +0x17ee InDebugPrint : Pos 1, 1 Bit +0x17ee HasFiberData : Pos 2, 1 Bit +0x17ee SkipThreadAttach : Pos 3, 1 Bit +0x17ee WerInShipAssertCode : Pos 4, 1 Bit +0x17ee RanProcessInit : Pos 5, 1 Bit +0x17ee ClonedThread : Pos 6, 1 Bit +0x17ee SuppressDebugMsg : Pos 7, 1 Bit +0x17ee DisableUserStackWalk : Pos 8, 1 Bit +0x17ee RtlExceptionAttached : Pos 9, 1 Bit +0x17ee InitialThread : Pos 10, 1 Bit +0x17ee SpareSameTebBits : Pos 11, 5 Bits +0x17f0 TxnScopeEnterCallback : Ptr64 Void +0x17f8 TxnScopeExitCallback : Ptr64 Void +0x1800 TxnScopeContext : Ptr64 Void +0x1808 LockCount : Uint4B +0x180c SpareUlong0 : Uint4B +0x1810 ResourceRetValue : Ptr64 Void
|
第一个是线程信息块TIB,结构如下:
1 2 3 4 5 6 7 8 9 10
| 0:000> dt _nt_tib ntdll!_NT_TIB +0x000 ExceptionList : Ptr64 _EXCEPTION_REGISTRATION_RECORD +0x008 StackBase : Ptr64 Void +0x010 StackLimit : Ptr64 Void +0x018 SubSystemTib : Ptr64 Void +0x020 FiberData : Ptr64 Void +0x020 Version : Uint4B +0x028 ArbitraryUserPointer : Ptr64 Void +0x030 Self : Ptr64 _NT_TIB
|
第二个是进程环境块PEB,下一篇记录。
用户模式下获取TEB地址就是使用fs:[0x18]:
书上的公式:
1 2 3
| FS:[0X18] = TEB.NtTib.Self = address of TIB = address of TEB = FS:0 FS:[0X30] = TEB.Process.EnvironmentBlock = address of PEB FS:[0] = TEB.NtTib.ExceptionList = address of SEH
|
在用户态下FS指向的区段保存着TEB,FS:[0]是FS:0地址处的值。这样就会好理解一些,并且,第二个公式在Win7 64位上应该是FS:[60] 扩展内容
PEB
64位的:
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
| 0:000> dt peb ole32!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 IsLegacyProcess : Pos 2, 1 Bit +0x003 IsImageDynamicallyRelocated : Pos 3, 1 Bit +0x003 SkipPatchingUser32Forwarders : Pos 4, 1 Bit +0x003 SpareBits : Pos 5, 3 Bits +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 Void +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 ReservedBits0 : Pos 5, 27 Bits +0x058 KernelCallbackTable : Ptr64 Void +0x058 UserSharedInfoPtr : Ptr64 Void +0x060 SystemReserved : [1] Uint4B +0x064 AtlThunkSListPtr32 : Uint4B +0x068 ApiSetMap : Ptr64 Void +0x070 TlsExpansionCounter : Uint4B +0x078 TlsBitmap : Ptr64 Void +0x080 TlsBitmapBits : [2] Uint4B +0x088 ReadOnlySharedMemoryBase : Ptr64 Void +0x090 HotpatchInformation : 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 +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 +0x138 ActiveProcessAffinityMask : Uint8B +0x140 GdiHandleBuffer : [60] Uint4B +0x230 PostProcessInitRoutine : Ptr64 void +0x238 TlsExpansionBitmap : Ptr64 Void +0x240 TlsExpansionBitmapBits : [32] Uint4B +0x2c0 SessionId : Uint4B +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 FlsCallback : Ptr64 _FLS_CALLBACK_INFO +0x328 FlsListHead : _LIST_ENTRY +0x338 FlsBitmap : Ptr64 Void +0x340 FlsBitmapBits : [4] Uint4B +0x350 FlsHighIndex : Uint4B +0x358 WerRegistrationData : Ptr64 Void +0x360 WerShipAssertPtr : Ptr64 Void +0x368 pContextData : Ptr64 Void +0x370 pImageHeaderHash : Ptr64 Void +0x378 TracingFlags : Uint4B +0x378 HeapTracingEnabled : Pos 0, 1 Bit +0x378 CritSecTracingEnabled : Pos 1, 1 Bit +0x378 SpareTracingBits : Pos 2, 30 Bits
|
32位的:
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
| 0:000> dt _peb 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 IsLegacyProcess : Pos 2, 1 Bit +0x003 IsImageDynamicallyRelocated : Pos 3, 1 Bit +0x003 SkipPatchingUser32Forwarders : Pos 4, 1 Bit +0x003 SpareBits : Pos 5, 3 Bits +0x004 Mutant : Ptr32 Void +0x008 ImageBaseAddress : Ptr32 Void ################## +0x00c Ldr : Ptr32 _PEB_LDR_DATA ################# +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS +0x014 SubSystemData : Ptr32 Void +0x018 ProcessHeap : Ptr32 Void ##################### +0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION +0x020 AtlThunkSListPtr : Ptr32 Void +0x024 IFEOKey : Ptr32 Void +0x028 CrossProcessFlags : Uint4B +0x028 ProcessInJob : Pos 0, 1 Bit +0x028 ProcessInitializing : Pos 1, 1 Bit +0x028 ProcessUsingVEH : Pos 2, 1 Bit +0x028 ProcessUsingVCH : Pos 3, 1 Bit +0x028 ProcessUsingFTH : Pos 4, 1 Bit +0x028 ReservedBits0 : Pos 5, 27 Bits +0x02c KernelCallbackTable : Ptr32 Void +0x02c UserSharedInfoPtr : Ptr32 Void +0x030 SystemReserved : [1] Uint4B +0x034 AtlThunkSListPtr32 : Uint4B +0x038 ApiSetMap : Ptr32 Void +0x03c TlsExpansionCounter : Uint4B +0x040 TlsBitmap : Ptr32 Void +0x044 TlsBitmapBits : [2] Uint4B +0x04c ReadOnlySharedMemoryBase : Ptr32 Void +0x050 HotpatchInformation : Ptr32 Void +0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void +0x058 AnsiCodePageData : Ptr32 Void +0x05c OemCodePageData : Ptr32 Void +0x060 UnicodeCaseTableData : Ptr32 Void +0x064 NumberOfProcessors : Uint4B +0x068 NtGlobalFlag : Uint4B ####################### +0x070 CriticalSectionTimeout : _LARGE_INTEGER +0x078 HeapSegmentReserve : Uint4B +0x07c HeapSegmentCommit : Uint4B +0x080 HeapDeCommitTotalFreeThreshold : Uint4B +0x084 HeapDeCommitFreeBlockThreshold : Uint4B +0x088 NumberOfHeaps : Uint4B +0x08c MaximumNumberOfHeaps : Uint4B +0x090 ProcessHeaps : Ptr32 Ptr32 Void +0x094 GdiSharedHandleTable : Ptr32 Void +0x098 ProcessStarterHelper : Ptr32 Void +0x09c GdiDCAttributeList : Uint4B +0x0a0 LoaderLock : Ptr32 _RTL_CRITICAL_SECTION +0x0a4 OSMajorVersion : Uint4B +0x0a8 OSMinorVersion : Uint4B +0x0ac OSBuildNumber : Uint2B +0x0ae OSCSDVersion : Uint2B +0x0b0 OSPlatformId : Uint4B +0x0b4 ImageSubsystem : Uint4B +0x0b8 ImageSubsystemMajorVersion : Uint4B +0x0bc ImageSubsystemMinorVersion : Uint4B +0x0c0 ActiveProcessAffinityMask : Uint4B +0x0c4 GdiHandleBuffer : [34] Uint4B +0x14c PostProcessInitRoutine : Ptr32 void +0x150 TlsExpansionBitmap : Ptr32 Void +0x154 TlsExpansionBitmapBits : [32] Uint4B +0x1d4 SessionId : Uint4B +0x1d8 AppCompatFlags : _ULARGE_INTEGER +0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER +0x1e8 pShimData : Ptr32 Void +0x1ec AppCompatInfo : Ptr32 Void +0x1f0 CSDVersion : _UNICODE_STRING +0x1f8 ActivationContextData : Ptr32 _ACTIVATION_CONTEXT_DATA +0x1fc ProcessAssemblyStorageMap : Ptr32 _ASSEMBLY_STORAGE_MAP +0x200 SystemDefaultActivationContextData : Ptr32 _ACTIVATION_CONTEXT_DATA +0x204 SystemAssemblyStorageMap : Ptr32 _ASSEMBLY_STORAGE_MAP +0x208 MinimumStackCommit : Uint4B +0x20c FlsCallback : Ptr32 _FLS_CALLBACK_INFO +0x210 FlsListHead : _LIST_ENTRY +0x218 FlsBitmap : Ptr32 Void +0x21c FlsBitmapBits : [4] Uint4B +0x22c FlsHighIndex : Uint4B +0x230 WerRegistrationData : Ptr32 Void +0x234 WerShipAssertPtr : Ptr32 Void +0x238 pContextData : Ptr32 Void +0x23c pImageHeaderHash : Ptr32 Void +0x240 TracingFlags : Uint4B +0x240 HeapTracingEnabled : Pos 0, 1 Bit +0x240 CritSecTracingEnabled : Pos 1, 1 Bit +0x240 SpareTracingBits : Pos 2, 30 Bits
|
先到TEB:
转到PEB:
BeingDebugged:
上图由于插件原因第三位是0,否则应该为1.Kernel32!IsDebuggerPresent()就是返回此值。
ImageBaseAddress:
上图第8位的四字节,01000000H为加载基址,GetModuleHandle()就是返回它。
Ldr:
指向_peb_ldr_data结构体,它可以用来枚举Dll详情,包括加载基址等,很有用:
1 2 3 4 5 6 7 8 9 10 11
| 0:000> dt _peb_ldr_data ntdll!_PEB_LDR_DATA +0x000 Length : Uint4B +0x004 Initialized : UChar +0x008 SsHandle : Ptr32 Void +0x00c InLoadOrderModuleList : _LIST_ENTRY +0x014 InMemoryOrderModuleList : _LIST_ENTRY +0x01c InInitializationOrderModuleList : _LIST_ENTRY +0x024 EntryInProgress : Ptr32 Void +0x028 ShutdownInProgress : UChar +0x02c ShutdownThreadId : Ptr32 Void
|
主要就是那三个结构体,他们其实是双向链表,指向_LDR_DATA_TABLE_ENTRY结构,每个dll被加载到进程中都有一个_LDR_DATA_TABLE_ENTRY结构体,他们互相连接。。。
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
| 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; }; _ACTIVATION_CONTEXT * EntryPointActivationContext; PVOID PatchInformation; LIST_ENTRY ForwarderLinks; LIST_ENTRY ServiceTagLinks; LIST_ENTRY StaticLinks; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
|
详细讲解请戳我!
ProcessHeap&NtGlobalFlag:
在调试时会有特征,想到了0day安全里讲的,调试堆先用int 3 断点暂停再调试。
静态反调试
PEB
调试时,它里面有些值会和非调试状态有明显的不同(PEB = FS:[30]):
+0x002 BeingDebugged : UChar
调试状态它为1,IsDebbeggerPresent()可以获取这个值,直接改掉内存中的值就可以破解。
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
调试状态堆区未使用状态会全部填充0XFEEEEEEE,而Ldr指向的_PEB_LDR_DATA结构体存在于堆区,往下翻就可以判断了,破解方法就是全部填其他数据覆盖,也可以使用附加的方式。
(左下为调试状态,右下为非调试状态)
+0x018 ProcessHeap : Ptr32 Void
它指向HEAP结构体,那里面有两个值会在调试状态改变:
1 2 3 4 5 6 7 8
| 0:000> dt _heap ntdll!_HEAP +0x000 Entry : _HEAP_ENTRY +0x008 Signature : Uint4B +0x00c Flags : Uint4B +0x010 ForceFlags : Uint4B +0x014 VirtualMemoryThreshold : Uint4B …
|
ProcessHeap也可以通过GetProcessHeap()获取,在XP中,正常情况下,Flags和ForceFlags值是:
调试状态将会不一样:
破解方法依然是改内存中值即可。
+0x068 NtGlobalFlag : Uint4B
调试状态会被设置为0x70,改掉即可:
(书上还给了一个练习的例子,结果。。。不知道是哪个od插件直接把它破了,于是没我的事了,那就了解一下吧)
此函数结构:
1 2 3 4 5 6 7
| NTSTATUS WINAPI NtQueryInformationProcess( _In_ HANDLE ProcessHandle, _In_ PROCESSINFOCLASS ProcessInformationClass, _Out_ PVOID ProcessInformation, _In_ ULONG ProcessInformationLength, _Out_opt_ PULONG ReturnLength );
|
当第二个参数传入指定值,第三个参数将返回结果:
ProcessInformationClass |
调试状态(ProcessInformation) |
非调试状态(ProcessInformation) |
ProcessDebugPort(0x7) |
0xFFFFFFFF |
0 |
ProcessDebugObjectHandle(0x1E) |
调试对象 |
Null |
ProcessDebugFlags(0x1F) |
0 |
1 |
没有意外,书上给的例子又被od插件自动破解了。。。不过还是可以来看看它的流程的:
本来这里几个调用嵌套有点晕,不过看od的自动注释就一目了然咯,第二个参数是0x7,返回值与0比较,相等就输出未调试,下面的也一样就不继续了。
定义如下:
1 2 3 4 5 6
| NTSTATUS WINAPI NtQuerySystemInformation( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
|
当第一个参数传入SystemKernelDebuggerInformation(即0x23)时,第二个参数将会返回系统调试状态(是系统开启调试!bcdedit /debug on那个。。)
NtQueryObject()
定义如下:
1 2 3 4 5 6 7
| NTSTATUS ZwQueryObject( _In_opt_ HANDLE Handle, _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, _Out_opt_ PVOID ObjectInformation, _In_ ULONG ObjectInformationLength, _Out_opt_ PULONG ReturnLength );
|
当调试某个进程时,会创建一个调试对象,使用此函数可以获取所有对象,然后再遍历判断是否有调试对象就 。。。
它的定义如下:
1 2 3 4 5 6
| NTSTATUS NTAPI ZwSetInformationThread ( __in HANDLE ThreadHandle, __in THREADINFOCLASS ThreadInformationClass, __in_bcount(ThreadInformationLength) PVOID ThreadInformation, __in ULONG ThreadInformationLength );
|
当第二个参数为ThreadHideFromDebugger (0x11)时,被调试的进程将会被隐藏(分离),程序将会终止。
DebugActiveProcessStop()分离调试进程与被调试进程
TLS:
之前说道过,在程序运行前执行检查代码。
ETC:
即检查当前系统是否为调试专用系统:
1 2 3 4 5
| FindWindow() GetWindowText() CreateToolhelp32Snapshot() GetComputerName() GetCommandLine()
|
检查是否在虚拟机中
动态反调试
SEH
当程序抛出异常,若未处于调试状态,系统将接收异常并调用SEH处理异常,若处于调试状态,将会使用调试器处理异常,这将不再自动调用SEH,于是可以在程序中故意触发异常,并在SHE中修改寄存器保证程序正常执行,那么调试状态下寄存器的值不会被自动修改,程序将会非正常执行,就像下面这个:
主动触发异常,若是调试状态,将会运行到非正常地址(此处为非法的FFFFFFFFH),设置调试选项忽略此异常后,将会跳转到ntdll领空,在这里会做一些准备:
根据ExceptHandler结构,SS:[ESP+C]内容为CONTEXT起始地址,DS:[EAX+B8]即EIP,这里讲EIP的值改为401040,并返回0(即异常被处理)
接着移除了此SHE函数,并跳转到正常的(程序期待的)位置继续执行。
另一种方法是利用Last Exception Filter,当异常不能被处理时,将会一直调用SHE链直到最后一个SEH(即Last Exception Filter),最后一个SEH回调函数若未设置,将使用系统默认的处理函数(即终止程序),当然用户可以使用SetUnhandledExceptionFilter自行设置它(如调用转储内存的函数,以待分析异常原因),在调用Last Exception Filter之前,系统将会调用NtQueryInformationProcess判断是否处于调试状态,若处于调试状态将会把控制权交给调试器,否则才会执行Last Exception Filter,又可以做文章了:
这里挂载SHE回调函数的方法和前面不一样了,这里是直接调用API,将它放在了链尾,接着就触发了异常,仅仅忽略异常及在401000H处设置断点是不行的,因为过程中会判断是否处于调试状态,若处于调试状态根本不会运行到401000H!可以直接在UnhandledExceptionFilter或NtQueryInformationProcess设置断点,进行静态反反调试:
Timing Check
调试状态,存在中断等区域总执行时间会比正常长很多,可通过此判断是否处于调试状态(检查是否处于虚拟机也用到了这种方法)
基于计数器
1
| RDTSC>NtQueryPerformanceCounter()>GetTickCount()
|
基于时间
书上教的RDTSC,x86中有一个64位的TSC,保存时钟周期计数,RDTSC可将它读到EDX:EAX中:
上图,先读取TSC再循环接着再读TSC,先比较高位,若高位不同直接跳转到触发异常的代码处,若高位一样比较低位,低位有个阈值,差若大于FF FF FF则依然会执行到触发异常的代码处,否则执行中正常代码。破解就那样。。。
陷阱标志
单步执行
也是异常与SHE结合
置位陷阱标志后,执行下一条指令将会触发单步异常,上图中正常情况会调用安装的SE处理程序,这个SE处理程序值是单纯的改变了EIP的值,然后返回,到新的EIP值处执行,若调试器未忽略该异常,将会NOP处继续执行到非法地址。
INT 2D
一种调试时不会触发,正常运行才会触发的断点,在调试模式中,它的下一条指令第一字节将被忽略(可用来扰乱代码对齐),因此,用ollydbg单步执行时不会停止在下一条指令处,而是会一直运行直到遇到断点或程序退出。
如上图,正常正常运行到int 2d处将会触发异常执行SHE,调试时,单步执行会直接直接运行到程序终止,也看不出来是不是像书上说的忽略了下一字节,关于触发异常,逻辑和之前一样,至于破解方法就是手动触发异常(需要注意,这里的异常处理程序是通过更改EBP-4即本地变量来判断是否处于调试状态,若是更改EIP则需要在正确的地址处触发异常!)
0XCC探测
API断点
就是找0xCC数据,判断程序是否处于调试状态,但是CC可能不止出现在操作码里面,所以一般是检测API的首字节,破解方法就是别在首字节处下0xCC断点(如后移下断点或下硬件断点)
比较校验和
当对被校验的区域下软断点,计算校验和可以发现此区域被修改了,推出此程序正在被调试:
可以看出,若在计算过程中此区域被下cc断点,校验将会失败,于是判断出处于调试状态!
垃圾代码
大量无用的代码、、、
扰乱代码对齐
第一次做逆向就遇到的那个鬼东东,突然抓狂!!!!!!!!根本就不能好好分析了啊!
加密/解密
加解密很常见,先解码再执行,有的甚至会执行后再次加密,至于代码重组,除了变态就是变态。
Stolen Bytes
将部分源代码(主要是OEP代码)放在压缩器/保护器创建的内存区域,这样会使直接转储失败,并增加寻找OEP的复杂性(还有的会在运行完转移的代码后将其删除),啃不动了,这里就真心弄不下去了,太晕了。
API重定向
先将部分(或全部)主要的API代码复制到其他内存区域,然后分析要保护的目标进程代码,修改调用API的代码,从而使自身复制的API代码得以执行,这样即使在原API地址处设置断点也没用,且能反转储,书上的调不出来,扰乱对齐还动态解密。。。
Debug Blocker
调试模式下运行自身代码,为自我创建技术的演进形式(自我创建技术:子程序负责执行实际源代码,父进程负责创建子进程,修改内存,更改寄存器等),此方法中,不能只调试父进程,而由于子进程已经被父进程调试,不能再调试子进程,而且由于父进程能调试子进程,于是能优先处理进程异常,控制代码流程。
Nanomitite
Debug Blocker发展而来,将被保护的进程的的有条件跳转指令改成触发异常指令,它以被调试身份运行,在调试进程中维护一张表单记录原来的跳转信息,当运行到Jcc指令处,抛出异常,调试器接管进程,判断标志,修改EIP进行跳转。
计时器
寒假遇到的一道题,使用多线程,在关键API处定时调用,若在关键区域打断点将会影响程序执行(自动中断)。
参考
《逆向工程核心原理》
《加密与解密-第三版》