【逆向】PE结构分析和关于PE的一些问题及解决

本文详细探讨了PE文件的结构,包括程序装入、分页机制和地址转换。重点讲解了PE的各个部分,如导入表(IDT、IAT、INT)、导出表和重定位表。通过对经典例题的分析,解释了如何计算虚拟地址到文件偏移的转换,并介绍了如何在PE中查找和计算这些地址。同时,文章还涉及了分页和动态地址重定位的概念,以及如何在内存中查找和定位导入表。
摘要由CSDN通过智能技术生成

目录

前言:

开始需要大概了解一些名词:

程序的装入(地址的变换)

分页机制:

大概带着这些知识后,再来看PE的结构:

一些结构的定义

关于地址计算的一些问题

经典例题:

例题分析

☆如何在PE中查找并计算这些地址?

计算:解决地址转换问题(使用PEView)

①计算每个节区在内存中的位置、大小:

②每个节区在文件中的偏移

 ③每个节区在内存中的大小?每个节区在文件中的大小?

顺便一提,在这里要注意的一些点:

导入表

导入表在哪?定位导入表在源文件中的位置。

什么是INT、IAT、IDT?

IAT(Import Address Table)、INT(Import Name Table)、IDT(Import Directory Table)

从Import_Descriptor得到IAT的步骤:

导出表

对一个动态链接库里导出的函数的调用,既可以通过函数名称来进行,也可以通过函数在导出表的索引来进行。

重定位表


前言:

可以从这里入门https://www.cnblogs.com/milantgh/p/3953713.html

配合这个博客查找上面不懂的https://www.cnblogs.com/bokernb/articles/6116512.html 

开始需要大概了解一些名词:

关于地址和一些名词的理解,这个博客非常细致:https://blog.csdn.net/hguisu/article/details/5713099

需要知道的一些地址(后面会再来分析):

  • VA:virtrual address  虚拟地址,也就是内存中的地址:
  • RVA:relative virtrual address   相对虚拟地址,等于VA-ImageBase
  • ImageBase:基地址

虚拟内存不考虑物理内存的大小和信息存放的实际位置,只规定进程中相互关联信息的相对位置。每个进程都拥有自己的虚拟内存,且虚拟内存的大小由处理机的地址结构和寻址方式决定。

关于程序如何运行

  •          首先是要编译,由编译程序(Compiler)将用户源代码编译成cpu可执行的目标代码,产生了若干个目标模块(Object  Module)(即若干程序段),
  •         其次是链接,由链接程序(Linker)将编译后形成的一组目标模块(程序段),以及它们所需要的库函数链接在一起,形成一个完整的装入模块(Load  Module);
  •          最后是装入,由装入程序(Loader)将装入模块装入内存

程序的装入(地址的变换)

绝对装入方式:程序中的逻辑地址与实际内存地址完全相同

静态地址重定位(可重定位装入方式) :多道程序环境下,编译程序不可能预知所编译的目标模块应放在内存的何处,所得到的目标模块的起始地址通常是从 0 开始的,程序中的其它地址也都是相对于起始地址计算的,根据内存的当前情况,将装入模块装入到内存的适当位置。                              静态地址重定位即在程序开始运行前,程序中指令和数据的各个地址均已完成重定位,即完成虚拟地址到内存地址映射。地址变换通常是在装入时一次完成的,以后不再改变。                    采用可重定位装入程序将装入模块装入内存后, 会使装入模块中的所有逻辑地址与实际装入内存的物理地址不同

可以看个例题:

动态地址重地位(动态运行时装入方式):可重定位装入方式可将装入模块装入到内存中任何允许的位置,故可用于多道程序环境;但这种方式并不允许程序运行时在内存中移动位置。因为,程序在内存中的移动,意味着它的物理位置发生了变化, 这时必须对程序和数据的地址(是绝对地址)进行修改后方能运行。然而,实际情况是,在运行过程中它在内存中的位置可能经常要改变,此时就应采用动态运行时装入的方式。
在每次访问内存单元前才将要访问的程序或数据地址变换成内存地址

动态重定位可使装配模块不加任何修改而装入内存,装入之后再搬迁也不会影响其正确执行。为使地址转换不影响指令的执行速度,这种方式需要一个重定位寄存器的支持。一个程序由若干个相对独立的目标模块组成时,每个目标模块各装入一个存储区域,这些存储区域可以不是顺序相邻的,只要各个模块有自己对应的定位寄存器就行。

分页机制:

  • 当使用分页时,处理器会把线性地址空间划分成固定大小的页面(4KB),这些页面可以映射到物理内存或磁盘存储空间中,
  • 当一个程序引用内存中的逻辑地址时,处理器会把该逻辑地址转换成一个线性地址,然后使用分页机制把该线性地址转换成对应的物理地址

即(分页机制:https://blog.csdn.net/programmingring/article/details/21258529

背景:

因为段的长度不定, 在分配内存时:可能会发生内存中的空闲区域小于要加载的段, 或者空闲区域远远大于要加载的段.

在前一种情况下, 需要另外寻找合适的空闲区域;

在后一种情况下, 分配会成功, 但太过于浪费.

为了解决这个问题, 从80386处理器开始, 引入了分页机制.

分页功能从总体上来说, 是用长度固定的页来代替长度不一定的段,

藉此解决因段长度不同而带来的内存空间管理问题. 尽管操作系统也可以用软件来实施固定长度的内存分配, 但太过于复杂, 由处理器固件来做这件事, 可以使速度和效率最大化.

分页机制概述:

处理器中有负责分段管理的段部件, 每个程序或任务都有自己的段, 这些段都用段描述符定义. 随着程序的执行, 当要访问内存, 就用段地址加上偏移量, 段部件就会输出一个线性地址. (在单纯的分段模式(例如单任务DOS系统)下, 线性地址就是物理地址.)   

一旦决定采用页式内存管理, 就应当把4GB内存分成大小相同的页.

页的最小单位是4KB, 也就是4096字节, 用十六进制表示就是0x1000.

因此, 第一个页的物理地址就是0x00000000, 第2个页的物理地址是0x00001000, 第3个页的物理地址是0x00002000....最后一个页的物理地址是0xfffff000.

这样,  4GB内存划分为1048576(0x100000)个页. 很显然, 页的物理地址, 其低12位始终为0.(最后一个页的物理地址0xfffff000,32位地址,000表示最后12位(3*4)。)

段管理机制对于Intel处理器来说是最基本的, 任何时候都无法关闭. 也就是说, 即使启用页管理功能, 分段机制依然是起作用的, 段部件依然工作.

分页是分的进程,而内存是分块的,页表要解决的就是进程的分页的号码与内存分块号码的对应。 一定要学会用集合的概念去理解东西。即:

分页是对程序逻辑地址进行划分,

分块是对内存物理地址进行划分,

页表记录了页号和块号的对应关系页号和块号对应关系的集合

注:分页机制与分段机制的不同 :

分页与分段的最大的不同之处在于分页使用了固定长度的页面。

段的长度通常存放在其中的代码或数据结构有相同的长度

与段不同,页面有固定的长度。

  • 如果仅使用分段地址转换,那么存储在物理内存中的一个数据结构包含其所有的部分
  • 如果使用了分页,那么一个数据结构就可以一部分存储于物理内存中,而另一部分保存在磁盘中。

为了减少地址转换所要求的总线周期数量,最近访问的页目录和页表会被存放在处理器的一个叫做转换查找缓冲区(TLB)的缓冲器件中。

TLB 可以满足大多数读页目录和页表的请求而无需使用总线周期。

  • 程序载入内存的时候只是一种内存映射
  • 执行程序的时候系统会通过内存中的页表去查找数据和指令的真实地址

大概带着这些知识后,再来看PE的结构:

PE结构的一切元素,只有一个目的就是为了让程序载入内存。

这是一个根本解决也是伴随为什么产生PE这种结构的原因。 

它要解决的问题就是一个地址转换的问题:怎么将磁盘上的地址转换为内存中的地址,并利于程序的执行

一些结构的定义:

 可以看这篇博文比较详细的记载了导入表相关的结构:http://blog.sina.com.cn/s/blog_56ea069101000bcg.html

关于这一块,我参照上面的博文,写了一个Xmind思维导图,预览图大概如下:

PS:做的导图放在我的CSDN资源上了,不嫌弃的话可以自取。(#^.^#)

一些结构的定义如下:(方便以后查阅) 

_IMAGE_IMPORT_DESCRIPTOR结构

typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
    union
    {
        DWORD   Characteristics;           
        DWORD   OriginalFirstThunk;         (PIMAGE_THUNK_DATA)
        };
    DWORD   TimeDateStamp;        
                                            (new BIND)
    DWORD   ForwarderChain;                
    DWORD   Name;
    DWORD   FirstThunk;                    
} IMAGE_IMPORT_DESCRIPTOR;

IMAGE_THUNK_DATA结构

typedef struct _IMAGE_THUNK_DATA32
{
    union {
        PBYTE  ForwarderString;
        PDWORD Function;
        DWORD Ordinal; //**比较常用的成员**//
        PIMAGE_IMPORT_BY_NAME  AddressOfData; //**比较常用的成员**//
    } u1;
} IMAGE_THUNK_DATA32;   //******注意此结构有32位和64位之分*******//

IMAGE_SECTION_HEADER的结构:

typedef struct _IMAGE_SECTION_HEADER 

{
+0h BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节表名称,如“.text” 
//IMAGE_SIZEOF_SHORT_NAME=8
union
+8h {
DWORD PhysicalAddress; // 物理地址
DWORD VirtualSize; // 真实长度,这两个值是一个联合结构,可以使用其中的任何一个,一
// 般是取后一个
} Misc;
+
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值