重定位表

1.重定位的需求:

在生成程序的时候,很多涉及到地址的代码,都使用一个绝对的虚拟内存地址(这个虚拟内存地址是假设程序加载到0x400000的地方时才能够使用的),但是当程序的加载基址产生变化的时候,新的加载基址和默认的加载基址就不一样了,那些涉及到地址的代码就不能运行了,此时就需要将那些涉及到地址的代码,把他们的操作数修改(去掉默认加载基址,再加上新的加载基址)才能够使程序运行起来.

2.产生重定位需求的代码

  • 当代码段使用了其他区段的数据,所生成的代码就会产生重定位的需求,其他段数据的首地址,就是产生重定位需求的根源
  • 凡是出现全局变量的地方,都会导致重定位的产生
  • 调用外部模块的函数都会产生重定位

3.怎么修复重定位

0x0谁来修复重定位?
  • 加载器会负责修复所有会产生重定位的代码
0x1重定位怎么被修复?
0x1-1如何知道哪些地址上的代码会产生重定位?
  • 利用重定位表去知道产生产生重定位代码的地址
    重定位表所记录的信息:在哪个地址上产生了重定位。
0x1-2如何修改?
  • 将指令中的操作数按照指针字节数读取出来,然后将之减去默认加载基址(扩展头.ImageBase),在加上新的加载基址,最后把新地址存入原来的地址中.
0x1-3要修改的是什么?

要修改的内存地址操作数:0x403000
0x401000 A3 00304000 ------>对应汇编指令: mov dword ptr ds:[0x403000] ,eax
被修改的四个字节(0x4010001)
重定位块中的VirtualAddress+offset得到的是0x401001
*(DWORD*)0x401001得到的是----->0x403000

  • 修复重定位(假定默认基址为0x40 0000,新的加载基址0x30 0000):

*(DWORD*)(VirtualAddress+offset+当前加载基址)-=0x400000
*(DWORD*)(VirtualAddress+offset+当前加载基址)+=0x300000

再举一个例子(重点)

假设实际加载基址为0x80 0000默认加载基址为0x40 0000
在0x401000这个地方有这么一条指令:call 0x403000
0x40 1000 call 0x403000
重定位表中的VA为0x1000(4KB),此时的offset为1
那么此时0x403000这个指令所对应的地址为1000+1+400000
对其进行解引用,就会得到0x403000这个值,然后用0x403000减去0x400000+0x800000即可,得到的值为0x803000,再将0x803000写入原来的地址,则原来那条指令就变成了0x401000 call 0x803000

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;//这里的VA实际上是一个相对虚拟地址(RVA)
    DWORD   SizeOfBlock;
//  WORD    TypeOffset[1]; //这里的1表示不定长,长度不知道
} IMAGE_BASE_RELOCATION;

重定位表解析:

// 006_解析重定位表.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include 

struct TypeOffset {
    WORD Offset : 12;  // (1) 大小为12Bit的重定位偏移
    WORD Type   : 4;    // (2) 大小为4Bit的重定位信息类型值,m类型为3表示的需要重定位的类型,
                        //非3(一般指0)表示的是不需要重定位的类型
                        //以位段的方式来写的!!!!!
};


DWORD RvaToOffset( IMAGE_NT_HEADERS* pNtHdr , DWORD dwRva ) {

    // 1. 找Rva所在的区段
    // 2. 用Rva减去所在区段的首Rva ,再用减出来的差,加上所在
    //    区段的首区段偏移
    IMAGE_SECTION_HEADER* pSechdr = NULL;
    pSechdr = IMAGE_FIRST_SECTION( pNtHdr );
    for( int i = 0 ; i < pNtHdr->FileHeader.NumberOfSections; ++i ) {
        if( dwRva >= pSechdr[ i ].VirtualAddress
            && dwRva <= pSechdr[ i ].VirtualAddress + pSechdr[ i ].SizeOfRawData ) {

            dwRva -= pSechdr[ i ].VirtualAddress;
            dwRva += pSechdr[ i ].PointerToRawData;
            return dwRva;
        }
    }
    return -1;
}



int main() {
    

    HMODULE hModule = GetModuleHandle( L"kernel32.dll" );//loadlibrary就是把dll加载到4GB虚拟内存空间中
                                                //GetModuleHandle是在4GB内存空间中有了dll文件之后获得当前
                                                //加载基址

    ULONG_PTR dwAddres = (ULONG_PTR)GetProcAddress( hModule ,
                                                    "CreateFileW" );//通过GetProcAddress函数以及hModule的值
                                                    //获得CreateFileW这个函数的地址!!!

    //typedef struct _IMAGE_EXPORT_DIRECTORY {
    //  DWORD   Characteristics; 
    //  DWORD   TimeDateStamp;
    //  WORD    MajorVersion;
    //  WORD    MinorVersion;
    //  DWORD   Name;   /*导出DLL的名字(RVA)*/
    //  DWORD   Base;   /*序号基数,用此基数+地址表的下标,就是函数的导出序号*/
    //  DWORD   NumberOfFunctions; /*所有导出符号的个数*/
    //  DWORD   NumberOfNames;  /*以名称方式导出的函数的个数*/
    //  DWORD   AddressOfFunctions;     /*函数地址表*/
    //  DWORD   AddressOfNames;         /*名称表*/
    //  DWORD   AddressOfNameOrdinals;  /*名称的序号表*/
    //} IMAGE_EXPORT_DIRECTORY , *PIMAGE_EXPORT_DIRECTORY;
    // 解析表
    // 1. 读取文件到内存
    printf( "请输入要解析的DLL(PE文件)的路径:" );
    char szPath[ MAX_PATH ];
    gets_s( szPath , MAX_PATH );//获取需要解析的dll文件的路径

    HANDLE hFile = INVALID_HANDLE_VALUE;
    hFile = CreateFileA( szPath , GENERIC_READ , FILE_SHARE_READ ,
                         NULL , OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL );
    if( hFile == INVALID_HANDLE_VALUE ) {
        printf( "文件不存在\n" );
        system( "pause" );
        return 0;
    }
    DWORD dwHeight = 0;
    DWORD dwFileSize = GetFileSize( hFile , &dwHeight );

    BYTE* pBuff = new BYTE[ dwFileSize ];

    ReadFile( hFile , pBuff , dwFileSize , &dwFileSize , NULL );//将内存pe里面中的内容拷贝到pBuff缓冲区里面

    // 2. 解析出Dos头,Nt头,扩展头
    IMAGE_DOS_HEADER* pDosHdr = NULL;
    IMAGE_NT_HEADERS* pNtHdr = NULL;
    IMAGE_OPTIONAL_HEADER* pOptHdr = NULL;
    IMAGE_DATA_DIRECTORY* pDataDir = NULL;
    IMAGE_EXPORT_DIRECTORY* pExortTable = NULL;//导出表!!!


    pDosHdr = (IMAGE_DOS_HEADER*)pBuff;//为什么要进行类型强制转换,(指针有两重含义,一是指它本身里面的地址,
                                        //二是指它所指向地址所对应的内容),前面的指针是啥类型,右边就要转换成什么类型
    pNtHdr = (IMAGE_NT_HEADERS*)( (ULONG_PTR)pDosHdr + pDosHdr->e_lfanew );//为什个么要加(ULONG_PTR),指针不也4字节么
                                                                            //只是对pDosHdr进行转换!

    pOptHdr = & (pNtHdr->OptionalHeader);

    // 3. 得到扩展头中的数据目录表
    pDataDir = pOptHdr->DataDirectory;

    // 4. 获取重定位表数组的首地址
    IMAGE_BASE_RELOCATION* pRel = (IMAGE_BASE_RELOCATION*)
        ( RvaToOffset( pNtHdr , pDataDir[ 5 ].VirtualAddress ) + (ULONG_PTR)pDosHdr );

    // 5. 开始遍历重定位表数组
    while( pRel->SizeOfBlock != 0 ) {//SizeOfBlock ==0的话就不需要重定位!!!
        
        // 先得到TypeOffset数组的元素个数和地址
        TypeOffset* pTypeOffset ; //
        pTypeOffset = (TypeOffset*)( pRel + 1 );//由于pRel是指向IMAGE_BASE_RELOCATION类型的指针
                                        //所以这里 需要强转,加1指的是加的一个结构体大小(8字节)
        
//dwCount指的是需要重定位的个数
DWORD dwCount = ( pRel->SizeOfBlock - sizeof( IMAGE_BASE_RELOCATION ) ) / sizeof( WORD );

        for( DWORD i = 0; i < dwCount ; ++i ) {
            if( pTypeOffset[ i ].Type == 3 ) {//类型为3表示的需要重定位的类型,非3(一般指0)表示的是不需要重定位的类型
                printf( "RVA:%04X" , 
            pRel->VirtualAddress + pTypeOffset[ i ].Offset );


                // 要修复的是哪个位置?
                // 要修复的位置是: VirtualAdress + offset  => RVA

                // 
                // 需要分清重定位项的所在的地址 , 重定位项自身的地址.
                // 
                // 1. 通过pRel->VirtualAddress + pTypeOffset[ i ].Offset;得到
                //    的是重定位项的所在的地址
                // 2. 通过重定位项所在的地址找到的一块内存, 在此内存上的4字节的数据
                //    就是重定位项自身的地址,也就是需要被修复的值
                //

                DWORD dwFixAddr = pRel->VirtualAddress + pTypeOffset[ i ].Offset;
                DWORD dwFixAddrOffset = RvaToOffset( pNtHdr , dwFixAddr );
                printf( "需要修改的地址:RVA:0x%08X , OFS: 0x%08X\n" ,
                        dwFixAddr ,
                        dwFixAddrOffset );
                //ULONG_PTR* pFixAddr = (ULONG_PTR*)( dwFixAddr + (ULONG_PTR)pDosHdr );
            }
        }

        // 得到下一个块重定位块的首地址
        pRel = (IMAGE_BASE_RELOCATION*)
            ( (ULONG_PTR)pRel + pRel->SizeOfBlock );
    }

    system( "pause" );
    return 0;
}


下面这段话很重要!

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;//这里的VA实际上是一个相对虚拟地址(RVA)
    DWORD   SizeOfBlock;
//  WORD    TypeOffset[1]; //这里的1表示不定长,长度不知道
} IMAGE_BASE_RELOCATION;
        //重定位表里面 像上面这样的结构体有若干个(类似于结构体数组,但却不是结构体数组,因为结构体数组它是定长的,这里不定长),
        //DWORD   VirtualAddress;
        //DWORD   SizeOfBlock;这两个是定长的,而TypeOffset[1]紧跟在结构体之后,它是不定长的
        //每一个结构体中的VirtualAddress指的是每一个内存分页的起始的RVA
        //SizeOfBlock 指的是这个分页到下个分页之间需要重定位的所有数据的相关大小
        //实际上有两大块组成1.指的是DWORD   VirtualAddress; DWORD   SizeOfBlock;所在结构体大小
        //2.指的是需要重定位的偏移offset, 用offset+VirtualAddress得到的是一个RVA,
        //这个RVA是存放 需要修改的那个地址 的地址所对应RVA
        //就是说  需要修改的那个地址  作为一个变量 存放在内存里面,而这个内存自己本身 也有自己的地址
        //存放    需要修改的那个地址 的内存的地址 是需要通过 RVA + 一个加载基址得到的
        //而这个RVA就是通过上面的 offset+VirtualAddress得到的,所对应的这个加载基址是由GetModuleHandle获得的
        //得到 这个 需要修改的那个地址(通常需要解引用,解引用才能得到所要修改的地址)之后,用它减去原来的加载基址(默认的加载基址),
        //再加上此时的加载基址(由GetModuleHandle获得的),就得到了加载后的真正的地址
  • 重定位表实际上是一个结构体数组(不是标准的结构体数组),以全0结束,每个数组元素表示了4KB的大小


    重定位表_第1张图片
    image.png

你可能感兴趣的:(重定位表)