《逆向工程核心原理》学习笔记:高级逆向分析技术

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类型的成员(InLoadOrderModuleListInMemoryOrderModuleList;、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指令需要进行进一步学习

以上是关于《逆向工程核心原理》学习笔记:高级逆向分析技术的主要内容,如果未能解决你的问题,请参考以下文章

《逆向工程核心原理》学习笔记:高级逆向分析技术

《逆向工程核心原理》学习笔记:高级逆向分析技术

《逆向工程核心原理》学习笔记:反调试技术

《逆向工程核心原理》学习笔记:反调试技术

《逆向工程核心原理》学习笔记:反调试技术

《逆向工程核心原理》学习笔记:API钩取