• 逆向笔记——输出PE重定位表


    重定位表

    什么是重定位表?

    用于程序在加载依赖模块时,发生地址冲突的情况下,用来进行模块基址修正的表。

    重定位表的结构

    数据目录项的第六个就是重定位表结构

    	typedef struct _IMAGE_DATA_DIRECTORY {					
    	    DWORD   VirtualAddress;	//RVA			
    	    DWORD   Size;					
    	} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;					
    

    通过RVA转FOA,定位第一个IMAGE_BASE_RELOCATION

    	typedef struct _IMAGE_BASE_RELOCATION {						
    	    DWORD   VirtualAddress;						
    	    DWORD   SizeOfBlock;						
    	} IMAGE_BASE_RELOCATION;						
    	typedef IMAGE_BASE_RELOCATION ,* PIMAGE_BASE_RELOCATION;						
    

    上图看看重定位表数据

    1、内存的块数如何确定:if判断 VirtualAddress 和SizeOfBlock都为0,则内存块结束

    2、一个块中的具体项,如0011 1010 1010 1010 ,高4位0011表示类型,0011表示需要修改,0000表示不需要修改只是为了做内存对齐用的。低12位表示一个页内偏移地址。

    3、如何计算真正需要的修复的RVA地址:数据项中低12位+该页的VirtualAddress

    为什么需要重定位表?

    程序的加载过程

    1. 先读入PE文件的DOS头、PE头和Section节表
    2. 判断PE头中的ImageBase是否可用,如果该ImageBase已被其他模块占用,则重新分配一块空间。
    3. 根据Section节表,把文件的各个Section映射到分配的空间,并根据各个Section定义的数据来修改所映射的页的属性。
    4. 如果文件被加载的地址不是ImageBase定义的地址,则重新修正ImageBase。
    5. 根据PE文件的输入表加载所需的dll到进程空间。
    6. 替换IAT白哦内的数据为函数的实际调用地址。
    7. 根据PE头内的数据生成初始化的堆和栈。
    8. 创建初始化线程,开始运行进程。

    ​ 在步骤2中,如果PE头中的ImageBase发生冲突,则需要分配新的ImageBase,这时ImageBase发生了变化,而各种头部中的相对虚拟地址不会发生改变,这就导致无法正确定位虚拟地址。这时,就诞生了重定位表 。

    Windows加载器是如何实现重定位的?

    在程序加载的第4步中如果文件被加载的地址不是ImageBase定义的地址,则重新修正ImageBase,修正的步骤如下:

    当某模块未被载入到首选基地址时,加载器会计算模块实际载入的地址(dwVA)跟首选基地址(ImageBase)的差值,将这个差值加到机器指令所引用的原来的地址,得到的就是模块中各指令所引用的数据在本进程地址空间的正确地址,随后加载器会修正模块中对每个内存地址的引用。

    动手实现重定位表的输出

    // ReloadTableInfo.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include <stdio.h>
    #include <windows.h>
    
    unsigned char* FileBuffer(const char* FileName);
    void PrintReloadTb();
    DWORD RVA2Offset(PIMAGE_NT_HEADERS pNTHeader, DWORD dwRVA);
    
    
    int main(int argc, char* argv[])
    {
    	PrintReloadTb();
    	return 0;
    }
    
    //打印重定位表
    void PrintReloadTb()
    {
    	
    	PIMAGE_DOS_HEADER pDosHeader;
    	PIMAGE_NT_HEADERS pNtHeaders;
    	PIMAGE_BASE_RELOCATION  pBaseRec;
    	PIMAGE_SECTION_HEADER pSectionHeader;
    
    
    	unsigned char* fileBuffer = FileBuffer("C:\TestLib\TestDynLib2.dll");
    	pDosHeader = (PIMAGE_DOS_HEADER)fileBuffer;
    	pNtHeaders = (PIMAGE_NT_HEADERS)(fileBuffer+pDosHeader->e_lfanew);
    
    	printf("重定位表的相对虚拟地址:%x
    ",pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
    	printf("重定位表的大小:%x
    ",pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size);
    
    	DWORD pBaseRecOffset = RVA2Offset(pNtHeaders, pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); //注意这个VirtualAddress是VA
    	pBaseRec = (PIMAGE_BASE_RELOCATION)(pBaseRecOffset+fileBuffer);
    
    	pSectionHeader = (PIMAGE_SECTION_HEADER)(pNtHeaders+1);
    	BYTE secName[9] = {0};
    	int i =0;
    	for(i; pBaseRec->SizeOfBlock && pBaseRec->VirtualAddress; i++)
    	{
    		DWORD FOA = RVA2Offset(pNtHeaders, pBaseRec->VirtualAddress);
    		DWORD size = (pBaseRec->SizeOfBlock - 8 )/2; // VirtualAddress SizeOfBlock共占8字节,每个项2字节
    
    		//确定该结构所属的节
    		 DWORD j=0;
    		 for(j=0;j<pNtHeaders->FileHeader.NumberOfSections;j++)
    		 {
    			 DWORD lower =RVA2Offset(pNtHeaders, pSectionHeader[j].VirtualAddress);
    			 DWORD upper =RVA2Offset(pNtHeaders, pSectionHeader[j].VirtualAddress+pSectionHeader[j].Misc.VirtualSize);
    
    			if(FOA>=lower && FOA<=upper )
    			{
    				memcpy(secName,pSectionHeader[j].Name,8);
    				break;
    			}
    		 }
    		 printf("%d.RELOCATION
     VirtualAddress %x(%s)
     SizeOfBlock %x
    ", i, pBaseRec->VirtualAddress,secName, size); //打印本页的主要信息
    		 printf("RVA,TYPE
    ");
    
    		 //打印一个页中,所有重定位地址与信息
    		 WORD * recAddr = (WORD *)((BYTE *)pBaseRec+8); //指向第一个目录项
    
    		 for(j=0; j<size; j++)
    		 {
    			DWORD offset = (recAddr[j] & 0x0FFF) + FOA ;
    			WORD type = recAddr[j] >> 12;
    
    			if(type!=0)
    			{
    				 printf("%08X,  %x
    ",offset+pBaseRec->VirtualAddress,type);
    			}
    		 }
            memset(secName, 0, 9);
            pBaseRec = (PIMAGE_BASE_RELOCATION )((BYTE *)pBaseRec + pBaseRec->SizeOfBlock);//移到下一页
    	}
    	
    
    }
    
    
    //将PE文件读到FileBuffer
    unsigned char* FileBuffer(const char* FileName)
    {
         unsigned char* Heap = NULL;
         FILE* Stream;
    
         //打开文件
         Stream = fopen(FileName,"rb");
         //计算文件大小
         fseek(Stream,0,SEEK_END);
         long FileSize = ftell(Stream);
         fseek(Stream,0,SEEK_SET);
    
         //分配堆空间
         Heap = (unsigned char*)malloc(sizeof(char)*FileSize);
         //将文件拷到堆
         fread(Heap,sizeof(char),FileSize,Stream);
         fclose(Stream);
    
    	 return Heap;
    }
    
    DWORD RVA2Offset(PIMAGE_NT_HEADERS pNTHeader, DWORD dwRVA)
    {
        PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)((DWORD)pNTHeader + sizeof(IMAGE_NT_HEADERS));
    
        for(int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++)
        {
            if(dwRVA >= pSection[i].VirtualAddress && dwRVA < (pSection[i].VirtualAddress + pSection[i].SizeOfRawData))
            {
                return pSection[i].PointerToRawData + (dwRVA - pSection[i].VirtualAddress);
            }
        }
    
        return 0;
    }
    
    

    部分结果图如下:

    LordPE查看结果如下:

  • 相关阅读:
    微信小程序开发前期准备
    怎样在vs2013和vs2015中实现自动编译sass
    在MVC中使用Bundle打包压缩js和css
    Html5 突破微信限制实现大文件分割上传
    Automapper 实现自动映射
    Polly一种.NET弹性和瞬态故障处理库(重试策略、断路器、超时、隔板隔离、缓存、回退、策略包装)
    关于transactionscope 事务的脏数据
    IIS设置session时长
    已禁用对分布式事务管理器(MSDTC)的网络访问的解决方法之一
    DAL.SQLHelper 的类型初始值设定项引发异常的处理
  • 原文地址:https://www.cnblogs.com/Erma/p/12643351.html
Copyright © 2020-2023  润新知