BetaMao

反调试

字数统计: 5.7k阅读时长: 23 min
2017/03/20 Share

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

参考

《逆向工程核心原理》
《加密与解密-第三版》
CATALOG
  1. 1. 基础
    1. 1.1. TLS
      1. 1.1.1. 调试程序
      2. 1.1.2. 手动添加回调函数:
    2. 1.2. SEH
    3. 1.3. TEB
    4. 1.4. PEB
  2. 2. 静态反调试
    1. 2.1. PEB
    2. 2.2. NtQueryInformationProcess()
    3. 2.3. NtQuerySystemInformation()
    4. 2.4. NtQueryObject()
    5. 2.5. ZwSetInformationThread()
    6. 2.6. TLS:
    7. 2.7. ETC:
  3. 3. 动态反调试
    1. 3.1. SEH
    2. 3.2. Timing Check
      1. 3.2.1. 基于计数器
      2. 3.2.2. 基于时间
    3. 3.3. 陷阱标志
      1. 3.3.1. 单步执行
      2. 3.3.2. INT 2D
      3. 3.3.3. 0XCC探测
        1. 3.3.3.1. API断点
        2. 3.3.3.2. 比较校验和
        3. 3.3.3.3. 垃圾代码
        4. 3.3.3.4. 扰乱代码对齐
    4. 3.4. 加密/解密
    5. 3.5. Stolen Bytes
    6. 3.6. API重定向
    7. 3.7. Debug Blocker
    8. 3.8. Nanomitite
    9. 3.9. 计时器
  4. 4. 参考