for ( INT i=1; i<FILE_HEADER.NumberOfSections; i++ )
{
if ( SECTION_HEADER[i].VirtualAddress>OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress )
{
ExVRk = SECTION_HEADER[i-1].VirtualAddress - SECTION_HEADER[i-1].PointerToRawData;
break;
}
}
接下来对于和输入表相关的数据,只需要用对应项减去ImVRk就是这一项在磁盘文件里的偏移。如输入表的OriginalFirstThunk的RVA是00318140,只要用这个值减去ImVRk,就可以得到OriginalFirstThunk在磁盘文件的偏移。So is Export Table!
其他就只剩下怎么处理读取的数据了。我用的是C++的文件输入流fstream,以二进制的形式读进一批数据,通常都是以相应块的大小读入数据,如以sizeof(IMAGE_FILE_HEADER),然后通过 ToNumeric( LPDWORD ptr, CHAR buf[], INT start, INT size )函数将字符形式的变量转换为数值型,有时有需要一些字符型的数据,如函数名,就要用ToString( LPSTR ptr, CHAR buf[], INT start, INT size)取出特定的某几位字符,这些在源代码里都可以看到。我在写的过程中,发现C++的输入流不是太稳定,有的时候会读不进数据。我在每一个涉及到读入数据的地方都加了输入流的clear()函数,它重置了流的状态,让流始终处于稳定的状态下。输出分析报告到txt文件,我用的是C++的输出流,为了保证输出的稳定性,我也调用了输出流的clear()函数。最后的报告会保存在和用户输入的可执行文件同名的文本文件里。
2)成员函数BOOL Show_EXPORT_TABLE( ifstream&, ofstream& ) const是负责输出表的显示的,但是也负责读取和显示输出表成员所指向的函数和函数地址等一类的东东。在DataDump里还有一个成员函数是BOOL Set_EXPORT_TABLE( CHAR [], INT ),它是负责对EXPORT_DIRECTORY所指向的结构的读取,因为这一部分是固定的,可以预先读取,当然是在存在输出表的情况下。BOOL Show_EXPORT_TABLE( ifstream&, ofstream& ) const中的关键代码如下:
if ( EXPORT_DIRECTORY->NumberOfFunctions )
{
OrdalOffset = EXPORT_DIRECTORY->AddressOfNameOrdinals-ExVRk; // Get The Raw Offset Of NameOrdinals
FunOffset = EXPORT_DIRECTORY->AddressOfFunctions-ExVRk; // Get The Raw Offset Of Functions
NameOffset = EXPORT_DIRECTORY->AddressOfNames-ExVRk; // Get The Raw Offset Of Name_RVA_Arrays
buf[31] = 0;
INT* OrdalArray = new INT [EXPORT_DIRECTORY->NumberOfFunctions];
for ( INT i=0; i<EXPORT_DIRECTORY->NumberOfFunctions; i++ )
{
OrdalArray[i] = -1;
}
for ( i=0; i<EXPORT_DIRECTORY->NumberOfNames; i++ )
{
PE_file.seekg(OrdalOffset+2*i); // Get The Item Of The Serial Table
PE_file.read(buf, 2);
Offset = 0x00000000;
ToNumeric((LPDWORD)&Offset, buf, 0, 2); // Store The Index In The Array "OrdalArray"
OrdalArray[Offset] = i;
}
for ( i=0; i<EXPORT_DIRECTORY->NumberOfFunctions; i++ )
{
fout<<setfill('0')<<" "<<setw(8)<<EXPORT_DIRECTORY->Base+i; // Get The Serial Of The Function
if ( PE_file.eof() )
break;
PE_file.seekg(FunOffset+4*i);
PE_file.read(buf, 4);
ToNumeric((LPDWORD)&Offset, buf, 0, 4); // Get The Item Of EAT into offset
fout<<setfill('0')<<" "<<setw(8)<<Offset;
if ( OrdalArray[i]!=-1 ) // If the OrdalArray[i]!=-1, which represents index i is injected into an Item ENT
{
PE_file.seekg(NameOffset+4*OrdalArray[i]);
// OrdalArray[i] is the Index of the ENT
PE_file.read(buf, 4);
ToNumeric((LPDWORD)&Offset, buf, 0, 4);
PE_file.seekg(Offset-ExVRk);
PE_file.read(buf, 31);
buf[31] = 0;
fout<<setfill(' ')<<setw(34)<<buf<<endl;
}
else
{
fout<<" ---------------"<<endl;
}
}
delete [] OrdalArray;
}
else
{
fout<<" ->No Exact Information !"<<endl;
}
}
在输出文件头时,其中有一个属性是TimeDateStamp,表示的是文件创建的时间,是自1970年1月1日以来的格林威治时间(GMT)计算的秒数。输出一串和大的数字,也不太明白他的意思,以该报它转化成我们日常生活中使用的日期时间格式。所以在中添加了以下代码:
struct tm Time = *gmtime((CONST LONG *)&FILE_HEADER.TimeDateStamp);
fout<<"----> 3. Created Time : "<<asctime(&Time);
在可选文件头里有一项属性是NumberOfRvaAndSizes,他的值一般为16。但是在一些特殊的PE文件,如miniPE里(稍后讨论),这个值会是1。而且,这个值在某些情况下也可能会比16大。所以,在NumberOfRvaAndSizes>16时,把它的值限定在16的范围内是有必要地,主要是考虑到后面计算输入输出表RVA和磁盘物理偏移的差值VRk的正确性。所以在BOOL DataDump::Set_OPTIONAL_HEADER32( CHAR buf[], INT size )中加入了下列语句:
if ( OPTIONAL_HEADER32.NumberOfRvaAndSizes>16 )
OPTIONAL_HEADER32.NumberOfRvaAndSizes = 16;