• 【旧文章搬运】PE重定位表学习手记


    原文发表于百度空间,2008-11-02
    ==========================================================================

    先定义一下用到的几个变量:
    char *hModule=NULL;//映射后的基址
    PIMAGE_OPTIONAL_HEADER pOptHeader;//扩展头
    PIMAGE_DATA_DIRECTORY pRelocTable=NULL;//指向重定位表
    PIMAGE_BASE_RELOCATION pRelocBlock;//指向重定位块
    WORD *pRelocData;//16位的重定位数据指针
    PE头的定位和分析比较简单,不再多说。
    首先,先判断重定位表是否存在:
    pRelocTable=&(pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]);
    判断pRelocTable->VirtualAddress和pRelocTable->Size是否为0即可。
    若不为0,则重定位表存在。

    ModulBase+pRelocTable->VirualAddress即可定位到重定位表。
    重定位表的结构如下图所示:

    重定位表由一个个的重定位块组成,如果重定位表存在的话,必定是至少有一个重定位块。
    因为每个块只负责定位0x1000大小范围内的数据,因此如果要定位的数据范围比较大的话,
    就会有多个重定位块存在。

    每个块的首部是如下定义:
    typedef struct _IMAGE_BASE_RELOCATION {
        DWORD   VirtualAddress;
        DWORD   SizeOfBlock;
    } IMAGE_BASE_RELOCATION;

    把内存中需要重定位的数据按页的大小0x1000分为若干个块,而这个VirtualAddress就是每个块的起始RVA.只知道块的RVA当然还不行,我们要知道每一个需要重定位数据的具体地址。
    每个需重定位的数据其地址及定位方式用两个字节来表示,记为RelocData,紧跟在IMAGE_BASE_RELOCATION结构之后,如图所示。
    每个块中重定位信息的个数如何确定?
    这个可由每个块结构中的Size来确定。Size的值是以DWORD表示的当前整个块的大小,先减去IMAGE_BASE_RELOCATION的大小,因为重定位数据是16位WORD的,再除以2,就得到个重定位数据的个数。由Size可以直接到达下一个重定位块,如图所示:0x5E850000+0x000000EC=0x5E8500EC即为第二个重定位块的地址,直至某个块首结构的VirtualAddress为0,表明重定位表结束。

    每个块中重定位数据的个数确定了,如何得知具体每个需进行重定位的数据的地址呢?

    每个16位重定位信息包括低12位的重定位位置和高4位的重定位类型。要得到重定位的RVA,IMAGE_BASE_RELOCATION'的'VirtualAddress'需要加上12位位置偏移量. 类型是下列之一:

        IMAGE_REL_BASED_ABSOLUTE (0) 使块按照32位对齐,位置为0。
        IMAGE_REL_BASED_HIGH (1) 高16位必须应用于偏移量所指高字16位。
        IMAGE_REL_BASED_LOW (2) 低16位必须应用于偏移量所指低字16位。
        IMAGE_REL_BASED_HIGHLOW (3) 全部32位应用于所有32位。.
        IMAGE_REL_BASED_HIGHADJ (4) 需要32位,高16位位于偏移量,低16位位于下一个偏移量数组元素,组合为一个带符号数,加上32位的一个数,然后加上8000然后把高16位保存在偏移量的16位域内。
        IMAGE_REL_BASED_MIPS_JMPADDR (5)        Unknown
        IMAGE_REL_BASED_SECTION (6)        Unknown
        IMAGE_REL_BASED_REL32 (7)        Unknown

    以第一个重定位数据0x34AC为例,其高四位表明了重定位类型为3,即IMAGE_REL_BASED_HIGHLOW,Win32环境下的重定位基本都是这个类型的。

    其低12位则表明了相对于VirtualAddress的RVA偏移量。VirtualAddress即需重定位的数据块的起始RVA,再加上这低12位的值就得到了具体的需要进行重定位处理的数据的RVA。感觉说得有点乱,总之就是VirtualAddress与每一个16位重定位数据一起可以得到一个具体要进行重定位处理的数据的RVA。

    也就是说:
    要进行重定位处理的数据的RVA=VirtualAddress+RelocData&0x0FFF 
    =0x00001000+(0x34AC)&0x0FFF
    =0x000014AC

    再加上模块基址,就得到了在内存中的真实地址了
    由ModuleBase=0x5E830000,可得这个重定位数据的地址为:0x5E830000+0x000014AC=0x5E8314AC
    转到OD的反汇编窗口,Ctrl+G跳到这个地址,可以看到:

    OD自动标上了下划线,表明这是一个重定位数据,接下来的几个数据也可以一一进行对应.
    至此,重定位算是搞明白了。

    那如何进行修正呢?
    把需要修正的数据减去IMAGE_OPTINAL_HEADER中的ImageBase,再加上当前加载的实际基址,就可以了。至此重定位完成!当然,也可以根据别的基址进行重定位。比如加载ntoskrnl.exe时,按照ntoskrnl.exe在内存中加载的实际基址0x804E0000(我系统上的数据)进行重定位之后,然后就可以干很多事情了~~比如查找原始SSDT,或者进行 InlineHook检测,搜索未导出函数等等,总之比较有用。
    下面是我程序中的一段代码:

    pRelocBlock=(PIMAGE_BASE_RELOCATION)(m_hModule + m_pRelocTable->VirtualAddress);
    //printf("After Loaded,Reloc Table=0x%08X
    ",pRelocBlock);
    do
    {    //处理一个接一个的重定位块,最后一个重定位块以RAV=0结束
        //需要重定位的个数,是本块的大小减去块头的大小,结果是以DWORD表示的大小
        //而重定位数据是16位的,那就得除以2
        int numofReloc=(pRelocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/2;
        //printf("Reloc Data num=%d
    ",numofReloc);
        //重定位数据是16位的
        WORD offset=0;
        WORD *pRelocData = (WORD*)((char*)pRelocBlock + sizeof(IMAGE_BASE_RELOCATION));
        for (i=0;i<numofReloc;i++)//循环,或直接判断*pData是否为0也可以作为结束标记
        {
            DWORD *RelocAddress=0;//需要重定位的地址 //重定位的高4位是重定位类型, 
            if (((*pRelocData)>>12) == IMAGE_REL_BASED_HIGHLOW)//判断重定位类型是否为IMAGE_REL_BASED_HIGHLOW 
            {    //计算需要进行重定位的地址 //重定位数据的低12位再加上本重定位块头的RAV即真正需要重定位的数据的RAV 
                minioffset=(*pRelocData)&0xFFF;//小偏移 //模块基址+重定位基址+每个数据表示的小偏移量 
                RelocAddress=(DWORD*)(hModule + pRelocBlock->VirtualAddress+offset); //对需要重定位的数据进行修正 
                //修正方法:减去IMAGE_OPTINAL_HEADER中的基址,再加上实际基址即可 
                *RelocAddress=*RelocAddress - pOptHeader->ImageBase + hModule; 
            } 
            //指向下一个重定位数据 
            pRelocData++; 
        } 
        //指向下一个重定位块 
        pRelocBlock=(PIMAGE_BASE_RELOCATION)((char*)pRelocBlock + pRelocBlock->SizeOfBlock);
    }while (pRelocBlock->VirtualAddress);

    这些工作只是自己加载PE过程的一部分,自己加载PE有很多用处,主要是因为加载的是原始的文件,只要不是固化挂钩,都可以自己加载然后与内存中的进行对比,用处还是不少的~

    参考:
    1.sudami的《SDT Restore v0.2 学习手记》。不过那个源码中用了太多自定义的结构,使得读起来稍有些困难,其实这些结构都是有现成定义的。
    2.看雪论坛的《PE文件格式》

  • 相关阅读:
    C++ 面向对象编程3 封装 继承 多态
    C++ 面向对象编程2
    C++ 面向对象编程1
    C++开发环境和基础语法
    RTOS概述
    STM32F4 窗口看门狗(WWDG)
    STM32F407 独立看门狗 (IWDG)
    DHT11温湿度传感器
    Ubuntu20.04安装、配置、卸载QT5.9.9与QT creator以及第一个编写QT程序
    Linux,Ubuntu20.04LTS环境下安装JDK1.8和IDEA2021
  • 原文地址:https://www.cnblogs.com/achillis/p/10180339.html
Copyright © 2020-2023  润新知