ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

逆向工程核心原理

2022-06-23 10:35:09  阅读:233  来源: 互联网

标签:逆向 文件 核心 PE 地址 内存 寄存器 原理 节区


一.熟悉调试器

1.设置“大本营”的四种方法

每次重新运行调试器,都会回到程序的入口点,为方便使用,可以设置某个重要的点(地址),使调试可以快速转到设置点上。

(1)Goto命令

记录设置大本营的地址,执行Go to(Ctrl + G)命令,输入地址,使光标定位到该地址,按F4,让调试流运行到该处。

(2)设置断点

按F2在大本营设置断点,设置后调试运行到断点处将会暂停。也可在Breakpoint对话框双击断点跳转。

(3)注释

按键盘上的 ";" 键 ,可以在指定地址添加注释。同样鼠标右键菜单中选择Search for—User defined comment,可以看到用户的所有注释,双击地址可定位。

(4)标签

按键盘上的 ":" 键 ,可以在指定地址添加标签。同样鼠标右键菜单中选择Search for—User defined labels菜单可打开标签窗口

2.快速查找指定代码的四种方法

(1)代码执行法

若程序功能非常明确时,可以逐条执行来查找需要查找的位置。

(2)字符串检索法

Search for—All referenced text strings

查看相应字符串所在地址

(3)API检索法
①在调用代码中设置断点

Search for—All intermodular calls

当应用程序向显示器画面输出内容时,需要在程序内部调用Win32 API。当我们能够推测出来程序运行调用的API时,可以查找所有的API,寻找地址。

②在API代码中设置断点

Search for—Name in all calls

当不能列出API函数时,可以向DLL代码库添加断点。因为当编写的应用程序执行操作时,必然会使用操作系统提供的API想OS提出请求,然后被调用API对应的系统DLL文件会加载到应用程序的内存中,Alt+M打开内存映射窗口,找到系统库函数,右键Name in all modules可以显示该函数所有API,查找函数。

3.“打补丁”修改字符串

"打补丁"不仅可以修复bug,还能向程序中添加新功能。对象可以是文件、内存,还能是程序的代码、数据等。

修改字符串的两种方法
①直接在缓冲区修改字符串

选中16进制代码部分,Ctrl+E打开编辑模式,修改字符串。注意,当修改范围超过原有字符串,可能损失后面的数据。

  • 保存更改到可执行文件

上面的修改是暂时的,若想永久保存,要把更改后的程序保存为一个可执行文件。

在dump窗口选中更改后的字符串,右键菜单中选择Copy to executable file,打开Hex窗口,继续右键选择Save file,输入文件名,保存为.exe文件。

②在其他内存区域新建字符串并传递给消息函数

字符串以参数形式传递给函数,此时传递的是所在区域的首地址,若改变字符串地址,消息框就可以变成更改后的字符串。在内存中选择NULL填充区域,Ctrl+E填充字符串,将填充后的首地址传递给函数,光标定位到要修改的首地址处,按空格键打开Assemble窗口,修改地址即可。

二.小端序标记法

字节序

字节序是多字节数据在计算机内存中存放的字节顺序,主要分为小端序和大端序。

举例来说,数值0x2211使用两个字节储存:高位字节是0x22,低位字节是0x11

  • 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。

  • 小端字节序:低位字节在前,高位字节在后,即以0x1122形式储存。

三.IA-32寄存器

1.CPU寄存器

寄存器是CPU内部用来存放数据的一些小型存储区域,与RAM(随机存储器)不同,CPU访问RAM要经过较长的物理路径,话费时间更长;寄存器在CPU内部,具有更快的读写速度。

2.IA-32寄存器

包括基本程序运行寄存器、控制寄存器、内存管理寄存器、调试寄存器。

基本程序运行寄存器

  • 通用寄存器(32位,8个)

 

 

  • 段寄存器(16位,6个)

 

 

  • 程序状态与控制寄存器(32位,1个)

 

 

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

 

 

(1)通用寄存器

用于传送和暂存数据,也可参与算数逻辑运算,并保存运算结果。

为了实现对低16位的兼容,各寄存器又可以分为高(High)和低(Low)。

以EAX为例:

  • EAX:(0~31)32位

  • AX:(0~15)EAX的低16位

  • AH:(8~15)AX的高8位

  • AL:(0~7)AX的低8位

    其余寄存器如图所示:

 

 

各寄存器的名称及作用:

EAX:(针对操作数和结果数据的)累加器

EBX:(DS段中的数据指针)基址寄存器

ECX:(字符串和循环操作的)计数器

EDX:(I/O指针)数据寄存器

上述寄存器只要用在算术运算(ADD、SUB、XOR、OR等)指令中。此外,ECX也可在循环命令中循环次数,每循环一次,ECX减1;EAX一般用在函数返回值中,所有Win32API函数都会先把返回值保存到EAX再返回。

EBP:(SS段中栈内数据指针)扩展基址指针寄存器

ESI:(字符串操作源指针)源变址寄存器

EDI:(字符串操作目标指针)目的变址寄存器

ESP:(SS段中栈指针)栈指针寄存器

(2)段寄存器

段是一种内存保护技术,它把内存划分为多个区段,并为每个区段赋予起始地址、范围、访问权限等,以保护内存。此外,它还同分页技术一起用于将虚拟内存变更为实际物理内存。

段寄存器总共由6种寄存器组成,分别为CS、SS、DS、ES、FS、GS,每个寄存器的大小为16位,即2个字节。每个段寄存器指向的段描述符与虚拟内存结合,形成一个线性地址,借助分页技术,线性地址最终转换为实际的物理地址。

CS:代码段寄存器

SS:栈段寄存器

DS:数据段寄存器

ES:附加(数据)段寄存器

FS:数据段寄存器

GS:数据段寄存器

顾名思义,CS用来存放应用程序代码所在段的段基址,SS用于存放栈段的段基址,DS用于存放数据段的段基址,ES、FS、GS来存放程序使用的附加数据段的段基址。

(3)程序状态与控制寄存器

EFLAGS:Flag Register,标志寄存器

大小为4字节,32位,由原来的16位FLAGS寄存器扩展而来。每一位都有意义,每一位的值或为1或为0,代表On/Off或True/False。目前只需掌握三个标志:ZF(Zero FLag零标志),OF(Overflow Flag)溢出标志、CF(Carry Flag,进位标志)。

ZF:若运算结果为0,则结果为1(True),否则其值为0(False)

OF:有符号整数(signed integer)溢出时,OF值被置为1。

CF:无符号整数(unsigned integer)溢出时,其值被置为1。

(4)指令指针寄存器

EIP:Instruction Pointer,指令指针寄存器

指令指针寄存器保存着CPU要执行的指令地址,大小为32位(4个字节)。程序运行时,CPU会读取EIP中一条指令的地址,传送指令到指令缓冲区,EIP寄存器的值自动增加,增加的大小是读取指令的字节大小。

四、栈

1.栈

栈内存在进程中的作用如下

(1)暂时保存函数内的局部变量

(2)调用函数时传递参数

(3)保存函数返回后的地址

栈作为一种数据结构,按照先进后出的原则存储数据。

栈的特性

 

 

一个进程中,栈顶指针(ESP)初始状态指向栈底端。执行PUSH命令将数据压入栈时,栈顶指针就会上移到栈顶端,栈顶指针减小,执行POP命令从栈中弹出数据时,若栈为空,则栈顶指针重新移动到栈底端,栈顶指针增大。所以,栈是一种由高地址向低地址扩展的数据结构。

五.栈帧

栈帧是利用EBP寄存器访问栈内局部变量、参数、函数返回地址等的手段。栈顶指针ESP寄存器会在程序运行中发生变化,若以ESP为基准访问数据,会产生其他问题。所以,把ESP的值保存在EBP中,以EBP为基准访问数据,这就是栈帧的作用。

六、函数调用约定

函数调用约定是对函数如何传递参数的一种约定。调用函数前先把参数压入栈再传递给参数,栈是定义在进程中的内存空间,当进程运行时确定栈内存的大小。

Q:函数执行完成后,栈中参数如何处理。

A:不用管。

参数临时储存在栈中,再向栈存放其他值时,原有值会被覆盖掉。且栈内存固定,既不能也没必要释放内存。

Q:函数执行完毕,ESP的值如何变化?

A:ESP值要恢复到函数调用之前,这样可引用的栈大小才不会缩减。

因为栈内存固定,ESP来指示当前栈的位置,若ESP指向栈底,则无法再使用该栈。函数调用后如何处理ESP,是函数调用约定要解决的问题。

主要的函数调用如下:

  • cdecl

  • stdcall

  • fastcall

1.cdecl

cdecl是主要在C语言中使用的方式,调用者负责处理栈。参数由右向左入栈。

调用函数的参数入栈后,调用者函数直接清理其压入栈的函数参数。

2.stdcall

此方式常用于Win32API,调用函数的参数入栈(由右向左)后,调用者函数直接清理其压入栈的函数参数。

好处在于,代码尺寸小,拥有更好的兼容性。

3.fastcall

fastcall与stdcall方式基本类似,当该方式通常会使用寄存器(函数的第一个和第二个参数通过ecx和edx传递)而非栈内存来传递参数。若某函数有四个参数,则前两个参数分别使用ECX、EDX寄存器传递。

七.PE文件

PE(Portable Executable)文件是Windows操作系统下使用的可执行文件格式。PE文件是指32位的可执行文件,64位的可执行文件称为PE+或PE32+,是PE(32)文件的一种扩展形式。

1.PE文件格式

 

 

严格的说,OBJ(对象)文件之外的所有文件都是可执行的。DLL、SYS文件不能直接在shell中运行,但可以使用调试器等等执行。

(1)基本结构

DOS(磁盘操作系统)头到节区头是PE头部分,其下的节区合称PE体。文件中使用偏移,内存中使用VA(Virtual Address,虚拟地址)来表示位置。文件的内容一般可分为代码(.text)、数据(.data)、资源(.rsrc)节,分别保存。

节区:Flash芯片的最小数据存储单位

节区头定义了各节区在文件或内存中的大小、位置、属性等。PE头与各节区的尾部存在一个区域,称为NULL填充。

 

 

(2)VA&RVA

VA指进程虚拟内存的绝对地址,RVA(相对虚拟地址)指从某个基准位置(ImageBase)开始的相对地址。

换算关系:RVA+ImageBase=VA

2.PE头

(1)DOS头

DOS文件广泛使用时,微软为了PE文件对DOS文件的兼容性,在PE头的最前面添加了一个IMAGE—DOS—HEADER结构体,来扩展已有的DOS EXE头。

IMAGE—DOS—HEADER结构体大小为40字节,必须知道两个重要成员。e_magic与e_lfanew.

e_magic:DOS签名(4D5A=>ASCII值"MZ")

e_lfanew:指示NT头的偏移(不同文件拥有可变值)

所有PE文件在开始部分都有DOS签名(“MZ”),e_lfanew值指向NT头所在位置。

(2)DOS存根

DOS存根在DOS头下方,是可选项,且大小不固定。

DOS存根的内容是当我们的程序在DOS环境中运行时执行的代码, 也就是给一个提示信息:This is program cannot be run in DOS mode, 那我们是可以随便将其内容修改为自己想填充的东西, 反正不会影响在window os中的运行, 但记住这个大小是不能修改的, 会影响后面指令索引地址跟着出错, 最后程序崩溃

(3)NT头(IMAGE—NT—HEADERS)

此结构体由三个成员组成,第一成员为签名结构体,值为50450000h(“PE”00)另外两个成员分别为文件头与可选头结构体。

(4)文件头

表现文件大致属性的IMAGE—FILE—HEADERS结构体。结构体有以下4种重要成员。

#1.Machine

每个CPU都拥有唯一的Machine码,兼容32位Intel x86芯片的Machine码为014c。

#2.NumberOfSection

用来指出文件中存在的节区数量。该值必须大于0,且当定义的节区数量与实际节区不同时,会发生运行错误。

#3.SizeOfOptionalHeader

IMAGE—NT—HEADERS结构体的最后一个成员是IMAGE—OPTIONAL—HEADER32结构体。

SizeOfOptionalHeader成员用来指出IMAGE—OPTIONAL—HEADER32结构体的长度。

#4.Characteristics

用来标识文件的属性,文件是否是可运行的状态、是否为DLL文件等信息。

(5)可选头

IMAGE—OPTIONAL—HEADER32是结构体中最大的,需要关注下列成员。

#1.Magic

为IMAGE—OPTIONAL—HEADER32结构体时,Magic码为10B;为IMAGE—OPTIONAL—HEADER64结构体时,Magic码为20B。

#2.AddressOfEntryPoint

AddressOfEntryPoint持有EP的RVA值,该值指出程序最先执行的代码起始地址。

#3.ImageBase

32位系统进程虚拟内存范围是0~FFFFFFFF,ImageBase指出文件的优先装入地址。EXE、DLL文件被装载到用户内存的0~7FFFFFFF中,SYS文件被载入内核内存的80000000~FFFFFFFF中。

#4.SectionAlignment,FileAlignment

PE文件的Body部分划分为若干节区,FileAlignment指定了节区在磁盘文件中的最小单位,而SectionAlignment则指定了节区在内存中的最小单位。

#5.SizeOfImage

加载PE文件到内存时,SizeOfImage指定了PE Image在虚拟内存中所占空间的大小。

#6.SizeOfHeader

用来指出整个PE头的大小。

#7.Subsystem

该Subsystem值用来区分系统驱动文件(*.sys)与普通的可执行文件(*.exe,*.dll)。

#8.NumberOfRvaAndSizes

用来指定DataDirectory数组的个数

#9.DataDirectory

是由IMAGE_DATA_DIRECTORY结构体构成的数组。

(5)节区头

PE文件中的code(代码)、data(数据)、resource(资源)等按照属性分类在不同节区,然后把各节区属性记录在节区头中(节区属性中有文件/内存的起始位置、大小、访问权限等)。

IMAGE_SECTION_HEADER

节区头是由IMAGE_SECTION_HEADER结构体组成的数组,每个结构体对应一个节区。

重要成员如下:

 

 

3.RVA to RAW

PE文件加载到内存时,每个节区需要准确完成内存地址与文件偏移间的映射,称为RVA to RAW

公式:RAW=RVA - VirtualAddress(内存中节区起始位置) + PointerToRawData(磁盘中节区起始位置)。

4.IAT(Import Address Table,导入地址表)

IAT保存的内容与Windows操作系统的核心进程、内存、DLL结构有关,简言之,IAT是一种表格,用来记录正在使用哪些库中的哪些函数。

(1)DLL(动态链接库)

为了提高计算机效率, 引入了DLL概念。描述如下:

  • 不把库包含到程序中,单独组成DLL文件,需要时调用;

  • 内存映射技术使加载后的DLL代码、资源在多个进程中实现共享;

  • 更新库时只要替换相关DLL文件即可,简便易行。

加载DLL方式实际有两种:一种是“显式链接”,程序使用DLL加载,使用完毕后释放内存;另一种是“隐式链接”,程序开始时即一同加载DLL,程序终止时再释放占用的内存。IAT提供的机制与隐式链接有关。

(2)IMAGE_IMPORT_DESCRIPTOR

IMAGE_IMPORT_DDESCRIPTOR结构体记录着PE文件要导入哪些库文件。

注意

Import:导入,向库提供服务(函数)

Export:导出,从库向其他PE文件提供服务(函数)

执行一个普通程序时往往需要导入多个库,导入多少库就存在多少IMAGE_IMPORT_DDESCRIPTOR结构体,这些结构体形成了数组,且结构体数组最后以NULL结构体结束。

重要成员如下:

 

 

5.EAT

在Windows操作系统中,库是问了方便程序调用而组合起来的包含函数的集合体。EAT是一种核心机制,应用程序只有通过EAT才能准确求得从库中导出函数的起始地址。与IAT一样,特定结构体IMAGE_EXPORT_DESCRIPTOR(在PE头中)保存导出信息,且PE文件中仅有一个用来说明库EAT的结构体。

用来说明IAT的IMAGE_IMPORT_DESCRIPTOR以数组存在,且拥有多个成员,是因为PE文件可以同时导入多个库

重要成员

 

 

GetProcAddress()

从库中获得函数地址的API为GetProcAddress()函数,该API引用EAT来获取指定API的地址。

 

 

八.运行时压缩

1.数据压缩

任何文件(数据)都由0或1组成,只要有合适的压缩算法,就能缩减大小。若压缩后文件能100%恢复,称为无损压缩;若不能恢复原状,称为有损压缩。

2.运行时压缩器

运行时压缩器是针对可执行文件而言的,可执行文件内部含有解压缩代码,文件在运行瞬间于内存中解压缩后执行。

运行时压缩文件也是PE文件,内部含有原PE文件与解码程序

 

 

把普通PE文件创建成运行时压缩文件的实用程序称为压缩器,经反逆向技术特别处理的压缩器称为保护器。

(1)压缩器

PE压缩器指可执行文件压缩器,是PE文件的专用压缩器

目的:

  • 缩小PE文件的尺寸

便于传输和保存

  • 隐藏PE文件内部代码与资源

可以隐藏PE文件内的代码及资源(字符串、API名称字符串)

(2)保护器

PE保护器是一类保护PE文件免受代码逆向分析的实用程序。它们不像普通的压缩器一样仅对PE文件进行运行时压缩,还应用了多种防止代码逆向分析的工具。

使用目的

  • 防止破解

  • 保护代码与资源

不仅可以保护PE文件本身,还可在文件运行时保护进程内存,防止打开Dump窗口。

3.运行时压缩的文件

以notepad.exe与notepad_upx.exe为例

 

 

  • PE头大小一样

  • 节区名称改变(.text——>UPX0、.data——>UPX1)

  • 第一个节区的RawDataSize(磁盘文件中节区所占大小) = 0(文件大小为0)

  • 资源节区(.rsrc)大小几乎无变化

在第一个节区中RawDataSize为0,即第一节区在磁盘文件中不存在,但第一节区VirtualSize为0010000,即在内存中存在。由此可知,经过UPX压缩后的PE文件在运行瞬间将压缩的代码解压到内存中的第一节区中,解压缩代码和压缩的源代码都在第二节区,文件运行时首先执行解压缩代码,将处于压缩状态的源代码解压到第一节区,解压结束后再运行源文件的EP代码。

4.快速查找UPX OEP的方法

OEP:源文件的EP为OEP

(1)在POPAD指令后的JMP指令处设置断点

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

(2)在栈中设置硬件断点

该方法也利用UPX的PUSHAD/POPAD指令的特点。在执行PUSHAD命令后,EAX到EDI寄存器的值依次被存储到栈,对该栈地址设置硬件断点,当执行POPAD指令时会访问该内存地址来获取寄存器的值,从而触发断点。

九.基址重定位表

1.PE重定位

基址重定位表(Base Relocation Table),记录PE重定位时需要修改的硬编码地址的位置。

一般地,向进程的虚拟内存加载PE文件(EXE、DLL、SYS)时,文件会被加载到PE头的ImageBase所指的地址处。若加载的文件为DLL或SYS,且ImageBase位置加载了其他DLL或SYS文件时,则会进行PE重定位。

PE重定位是指PE文件无法加载到ImageBase所在位置时,加载到其他地址所发生的处理行为。

在进程创造好之后,EXE文件会首先加载到内存当中,因而无需考虑重定位的问题。

系统的DLL实际不会发生重定位,因为同一系统的kernel32.dll、user32.dll等会被加载到自身固有的ImageBase。

基址重定位表(以notepad.exe为例)

位于PE头的DataDirectory数组

 

 

基址重定位表的地址为RVA 2F000

 

 

第一个成员为VirtualAddress,实际是RVA值。

第二个成员SizeOfBlock,指重定位块的大小。

最后一项是TypeOffset数组,不属于结构体成员,是以注释形式存在,表示在该结构体下会出现WOED类型的数组。

TypeOffset值为2个字节,16位大小,由4位Type与12位的Offset合成的。

高4位用作Type,PE文件常见值为3,64位的PE+文件中常见值为A

低12位是真正的位移。

 

2.PE重定位操作原理

1、在应用程序中查找硬编码的地址位置;

换算等式:VirtualAddress+Offset=RVA

用RVA查找寻找硬编码地址

2、读取值后,减去ImageBase(VA转换为RVA);

3、加上实际加载地址(RVA转换为VA)。

查找硬编码的地址的位置,会使用到重定位表,它是记录硬编码地址偏移的列表。

 

标签:逆向,文件,核心,PE,地址,内存,寄存器,原理,节区
来源: https://www.cnblogs.com/zzy-AVA/p/16404248.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有