逆向笔记——输出PE导入表、绑定导入表

导入表

导入表是什么?

记录一个可执行文件所用到的其他模块的导出的函数

记录信息:dll列表及其所用用到的函数

在PE中的结构

struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics; 
        DWORD   OriginalFirstThunk;   // RVA 指向IMAGE_THUNK_DATA结构数组
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp; 
    DWORD   ForwarderChain;         
    DWORD   Name; 					//RVA 指向dll的名字,名字以0结尾
    DWORD   FirstThunk;      		 //RVA 指向IMAGE_THUNK_DATA结构数组       
} IMAGE_IMPORT_DESCRIPTOR;
				
	typedef struct _IMAGE_THUNK_DATA32 {						
	    union {						
	        PBYTE  ForwarderString;						
	        PDWORD Function;						
	        DWORD Ordinal;						//序号
	        PIMAGE_IMPORT_BY_NAME  AddressOfData; //指向IMAGE_IMPORT_BY_NAME					
	    } u1;						
	} IMAGE_THUNK_DATA32;						
	typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;						
							
	typedef struct _IMAGE_IMPORT_BY_NAME {						
	    WORD    Hint;	//可能为空,编译器决定,如果不为空,是函数在导出表中的索引
	    BYTE    Name[1];//函数名称,以0结尾						
	} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;												

这3个结构的关系如下图

如图可知,PE加载前和加载后是有些不同的——IAT表中的地址变为模块的RVA或绝对地址

 IMAGE_IMPORT_DESCRIPTOR

	OriginalFirstThunk 未绑定导入表的RVA

	TimeDateStamp

		0:未绑定

		1:已绑定

	FirstThunk  IAT的RVA

		绑定:绝对地址

		未绑定:RVA

导入表有什么作用?

通过导入表中提供的dll所依赖的函数清单去定位函数地址。

打印导入表的步骤

1、定位导入表

目录项的第二个:

2、RVA转FOA

3、输出导入表的描述信息:以0结束

4、输出dll名字:Name字段以0结尾的字符串为dll的名字

5、遍历OriginalFirstThunk

根据最高位的值判断是名称还是序号:为1==》除去最高位的值就是函数的导出序号(&80000)

​ 为0==》指向函数名的地址

​ IMAGE_IMPORT_BY_NAME : HIT 2字节

​ NAME 长度不确定 以‘\0’ 结尾

6、遍历FirstThunk: 同上

#include <stdio.h>
#include <windows.h>
#include <string.h>
 
#define SRC1 "ipmsg.exe"
#define SRC "notepad.exe"

DWORD RvaToFoa(PIMAGE_NT_HEADERS pNTHeader, DWORD dwRVA);
DWORD FoaToRva(PIMAGE_NT_HEADERS pNTHeader, DWORD dwFOA);
LPVOID ReadPEFile(LPVOID pFileBuf);
DWORD FileSize(LPVOID pFileBuf);

//移动重定位表
void ImportTbInfo(){
    //定义pe头结构指针
    PIMAGE_DOS_HEADER pDosHeader;        //dos头指针
	PIMAGE_NT_HEADERS pNtHeaders;
    PIMAGE_IMPORT_DESCRIPTOR importDesc;  //导入表描述
	PIMAGE_IMPORT_BY_NAME pImportName;  //函数地址表
	PIMAGE_THUNK_DATA  pThunkData;  //数据表
 
    //1.将文件读入内存
    LPVOID pFileBuf = fopen(SRC,"rb");
    DWORD fileSize = FileSize(pFileBuf);
	LPVOID pFileBuffer = ReadPEFile(pFileBuf);
	
    if(!pFileBuffer){
        printf("读取dll文件失败\n");
        return;
    }

 
    //3.初始化头结构指针
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	PIMAGE_DATA_DIRECTORY dataDir = pNtHeaders->OptionalHeader.DataDirectory;
	WORD numOfSec = pNtHeaders->FileHeader.NumberOfSections;
    importDesc = (PIMAGE_IMPORT_DESCRIPTOR) ((DWORD)pFileBuffer + RvaToFoa(pNtHeaders, dataDir[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));

	//4遍历模块表
	while(importDesc->Name)
	{
		pThunkData = (PIMAGE_THUNK_DATA)(RvaToFoa(pNtHeaders,importDesc->OriginalFirstThunk)+(DWORD)pFileBuffer);
		char* pName = (char*)(RvaToFoa(pNtHeaders,importDesc->Name)+(DWORD)pFileBuffer);
		printf("模块名称(Name):%s\n", RvaToFoa(pNtHeaders,importDesc->Name)+(DWORD)pFileBuffer );   //这个打印出来的字符串应该如何输出
		while(pThunkData->u1.AddressOfData)
		{
			if(IMAGE_SNAP_BY_ORDINAL32((DWORD)pThunkData->u1.AddressOfData)) //如果是序号导入 最高位为1
				printf("导入函数序号:%04x\n", (pThunkData->u1.Ordinal) & 0xFFFF); //去掉最高位的1,后面的就是导入序号
			else
			{
				pImportName = (PIMAGE_IMPORT_BY_NAME)(RvaToFoa(pNtHeaders,(DWORD)pThunkData->u1.AddressOfData)+(DWORD)pFileBuffer);
				printf("导入类型:%04x, 函数名:%s\n", pImportName->Hint,pImportName->Name); //去掉最高位的1,后面的就是导入序号
			}
			pThunkData++;
		}
		importDesc++;
	}

	//4.1 遍历模块中的函数
    return;
}
 


int main(int argc, char* argv[])
{

	ImportTbInfo();
	getchar();
	return 0;
}
DWORD RvaToFoa(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;
}
DWORD FoaToRva(PIMAGE_NT_HEADERS pNTHeader, DWORD dwFOA)
{
    PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)((DWORD)pNTHeader + sizeof(IMAGE_NT_HEADERS));

    for(int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++)
    {
        if(dwFOA >= pSection[i].PointerToRawData && dwFOA < (pSection[i].PointerToRawData + pSection[i].SizeOfRawData))
        {
            return pSection[i].VirtualAddress + (dwFOA - pSection[i].PointerToRawData);
        }
    }

    return 0;
}


//将PE文件读到FileBuffer
DWORD FileSize(LPVOID pFileBuf)
{
     //计算文件大小
     fseek((FILE* )pFileBuf,0,SEEK_END);
     long fileSize = ftell((FILE* )pFileBuf);
     fseek((FILE* )pFileBuf,0,SEEK_SET);
	 return fileSize;
}
LPVOID ReadPEFile(LPVOID pFileBuf)
{
     //打开文件
     //Stream = fopen(fileName,"rb");
     //计算文件大小
	 DWORD fileSize = FileSize(pFileBuf);

     //分配堆空间
     LPVOID pFileBuffer = malloc(sizeof(char)*fileSize);
     //将文件拷到堆
     fread(pFileBuffer,sizeof(char),fileSize,(FILE*)pFileBuf);
     fclose((FILE*)pFileBuf);

	 return pFileBuffer;
}

绑定导入表

what?

1、起始:INT=IAT,都是程序引用的dll中的函数名或序号

2、加载完后:IAT表中替换成函数真正的地址

这个真正的地址,有两种情况

​ 2.1 程序在加载的过程中,IAT表中的地址替换成函数的RVA。

​ 2.2 程序在加载前,IAT表中的地址为绝对地址,如此一来,省去了修复IAT表的时间。但是有两种情况比较糟糕:

​ 2.2.1 dll基址冲突,需要重定位

​ 2.2.2 dll被修改,IAT表中对应的函数地址可能会被修改

why?

提高加载的效率

when: 时间戳: 0 没绑定 -1 当前dll已绑定,并且已经存在另外一张表中

how?

数据目录项的第二项

offsetmoudleName 第一个Desc的值+offsetmoudleName 才是名字的偏移

1、根据导入表结构中的 :TimeDataStamp来判断是否绑定导入

​ 1.1 TimeDataStamp = 1;未绑定导入

​ 1.2 TimeDataStamp = 0xFFFF FFFF ; 绑定导入

3、目录项中的位置

define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers

4、绑定导入表的结构

typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
    DWORD   TimeDateStamp;   //时间戳
    WORD    OffsetModuleName; //dll的名字 : 
    WORD    NumberOfModuleForwarderRefs;
// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR,  *PIMAGE_BOUND_IMPORT_DESCRIPTOR;

OffsetModuleName 计算的方法: 第一个 *(_IMAGE_BOUND_IMPORT_DESCRIPTOR )+OffsetModuleName

5、绑定导入表结束标记

最后一个全0的结构说明已经没有绑定导入表

输出绑定导入表代码

#include <stdio.h>
#include <windows.h>
#include <string.h>
 
#define SRC1 "ipmsg.exe"
#define SRC "notepad.exe"

DWORD RvaToFoa(PIMAGE_NT_HEADERS pNTHeader, DWORD dwRVA);
DWORD FoaToRva(PIMAGE_NT_HEADERS pNTHeader, DWORD dwFOA);
LPVOID ReadPEFile(LPVOID pFileBuf);
DWORD FileSize(LPVOID pFileBuf);

//移动重定位表
void PrintBoundImport(){
    //定义pe头结构指针
    PIMAGE_DOS_HEADER pDosHeader;        //dos头指针
	PIMAGE_NT_HEADERS pNtHeaders;
	PIMAGE_BOUND_IMPORT_DESCRIPTOR  boundDir;  //数据表
	PIMAGE_BOUND_FORWARDER_REF boundDef;
 
    //1.将文件读入内存
    LPVOID pFileBuf = fopen(SRC,"rb");
    DWORD fileSize = FileSize(pFileBuf);
	LPVOID pFileBuffer = ReadPEFile(pFileBuf);
	
    if(!pFileBuffer){
        printf("读取dll文件失败\n");
        return;
    }

 
    //3.初始化头结构指针
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	PIMAGE_DATA_DIRECTORY dataDir = pNtHeaders->OptionalHeader.DataDirectory;


	
	DWORD boundDirRVA = dataDir[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress;
    boundDir = (PIMAGE_BOUND_IMPORT_DESCRIPTOR)((DWORD)pFileBuffer + boundDirRVA);
    PIMAGE_BOUND_IMPORT_DESCRIPTOR currentBound = boundDir;

	//4遍历模块表
   //3.输出绑定导入表信息
    while(currentBound -> TimeDateStamp){
        getchar();
        LPSTR moduleName = (LPSTR) ((DWORD)(currentBound->OffsetModuleName) + (DWORD)boundDir); //boundDir 第一个descriptor的值+OffsetModuleName
        printf("OffsetModuleName:%s\n", moduleName);
        printf("TimeDateStamp:%d\n",currentBound->TimeDateStamp );
        int i = currentBound->NumberOfModuleForwarderRefs;
        printf("依赖dll个数:%d\n", i);
        
        if(i>0){
            boundDef = (PIMAGE_BOUND_FORWARDER_REF) ((DWORD) currentBound + 8); // 8 = DWORD + DWORD  len(PIMAGE_IMPORT_DESCRIPTOR)+len(PIMAGE_BOUND_IMPORT_DESCRIPTOR)

            for(int j=0;j<i;j++){
                LPSTR refName = LPSTR((DWORD)((boundDef + j)->OffsetModuleName) + (DWORD)boundDir); //计算ref的名称,原理同上
                DWORD refTime = (boundDef + j) -> TimeDateStamp;
                printf("依赖dll:%s,时间戳:%d\n",refName,refTime);
            }
            currentBound = currentBound + (i+1);
        }else{
            currentBound++;
        }
    }

    return;
}
 


int main(int argc, char* argv[])
{

	PrintBoundImport();

	getchar();
	return 0;
}



DWORD RvaToFoa(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;
}
DWORD FoaToRva(PIMAGE_NT_HEADERS pNTHeader, DWORD dwFOA)
{
    PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)((DWORD)pNTHeader + sizeof(IMAGE_NT_HEADERS));

    for(int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++)
    {
        if(dwFOA >= pSection[i].PointerToRawData && dwFOA < (pSection[i].PointerToRawData + pSection[i].SizeOfRawData))
        {
            return pSection[i].VirtualAddress + (dwFOA - pSection[i].PointerToRawData);
        }
    }

    return 0;
}


//将PE文件读到FileBuffer
DWORD FileSize(LPVOID pFileBuf)
{
     //计算文件大小
     fseek((FILE* )pFileBuf,0,SEEK_END);
     long fileSize = ftell((FILE* )pFileBuf);
     fseek((FILE* )pFileBuf,0,SEEK_SET);
	 return fileSize;
}
LPVOID ReadPEFile(LPVOID pFileBuf)
{
     //打开文件
     //Stream = fopen(fileName,"rb");
     //计算文件大小
	 DWORD fileSize = FileSize(pFileBuf);

     //分配堆空间
     LPVOID pFileBuffer = malloc(sizeof(char)*fileSize);
     //将文件拷到堆
     fread(pFileBuffer,sizeof(char),fileSize,(FILE*)pFileBuf);
     fclose((FILE*)pFileBuf);

	 return pFileBuffer;
}

猜你喜欢

转载自www.cnblogs.com/Erma/p/12649863.html