精华内容
下载资源
问答
  • 逆向工程核心原理
    千次阅读
    2020-08-08 16:15:44

    链接:https://pan.baidu.com/s/1qvXbeE9AIZyTpVTKIIyuGA
    提取码:zofw

    更多相关内容
  • 2015 逆向工程核心原理 程序源码 资源
  • 逆向工程核心原理

    千次阅读 2021-03-30 20:41:13
    关于逆向工程 代码逆向工程,是逆向工程在软件领域的应用。使用RCE、RE、逆向工程等简称。 分析逆向工程的方法有静态分析法和动态分析法。 静态分析法:是在不执行代码文件情形下,对代码静态分析的方法。不执行代码...

    关于逆向工程

    代码逆向工程,是逆向工程在软件领域的应用。使用RCE、RE、逆向工程等简称。
    分析逆向工程的方法有静态分析法和动态分析法。
    静态分析法:是在不执行代码文件情形下,对代码静态分析的方法。不执行代码,而是观察代码外部特征,获取文件类型、大小、PE头信息、导入导出API、内部字符串、是否运行时解压缩、注册信息、调试信息、数字证书等信息。使用反汇编工具查看内部代码、分析代码结构也属于静态分析。
    动态分析法:程序文件执行过程中对代码进行动态分析。通过调试获得代码流,获得内存状态。通过动态分析法可以观察文件、注册表、网络等同时分析软件行为。常使用debugger分析程序的内部结构和动作原理。

    逆向分析Hello World程序

    HelloWorld代码在VS2010中写的。解决方案配置选择release,然后生成exe可执行文件。代码如下:
    在这里插入图片描述使用OD打开helloworld可执行文件。
    在这里插入图片描述左上为代码区域,左下为数据窗口区域,右上为寄存器窗口区域,右下为栈窗口区域。

    入口点

    调试器载入程序后,停止的地方即为HelloWorld.exe的起始地址。是一段EP(EntryPoint)代码,其中,Address为进程虚拟内存地址,Instruction为指令,Disassembled code为反汇编代码,comment为注释。EP是windows可执行文件的代码入口点,是执行文件最先执行代码的位置。
    OllyDbg基本指令:
    1,Restart(ctrl + F2):重新开始调试(终止正在调试的进程再次运行)
    2,step into(F7):执行一句OP code,如果遇到调用指令则进入函数代码内部
    3,Step over(F8):执行一句OP code,如果遇到调用指令仅执行函数本身,不跟随进入
    4,Excute till Return(Ctrl + F9):一直在函数代码内部运行,直到遇到RETURN命令跳出函数。
    5,Go to(Ctrl + G)移动到指定位置,用来查看内存或代码,运行时不可用
    6,Execute till Cursor(F4)执行到光标位置,即直接转到调试地址
    7,F2 设置或取消断点(BP)
    8,F9 运行(如果设置断点则执行到断点处)
    9,Preview CALL/JUMP address:enter,如果光标处有CALL、JMP等命令,则跟踪并显示相关地址(运行时不可用,简单查看函数时候有用)
    通过步进调试,找到了main()函数,显示出来两个字符串参数。
    在这里插入图片描述#### 快速查找指定代码
    1,逐条执行命令来查找需要查找的位置。仅适用于被调试的代码量不大、程序功能明确的情况。
    2,字符串检索法:鼠标右键菜单 — Search for — All referenced text strings.VC++中,static字符串会被默认保存为Unicode码形式,static字符串是指在程序内部被硬编码的字符串。
    在windows中,代码和数据所在区域是分开的。
    3,API检索法:调用代码中设置断点
    Windows编程中,如果向显示器显示内容,需要使用Win32 API向OS请求显示输出,一定会调用Win32 API。如果弹出消息窗口,会调用user32.MessageBoxW() API。如果能推测出代码使用的API,使用(search for - all intermodular calls)会很好用。
    4,API检索法:在API代码中设置断点
    鼠标右键 — search for — Name in all calls
    OD不能为所有可执行文件列出API函数调用列表,使用压缩器、保护器工具对可执行文件进行压缩或保护后,文件结构就会改变。此时OD无法列API调用列表。此时可以用直接向DLL代码库添加断点。DLL代码库存在C:\Windows\system32文件夹中。
    方法:首先使用OD菜单栏选择View-Memory菜单,然后找到加载库(例子中为User32),使用OD的Name in all modules命令列出被加载的DLL提供所有API,找出MessageBoxW后,光标会定位到MessageBoxW。双击打开代码,按F2设置断点。然后按F9继续执行。即可。

    使用打补丁方式修改HelloWorld字符串

    1,直接修改字符串缓冲区(buffer)(一定要注意新字符串长度不能长于原来字符串,否则可能破坏数据,Unicode字符串必须以NULL结束,在HEX项目中添加)这种方法用起来十分简单,但是缺点是新字符串长度有限制,新字符串长度不应比原字符串长。
    修改之后,在Dump窗口中,选择更改后的hello reversing字符串,点击鼠标右键选择copy to executable file菜单,在弹出的Hex窗口中单击鼠标右键,选择save file菜单,在save file as中输入名称后保存为可执行文件,然后运行该文件发现已经改变。
    2,在其它内存区域新建字符串并传递给消息函数
    在向MessageBoxW()函数传递字符串参数时,传递的是字符串所在区域首地址。如果改变字符串地址,消息框就会显示变更后的字符串。在内存某个区域新建一个长字符串,并把新字符串首地址传递给MessageBoxW()函数,可以认为传递的是完全不同的字符串地址。首先找个空的位置,设置成新的字符串,然后在原函数PUSH的地址那输入Assemble指令,输入PUSH 新的字符串地址的首地址 指令,然后按F9运行程序即可修改成功。
    Assembly基础指令
    CALL xxxx 调用xxxx地址处的函数
    JMP xxxx 跳转到xxxx地址
    PUSH xxxx 保存xxxx到栈
    RETN 跳转到栈中保持的地址
    PE文件
    是Portable Executable的简称。是Windows可执行文件的格式,主要包括对文件规格的描述。

    小端序标记法

    字节序

    是多字节数据在计算机内存中存放的字节顺序。采用大端序存储数据时,内存地址低位存储数据的高位,内存地址高位存储数据低位,是最直观的字节存储顺序。采用小端序存储数据时,地址高位存储数据高位,地址低位存储数据低位,是逆序存储方式。当数据为单一字节时,无论采取大端序还是小端序字节存储顺序都一样。只有数据长度在2个字节以上时选择大端序或者小端序会导致数据存储顺序不同。字符数组在内存中是连续的,此时无论采用大端序还是小端序存储顺序都相同。
    WORD w:0x1234 大端序 [12][34],小端序[34][12]
    DWORD dw:0x12345678 大端序[12][34][56][78],小端序[78][56][34][12]
    char[] str:大端序[61][62][63][64][65][00],小端序[61][62][63][64][65][00]
    对大型UnIx服务器,常采用大端序保存多字节数据,而对Intel X86 CPU则采用小端序。小端序使用逆序方式存储数据,进行算数运算和扩展、缩小数据效率非常高。
    在这里插入图片描述

    IA-32寄存器(Intel Architecture Register)

    寄存器是CPU内部用来存放数据的小型存储区域,寄存器集成在CPU内部,有非常高的读写速度。因为大部分汇编指令用于操作寄存器或者检查其中的数据,必须掌握寄存器的相关内容才能真正明白这些汇编指令的意义。
    寄存器分类

    通用寄存器(General Purpose Registers)(32位,8个)

    是通用型寄存器,用于传送和暂存数据,也可参与算数逻辑运算并保存运算结果。IA-32中通用寄存器主要用来存储常量和地址等。各个寄存器的名称如下:
    EAX(针对操作数和结果数据)累加器:一般用在函数返回值中,所有Win32 API函数会先把返回值保存到EAX再返回。
    EBX(DS段中的数据指针)基址寄存器
    ECX(字符串和循环操作的)计数器:循环指令LOOP中,ECX用来循环计数,每执行一次操作ECX会减一。
    EDX(I/O指针)数据寄存器
    EBP:扩展基址指针寄存器:表示栈区域基地址,函数被调用时候保存ESP值。函数返回时把值返回ESP,保证栈不会崩溃(称为栈帧技术)
    ESI:字符串操作源指针(源变址寄存器)
    EDI:字符串操作目标指针(目的变址寄存器)
    ESI和EDI主要用于内存复制
    ESP:(SS段中栈指针)栈指针寄存器:指示栈区域的栈顶地址,某些指令可以直接用来操作ESP如PUSH、POP、CALL、RET等。ESP不能用于其它用途。

    段寄存器(segment registers)(16位,6个)

    :是一种内存保护技术,将内存划分为多个区段,并为每个区段赋予起始地址、范围、访问权限等,以保护内存。段内存记录在Segment Descriptor Table 段描述符表中,段寄存器持有这些SDT的索引。段寄存器的名称如下:
    1,CS Code Segment:代码段寄存器
    2,SS Stack Segment:栈段寄存器
    3,DS Data Segment:数据段寄存器
    4,ES:Extra data Segment:附加数据段寄存器1
    5,FS:Data Segment:数据段寄存器
    6,GS:Data Segment:数据段寄存器
    CS用来存放应用程序代码所在段的段基址,SS寄存器用来存放栈段的段基址,DS寄存器用于存放数据段的段基址,ES、FS、GS用来存放程序使用的附加数据段的段基址。

    程序状态和控制寄存器(Program Status and Control Registers)(32位,1个)

    EFLAGS:Flag Register 标志寄存器
    大小为4个字节,每一位都有意义,数值为0或者1,有些位由系统直接设定,有些根据程序命令的执行结果设置。
    ZF:ZeroFlag,若运算结果为0则值为1,否则值为0
    OF:Overflow Flag,有符号整数溢出时,OF值被设为1,MSB(Most Significant Bit 最高有效位)改变时,其值也被设为1
    CF:Carry Flag,无符号整数溢出时,其值被设为1

    指令指针寄存器(Instruction Pointer)(32位,1个)

    保存在CPU要执行的指令地址,大小为32位。EIP的数值不能直接被修改,只能通过其它指令间接修改。

    通常被用来存储局部变量、传递函数参数、保存函数返回地址等。调试程序时需要不断查看栈内存。按照FILO原则存储数据,后进先出。栈顶指针ESP初始状态指向栈底端,执行PUSH命令将数据压入栈,栈顶指针会上移到栈顶端。执行POP命令时候,栈顶指针会下移。栈是由高地址向低地址扩展的数据结构,从下向上扩展。栈是逆向扩展的。
    向栈压入数据时,栈顶指针减小,向低地址移动;从栈中弹出数据,栈顶指针增加,向高地址移动。
    栈顶指针在初始状态下指向栈底

    栈内存在进程中作用

    1,暂时保存函数内的1局部变量
    2,调用函数时传递参数
    3,保存函数返回后的地址

    分析abex’ crackme

    绝大多数crackme小程序都是猜序列号(serial key)
    PUSH ---- 入栈指令
    CALL ---- 调用指定位置的函数
    INC ---- 值加1
    DEC ---- 值减1
    JMP ---- 跳转到指定位置
    CMP ---- 比较给定的两个操作数,和SUB命令类似,但操作数的值不会改变,仅改变EFLAGS寄存器(如果2个操作数值一致则SUB结果为0,ZF变为1)
    JE ---- 条件跳转指令(Jump if equal)若ZF为1则跳转
    破解思路:将汇编指令 JE SHORT 0040103D换成 JMP 0040103D,由条件跳转变成无条件跳转。然后使用Copy to executable命令将修改后的代码保存为文件。

    函数调用时压栈

    一般调用函数,传参时候用压栈方法把函数需要的参数逆序压入栈。用PUSH指令。函数调用时候用正序,函数入栈时用逆序。

    栈帧

    使用EBP寄存器访问栈内局部变量、参数、函数返回地址等的手段。ESP寄存器承担着栈顶指针的作用,而EBP寄存器则负责行使栈帧指针的作用。程序运行时ESP的值随时改变,因此在调用函数时,先把基准点(函数起始位置)的ESP值保存到EBP,并维持在函数内部。这样无论ESP值如何变化,以EBP值为基准能够安全访问到相关函数的局部变量、参数、返回值等。其中EBP寄存器作为栈帧指针的作用。
    在栈中保存函数返回地址是系统安全隐患之一,攻击者使用缓冲区溢出技术能把保存在栈内存的返回地址更改为其它地址。
    MOV EBP,ESP(把ESP值传到EBP中)
    在这里插入图片描述
    main函数的汇编过程如下图所示:
    在这里插入图片描述
    1,PUSH EBP 将EBP寄存器压栈。
    2,将ESP的值(栈顶指针)给EBP(栈帧指针)
    3,SUB ESP 8 将ESP的值减少8个字节,为函数的局部变量a,b开辟空间,以便保存在栈中。a和b都是long类型,因此分别占据4个字节。

    abex’ crackme #2 (暂时未找到)

    Process Explorer

    是Windows操作系统下最优秀的进程管理工具。有 Parent/Child进程树结构,以不同颜色(红色、绿色)显示进程运行、终止,有进程的Suspend、Resume功能(挂起、恢复运行),进程的终止功能(kill,支持kill process tree功能),检索DLL、Handle(检索加载到进程中的DLL或者进程占有的句柄)。这些功能是逆向分析时候最常用的。
    在这里插入图片描述

    函数调用约定(calling convention)

    是对函数调用时候如何传递参数的一种约定。
    在函数执行完成后,栈中的参数不用再管。当下一次向栈里面存入其它值时,原有值会被自然覆盖掉。栈内存是固定的,没必要释放。
    函数执行完毕,ESP值要恢复到函数调用之前,这样可引用的栈大小才不会缩减。由于栈内存是固定的,ESP用来指示栈当前位置。若ESP指向栈底则无法再使用该栈。函数调用后处理ESP的方法是函数调用约定要解决的问题。主要有cdecl、stdcall、fastcall三种。
    术语 调用者:调用函数的一方,被调用者:被调用的函数。

    cdecl

    主要是在C语言中使用,由调用者负责处理栈。如示例,在调用完了add()函数后,使用ADD ESP,8命令整理栈。调用者main()函数直接清理压入栈的函数参数。好处在于可以像C语言的printf()函数一样,向被调用函数传递长度可变的参数。
    在这里插入图片描述

    stdcall

    常见于Win32 API中。由被调用者清理栈。在main()函数调用add函数后,省略了清理栈的代码(ADD ESP,8)。而在add()函数的最后面,有RETN 8 命令。该命令含义 RETN + POP 8字节。stdcall的代码尺寸要小。不需要每次调用函数都需要增加和清理。还可以有更好的兼容性。在这里插入图片描述

    fastcall

    和stdcall方式类似,但该方式通常会使用寄存器而不是栈内存来传递需要传递给函数的部分参数(前两个参数)。若某函数有4个参数,则前两个参数分别使用ECX、EDX寄存器传值。
    优势在于可以实现对函数的快速调用。
    单从函数调用来看,fastcall方式非常快,但是有时候需要额外的系统开销来管理ECX、EDX寄存器。如果调用函数钱这些寄存器有重要数据需要提前备份,可能得不偿失。

    第十一章 视频讲座(P98) — 访问不了

    PE文件格式

    PE(Portable Executable)文件是windows操作系统下使用的可执行文件格式。这种文件格式仅用于在Windows系列的操作系统下。PE文件指32位的可执行文件,也称PE32。64位的可执行文件成为PE+或者PE32+,是PE32文件的扩展形式。(不是PE64)

    PE文件格式

    PE文件格式包括可执行系列、库系列、驱动程序系列、对象文件系列。
    可执行系列 EXE、SCR文件
    驱动程序系列 SYS、BXD
    库系列 DLL、OCX、CPL、DRV
    对象文件系列 OBJ
    OBJ文件之外的所有文件都是可执行的,DLL、SYS文件虽然不能直接在shell中运行,但是可以使用其它方法(调试器、服务等)执行。PE头存储exe文件运行需要的所有信息,如何加载到内存、从何处开始运行、运行中需要的DLL、需要多大的堆和栈等都以结构体形式存储在PE头中。学习PE文件格式就是学习PE头中的结构体。

    PE基本结构

    从DOS头(DOS header)到节区头(Section header)是PE头部分,其下节区合称PE体。文件中使用偏移(offset),内存中使用VA(virtual address)来表示位置。文件加载到内存后情况会发生变化(节区的大小、位置等)。文件内容可以分为代码、数据、资源节等,分别保存。
    节区头定义了各节区在文件或内存中的大小、位置、属性。
    PE头和各节区的尾部存在一个区域,称为NULL填充。计算机中为了提高处理文件、内存、网络包的效率,使用了最小基本单位概念,PE文件也类似。文件、内存中节区的起始位置应该在各文件、内存最小单位的倍数位置上,空白区域用NULL填充。

    VA&RVA

    VA指进程虚拟内存的绝对地址,RVA(Relative Virtual Address,相对虚拟地址)指从某个基准位置开始的相对地址。VA = ImageBase + RVA
    32位Windows OS中,各进程分配4GB的虚拟内存,因此进程中VA值为00000000 -FFFFFFFF。
    notepad.exe在windows10系统的位置为C:\WINDOWS\system32\notepad.exe。
    在这里插入图片描述

    PE头

    DOS头

    每个PE文件最前面都有IMAGE_DOS_HEADER结构体,用来扩展已有的DOS EXE头。大小为40字节。其中必须包括e_magic和elfanew。
    e_magic:DOS签名(signature,4D5A --> ASCII值 MZ)
    e_lfanew:指示NT头的偏移(根据不同文件拥有可变值)界面中为000000E8.
    在这里插入图片描述

    DOS存根

    在DOS头下方,可选择,大小不固定。DOS存根由代码和数据混合而成。
    在这里插入图片描述

    NT头

    IMAGE_NT_HEADERS结构体由3个成员自称,第一个成员为签名(signature)结构体,值为50450000h(“PE”)。
    文件头 是表现文件大致属性的IMAGE_FILE_HEADER结构体,主要有4种重要成员。如果设置不正确会导致文件无法正常运行。
    1,Machine码:每个CPU拥有唯一的Machine码,兼容32位Intel x86芯片的Machine码位14C。
    2,NumberOfSection码:PE文件把代码、数据、资源等依据属性分类到各类节区存储。NumberOfSection的值一定要大于0,当定义节区数和实际不同时将发生运行错误。
    3,SizeOfOptionalHeader:
    4,Characteristics:
    对Notepad.exe来说,如下图
    在这里插入图片描述
    可选头 IMAGE_OPTIONAL_HEADER32是PE头结构体中最大的。有些值是文件运行必须的,设置错误将导致文件无法正常运行。
    1,Magic:位IMAGE_OPTIONAL_HEADER32结构体时,Magic码为10B,为IMAGE_OPTIONAL_HEADER64时,Magic码为20B。
    2,AddressOfEntryPoint:该值指出程序最先执行的代码起始地址。
    3,ImageBase:指出文件的优先装入地址。使用开发工具创建好EXE文件后,其ImageBase的值为00400000,DLL文件的ImageBase值为10000000.执行PE文件时候PE装载器首先创建进程,再将文件载入内存,然后把EIP寄存器的值设置为ImageBase+AddressofEntryPoint(第一个CPU要执行的指令位置)
    4,SectionAlignment,FileAlignment:FileAlignment指定了节区在磁盘文件中的最小单位,而SectionAlignment指定了节区在内存中的最小单位(文件中两者可能相等也可能不等)。磁盘文件或内存的节区大小必定为FileAlignment或SectionAlignment值的整数倍。
    5,SizeOfImage:指定了PE Image在虚拟内存中所占空间大小。一般来说文件大小和加载到内存中内存大小不同。
    6,sizeOfHeader:用来指出整个PE头的大小,该值必须是FileAlignment的整数倍。第一节区所在位置和SizeOfHeader距文件开始偏移的量相同。
    7,Subsystem:用来区分系统驱动文件(.sys)和普通的可执行文件(.exe,.dll)。值为1时为Driver文件,为系统驱动(ntfs.sys)等。值为2时为GUI文件,即窗口应用程序(notepad.exe),值为3时为CUI文件,为控制台应用文件(cmd.exe)。
    8,NumberOfRvaAndSizes:用来指定DataDirectory数组的个数。PE装载器通过查看该值来识别数组大小。
    9,DataDirectory:由IMAGE_DATA_DIRECTORY结构体组成的数组,数组的每项都有被定义的值。
    在这里插入图片描述
    节区头 PE文件中code、data、resource等按照属性分类存储在不同节区,这样可以保证程序的安全性。如果把code和data放在一个节区中相互纠缠很容易引发安全问题。(如向字符串data写数据,由于某个原因导致溢出(输入超过缓冲区大小),则其下的code会被覆盖,应用程序就会崩溃)。需要把各节区属性记录在节区头中(节区属性中有文件、内存起始位置、大小、访问权限等)。不同的内存属性有不同的访问权限。code有执行读取权限;data非执行,有读写权限;resource非执行,有读写权限。
    1,IMAGE_SECTION_HEADER:节区头由IMAGE_SECTION_HEADER结构体组成的数组。每个结构体对应一个节区。其中重要成员如下图:
    在这里插入图片描述
    其中VirtualSize和PointerToRawData不带有任何值。分别由SectionAlignment和FileAlignment确定。

    RVA to RAW

    PE文件加载到内存时,每个节区都要准确完成内存地址和文件偏移之间的映射,这种映射一般称为RVA to RAW。
    计算方法
    (1)查找RVA所在节区
    (2)使用简单的公式计算文件偏移(RAW)
    RAW - PointerToRawData = RVA - VirtualAddress
    RAW = RVA - VirtualAddress + PointerToRawData

    IAT(Import Address Table 导入地址表)

    IAT是一个表格,记录程序正在使用哪些库中的哪些函数。

    DLL

    产生原因:可以独立,不需要将库包含到程序中,需要时调用即可;内存映射技术使得加载后的DLL代码资源可以在多个进程中实现共享;更新库的时候只需要替换相应DLL文件即可,方便。
    加载DLL方式
    1,显式链接(Explicit Linking):程序使用DLL时候加载,使用完毕后释放内存。
    2,隐式链接(Implicit Linking):程序一开始时候一同加载DLL,程序终止时候再释放占用的内存。
    PE头的IAT是代码逆向分析的核心内容。

    IMAGE_IMPORT_DESCRIPTOR

    该结构体中记录了PE文件要导入哪些库文件。执行一个普通程序需要导入多个库,导入多少库就存在多少个IMAGE_IMPORT_DESCRIPTOR结构体。这些结构体形成了数组,且结构体数组最后以NULL结构体结束。结构体中重要成员如下:
    在这里插入图片描述

    EAT

    Windows操作系统中,库是为了方便其它程序调用而集中包含相关函数的文件。WIN32API是最有代表性的库,而kernal32.dll是最核心的库文件。
    EAT是一种核心机制,使得不同的应用程序可以调用库文件中提供的函数。PE文件中的特定结构体(IMAGE_EXPORT_DIRECTORY)保存着到处信息。PE文件中仅有一个用来说明EAT的IMAGE_EXPORT_DIRECTORY结构体。

    IMAGE_EXPORT_DIRECTORY

    在这里插入图片描述
    从库中获取函数地址的API为GetProcAddress()函数,该API引用EAT获取指定API的地址。具体过程:
    1,利用AddressOfNames转到函数名称数组;
    2,函数名称数组中存储着字符串地址,通过比较字符串查找指定的函数名称;
    3,利用AddressOfNameOrdinals成员,转到orinal数组;
    4,再ordinal数组中通过name_index查找相应ordinal值;
    5,利用AddressOfFunctions成员转到函数地址数组(EAT);
    6,在函数地址数组中将刚刚求得的ordinal用作数组索引,获得指定函数的起始地址。**ordinal(固有编号)**Ordinal是导出函数的固有编号。有些函数不会对外公开函数名,仅公开函数的固有编号(ordinal),下面两个读取结果实际上是一样的:
    pFunc = GetProcAddress(“TestFunc”)
    pFunc = GetProcAddress(5)

    使用PEView软件可以比较快速容易的查找出PE中对应的内容

    运行时压缩

    数据压缩

    所有形态的文件都是由0和1组成,只要有合适的压缩算法就能缩减其大小。经过压缩的文件若能100%恢复则称为无损压缩(Lossless Data Compression),若不能恢复原状则称为有损压缩(Loss Data Compression)。
    无损压缩代表有Run-Length、Lempel-Ziv、Huffmen等。ZIP、RAR是具有代表性的压缩文件格式。
    有损压缩压缩文件时损失一定信息,以此换取高压缩率。压缩多媒体文件(jpg、mp3、mp4)时,大部分使用有损压缩方式。解压缩后不能完全恢复原始数据,但人几乎发现不了。

    运行时压缩

    运行时压缩是针对可执行(PE)文件,可执行文件内部含有解压缩代码。文件运行瞬间在内存解压缩后执行。和普通压缩器相比,运行时压缩器明显不同是“PE文件的可运行性”。

    运行时压缩器 PE压缩器

    PE压缩器可以缩减PE文件的大小,便于网络传输和保存。还可以隐藏PE文件内部代码和资源。
    压缩器种类
    1,单纯用于压缩普通PE文件的压缩器
    2,对源文件进行较大变形、严重破坏PE头、意图不纯的压缩器。专门用于恶意程序(Virtus、Trojan、Worm)的压缩器。
    保护器
    是一类保护PE文件免受代码逆向分析的实用程序。不仅像普通的压缩器一样对PE文件进行运行时压缩,还应用多种代码逆向分析技术(反调试、反模拟、代码混乱、多态代码、垃圾代码、调试器监视等)。压缩后的PE文件尺寸比原为见还难,调试困难。
    使用保护器的目的为
    1,防止破解:有效保护PE文件
    2,保护代码和资源:不仅可以保护PE文件本身,还可以在文件运行时保护进程内存,防止打开Dump窗口。
    使用UPX软件可以进行运行时压缩。加密命令:UPX -o notepad_upx.exe notepad.exe
    在这里插入图片描述
    加密之后,PE头大小一样(0-400h);节区名称改变(.text --> UPX0,.data --> UPX1);第一个节区的RawDataSize=0(文件中大小为0);EP位于第二个节区(原notepad.exe的EP在第一个节区);资源节区(.rsrc)大小几乎无变化。

    快速查找UPX OEP(程序入口点)方法

    1,UPX压缩器特征之一,是其EP代码被包含在PUSHAD、POPAD指令之间,并且跳转到OEP代码的JMP指令紧接着出现在POPAD指令之后,只要在JMP指令处设置好断点,运行后就能直接找到OEP。

    基址重新定位

    PE文件重定位过程中会用到基址重定位表(Base Relocation Table)。向进程的虚拟内存加载PE时候,文件会被加载到PE头所指的地址处。如果加载的是DLL文件,但在ImageBase位置处已经加载了其它DLL文件,PE装载器会把它们加载到其它未被占用的空间。使用SDK或VC++创建PE文件,EXE默认ImageBase为00400000,DLL默认ImageBase为10000000.使用DDK(Driver Development Kit 驱动开发工具包)创建的SYS文件默认的ImageBase为10000.
    从Windows Vista开始,微软引入ASLR安全机制,EXE程序每次运行时程序都会被加载到不同地址。
    在这里插入图片描述
    相对偏移都是D8000.是一样的。才能保证程序最终运行时候不会出现错误。

    PE重定位操作原理

    1,在应用程序中查找硬编码的地址位置
    2,读取值后,减去ImageBase(VA --> RVA)
    3,加上实际加载位置(RVA --> VA)
    基址重定位表位于PE头的DataDirectory的第六个元素。
    即 IMAGE_NT_HEADERS\IMAGE_OPTION_HEADER\IMAGE_DATA_DIRECTORY[5]
    在这里插入图片描述
    IMAGE_BASE_RELOCATION结构体第一个成员为VirtualAddress,是一个基准地址(Base Address),实际上是RVA值。第二个成员为Size of Block,指重定位块的大小。第三个成员为TypeOffset,由4位的Type和12位的Offset合成。如果TypeOffset值为3420(H),高4位为type,PE文件中常见为3(32位,IMAGE_REL_BASED_HIGHLOW),64位的PE+文件常见值为A(IMAGE_REL_BASED_DIR64)。恶意代码中修改文件代码后,有时候要修改指向相应区域的重定位表,把Type值修改为0(IMAGE_REL_BASED_ABSOLUTE)。TypeOffset低12位是真正的位移,是基于VirtualAddress偏移。在实际计算时候应该相加。

    从可执行文件删除.reloc节区

    EXE格式的PE文件中,“基址重定位表”对运行没影响,删除后程序仍正常运行。(基址重定位表对DLL、SYS形式文件来说几乎是必需的)。删除之后文件大小会缩减。.reloc节区一般位于所有节区的最后,删除并没什么影响。
    1,删除reloc节区头:使用Hex Editor操作,对相应区域全部用0覆盖(使用HxD的“Fill Selection”功能)。
    2,删除.reloc节区:从偏移开始,一直删除到结尾的所有数据(使用HxD的Delete功能)。
    3,修改IMAGE_FILE_HEADER-Numbers of Sections选项,减一。
    4,修改IMAGE_OPTIONAL_HEADER - size of Image值,减去已经删掉的数值,即可。

    UPack PE文件头分析

    使用Stud_PE可以比较好的看UPack加密头,PEView无法看。

    重叠文件头

    借助该方法可以把MZ文件头(IMAGE_DOS_HEADER)和PE文件头(IMAGE_NT_HEADERS)巧妙重叠在一起,可有效节约文件头空间。但会额外增加文件头的复杂性,分析困难。
    修改IMAGE_FILE_HEADER.SizeOfOptionalHeader的数值,可以向文件头插入解码代码。UPack使用这个方法在IMAGE_OPTONAL_HEADER和IMAGE_SECTION_HEADER之间插入解码代码,这样可以混淆PE。
    随后,IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes也发生了改变,向文件头中也插入了代码。NumberOfRvaAndSizes数值用来指出紧接在后面的IMAGE_DATA_DIRECTORY结构体数组的元素个数,正常个数为10个,但是Upack中更改为了A个。
    在IMAGE_SECTION_HEADER中,Upack会向PE文件头中不实用的区域里面写入自己的代码。

    重叠节区

    UPack会对PE文件头、第一个节区、第三个节区进行重叠。如图
    在这里插入图片描述
    如notepad文件,压缩的notepad文件会在内存的第二个节区,解压缩的同时被记录到第一个节区。notepad.exe的内存映像会被整体解压,因此程序能够正常运行。

    RVA to RAW

    PE实用程序对UPack束手无策对原因是无法正确进行RVA->RAW的变换。正常变换方法为:

    RAW - PointerToRawData = RVA - VirtualAddress
    Raw = RVA - VirtualAddress + PointerToRawData
    

    正常来说,PointerToRawData的数值应该是FileAlignment的整数倍,UPack的FileAlignment为200,但是使用UPack后,第一个节区的PointerToRawData为10,不是FileAlignment的整数倍,系统会将其识别为0,使得UPack文件能正常运行。但是许多PE相关实用程序会发生错误。

    导入表(IMAGE_IMPORT_DESCRIPTOR array)

    导入地址表

    UPack本身不是恶意程序,但是很多恶意程序使用UPack压缩恶意代码使得代码变得畸形。很多杀毒软件将UPack压缩的文件全部识别为病毒文件并删除。

    内嵌补丁

    当难以直接修改指定代码时,插入并运行被称为 CodeCave的补丁代码,对程序打补丁,常用于对象程序经过运行时压缩(或加密处理)而难以直接修改对情况。
    在这里插入图片描述
    内嵌补丁的位置非常重要。当补丁文件比较少时,补丁可以被设置到文件空白区域,当文件比较多时,可以扩展最后节区后设置或者添加新节区后设置。使用ap0x - Reversing Labs可进行尝试。一般使用OD调试。
    调试思路
    1,使用OD打开程序文件,右键Search for All referenced text strings,没有结果
    2,找到程序当加密循环和解密循环。发现有三个解密循环,很难直接破解。转换思路通过打补丁方式破解。
    3,在合适文件位置插入用于修改字符串的代码,然后将JMP OEP命令修改为JMP 补丁代码。在运行程序时遇到JMP补丁代码语句就在补丁代码中更改字符串,然后再通过JMP命令跳转到OEP处,就完成打补丁过程了。

    DLL注入

    Windows消息勾取 Message Hook

    Windows操作系统向用户提供GUI,以事件驱动方式工作。如操作系统中借助键盘、鼠标、选择菜单、按钮、鼠标、改变窗口大小和位置等都是事件。发生这样事件后,OS会把事先定义好的消息发送给相应等应用程序,应用程序收到信息后执行相应动作。比如敲击键盘时,消息会从OS移动到应用程序,Message Hook会在中间偷看这些信息。在键盘消息钩子函数内部除了可以查看消息外,还能修改消息本身,还可对消息实行拦截,阻止消息传递。

    SetWindowsHookEx() API

    使用该API可以轻松实现消息钩子。定义如下:
    在这里插入图片描述
    钩子过程是由操作系统调用到回调函数,安装消息钩子时,钩子应存在于某个DLL内部,该DLL的示例句柄即是hMod。如果dwThreaID参数被设置为0,安装的钩子为全局钩子,会影响到运行中的所有进程。
    当使用该进程设置好钩子之后,在某个进程中产生指定消息时,操作系统会将相关的DLL文件强制注入(injection)相关进程,然后再调用注册当钩子过程。

    为了尝试使用这些注入程序,我安装了一台windowsXP虚拟机。XP虚拟机的密钥为MRX3F-47B9T-2487J-KWKMF-RPWBY,很好用。

    事实确是如此。
    查找核心代码方法:逐行搜索、检索相关API、检索相关字符串。
    通过检索相关字符串,顺利找到了主函数。
    在这里插入图片描述
    设置main()函数开始处硬件断点,开始F7调试。再401006处调用了Load Library(KeyHook.dll),然后在40104B处用CALL EBX命令调用KeyHook.HookStart()函数。摁F7步进追踪后发现进入了该函数。
    开启OD的Debugging options中,Break on new module(DLL)复选框,此时每当有新的DLL装入被调试进程,进程就会自动暂停调试。
    钩子抓取键盘内容的流程
    1,用OD运行notepad.exe
    2,开启Break on new module(DLL)选项
    3,运行KeyLogger.exe -->安装 global keyboard message hook
    4,在notepad中使用键盘输入 --> 发生键盘消息事件
    5,Keylogger.dll被注入到notepad.exe进程中
    6,在OD中向KeyboardProc(钩子进程)设置断点
    回调函数
    回调函数是特定事件发生时候OS指定调用的函数。窗口Windows过程(WndProc)就是一个简单的回调函数(键盘、鼠标等事件发生时OS会调用注册的窗口Windows过程)。

    防范恶意键盘记录器方法

    1,绝不在公共场合的PC中输入个人信息
    2,经常更新安全软件
    3,输入个人信息时灵活使用复制和粘贴
    4,使用个人防火墙(即使键盘输入遭到非法记录,只要不泄露信息就没有危险)
    一定要保管好重要的个人信息

    DLL注入

    DLL injection是渗透其它进程最简单有效的方法。借助DLL注入技术,可以勾取API、改进程序、修复bug等。
    DLL注入指向运行中的其它进程强制插入特定DLL文件。DLL注入命令其它进程自行调用LoadLibrary()API,加载用户指定的DLL文件。

    在这里插入图片描述
    DLL(Dynamic Linked Library)被加载到进程后会自动运行DllMain()函数,用户可以把想执行到函数放在DllMain()函数里面,每当加载DLL时,添加的代码就会自动执行。

    DLL注入作用

    改善功能和修复Bug
    即使没有程序的源码,或直接修改程序比较困难,可以使用DLL注入技术为程序添加新功能,或者修改有问题的代码、数据。
    消息勾取
    Windows OS默认提供的消息钩取功能应用的就是DLL注入技术。和常规DLL注入相比,OS会直接将已注册的钩取DLL注入目标进程。
    API钩取
    广泛应用于实际项目开发。先创建好DLL形态的钩取函数,再轻松注入到要钩取的目标进程,就完成了API钩取。
    恶意代码
    将恶意代码隐藏到正常进程(winlogon.exe,services.exe,svchost.exe,explorer.exe)。通过打开后门端口、或用键盘偷录等功能将用户个人信息偷走。

    DLL注入实现方法

    1,CreateRemoteThread() 创建远程线程
    eg, 将myhack.dll注入到notepad.exe中,被注入到myhack.dll被用来联网并下载http://www.naver.com/index.html文件的。
    先运行notepad.exe程序,再运行Process Explorer或者Windows任务管理器,找到notepad.exe的PID。
    运行DebugView,来实时捕获并显示系统中运行的进程输出的所有调试字符串。示例的DLL被成功注入到notepad.exe时就会输出调试字符串,使用DebugView即可查看。
    InjectDll.exe是用来向目标进程注入DLL文件的实用程序,输入相应参数即可运行InjectDll.exe
    在这里插入图片描述
    查看DebugView日志,看到<myhack.dll> injection!!!,显示注入成功。
    在Process Explorer中的View菜单中,选择 Show Lower Pane和Lower Pane Views-DLLs项,选择notepad.exe进程,会列出所有加载到notepad.exe进程中的dll。
    Windows OS核心DLL会加载到自身故有地址,DLL注入利用的就是Windows OS的这一特性。对Windows XP SP3来说,核心DLL文件的ImageBase值如下图:
    在这里插入图片描述
    2,使用注册表(AppInit_DLLs值)
    Windows操作系统注册表中默认提供了AppInit_DLLs和LoadAppInit_DLLs两个注册表项。在注册表编辑器中,将要注入的DLL的路径字符串写入AppInit_DLLs项目,然后把LoadAppInit_DLLs的项目值设置为1.重启电脑后指定的DLL会注入所有运行进程中。
    注册表路径为 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows,编辑修改AppInit_DLLs表项的值为完整路径,修改LoadAppInit_DLLs注册表项的值为1.重启系统后,即可生效。
    3,消息钩取(SetWindowsHookEx() API)
    学习代码逆向,C语言和Win32 API一定要学好

    DLL卸载

    DLL卸载(DLL Ejection)是将强制插入进程的DLL弹出的一种技术,基本原理和使用CreateRemoteThread API进行DLL注入的原理类似。驱使目标进程调用FreeLibrary() API即可。代码需要再调试一下。

    通过修改PE加载DLL

    可采用“手工修改可执行文件”方式加载用户指定的DLL文件。通过直接修改目标程序的可执行文件,使其运行时强制加载指定的DLL文件。这种方法只要应用过一次后,每次进程开始运行时就会自动加载指定的DLL文件。
    eg.TextView.exe文件注入
    使用PEView查看,发现该程序直接导入的DLL文件为KERNEL32.dll,USER32.dll,GDI32.dll,SHELL32.dll等。
    修改IDT后,在IDT中添加导入myhack3.dll的部分,运行时候会自动导入myhack3.dll文件。

    修改TextView.exe文件的准备工作

    修改思路
    PE文件中导入的DLL信息以结构体列表形式存储在IDT中,只要将myhack3.dll添加到列表尾部就可以了。此前要确认一下IDT中有无足够空间。
    查看IDT是否有足够空间
    移动IDT
    有三种方式:
    1,查找文件中到空白区域
    2,增加文件最后一个节区的大小
    3,在文件末尾添加新节区
    首先尝试第一种方法,即查找文件中的空白区域。如图所示,.rdata节区尾部恰好存在大片空白区域(PE文件中这种空白区域称为Null-Padding区域)。必须注意要确认该区域是否是空白可用区域,并不是文件中所有区域都会被无条件加载到进程的虚拟内存,只有节区头明确记录的区域才会被加载。使用PEView工具的.rdata节区头查看存储着对应节区的位置、大小、属性等信息。
    改变导入表的RVA的值为新IDT的RVA值。SIZE值在原来值的基础上再增加14个字节。
    然后删除BOUND IMPORT TABLE(修改其值为0即可)
    然后创建新IDT。使用HexEditor完全复制原IDT,然后Paste write到IDT新的位置。在新的IDT尾部添加和myhack3.dll对应到IID。

    PE TOOLS工具

    这是一款来自俄罗斯的工具,非常好用。
    下载地址:https://github.com/petoolse/petools/releases
    该软件可以获取系统中正在运行的所有进程的列表,并显示在主窗口中。程序上半部分显示正在运行的进程,下半部分显示当前所选进程加载的DLL模块。

    内存转储

    转储进程的可执行文件映像时,现在上半窗口中选中相应进程,然后单击鼠标右键,弹出快捷菜单。PE Tools提供三个转储选项:
    Dump Full 完整转储
    PE Tools会检测进程的PE文件头,并从ImageBase地址开始转储SizeOfImage大小的区域。
    Dump Partial 部分转储
    从相应进程内存的指定地址开始转储指定大小的部分,转储起始地址与大小在如图所示窗口设置:
    在这里插入图片描述
    Dump Region 区域转储
    进程内存(用户区域)中所有分配区域都被标示为某种状态,区域转储功能用于转储状态标示为COMMIT的内存区域。

    PE编辑器

    直接手动修改PE文件时,需要修改PE文件头。使用时直接拖动目标PE文件,或在工具栏中选择 Tools – PE Editor即可,可以进行很方便的编辑操作。

    代码注入

    代码注入是向目标进程插入独立运行代码并使之运行的技术,一般调用CreateRemoteThread() API以远程线程方式运行插入的代码,也被称为线程注入。
    在这里插入图片描述
    在这个过程中,代码以线程过程(Thread Procedure)形式插入,代码使用的数据则以线程参数形式传入。代码和数据是分别注入的。

    DLL注入和代码注入比较

    使用DLL注入技术,需要把代码放到某个DLL文件中,然后再将整个DLL文件注入目标进程。使用DLL注入技术时,整个DLL会被插入目标进程,代码和数据共存于内存,所以代码能正常运行。
    使用代码注入技术,仅向目标进程注入必要的代码。要想使注入代码正常运行,必须将代码中使用的数据一同注入(需要将已注入数据的地址明确告知代码)。使用代码注入技术的注意事项比DLL注入技术多很多。
    使用代码注入的原因
    1,占用内存少:如果注入代码和数据较少,就不需要做成DLL形式的注入。此时用代码注入和DLL注入有相同的效果,占用内存会更少。
    2,难以查找痕迹:使用DLL注入会在目标进程的内存中留下相关痕迹。使用代码注入方式几乎不会留下任何痕迹。
    3,其它:代码注入不需要另外的DLL文件,只要有代码注入程序即可。
    DLL注入技术适用于代码量大且复杂的情况,代码注入技术适用于代码量小且简单情况

    调试练习

    1,适用OD运行notepad.exe,设置Debugging options的events break on new thread选项,这样在新线程开启时候,会自动暂停调试。
    2,借助Process Explorer工具,查看notepad.exe的PID。
    3,在命令窗口输入PID作为参数,运行CodeInjection.exe。
    4,当代码注入成功后,调试器准确暂停在ThreadProc()函数开始位置。

    使用汇编语言编写注入代码

    借助OD汇编功能,使用汇编语言编写注入代码。使用汇编语言能生成比C语言更自由、更灵活的代码(如直接访问栈、寄存器的功能),然后将纯汇编语言编写的ThreadProc()函数注入notepad.exe进程。

    使用MASM编译器编译汇编语言

    MASM编译器支持多样化的Macro函数和库文件,比较便捷。
    使用OD命令:New origin here,可以把EIP更改为指定地址。OD代码窗口中移动光标到401000处,然后右键选该命令,EIP地址变为401000.
    在401000处执行汇编命令(快捷键 space)将弹出输入汇编命令窗口,就可使用OD编写简单汇编程序了。
    编写完成后,在OD代码菜单中选择鼠标右键 Copy to executable - ALL modification菜单。单击Copy all按钮。鼠标右键选择Save file项目,弹出保存文件对话框输入文件名称后保存。
    OD常用命令快捷键
    Assemble(Space):输入汇编代码
    Analysis(Ctrl + A):再次分析代码
    New origin here(Ctrl + Gray*):更改EIP

    API钩取

    钩取(Hooking)是一种截取信息、更改程序执行流向、添加新功能的技术。钩取的流程如下:
    1,使用反汇编器、调试器把握程序的结构和工作原理
    2,开发需要的“钩子”代码,用于修改bug、改善程序功能
    3,灵活操作可执行文件和进程内存,设置“钩子”代码
    钩取Win32 API的技术被称为API钩取,和消息钩取共同广泛应用于用户模式。API钩取是一种应用范围广泛的技术。当程序有源代码时,大部分不需要使用钩取技术。如果没有源代码或者难以修改源代码,使用钩取技术非常必要。

    API Application Programming Interface

    Windows OS中,用户程序需要使用系统资源(内存、文件、网络、音频、视频等),必须向系统内核申请。申请方法就是使用微软提供的Win32 API。如果没有API函数,就不能访问进程、线程、内存、文件、网络、注册表、图片、音频等系统资源。几乎所有进程都会默认加载kernel32.dll库,kernel32.dll会加载ntdll.dll库。
    在这里插入图片描述
    用户模式的应用程序代码访问系统资源时,由ntdll.dll向内核模式提出访问申请。

    API钩取优势

    1,在API调用前、后运行用户的钩子代码
    2,查看或操作传递给API的参数或API函数的返回值
    3,取消对API的调用,或更改执行流,运行用户代码
    正常调用API的流程
    在这里插入图片描述

    钩取API调用

    首先使用DLL注入技术将hook.dll注入目标进程的内存空间,然后用hook.dll中的钩取函数实现对kernel32中钩取函数的调用。这样每当由目标进程调用kernel32的钩取函数API都会首先调用hook.dll中的函数。
    在这里插入图片描述
    相当于中间人攻击
    API钩取的技术图表 经常使用的已经下划线标出
    在这里插入图片描述
    方法对象
    根据针对对象不同,API钩取方法可分为静态方法和动态方法。
    静态方法针对的是”文件“,动态方法针对的是进程内存。一般API钩取技术指动态方法。区别如下:
    在这里插入图片描述
    位置
    用来指出实施API钩取时应该操作哪部分(通常有3个部分)
    1,IAT:将内部的API地址更改为钩取函数地址,优点是实现非常简单,缺点是无法钩取不在IAT而是在程序中使用的API(如动态加载并使用DLL时)。
    2,代码:系统库映射到进程内存时,从中查找API实际地址并直接修改代码。该方法应用范围非常广泛。包括使用JMP指令修改起始代码、覆写函数局部、仅更改必需部分的局部。
    3,EAT:将记录在DLL的EAT中的API起始地址更改为钩取函数地址,也可以实现API钩取。
    技术
    向目标进程内存设置钩取函数的具体技术,大致分为调试法和注入法两类。注入法分为代码注入和DLL注入两种。
    1,调试:通过调试目标进程钩取API
    2,注入:向目标进程内存区域进行渗透的技术,根据注入对象不同可分为DLL注入和代码注入两种,DLL注入最为广泛。DLL注入时,首先在注入的DLL中创建钩取代码和设置代码,然后在DLLMain()中调用设置代码,注入同时会完成API钩取。而代码注入,比DLL注入更复杂。(杀毒软件能检测出DLL注入操作,但很难检测出代码注入操作)代码注入不是针对完整的PE映像,而是在执行代码和数据被注入状态下直接获取自身所需API地址来使用的。访问代码时需要十分小心防止访问到错误地址。
    API
    最后一列介绍的是相关API。

    记事本WriteFile() API钩取

    通过钩取kernel32的WriteFile() API执行不同动作。

    术语

    调试器:Debugger 调试的程序
    被调试者:Debuggee 被调试的程序

    调试器功能

    用来确认被调试者是否正常运行,发现程序错误。调试器能逐一执行被调试者的指令,拥有对寄存器和内存的所有访问权限。

    调试器工作原理

    调试进程注册后,每当Debuggee发生调试事件,OS就会暂停其运行,并向调试者报告相应事件。调试器对相应事件做处理后,被调试者继续运行。
    一般异常也属于调试事件
    若相应进程处于非调试状态,调试事件会在其自身的异常处理或者OS的异常处理机制中被处理掉
    调试器无法处理或不关心的调试事件最终由OS处理
    在这里插入图片描述
    调试器实现断点方法:找到要设置断点的代码在内存中的起始地址,将一个字节修改成0xCC即可。想继续调试时,再将其恢复原值即可。通过调试钩取API的技术就是利用断点这个特性。

    调试技术流程

    1,对想钩取的进程进行附加操作,使之成为被调试者
    2,钩子:将API起始地址的第一个字节修改为0xCC
    3,调用相应API时,控制权转移到调试器
    4,执行需要的操作(操作参数、返回值等)
    5,脱钩(将0xCC恢复原值)
    6,运行相应API(无0xCC的正常状态)
    7,钩子:再次修改为0xCC(为了继续钩取)
    8,控制权返还被调试者

    Practice 钩取Notepad.exe将小写字母改成大写字母

    工作流程

    312到324

    关于调试器

    OD

    是一款免费的调试器,免费但有强大而多样的功能,且支持插件扩展。但是由于OD是个人开发的工具,更新速度慢,后续版本开发周期很长,也不大稳定。

    IDA PRO

    是目前最强大的反汇编器&调试器。具有及其明显的反编译器特征。IDA拥有庞大多样的功能,安装反编译插件(Decompiler Plugin)可以为代码逆向分析提供极大便利。但使用起来比较复杂,加载时间比较长。

    WinDbg

    是Windows平台下的调试工具,是微软对外提供的免费Windows调试器。
    OD和IDA是用户态调试器,而WinDbg既是用户态调试器又是内核态调试器。主要用于内核模式调试。

    改变计算器显示文字

    步骤

    1,使用PEView看软件加载的DLL,发现SetWindowsTextW(),SetDlgItemTextW()API,它们负责向计算器文本显示框显示文本,假设只需要钩取SetWindowsTextW()一个API即可。(如果API名称最后面是W,代表API是宽字符版本,即Unicode码。如果最后面字符为A,代表该API是ASCII码字符)。
    2,通过打断点,找到API调用的位置,使用大写数字的Unicode码覆盖原来小写Unicode码的位置,发现发生改变。(Windows使用小端序标记法,覆写时候要逆序进行)。
    3,调用脚本写入即可。

    隐藏进程

    API代码修改技术

    API代码钩取技术通过将API代码的前五个字节修改为JMP xxxx指令来钩取API。调用执行被钩取的API时,修改后的JMP指令就会被执行,转而控制hooking函数。
    在这里插入图片描述

    进程隐藏

    为了隐藏某个进程,要潜入其它所有进程内存,钩取相关API。实现进程隐藏的关键不是进程本身而是其它进程。
    借助ZwQuerySystemInformation() API可以获取运行中的所有进程信息,形成一个链表。操作该链表(从链表中删除)即可隐藏相关进程。
    Eg
    假设要隐藏的进程为text.exe,钩取运行中的ProcExp.exe(进程查看器)或者taskmgr.exe(任务管理器)进程的ZwQuerySystemInformation() API,那么它们就无法查找到test.exe。但存在问题,比如要钩取全部类似工具是不可能的,或者如果用户新创建新的任务管理器进程,由于没有被钩取因此仍然能查找到想被隐藏的进程。
    解决方案是使用全局钩取系统中运行的所有进程的ZwQuerySystemInformation() API。并对后面启动的所有进程也做相同的钩取操作。

    全局API钩取

    全局API钩取实质是一种API钩取技术。针对进程为当前运行的所有进程和将来要运行的所有进程。所有进程新建时候,都要调用一个底层API,称为ntdll.ZwResumeThread() API.这个函数在进程创建后,主线程运行前被调用执行。只要钩取制革函数即可在不运行子进程代码的状态下钩取API。

    64位计算

    x64位CPU是指和现有x86兼容的64位CPU,主要用在普通PC和服务器,IA64和x64具有完全不同形态的CPU,主要用在大型服务器和超级计算机中。
    Win32 API在64位应用程序中几乎可以照搬使用。

    WOW64(Windows on Windows64)

    64位Windows提供了32位Windows的系统环境,用来运行32位应用程序。并借助WOW64将其变换为64位环境。
    WOW64只能运行在用户模式中,运行在内核模式中的驱动程序文件必须编译成64位。保证系统运行稳定。在64位windows文件夹结构中,system32文件夹是64位程序专用的,而SysWOW64则为32位程序专用的。
    在注册表中,64位Windows注册表分为32位注册表项和64位注册表项。32位进程访问HKLM\SOFTWARE的键时,WOW64会重定位到32位的HKLM\SOFTWARE\Wow6432Node下的键。

    x64处理器

    64位处理器中,内存地址为64位,含有绝对地址(VA)的指令大小都比原来增加4个字节,寄存器大小和栈的基本单位也变成64位。虚拟内存的实际大小为2的64次方,比x86要大很多。

    参考文献
    1,https://blog.csdn.net/qq_43633973/article/details/102378477 (PE头讲解,PEView使用)
    2,https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html(Intel 所有完整文档)
    3,http://blog.nsfocus.net/upack-works-case-analysis1/ UPACK工作原理及示例分析

    InjDll.exe dll注入专用工具

    是一个命令行注入工具,64位版本和32位版本一样。使用之前要先用PE工具看一下64位还是32位,用对应的注入专用工具。
    注意,必须执行线程要调用LoadLibrary()工作方式才可以,如果向应用反注入技术进程注入时候可能失败。原则上注入多少次就需要卸载多少次,这样才能正常。
    469

    展开全文
  • 开始学习逆向,从《逆向工程核心原理》这本经典开始,本篇笔记是第一部分:代码逆向技术基础

    前言

    开始学习逆向
    从《逆向工程核心原理》这本经典开始
    本篇笔记是第一部分:代码逆向技术基础

    一、关于逆向工程

    1、代码逆向工程

    代码逆向工程(Reverse Code ENgineering, RCE)(吐槽下,英文缩写真的太乱了,代码执行漏洞也是RCE)

    • 静态分析:不执行代码文件
    • 动态分析:调试分析代码流

    然后是些鸡汤
    建议循序渐进
    不要急躁贪婪
    共勉

    二、逆向分析hello world程序

    运行hello world

    在这里插入图片描述

    1、调试程序:熟悉基础指令

    扔进OD
    目标是寻找main函数

    在这里插入图片描述
    入口点(EntryPoint, EP)
    win可执行文件的代码入口点,依赖于CPU

    在这里插入图片描述
    依次是地址、指令、汇编代码、注释

    OD基本指令

    在这里插入图片描述
    用F7进入CALL命令

    在这里插入图片描述
    执行JMP命令

    在这里插入图片描述
    从40104F开始
    用上面的基本指令进行调试查看
    寻找main函数

    最终在40100有所发现

    在这里插入图片描述

    2、进一步熟悉调试器:更多指令和大本营

    指令

    在这里插入图片描述
    设置大本营的方法

    • cril+G定位地址,F4让调试流到定位处
    • F2设置断点(BP)
    • ;添加注释,通过查找命令找到
    • :输入标签

    3、查找指定代码

    (1)代码执行法

    即上面使用的方法
    适用于调试代码量不大且程序功能明确的情况

    不断F8寻找即可

    (2)字符串检索法

    右键 -> Search for -> All referenced text strings

    在这里插入图片描述

    (3)API检索法

    右键 -> Search for -> All intermodular calls

    由于程序跳出了消息窗口
    推断调用了user32.MessageBoxW()API

    在这里插入图片描述

    有时候OD不能列出所有API
    那就向DLL代码库寻找
    右键 -> Search for -> Name in all modules

    在这里插入图片描述

    4、打补丁修改字符串

    (1)直接修改字符串缓冲区

    即在dump直接修改
    ctrl+G找到位置
    ctrl+E修改

    在这里插入图片描述
    在这里插入图片描述

    但是这对字符串有长度限制
    且只是暂时的
    若要真正修改,需要做保存

    (2)内存里新建字符串并传递给消息函数

    在程序中找空白填写字符串
    然后改main函数里的push地址

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    5、小结:常用指令

    借hello world简单感受下调试

    指令如下

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    三、小端序标记法

    字节序:多字节数据的储存顺序

    • 小端序(little endian):内存地址低位存储数据的低位,内存地址高位存储数据的高位,逆序。常用于Intel x86 CPU
    • 大端序(big endian):内存地址低位存储数据的高位,内存地址高位存储数据的低位,直观。常用于大型UNIX服务器的RISC系列的CPU、网络协议

    在这里插入图片描述

    四、IA-32寄存器基本讲解

    IA-32(Intel Architecture 32位)寄存器

    • 通用寄存器:32位,8个
    • 段寄存器:16位,6个
    • 程序状态与控制寄存器:32位,1个
    • 指令指针寄存器:32位,1个

    在这里插入图片描述

    1、通用寄存器

    通用寄存器共8个,且在16位时就存在,故加E表示扩展

    • EAX:累加器,针对操作数和结果数据,所有win32 API函数都会把返回值保存在此
    • EBX:基址寄存器,DS段中的数据指针
    • ECX:计数器,字符串和循环操作,每循环一次减1
    • EDX:数据寄存器,I/O指针
    • EBP:扩展基址指针寄存器,SS段中栈内数据指针,表示栈区域的基地址
    • ESI:源变址寄存器,字符串操作源指针
    • EDI:目的变址寄存器,字符串操作目标指针
    • ESP:栈指针寄存器,SS段中栈指针,指示栈顶地址

    在这里插入图片描述

    2、段寄存器

    • 内存保护技术
    • 将内存划分为多个区段,并赋予起始地址、范围、访问权限
    • 和分页技术一起将虚拟内存变更为实际物理内存

    段寄存器共6个,指向的段描述符与虚拟内存结合形成线性地址,借助分页技术(如果有的话),转为实际物理地址

    • CS:代码段寄存器,存放程序代码所在段的段基址
    • SS:栈段寄存器,存放栈段的段基址
    • DS:数据段寄存器,存放数据段的段基址
    • ES:附加段寄存器
    • FS:数据段寄存器,调试中常用,计算SEH\TEB\PEB
    • GS:数据段寄存器

    在这里插入图片描述

    3、程序状态与控制寄存器

    名称为EFLAGS,标志寄存器
    每一位都有意义,看图
    重要的三位是

    • ZF:零标志,运算结果为0则置1
    • OF:溢出标志,有符号整数溢出或最高有效位改变,置1
    • CF:进位标志,无符号整数溢出,置1

    在这里插入图片描述

    4、指令指针寄存器

    名称为EIP,指令指针寄存器

    • 保存CPU要执行的指令地址
    • 不能直接修改
    • 可以通过JMP、Jcc、CALL、RET、中断、异常来间接修改

    五、栈

    • FILO原则
    • 高地址向低地址扩展
    • 特征:栈顶指针(ESP)初始状态指向栈底

    在这里插入图片描述

    六、分析abex’ crackme

    这个abex’ crackme运行如下

    在这里插入图片描述
    在这里插入图片描述

    1、OD打开

    在这里插入图片描述
    非常简短
    因为这是直接汇编写的

    逻辑非常简单清晰

    • 调用MessageBox API出现第一个窗口
    • 调用GetDriveType API获取C驱动器类型,然后操作它,使之被识别为CD-ROM类型
    • 对寄存器做些操作
    • 条件判断弹出调用成功或调用失败的窗口

    2、破解

    401026地址处指令
    修改为JMP 0040103D
    即直接改为跳转

    在这里插入图片描述
    在这里插入图片描述

    七、栈帧

    1、栈帧

    栈帧在程序中用于声明局部变量、调用函数

    • 用EBP寄存器访问站内局部变量、参数、函数返回地址等
    • 调用函数时,将作为基准点的ESP值保存到EBP

    栈帧结构如下:
    在这里插入图片描述

    2、示例:stackframe

    以stackframe这个程序为例
    感受下栈帧的使用
    观察栈的行为动作

    (1)生成栈帧

    源码:

    在这里插入图片描述

    在main函数的起始地址401020设置断点

    在这里插入图片描述
    此时寄存器EBP和ESP如下:

    在这里插入图片描述
    运行完401020和401021两步后就生成了栈帧

    (2)设置局部变量

    首先是分配空间:从ESP减去8个字节,为函数的局部变量a, b开辟8个字节的空间

    00401023  sub esp,0x8
    

    然后是指针两个

    在这里插入图片描述

    执行完后的栈内如下

    在这里插入图片描述

    (3)add函数

    在这里插入图片描述
    这一段是把b和a压入栈,注意顺序与c语言相反
    然后call401000的函数,即add

    在这里插入图片描述

    执行完之后
    栈内

    在这里插入图片描述

    (4)printf函数

    在这里插入图片描述

    • 地址0401044处的EAX寄存器中存储着函数add()的返回值,它是执行加法运算后的结果值3
    • 地址0040104A处的CALL 00401067命令中调用的是00401067地址处的函数,它是一个c标准库函数printf(),所有C标准库函数都有Visual C++编写而成
    • 由于上面的printf()函数有2个参数,大小为8个字节(32位寄存器+32位常量=64位=8字节),所以在0040104F地址处使用ADD命令,将ESP加上8个字节,把函数的参数从栈中删除

    (5)删除栈帧

    在这里插入图片描述
    释放EBP
    然后return

    八、分析abex’ crackme #2

    程序打开如下
    在这里插入图片描述
    在这里插入图片描述

    使用Visual Basic写的

    • 使用msvbvm60.dll专用引擎
    • VB文件可以便以为本地代码和伪代码
    • 各种信息以结构体形式保存

    1、OD打开

    EP的地址是401238

    • 把RT_MainStruct结构体的地址401E14压入栈
    • 然后CALL命令调用JMP,跳转到VB引擎的主函数ThunRTMain(),这是VB常用的间接调用法

    在这里插入图片描述

    2、破解

    通过字符串搜索找到“congratulation”

    在这里插入图片描述

    发现403332 的条件判断
    往上面看,push的edx和eax是我们需要的参数
    再往上看,找到栈帧
    设置断点并调试

    在这里插入图片描述

    然后一步步调试

    • 找到记录Name的地方
    • 找到加密过程
      • 从Name逐一读取字符
      • 字符转换ASCII码
      • ASCII码加64
      • 转换为字符
      • 连接字符
    • 最后找到Serial=B6C9DAC9

    在这里插入图片描述
    这里过程太长了
    就不一一记录
    可以参考逆向工程核心原理学习笔记(二十七):abex’crackme #2 破解算法

    九、Process Explorer

    工具推荐
    使用可参考
    Process Explorer使用图文教程

    十、函数调用约定

    函数调用约定(Calling Convention),是对函数调用时如何传递参数的约定

    • 函数执行完成后,栈中的参数不用管,会被覆盖
    • 函数执行完成后,ESP要恢复到调用之前,不然ESP指向栈底,无法使用栈,这就是函数调用约定要解决的问题

    1、cdecl

    主要在C语言中使用,调用者负责处理栈

    例子

    #include "stdio.h"
    
    int add(int a, int b)
    {
    	return (a+b);
    }
    int main(int argc, char* argv[])
    {
    	return add(1,2);
    }
    

    扔进OD可以看到

    • add函数的参数逆序压入栈
    • 然后用 ESP, 8 命令整理栈
    • main函数直接清理压入栈中的函数参数
      在这里插入图片描述

    2、stdcall

    常用于Win32 API,由被调用者清理栈
    比cdecl的代码尺寸小

    例子

    #include "stdio.h"
    
    int _stdcall add(int a, int b)
    {
    	return (a+b);
    }
    int main(int argc, char* argv[])
    {
    	return add(1,2);
    }
    

    扔进OD可以发现

    • 省略了 ESP, 8 命令
    • 用 retn 8 命令(retn + pop 8),使ESP增加到指定大小

    在这里插入图片描述

    3、fastcall

    与stdcall类似,但通常用寄存器(而非栈内存)传递参数,若有4个参数,则前2个参数分别用ECX和EDX传递

    由于使用寄存器,对函数的调用显然要快很多,但可能需要额外的系统开销来管理ECX和EDX

    结语

    主要是以下内容

    • OD的基础指令
    • 注意小端字节序
    • IA-32位寄存器的基本介绍
    • 栈的简单介绍
    • 栈帧的介绍
    • 函数调用约定

    一个网站:https://www.tuts4you.com/

    展开全文
  • 继续学习《逆向工程核心原理》,本篇笔记是第六部分:高级逆向分析技术,包括TLS、TEB、PEB、SEH和IA-32指令等内容

    前言

    继续学习《逆向工程核心原理》,本篇笔记是第六部分:高级逆向分析技术,包括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指令需要进行进一步学习

    展开全文
  • 文章目录OllyDbg窗口及...visual studio新建工程文件,编译以下程序时选择visual studio的Release模式,减少调试信息。点击本地windows调试器进行编译运行。 #include "windows.h" #include "tchar.h" int _tmain(int..
  • 逆向工程一般指,通过分析物体、机械设备或系统,了解其结构、功能、行为等,掌握其中原理并改善其不足之处、添加新创意的一系列过程。 代码逆向工程逆向工程在软件领域中的应用。 逆向分析方法 逆向分析方法大致...
  • 逆向工程核心原理——消息钩取

    千次阅读 2021-12-07 12:07:44
    钩子(HOOK) 英文Hook一词,翻成中文是“钩子”、“鱼钩”的意思,泛指钓取所需东西而使用的一切工 具。“钩子”这一基本含义延伸发展为“偷看或截取信息时所用的手段或工具”。下面举例向各位 进一步说明“钩子”...
  • 继续学习《逆向工程核心原理》,本篇笔记是第七部分:反调试技术,包括一些静态反调试技术和动态反调试技术
  • 逆向工程核心原理代码以及示例文件,和这篇博客中提到的文件相同,我花了5块钱叫别人帮忙下载的,现在共享给大家,不用谢我,我叫雷锋https://blog.csdn.net/reversecore/article/details/23971921
  • 逆向工程核心原理的配套源文件 逆向工程核心原理的配套源文件
  • 逆向工程核心原理 小结

    千次阅读 2018-05-21 12:52:15
    首先,先说明我在阅读和动手过程中的第一个问题。1.如果要跟着书上的过程进行调试,并且你是用的是VS,那么建议你关闭C/C++优化。因为优化后的.exe会省略很多细节,不利于小白进行分析。具体关闭细节如下:在解决方案...
  • 逆向工程核心原理

    千次阅读 2019-01-08 08:06:35
    逆向工程核心原理》 基本信息 作者: (韩)李承远  译者: 武传海 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9787115350183 上架时间:2014-4-15 出版日期:2014 年5月 开本:16开 ...
  • 原理是:在文件中另外设置被称为“洞穴代码”的“补丁代码”,EP代码解码后修改JMP指令,运行洞穴代码。在洞穴代码中执行补丁代码后再跳转到OEP处。 patchme程序 运行 点击确定 修改上述两处字符串即可。 调试 OD...
  • 主要方法有三:消息钩取,创建远程线程和修改注册表 一、消息钩取 主要运用Windows系统的SetWindowsHookExA() API 在21章有个阻塞notepad键盘输入的例子,有两个文件,HookMain.exe和KeyHook.dll。...
  • hookiat.dll // include #include "stdio.h" #include "wchar.h" #include "windows.h" // typedef typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString); ...FARPROC g_pOrgFunc = NU...
  • 什么是PE文件? PE文件的全称是Portable Executable,意为可移植的可执行的文件 PE文件是指32位可执行文件,也称为PE32。64位的可执行文件称为PE+或PE32+,是PE(PE32)的一种扩展形式(请注意不是PE64)。...
  • 逆向工程核心原理中的unpackme,之前写了博文,有几个人都在求这个资源
  • 什么是代码注入? 代码注入是一种向目标进程插入独立运行代码并使之运行的技术,它一般调用CreateRemoteThread()API以远程线程形式运行插入的代码,所以也被称为线程注入。 使用代码注入的原因 ...
  • 逆向工程核心原理1:基本介绍

    千次阅读 2018-06-25 21:07:59
    逆向工具主要分为以下几种,每种对应特定的用途IDA pro : 主要用于静态分析,能够还原成C代码Ollydbg : 主要用于动态进程调试,能够直接挂载进程进行内存修改(例如外挂等)ProcessExplorer : 进程查看工具。...
  • 逆向分析Hello World!程序 源程序 #include "windows.h" #include "tchar.h" int _tmain(int argc,TCHAR *argv[]) { MessageBox(NULL, L"Hello World!", L"www.reversecore.com", MB_OK); return 0; } ctrl...
  • 继续学习《逆向工程核心原理》,本篇笔记是第三部分:DLL注入,主要包括三种DLL注入、DLL卸载、修改PE、代码注入等内容
  • 本文总结自《逆向工程核心原理》 以代码为主,普通的调试逆向就不跟了,有感觉特别好的知识点再跟一下记录一下。 1.消息钩子 2. 代码 dll代码 #include "main.h" #include #include // a sample exported ...
  • 包含有逆向工程核心原理源代码
  • 逆向工程源码.zip

    2019-10-07 21:53:32
    逆向工程核心原理一书的源代码,有需要的朋友可以下载。
  • 逆向工程核心原理》学习笔记7 UPack加壳 (好久没更了orz) UPack是一款用于给软件加壳的程序,通过对PE头的变形和压缩,达到不让别人识别文件作用的功能。因此骇客经常使用这种加壳程序对他们所编写的病毒进行...
  • 什么是DLL注入? ...dll注入技术在逆向工程,病毒,外挂,调试等技术领域都有广泛的应用,它也是Windows API hook技术的基础。 改善功能与修复Bug改善功能与修复Bug DLL注入技术可用于改善功.
  • 1.1 给TextView.exe添加myhack3.dll1.1.1 查看IDT是否有地方再加一个dll查看IDT,发现没有地方再添加一个dll了,需要移动IDT到更大的地方。在文件中找一个空白的区域,将IDT移动过去。.rdata区域,从0x8C60后就是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,477
精华内容 3,390
关键字:

逆向工程核心原理