• 滴水逆向-重定位表


    相关知识点




    文字版知识点

    摘自WINNT.H
    //
    // Optional header format.
    //
    
    typedef struct _IMAGE_OPTIONAL_HEADER {
        //
        // Standard fields.
        //
    
        WORD    Magic;
        BYTE    MajorLinkerVersion;
        BYTE    MinorLinkerVersion;
        DWORD   SizeOfCode;
        DWORD   SizeOfInitializedData;
        DWORD   SizeOfUninitializedData;
        DWORD   AddressOfEntryPoint;
        DWORD   BaseOfCode;
        DWORD   BaseOfData;
    
        //
        // NT additional fields.
        //
    
        DWORD   ImageBase;
        DWORD   SectionAlignment;
        DWORD   FileAlignment;
        WORD    MajorOperatingSystemVersion;
        WORD    MinorOperatingSystemVersion;
        WORD    MajorImageVersion;
        WORD    MinorImageVersion;
        WORD    MajorSubsystemVersion;
        WORD    MinorSubsystemVersion;
        DWORD   Win32VersionValue;
        DWORD   SizeOfImage;
        DWORD   SizeOfHeaders;
        DWORD   CheckSum;
        WORD    Subsystem;
        WORD    DllCharacteristics;
        DWORD   SizeOfStackReserve;
        DWORD   SizeOfStackCommit;
        DWORD   SizeOfHeapReserve;
        DWORD   SizeOfHeapCommit;
        DWORD   LoaderFlags;
        DWORD   NumberOfRvaAndSizes;
        IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
    
    // Directory Entries
    
    #define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
    #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
    #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
    #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
    #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
    #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
    #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
    //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
    #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
    #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
    #define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
    #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
    #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
    #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
    #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
    #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
    
    1.程序加载的过程
    
    一个exe文件会被拉伸加载至内存,加载完成之后她在内存获得了她想要的那份内存空间,同时exe
    需要加载dll模块,此时也会占用内存空间;
    
    特别说明:
    (1)一般情况下,EXE都是可以按照ImageBase的地址进行加载的.因为Exe拥有自己独立的4GB 的虚拟内存空间
       但DLL 不是  DLL是有EXE使用它,才加载到相关EXE的进程空间的.
    (2)为了提高搜索的速度,模块间地址也是要对齐的 模块地址对齐为10000H 也就是64K
    	10000H --> 65536D --> 65536byte --> 65536/1024=64K
    
    2.为什么要用重定位表
    
    打开一个程序,观察一下全局变量的反汇编
    
    00401D58 A1 44 CA 42 00       mov         eax,[x (0042ca44)]
    00401D5D 50                   push        eax
    00401D5E 68 EC 91 42 00       push        offset string "%d
    " (004291ec)
    00401D63 E8 28 62 00 00       call        printf (00407f90)
    
    编译时生成的地址 = ImageBase + RVA
    这个地址在程序编译完成后,已经写入文件了
    那假设,程序在加载的时候,没有按照预定的400000 载入到指定的位置
    但程序执行的时候,仍然会按照0042ca44 和 004291ec 的地址去使用这个值!
    
    (1)也就是说,如果程序能够按照预定的ImageBase来加载的话,那么就不需要重定位表
    这也是为什么exe很少有重定位表,而DLL大多都有重定位表的原因
    (2)一旦某个模块没有按照ImageBase进行加载,那么所有类似上面中的地址就都需要修正,否则,引用的地址就是无效的.
    (3)一个EXE中,需要修正的地方会很多,那我们如何来记录都有哪些地方需要修正呢?
    答案就是重定位表
    
    重定位表定位
    
    数据目录项的第6个结构,就是重定位表.
    
    typedef struct _IMAGE_DATA_DIRECTORY {
        DWORD   VirtualAddress;
        DWORD   Size;
    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
    
    上面的VirtualAddress同样需要从RVA转到FOA
    
    typedef struct _IMAGE_BASE_RELOCATION {
    	DWORD   VirtualAddress;
        DWORD   SizeOfBlock;
    } IMAGE_BASE_RELOCATION;
    typedef IMAGE_BASE_RELOCATION ,* PIMAGE_BASE_RELOCATION;
    
    下面重定位表是摘自WINNT.T
    
    typedef struct _IMAGE_BASE_RELOCATION {
        DWORD   VirtualAddress;
        DWORD   SizeOfBlock;
    //  WORD    TypeOffset[1];
    } IMAGE_BASE_RELOCATION;
    typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
    
    #define IMAGE_SIZEOF_BASE_RELOCATION         8
    
    解析说明:
    
    (1)通过IMAGE_DATA_DIRECTORY结构的VirtualAddress
    属性 找到第一个IMAGE_BASE_RELOCATION
    
    (2)判断一共有几块数据:
    最后一个结构的VirtualAddress与SizeOfBlock都为0
    
    (3)具体项 宽度:2字节
    也就是这个数据
    内存中的页大小是1000H 也就是说2的12次方 就可以表示
    一个页内所有的偏移地址 具体项的宽度是16字节 高四位
    代表类型:值为3 代表的是需要修改的数据 值为0代表的是
    用于数据对齐的数据,可以不用修改.也就是说 我们只关注
    高4位的值为3的就可以了.
    1000H --> 4096D --> 2的12次方
    12 + 4 = 16 bit
    
    (4)VirtualAddress 宽度:4字节
    当前这一个块的数据,每一个低12位的值+VirtualAddress 才是
    真正需要修复的数据的RVA
    真正的RVA = VirtualAddress + 具体项的低12位
    
    (5)SizeOfBlock 宽度:4字节
    当前块的总大小
    具体项的数量 = (SizeOfBlock - 8)/2
    这是指具体要修改的项
    

    课后练习相关代码演示

    #include "stdafx.h"
    #include <windows.h>
    #include "stdlib.h"
    #include "stdio.h"
    
    
    #define FilePath_In "C:\cntflx\dtljkcntf.dll"
    #define debug 0
    
    //RVA格式转换FOA  --- RvaToFileOffset
    DWORD RvaToFileOffset(IN LPVOID pFileBuffer,IN DWORD dwRva)
    {
        PIMAGE_DOS_HEADER pDosHeader = NULL;
        PIMAGE_NT_HEADERS pNTHeader = NULL;
        PIMAGE_FILE_HEADER pPEHeader = NULL;
        PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
        PIMAGE_SECTION_HEADER pSectionHeader = NULL;
        DWORD numberOfSection = 0;
        DWORD dwFOAValue = 0;
    
        //判断指针是否有效
        if (!pFileBuffer)
        {
            printf("pFileBuffer 指针无效
    ");
            return 0;
        }
        //判断是否是有效的MZ标志
        if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
        {
            printf("pFileBuffer不是有效的MZ标志
    ");
            return 0;
        }
        //判断是否是一个有效的PE标志
        pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
        if (*((PWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
        {
            printf("pFileBuffer不是一个有效的PE标志
    ");
            return 0;
        }
    
        //printf("当前的Rva地址: %#X 
    ",dwRva);
        pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
        pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader+0x04);
        pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
        pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    
        //定义个临时节表指针进行下面的计算操作
        numberOfSection = pPEHeader->NumberOfSections;
        PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
    
    
        //判断dwRva所处的节
        if (dwRva <= pOptionHeader->SizeOfHeaders)
        {
            return (DWORD)dwRva;
        }
        //上面是判断如果rva地址所处的节在第一个节之前那么直接返回rva的地址;
        //否则下面就是开始遍历查找节;
        else
        {
            for (DWORD n = 0; n < numberOfSection; n++)
            {//下面是判断在哪个节的范围,然后根据rva所在的地址减去所在节的VirtualAddress得到的偏移值加上文件中对应节的偏移值PointerToRawData
                if ((dwRva >= pTempSectionHeader[n].VirtualAddress) && (dwRva < pTempSectionHeader[n].VirtualAddress + pTempSectionHeader[n].Misc.VirtualSize))
                {
                    dwFOAValue = dwRva - pTempSectionHeader[n].VirtualAddress + pTempSectionHeader[n].PointerToRawData;
                }
            }
        }
        return dwFOAValue;
    }
    
    DWORD ReadPEFile(IN LPSTR lpszFile,OUT LPVOID* pFileBuffer )
    {
    	FILE	*pFile		=	NULL;
    	DWORD	fileSize	=	0;			//文件大小
    	LPVOID	pTempFileBuffer	=	NULL;	//缓冲区首地址
    
    
    	pFile = fopen(lpszFile,"rb");	//打开文件
    	if(!pFile)
    	{
    		printf("打开文件失败");
    		return NULL;
    	}
    
    	//读取文件大小
    	fseek(pFile,0,SEEK_END);		//将指针从开始的位置移动到末尾
    	fileSize = ftell(pFile);		//获取数据大小
    
    	//分配缓冲区(申请内存)
    	pTempFileBuffer = malloc(fileSize);
    	if(!pTempFileBuffer)
    	{
    		printf("分配空间失败");
    		fclose(pFile);
    		return NULL;
    	}
    
    	//将文件数据读取到缓冲区
    	fseek(pFile,0,SEEK_SET);	//将指针指向开始
    	size_t n = fread(pTempFileBuffer,fileSize,1,pFile);	//将数据读取到缓冲区中
    	if(!n)
    	{
    		printf("读取数据失败");
    		free(pTempFileBuffer);		//释放内存
    		fclose(pFile);			//关闭文件
    		return NULL;
    	}
    
    	//关闭文件
    	*pFileBuffer = pTempFileBuffer;
    	pTempFileBuffer = NULL;
    	fclose(pFile);				//关闭文件
    	return fileSize;
    }
    
    
    DWORD GetDataDirectoyOfBaseRelocation(IN PVOID pFileBuffer)
    {
        // 初始化PE头部结构体
        PIMAGE_DOS_HEADER pDosHeader = NULL;
        PIMAGE_NT_HEADERS pNTHeader = NULL;
        PIMAGE_FILE_HEADER pPEHeader = NULL;
        PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
        PIMAGE_SECTION_HEADER pSectionHeader = NULL;
        PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
        PIMAGE_BASE_RELOCATION pBaseRelocation = NULL;
        DWORD RVA_BaseRelocationTable = 0;
        DWORD SizeOfBlock_BaseRelocationTable = 0;
        
        // 判断指针是否有效
        if (!pFileBuffer)
        {
            printf("pFileBuffer不是有效的指针
    ");
            return 0;
        }
        //判断是否是有效的MZ标志
        if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
        {
            printf("pFileBuffer不是有效的MZ文件
    ");
            return 0;
        }
        //判断是否是一个有效的PE标志
        pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
        if (*((PWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
        {
            printf("pFileBuffer不是一个有效的PE标志
    ");
            return 0;
        }
        
        // 强制结构体类型转换
        pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
        pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 0x04); // 这里必须强制类型转换
        pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
        pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
        //pSectionHeader = (PIMAGE_SECTION_HEADER)(pNTHeader+0x01);
        //上述表示节表指针pSectionHeader的另一种写法,就是通过NT头的这个一个整体的结构体宽度进行移动;
        //因为NT头整体结构体宽度是4+20+224=248 --> 16进制F8,通过加0x01就直接移动F8的字节,刚好落在节表位置;
        pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
    
        //定位重定位表VirtualAddress,即RVA地址
        RVA_BaseRelocationTable = pDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
        //定位重定位表SizeOfBlock, 即块的大小
        SizeOfBlock_BaseRelocationTable = pDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
    
        if (!debug)
        {
            printf("重定位表VirtualAddress地址: %#010X
    ", RVA_BaseRelocationTable);
            printf("重定位表SizeOfBlock大小: %#010X
    ", SizeOfBlock_BaseRelocationTable);
        }
        
        if (!RVA_BaseRelocationTable)
        {
            printf("这个程序没有导出表.
    ");
            return 0;
        }
    
        //重定位表FOA地址,这里的FOA地址是为从数据目录数组[5]获取的RVA并转换为FOA,目的是为了下面
        //偏移到FileBuffer中准确的BaseRelocation重定位表起始位置
        DWORD FOA_BaseRelocationTable = RvaToFileOffset(pFileBuffer,RVA_BaseRelocationTable);
    
        //定位重定位表文件偏移的位置,即:FIleBuffer的文件偏移
        pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + FOA_BaseRelocationTable);
    
        if (!debug)
        {
            printf("重定位表FOA地址: %#010X
    ", FOA_BaseRelocationTable);
            printf("重定位表文件偏移地址: %#010X
    ", pBaseRelocation);
        }
    
        //定义节里面名称的数组变量,并置为0
        BYTE secName[9] = {0};//这里是定义9个宽度的字节数组并置为0,为后面的节操作做准备
    
        //
        for (DWORD i = 0;pBaseRelocation->SizeOfBlock && pBaseRelocation->VirtualAddress; i++)
        {
            //参数pVirtualOfFOA_BaseReloc的FOA地址是从重定位表处获取的VirtualAddress将其转换为FOA地址;
            DWORD pVirtualOfFOA_BaseReloc = RvaToFileOffset(pFileBuffer,pBaseRelocation->VirtualAddress);
            DWORD pSizeOfBlock_BaseReloc = (pBaseRelocation->SizeOfBlock - 8)/2;
            /*上面是根据VirtualAddress,SizeOfBlock总共占8个字节,SizeOfBlock是当前块的中大小
            进行计算,为了算出真正具体项的数量,然后判断哪些是需要修改的项;
            
            具体项宽度:2字节
            也就是这个数据,内存中的页大小是1000H 也就是说2的12次方 就可以表示,一个页内所有的偏移地址
            具体项的宽度是16字节高四位,代表类型:值为3代表的是需要修改的数据值为0代表的是,用于数据对齐的数据
            可以不用修改.也就是说 我们只关注高4位的值为3的就可以了.
            1000H --> 4096D --> 2的12次方
            12 + 4 = 16 bit
            */
    
            //开始通过计算确定该结构所属哪个节里面
            for (DWORD j = 0; j < pPEHeader->NumberOfSections; j++)
            {
                DWORD pLowAddressOfFoa = RvaToFileOffset(pFileBuffer,pSectionHeader[j].VirtualAddress);
                DWORD pHighAddressOfFoa = RvaToFileOffset(pFileBuffer,pSectionHeader[j].Misc.VirtualSize);
    
                if (pVirtualOfFOA_BaseReloc >= pLowAddressOfFoa && pVirtualOfFOA_BaseReloc <= pHighAddressOfFoa)
                {
                    memcpy(secName,pSectionHeader[j].Name,8);
                    break;
                }
            }
            //下面是打印本页的主要信息
            printf("节:%X -> 重定位表VA:%#010X -> 节的名称:%s -> 具体项大小:%#010X
    ",
                i,pBaseRelocation->VirtualAddress,secName,pSizeOfBlock_BaseReloc);
            
            //打印一个页中所有重定位信息和地址
    
            //下面是通过偏移8个字节指向块中第一个具体项,因为宽度是2个字节,所以使用WORD并带指针类型
            WORD* recAddr = (WORD*)((BYTE*)pBaseRelocation+0x08);
            
            for(j=0; j<pSizeOfBlock_BaseReloc; j++)//每个结构的内容进行遍历
            {
                /*下面算法解释:
                第一行代码:通过获得的第一个具体项2个字节的地址与0x0FFF进行与操作,结果就去除了高4位;
                然后就得到了准确的后面12位的偏移,刚好是咱想要的,直接按照算法来操作,使用该节所属的
                VirtualAddress进行相加偏移,当然这里的VirtualAddress是需要转换为FOA的地址之后进行计算
                此时便得到了准确需要偏移的值offset参数,通过这个参数进行偏移可得到准确的需要修改的偏移
                地址,而参数type,就是我们所说的高4位代表一个类型,我们需要使用右移的方式将其置为0x0011
                也就是3,所以代码中操作对其进行右移12位即可得到0x0011-->3;
                这里回顾下右移:因为int如果是有符号的整形数,最左端的1位是符号位,0正1负,而现在我们的最
                左端是0011,符号位是0表示正,那么右移的时候前面补0即可,所以移动12位置之后type参数的结果
                就是0x0011->3
    
                下面是计算其中一个节的具体数据例子:
                节:0 -> 重定位表VA:0X00001000 -> 节的名称:.text -> 具体项大小:0X00000088
                recAddr[j]:0X0000315F ---> 0x315F ---> 0011 0001 0101 1111
                offset:0X0000115F     ---> 0011 0001 0101 1111 & 0000 1111 1111 1111 --->0x015F
                0x015F + 0x1000 = 0x115F ---> offset:0X0000115F
                type:0X00000003  ---> 0011 0001 0101 1111 >> 12 ---> 0000 0000 0000 0011 --> 0x0011 --> 3
                0X0000215F,0X00000003
                */
                DWORD pRepair_RvaOffset = (recAddr[j] & 0x0FFF) + pVirtualOfFOA_BaseReloc;
                //printf("recAddr[j]:%#010X 
    ",recAddr[j]);
                //printf("offset:%#010X 
    ",offset);
                WORD type = recAddr[j] >> 12;//三位
                //printf("type:%#010X 
    ",type);
                
                if(type!=0)
                {
                    printf("%#010X,%#010X
    ",pRepair_RvaOffset,type);
                    //	system("pause");
                }
            }
            memset(secName, 0, 9);
            pBaseRelocation = (PIMAGE_BASE_RELOCATION )((BYTE *)pBaseRelocation + pBaseRelocation->SizeOfBlock);
        }
        return 0;
    }
    
    void PrintBaseRelocation()
    {
        LPVOID pFileBuffer = NULL;
        DWORD FileBufferSize = 0;
        DWORD BaseName_FunctionAddr = 0;
        DWORD BaseOrdinals_FUnctionAddr = 0;
        
        //File-->FileBuffer
        FileBufferSize = ReadPEFile(FilePath_In,&pFileBuffer);
        if (FileBufferSize == 0 || !pFileBuffer)
        {
            printf("文件-->缓冲区失败
    ");
            return ;
        }
        printf("FileBufferSize: %#X 
    ",FileBufferSize);
    
        GetDataDirectoyOfBaseRelocation(pFileBuffer);
        
        free(pFileBuffer);
    }
    
    int main(int argc, char* argv[])
    {
        PrintBaseRelocation();
        printf("Hello World That Fuck Successfully!
    ");
        return 0;
    }
    
    • 由于需要修改的重定位表信息过多,默认使用VC6 打印出来,显示不全,所以我这配置好环境变量通过命令行编译,并修改命令显示参数;
    • 相关命令行配置bat如下:其他配置参加为博客文章
    @echo off
    
    cd C:cntflxcommandlx
    del *.obj
    del *.exe
    cl /c /W3 /WX C:cntflxcommandlxpelx.cpp
    link pelx.obj
    pelx.exe
    pause
    
    • 展示结果



      省略一部分.........


    • 工具解析验证
    • 手工打开winhex比对验证
    • 首先是根据得出的FOA地址0x35000定位到重定位表位置,然后比对;
    • 根据上面结果,再比对工具里面的内容,可确认解析成功,没问题;
    迷茫的人生,需要不断努力,才能看清远方模糊的志向!
  • 相关阅读:
    ComboBox.DoubleClick事件
    mktime 夏令时
    STL String的使用[转]
    加在电源后至进入操作系统前的计算机的行为
    C语言数据类型大小分析(基于VC2005编译器)
    linux线程同步之条件变量
    windows 下架设svn服务器(转载+修改) (非利用Google项目托管)
    浅尝《Windows核心编程》之内核对象
    C——数组与指针
    如何用U盘做系统启动盘WINPE 并且 利用WINPE安装Ghost
  • 原文地址:https://www.cnblogs.com/autopwn/p/15330266.html
Copyright © 2020-2023  润新知