重定位表

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 <windows.h>

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的大小


    image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容