zoukankan      html  css  js  c++  java
  • PE文件结构部分解析以及输入的定位

    此图非常详细,可以看到每个字段的内容和大概含义,根据此图动手实践了PE文件结构,下面给出我自己理解的简约结构:

    为了更加形象生动,特把实验过程记录如下,从实验过程中我们可以发现不小细节: 

    使用一个简单的PE文件举例,下图是PE文件概要信息:

    图中没有列出DOS头信息,直接跳到了NT头的Image_File_Header,其中Machine=014C,有四个段,可选头大小为EO;其他是一些标志位。

    接下来是可选头部分image base=400000也就是镜像文件载入地址为400000,

    段落对齐1000(1024字节),文件对齐200(512字节),还有一些堆栈默认大小;

    接下来是可选头里面的Data Directory数据目录,这个目录表明了导入表的虚拟地址:

    2244(此表在PE文件是.rdata段,地址:E44,.rdata的地址为C00,也就是.rdata偏移244就可找到导入表,

    因为.rdata虚拟地址是2000所以导入表虚拟地址为2000+244=2244),以及导出表、IAI等的虚拟地址计算方法同上。

    接下来就是一个段表,里面标示了有几个段落,每个段落的PE文件地址是多少,大小多少,虚拟地址多少,虚拟地址空间中大小多少等信息。

    .txt段落PE地址400,大小800,虚拟地址1000,大小7B6,为什么大小一会儿是800,一会儿是7B6呢?

    7B6为段实际大小,PE文件要求段对齐为200,400+7B6 = BB6,那么下一个段起址是BB7,不符合200的倍数关系,

    C00才是200的倍数,因此填充了一些无用的0使下一个段地址为200的整数倍;

    这个段放的都是代码,实际映射到进程的地址空间是1000。

    .rdat段,PE地址C00,大小800,虚拟地址2000,大小656。这个段放的是一些只读量,里面就有导入表中的函数虚拟地址的值。

    因此修改这个值就可以HOOK API;还有字符串常量,例如char * a = "wo shi shui",这个字符串就放在这里面。

    .data PE地址1400,大小200,虚拟地址3000,大小394,这里394>200是因为有些变量没有分配空间,

    所以没有占磁盘空间,但是到虚拟地址中就必须为其预留空间。

    也就是说PE文件将全局变量压缩存储了。这个段都是数据,有全局初始化数据和未初始化数据,有静态变量。

    .rsrc PE地址1600,大小200,虚拟地址4000,大小1AC。这个段是资源段,可能用来存放图片。 

    在接下来就是各个具体段的数据了.txt,.rdat,.data,.rsrc,而导入表信息在.rdat里面存放着。

    接下来是填充,为了让PE文件512字节对齐,从而进行填充。

    然后PE文件结束了。 

    好了,下面来具体看看PE文件:

    下图是用WINHEX打开PE文件后的实际情况:

    下面是NT头开始的数据,包含了image file header、image optional header、四个段的信息表、以及四个段的具体数据

    上图已经圈出了头部的数据结构,每个颜色代表一个头,最下面是各段的具体数据,当然首先是被全0填充的,直到400才开始是真实的段数据。

    测试代码如下:,此代码最终生成dlltest.exe文件,并调用test.dll,主要是打印出EXE所在的堆栈地址,DLL和EXE如何协作。

    #include "stdafx.h"
    #include "../TestDelayDll/TestDelayDll.h"
    char abc;
    
    int cba = 0x0000a0a0;
    
    int _tmain( int argc, _TCHAR* argv[ ] )
    {
      int b = 0x1111;
      char *ok = "wo shi shui";
      
      char *xx = new
      char[4096];
      printf( "[exe]no-init=%X, init=%X, stack=%X, heap=%X, %X:%s
    ", &abc, &cba,
        &b, xx, ok, ok );
    //load
      fnTestDelayDll( );
      
      delete []xx;
    
      getchar( );
      
      return 0;
    }
    
    test.dll主要代码:
    int fnTestDelayDll()
    { 
      int sp = 0xabcd;
      char *a = new char[1024];
      printf("[dll]no-init=%X init=%X stack=%X heap=%X
    ", &nTestDelayDll, &g_testValue, &sp, a);
      delete []a;
      return 42;
    }

    上面两段代码可能会被编译器优化掉,因此你必须关闭编译器优化才能看到真实的情况。

    结果如下:

    进程的内存结构如下(我自己整理的,可能不是很对,堆是各个线程公用的,栈是一个线程一个,用完还给系统):

    我自己的理解是这样的,在执行一个EXE程序时,父进程负责为EXE创建进程地址空间,并将EXE文件映射到此空间,当然是以EXE文件所在的存储器为后备存储器,映射过程中是有一定流程的,首先将PE文件头(这个头包含了所有的小头)直接映射到400000开始的虚拟地址空间,然后根据头中的信息将4个段分别映射到地址空间,注意,这里的映射并不是和PE文件存储结构一致,而是根据PE头中的信息进行映射,例如.txt段在PE中偏移是400,那么按道理应该映射到400000+400=400400这是地址,可是实际上是映射到401000地址,就是因为PE头中已经指示了必须映射到此地址,其他各段以此类推。

    映射完成后,系统开始找导入表,按照导入表中的信息,导入EXE依赖的DLL,例如需要导入Test.dll文件,这时候执行的映射和EXE一致,只不过地址为定位从10000000开始映射,实际上具体的系统可能不是从这边映射,因为一般我们编写的DLL都从10000000开始映射,这样2个以上的DLL会出现重叠,因此这时候系统会重定位该DLL,重定位DLL后,因为载入地址不在是10000000,所以DLL代码中所有可能引用地址的地方都需要更新,这时候系统会找到DLL的重定位段,根据重定位段记录的地址表,逐个更新地址。

    对于DLL重定位我也做了一实验,让testdll.exe加载了两个DLL文件,这两个DLL文件镜像载入的虚地址都是10000000,测试发现,第一个DLL被正常映射到10000000地址,一切都是按照PE约定的虚拟地址进行映射,而第二个DLL镜像被映射到了3b0000地址,也就是被映射到了exe镜像的上面,哈哈,.txt代码段本应该是10001000的,却被映射到了3b1000地址,看来DLL镜像在映射的时候被重定位了,并且还发现,原来代码段中的内容被改掉了,果然是系统修复了地址,如图:

    原始汇编:

    看来DLL重定位确实会动态修改汇编代码段内容.

  • 相关阅读:
    Laravel 初始化
    ant design pro 左上角 logo 修改
    请求到服务端后是怎么处理的
    Websocket 知识点
    王道数据结构 (7) KMP 算法
    王道数据结构 (6) 简单的模式匹配算法
    王道数据结构 (4) 单链表 删除节点
    王道数据结构 (3) 单链表 插入节点
    王道数据结构 (2) 单链表 尾插法
    王道数据结构 (1) 单链表 头插法
  • 原文地址:https://www.cnblogs.com/shangdawei/p/4786748.html
Copyright © 2011-2022 走看看