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


    前言

    继续学习《逆向工程核心原理》,本篇笔记是第六部分:高级逆向分析技术,包括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
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    直接打开的弹窗如下:
    在这里插入图片描述
    扔进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;
    }
    
    
    • 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

    运行结果如下:
    在这里插入图片描述

    调试,需要先如下设置:
    在这里插入图片描述
    然后便可以调试了

    二、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;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2个重要成员:

    • ProcessEnvironmentBlock成员:指向PEB(Process Environment Block,进程环境块)结构体指针。PEB是进程环境块,每个进程对应1个PEB结构体
    • _NT_TIB结构体(TIB是Thread Information Block的简称,意为“线程信息块”)
    +0x000 NtTib            : _NT_TIB
    ...
    +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
    
    • 1
    • 2
    • 3

    _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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    几个重要成员

     +0x002 BeingDebugged    : UChar
     +0x008 ImageBaseAddress : Ptr32 Void
     +0x00c Ldr              : Ptr32 _PEB_LDR_DATA
     +0x018 ProcessHeap      : Ptr32 Void
     +0x068 NtGlobalFlag     : Uint4B
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    (2)ImageBaseAddress

    PEB.ImageBaseAddress成员用来表示进程的ImageBase

    GetModuleHandle()API用来获取ImageBase

    HMODULE WINAPI GetModuleHandle(
    	__in_opt 	LPCTSTR		lpModuleName 
    	//向lpModuleName参数赋值为NULL,调用GetModuleHandle()函数将返回进程被加载的ImageBase
    • 1
    • 2
    • 3
    • 4

    (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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    当模块(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
    
    • 1
    • 2
    • 3
    • 4

    _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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    每个加载到进程中的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
    
    • 1

    用汇编来描述如下:

    • 直接获取PEB
      MOV EAX,DWORD PTR FS:[0x30]
      
      • 1
    • 先获取TEB再获取PEB
      MOV EAX,DWORD PTR FS:[0x18]
      MOV EAX,DWORD PTR FS:[EAX+0x30]
      
      • 1
      • 2

    四、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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 第一个参数是一个指向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;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 第二个参数是一个指向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;
      
      • 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
    • 第四个参数被称为DispatcherContext

    五、IA-32指令

    在这里插入图片描述

    具体可参考:IA-32指令解析详解

    结语

    主要是对异常处理和IA-32指令需要进行进一步学习

  • 相关阅读:
    机器学习——机器学习概述
    Python知识汇总
    R语言学习:RFM分析,rfm包
    Yii2 关联查询结果AR对象 如何取到表以外的字段
    web3.0 会是下一代互联网的风口么?
    Java开发 - 你不知道的JVM优化详解
    通过nginx弄一个滑块加图片的人机验证
    大一学生WEB前端静态网页——唯品会1页 包含hover效果
    使用gitee部署静态网页
    【MindSpore】【TFRecordDataset】无法正确处理tfrecord文件
  • 原文地址:https://blog.csdn.net/weixin_44604541/article/details/124942263