• 滴水逆向-导出表-课堂笔记



    相关知识点

    导出表:
    
    导出表结构分析
    
    1、如何定位导出表:
    
    数据目录项的第一个结构,就是导出表.
    
    typedef struct _IMAGE_DATA_DIRECTORY {
        DWORD   VirtualAddress;
        DWORD   Size;
    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
    
    VirtualAddress  导出表的RVA
    
    Size 导出表大小
    
    2、导出表结构
    
    上面的结构,只是说明导出表在哪里,有多大,并不是真正的导出表.
    
    如何在FileBuffer中找到这个结构呢?在VirtualAddress中存储的是RVA,如果想在FileBuffer中定位
    
    必须要先将该RVA转换成FOA.
    
    真正的导出表结构如下:
    
    
    typedef struct _IMAGE_EXPORT_DIRECTORY {
        DWORD   Characteristics;				// 未使用
        DWORD   TimeDateStamp;				// 时间戳
        WORD    MajorVersion;				// 未使用
        WORD    MinorVersion;				// 未使用
        DWORD   Name;				        // 指向该导出表文件名字符串
        DWORD   Base;				        // 导出函数起始序号
        DWORD   NumberOfFunctions;			// 所有导出函数的个数
        DWORD   NumberOfNames;				// 以函数名字导出的函数个数
        DWORD   AddressOfFunctions;         // 导出函数地址表RVA
        DWORD   AddressOfNames;             // 导出函数名称表RVA
        DWORD   AddressOfNameOrdinals;      // 导出函数序号表RVA
    } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
    
    
    导出函数两种方式:
    (1)名字导出 (2)序号导出
    
    下面是重要参数选项:
    
    AddressOfFunctions			AddressOfNameOrdinals			AddressOfNames
    
    0	Fn1地址		            0	3		                    0	0x12345678
    1	Fn2地址		            1	1		                    1	0x12345678
    2	Fn3地址		            2	4		                    2	0x12345678
    3	Fn4地址		            3	7		                    3	0x12345678
    4	Fn5地址		            4	8		                    4	0x12345678
    	Fn6地址			        6			                        0x12345678
    	Fn7地址			        7			                        0x12345678
    	..						                                    0x12345678
    	..						                                    0x12345678
    	..						                                    0x12345678
    	..						                                    0x12345678
    	..						                                    0x12345678
    	Fnx地址			        N			                        0x12345678
    
    	宽度4			        宽度2			                宽度4
    
    数量:NumberOfFunctions		数量:NumberOfNames			    数量:NumberOfNames
    
    3、AddressOfFunctions说明:
    
    该表中元素宽度为4个字节
    
    该表中存储所有导出函数的地址
    
    该表中个数由NumberOfFunctions决定
    
    该表项中的值是RVA, 加上ImageBase才是函数真正的地址
    
    定位:
    
    IMAGE_EXPORT_DIRECTORY->AddressOfFunctions 中存储的是该表的RVA 需要先转换成FOA
    
    4、AddressOfNames说明:
    
    该表中元素宽度为4个字节
    
    该表中存储所有以名字导出函数的名字的RVA
    
    该表项中的值是RVA, 指向函数真正的名称
    
    AddressOfNames						特别说明:
    
    0x12345678						1、函数的真正的名字在文件中位置是不确定的
    0x12345678			DXXXXXXXXX
    0x12345678						2、但函数名称表中是按名字排序的
    0x12345678
    0x12345678			AXXXXXXXXXXX	也就是说,A开头的函数在AddressOfNames排在最前面.
    0x12345678
    0x12345678			CXXXXXX			但AXXXXXX这个真正的名字,可能排在BXXXXX后面
    0x12345678
    0x12345678			BXXXXXXXXX			3、如果想打印名字,要先将AddressOfNames转换为FOA
    0x12345678
    0x12345678
    0x12345678
    0x12345678
    
    
    5、AddressOfNameOrdinals
    
    该表中元素宽度为2个字节
    该表中存储的内容 + Base = 函数的导出序号
    
    
    总结:
    
    为什么要分成3张表?
    
    1.函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开.
    
    2.函数地址表是不是一定大于函数名称表?
    
    未必,一个相同的函数地址,可能有多个不同的名字.
    
    3.如何根据函数的名字获取一个函数的地址?
    函数VA = ImageBase + 0x1234
    函数名称表		函数序号表		函数地址表
    
    4.如何根据函数的导出序号获取一个函数的地址?
    
    假设导出序号是10
    
    Base 的值是5
    
    函数地址:10 - 5 = 5
    
    找出下标为5的函数地址即可;
    
    
    
    
    课后练习
    
    1.编写程序打印所有的导出表信息;
    
    typedef struct _IMAGE_EXPORT_DIRECTORY {
        DWORD   Characteristics;				// 未使用
        DWORD   TimeDateStamp;				// 时间戳
        WORD    MajorVersion;				// 未使用
        WORD    MinorVersion;				// 未使用
        DWORD   Name;				        // 指向该导出表文件名字符串
        DWORD   Base;				        // 导出函数起始序号
        DWORD   NumberOfFunctions;			// 所有导出函数的个数
        DWORD   NumberOfNames;				// 以函数名字导出的函数个数
        DWORD   AddressOfFunctions;         // 导出函数地址表RVA
        DWORD   AddressOfNames;             // 导出函数名称表RVA
        DWORD   AddressOfNameOrdinals;      // 导出函数序号表RVA
    } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
    
    
    地址空间:这个地址空间指的是PE文件被加载到内存的空间,是一个虚拟的地址空间,
    之所以不是物理空间是因为数据在内存中的位置经常在变,这样既可以节约内存开支又可以避开错误的内存位置。
    这个地址空间的大小为4G,但其中供程序装载的空间只有2G而且还是低2G空间,高2G空间则被用于装载内核DLL文件,
    所以也被称作内核空间。
    
    文件映射:PE文件在磁盘上的状态和在内存中的状态是不一样的,我们把PE文件在磁盘上的状态称作FileBuffer,
    在内存中的状态称为ImageBuffer。当PE文件通过装载器装入内存是会经过“拉伸”的过程,
    所以它在FileBuffer状态下和ImageBuffer状态下的大小是不一样的;
    
    VA:英文全称是Virual Address,简称VA,中文意思是虚拟地址。指的是文件被载入虚拟空间后的地址。
    
    ImageBase:中文意思是基址,指的是程序在虚拟空间中被装载的位置。
    
    RVA:英文全称是Relative Virual Address,简称RVA,中文意思是相对虚拟地址。
    可以理解为文件被装载到虚拟空间(拉伸)后先对于基址的偏移地址。
    计算方式:RVA = VA(虚拟地址) - ImageBase(基址)。它的对齐方式一般是以1000h为单位在虚拟空间中对齐的(传说中的4K对齐),
    具体对齐需要参照IMAGE_OPTIONAL_HEADER32中的SectionAlignment成员;
    
    FOA:英文全称是File Offset Address,简称FOA,中文意思是文件偏移地址。
    可以理解为文件在磁盘上存放时相对于文件开头的偏移地址。它的对齐方式一般是以200h为单位在硬盘中对齐的(512对齐),
    具体对齐需要参照IMAGE_OPTIONAL_HEADER32中的FileAlignment成员;
    
    下面是判断RVA和FOA的计算方法;
    
    <1> 得到RVA的值,RVA=内存地址-ImageBase(ImageBase是IMAGE_OPTION_HEADER中的成员);
    <2> 比较RVA与SizeofHeaders的大小,判断RVA是否位于PE头中,如果是的话,FOA=RVA,(SizeofHeaders是IMAGE_OPTION_HEADER中的成员);
    <3> 判断RVA位于哪个节,
      RVA >= 节.VirtualAddress
      RVA <= 节.VirtualAddress + 当前节内存对齐后的大小
      差值 = RVA - 节.VirtualAddress
    <4> FOA = 节.PointerToRawData + 差值
    
    总结:
    总体来说,首先就是要判断某一个内存地址是否是在SizeOfHeaders里面,如果是,那么RVA=FOA,因为SizeOfHeaders在拉伸前后不变;
    而如果不在SizeOfHeaders,里面那么就要判断这个内地址落在哪个节的范围,然后减去落在这个节VirtualAddress的地址,得到她们;
    的差值,将得到的这个差值加上这个节中对应在文件中偏移的地址(PointerToRawData)结果就是FOA;
    
    实例计算:
    1.判断不带ImageBase地址0x00051EC0在哪个节里面;
    2.通过计算查找,发现0x00051EC0是落在第二个节里面,因为:VirtualAddress + VirtualSize > 上面不带ImageBase的地址;
      第二个节对应的VirtualAddress和VirtuallSIze 0x00046000+0x0000D74D=0x0005374D > 0x00051EC0;
    3.确认在哪个节里面之后就可以根据上面的总结计算,下面是导出表给出的VirtualAddress地址加上ImageBase然后减去ImageBase;
    对应的距离,刚好就是0x00051EC0
    RVA = 0x00051EC0 + 0x00400000 - 0x00400000 = 0x00051EC0;
    4.计算出她们之间的差值
    差值 = 0x00051EC0 - 0x00046000 = 0x0000BEC0;
    5.计算FOA的值
    FOA = 对应节的PointerToRawData + 差值
    FOA = 0x00046000 + 0x0000BEC0 = 0x00051EC0;
    注意:这里的0x00046000是因为此程序中节里面的PointerToRawData=VirtualAddress=0x00046000;
    
    2.GetFunctionAddrByName(FileBuffer指针,函数名指针)
    
    3.GetFunctionAddrByOrdinals(FileBuffer指针,函数名导出序号)
    
    

    上面计算FOA的过程验证

    验证方式是使用winhex打开存储在硬盘位置的ipmsg.exe和意见打开的ipmsg.exe程序,找到其对应的导出表的VirtualAddress地址;





    迷茫的人生,需要不断努力,才能看清远方模糊的志向!
  • 相关阅读:
    【java】定时任务@Scheduled
    20180513 实参 形参 数组
    20180513 实参 形参
    20180513 数组 实参 形参
    <转载>二维数组回形遍历
    20180318 代码错题(8)
    20180318 代码错题(7)
    20180318 代码错题(6)
    20180318 代码错题(5)
    20180318 bit置0
  • 原文地址:https://www.cnblogs.com/autopwn/p/15309491.html
Copyright © 2020-2023  润新知