反调试

旧文迁移,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,//指向EXCEPTION_RECORD,见下面
void * EstablisherFrame, //SHE链起始位
struct _CONTEXT *ContextRecord, //指向context,具体可查看《Windows基础》那篇 void * DispatcherContext);
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 //指向_EXCEPTION_REGISTRATION_RECORD链表,用于SEH
+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 //32位
+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插件直接把它破了,于是没我的事了,那就了解一下吧)

NtQueryInformationProcess()

此函数结构:

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比较,相等就输出未调试,下面的也一样就不继续了。

NtQuerySystemInformation()

定义如下:

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
);

当调试某个进程时,会创建一个调试对象,使用此函数可以获取所有对象,然后再遍历判断是否有调试对象就 。。。

ZwSetInformationThread()

它的定义如下:

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()

基于时间

1
2
timeGetTime()
_ftime()

书上教的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处定时调用,若在关键区域打断点将会影响程序执行(自动中断)。

参考

《逆向工程核心原理》
《加密与解密-第三版》