《逆向工程核心原理》学习笔记:高级逆向分析技术
Posted 思源湖的鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《逆向工程核心原理》学习笔记:高级逆向分析技术相关的知识,希望对你有一定的参考价值。
目录
前言
继续学习《逆向工程核心原理》,本篇笔记是第六部分:高级逆向分析技术,包括TLS、TEB、PEB、SEH和IA-32指令等内容
一、TLS回调函数
TLS(Thread Local Storage,线性局部存储)回调函数要先于EP代码执行,故可作为反调试技术
1、TLS简介
TLS是各线程独立的数据存储空间,可以修改进程的全局数据或静态数据
(1)IMAGE_DATA_DIRECTORY
PE头中会设置TLS Table项目,如下图所示
(2)IMAGE_TLS_DIRECTORY
比较重要的成员如下所示,指向含有TLS回调函数地址(VA)的数组(以NULL结尾)
2、TLS回调函数简介
TLS回调函数:创建/终止进程的线程时会自动调用执行的函数,调用先于EP的执行
typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK)(
PVOID DllHandle,
DWORD Reason,
PVOID Reserved
);
3、示例1:HelloTls.exe
源码如下:
//HelloTls.exe
#include <windows.h>
#pragma comment(linker, "/INCLUDE:__tls_used")
void NTAPI TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved)
if( IsDebuggerPresent() )
MessageBoxA(NULL, "Debugger Detected!", "TLS Callback", MB_OK);
ExitProcess(1);
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = TLS_CALLBACK, 0 ;
#pragma data_seg()
int main(void)
MessageBoxA(NULL, "Hello :)", "main()", MB_OK);
直接打开的弹窗如下:
扔进OD如下:
4、示例2:TlsTest.exe
源码如下:
//TlsTest.exe
#include <windows.h>
#pragma comment(linker, "/INCLUDE:__tls_used")
void print_console(char* szMsg)
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsoleA(hStdout, szMsg, strlen(szMsg), NULL, NULL);
void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
char szMsg[80] = 0,;
wsprintfA(szMsg, "TLS_CALLBACK1() : DllHandle = %X, Reason = %d\\n", DllHandle, Reason);
print_console(szMsg);
//TLS_CALLBACK2在main之前执行,此时Reason值为1(DLL_PROCESS_ATTACH)
void NTAPI TLS_CALLBACK2(PVOID DllHandle, DWORD Reason, PVOID Reserved)
char szMsg[80] = 0,;
wsprintfA(szMsg, "TLS_CALLBACK2() : DllHandle = %X, Reason = %d\\n", DllHandle, Reason);
print_console(szMsg);
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = TLS_CALLBACK1, TLS_CALLBACK2, 0 ;
#pragma data_seg()
//TLS回调函数全部执行完毕后,ThreadProc()线程函数开始调用执行,其执行完毕后Reason=3(DLL_THREAD_DETACH),TLS回调函数被调用执行
//ThreadProc()线程函数执行完毕后,一直等待线程终止的main()函数(主线程)也会终止,此时Reason=0(DLL_PROCESS_DETACH),TLS回调函数最后依次被调用执行
DWORD WINAPI ThreadProc(LPVOID lParam)
print_console("ThreadProc() start\\n");
print_console("ThreadProc() end\\n");
return 0;
//所有TLS回调函数完成调用后,main()函数开始调用执行
//创建用户线程(ThreadProc)前,TLS回调函数会被再次调用执行,此时Reason=2(DLL_THREAD_ATTACH)
//创建用户线程(ThreadProc)后终止,main()与ThreadProc()内部分别将函数开始/终止日志输出到控制台
int main(void)
HANDLE hThread = NULL;
print_console("main() start\\n");
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForSingleObject(hThread, 60*1000);
CloseHandle(hThread);
print_console("main() end\\n");
return 0;
运行结果如下:
调试,需要先如下设置:
然后便可以调试了
二、TEB
1、TEB简介
TEB(Thread Environment Block,线程环境块):包含运行线程的各种信息,每个线程对应一个TEB结构体
typedef struct _TEB
PVOID Reserved1[12];
PPEB ProcessEnvironmentBlock;
PVOID Reserved2[399];
BYTE Reserved3[1952];
PVOID TlsSlots[64];
BYTE Reserved4[8];
PVOID Reserved5[26];
PVOID ReservedForOle;
PVOID Reserved6[4];
PVOID TlsExpansionSlots;
TEB, *PTEB;
2个重要成员:
ProcessEnvironmentBlock
成员:指向PEB(Process Environment Block,进程环境块)结构体指针。PEB是进程环境块,每个进程对应1个PEB结构体_NT_TIB
结构体(TIB是Thread Information Block的简称,意为“线程信息块”)
+0x000 NtTib : _NT_TIB
...
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
_NT_TIB
结构体的定义如下所示:
typedef struct _NT_TIB //sizeof 1ch
00h struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; //SEH链入口
04h PVOID StackBase; //堆栈基址
08h PVOID StackLimit; //堆栈大小
0ch PVOID SubSystemTib;
union
PVOID FiberData;
10h DWORD Version;
;
14h PVOID ArbitraryUserPointer;
18h struct _NT_TIB *Self; //本NT_TIB结构自身的线性地址
NT_TIB;
typedef NT_TIB *PNT_TIB;
2、TEB访问方法
通过OS提供的相关API在用户模式下进行访问
Ntdll.NtCurrentTeb()
API用来返回当前线程的TEB结构体地址,与FS段寄存器所指的段内存的基址是一样的
- FS寄存器并非直接指向TEB结构体的地址,它持有SDT 的索引,而该索引持有实际TEB地址
- SDT位于内核区域,其地址存储在特殊的寄存器GDTR(Global Descriptior Table Register,全局描述符表寄存器)中
总结下就是:
- TEB起始地址=[SDT+FS]
- FS:[0x18]=TEB起始地址
- FS:[0x30]=PEB起始地址
- FS:[0]=SEH起始地址
三、PEB
1、PEB简介
PEB(Process Environment Block,进程环境块):存放进程信息的结构体,尺寸非常大,其大部分内容都已被文档化
typedef struct _PEB
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
PEB, *PPEB;
几个重要成员
+0x002 BeingDebugged : UChar
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
+0x018 ProcessHeap : Ptr32 Void
+0x068 NtGlobalFlag : Uint4B
(1)BeingDebugged
kernel32.dll中有个名Kernel32!IsDebuggerPresent()
的API
- 该API就是用来判断当前进程是否处于调试状态,并返回判断结果
- 原理:通过检测
PEB.BeingDebugged
成员来确定是否正在调试程序(是,则返回1;否,则返回0) - Windows 7中,
IsDebuggerPresent()
API是在Kernelbase.dll中实现的;而在Windows XP 及以前版本的操作系统中,它是在kernel32.dll中
BOOL WINAPI IsDebuggerPresent()
(2)ImageBaseAddress
PEB.ImageBaseAddress
成员用来表示进程的ImageBase
GetModuleHandle()
API用来获取ImageBase
HMODULE WINAPI GetModuleHandle(
__in_opt LPCTSTR lpModuleName
//向lpModuleName参数赋值为NULL,调用GetModuleHandle()函数将返回进程被加载的ImageBase
)
(3)Ldr
PEB.Ldr
成员是指向了_ PEB _ LDR _ DATA
结构体指针,_ PEB _ LDR _ DATA
结构体如下:
ypedef struct _PEB_LDR_DATA
ULONG Length; // 00h
BOOLEAN Initialized; // 04h
PVOID SsHandle; // 08h
LIST_ENTRY InLoadOrderModuleList; // 0ch
LIST_ENTRY InMemoryOrderModuleList; // 14h
LIST_ENTRY InInitializationOrderModuleList; // 1ch
EntryInProgress //Ptr32 Void
ShutdownInProgress //Uchar
ShutdownThreadId //Ptr32 Void
PEB_LDR_DATA,
*PPEB_LDR_DATA; // 24h
当模块(DLL)加载到进程后,通过PEB.ldr
成员可以直接获得该模块的加载基地址
_PEB_LDR_DATA
结构体成员中有3个LIST_ENTRY
类型的成员(InLoadOrderModuleList
、InMemoryOrderModuleList
;、InInitializationOrderModuleList
;),LIST_ENTRY
结构体的定义如下所示:
typedef struct _LIST_ENTRY
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
LIST_ENTRY,*LIST_ENTRY
_LIST_ENTRY
结构体提供了双向链表机制,而链表中保存了_LDR_DATA_TABLE_ENTRY
结构体的信息,_LDR_DATA_TABLE_ENTRY
结构体如下:
typedef struct _LDR_DATA_TABLE_EBTRY // Start from Windows XP
PVOID Reservedl[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase;
PVOID EntryPoint;
PVOID Reserved3;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
PVOID Reserved4[8];
PVOID Reserved5[3];
union
PVOID SectionPointer;
ULONG CheckSum;
ULONG TimeDateStamp;
每个加载到进程中的DLL模块都有与之对应的_LDR_DATA_TABLE_EBTRY
结构体,这些结构体相互链接,最终形成_LIST_ENTRY
双向链表
(4)ProcessHeap & NtGlobalFlag
PEB.ProcessHeap & PEB.NtGlobalFlag (和PEB.BeingDebugger成员一样)应用于反调试技术,若处于被调试状态,则它们会被赋于特定的值
2、PEB访问方法
TEB.ProcessEnvironmentBlock
成员就是PEB 结构体的地址
TEB结构体位于FS段选择符所指的段内存的起始地址处,且ProcessEnvironmentBlock
成员位于距TEB结构体Offest 30
的位置,所以有:
FS:[30]=TEB.ProcessEnvironmentBlock=address of PEB
用汇编来描述如下:
- 直接获取PEB
MOV EAX,DWORD PTR FS:[0x30]
- 先获取TEB再获取PEB
MOV EAX,DWORD PTR FS:[0x18] MOV EAX,DWORD PTR FS:[EAX+0x30]
四、SEH
这块可参考:深入解析结构化异常处理(SEH)
结构化异常处理(Structured Exception Handling,SEH),使用__try
、__finally
和__except
来实现
1、OS的异常处理方法
进程正常运行时发生异常, OS会委托进程处理
- 若存在具体异常处理代码,就顺利处理
- 若无具体实现SEH,OS启动默认异常处理机制,终止进程
调试运行时发生异常,调试器暂停运行,采取某种措施处理
- 直接修改异常的代码/寄存器/内存
- 将异常抛给被调试者,OD中Shirt+F7/F8/F9
- OS的默认异常处理机制,终止进程
2、SEH说明
SEH以链的形式存在,一个异常处理器未能处理相关异常,就会将其传递到下一个
异常的回调函数的样子如下:
EXCEPTION_DISPOSITION
__cdecl _except_handler(
struct _EXCEPTION_RECORD *ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
void * DispatcherContext);
- 第一个参数是一个指向
EXCEPTION_RECORD
结构体的指针,EXCEPTION_RECORD
结构体如下:typedef struct _EXCEPTION_RECORD DWORD ExceptionCode; //异常代码 DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; //异常发生的地址 DWORD NumberParameters; DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; EXCEPTION_RECORD;
- 第二个参数是一个指向
establisher
帧结构体的指针 - 第三个参数是一个指向
CONTEXT
结构体的指针,CONTEXT
结构体用来备份CPU寄存器的值,每个线程里有一个CONTEXT
结构体,其定义如下:typedef struct _CONTEXT DWORD ContextFlags; DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; FLOATING_SAVE_AREA FloatSave; DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; DWORD Ebp; DWORD Eip; DWORD SegCs; DWORD EFlags; DWORD Esp; DWORD SegSs; CONTEXT;
- 第四个参数被称为
DispatcherContext
五、IA-32指令
具体可参考:IA-32指令解析详解
结语
主要是对异常处理和IA-32指令需要进行进一步学习
以上是关于《逆向工程核心原理》学习笔记:高级逆向分析技术的主要内容,如果未能解决你的问题,请参考以下文章