PE文件资源表加速键的解析

最近在做PE工具,在解析资源表中的加速表资源时遇到点困难,由于网上没有PE资源表里加速表的结构而无从下手解析加速表,不过经过一番研究后也是解决了,特地写这篇文章记录。

一、关于加速表、加速键

加速键是windows预定义的一类资源,在PE资源表中的标号是9,作用是当用户在键盘上按下某些组合键时触发WM_COMMAND消息,前提是在消息循环里使用了TranslateAccelerator()转换WM_KEYDOWN消息。具体使用方法不是本文讨论的重点,详细的可以参考msdn。
至于加速表,这是一张用来存放加速键信息的一个数组,一种是用来作为CreateAcceleratorTable()参数的加速表,另一种是存在于资源表里,使用LoadAccelerator()加载的加速表,本文主要讨论这种表。

二、在PE资源表里的加速表

相比于位图、图标、光标、对话框这些资源,加速键算是比较冷门的资源了,网上对于加速表的讲解主要都是其用法,而对于其解析,即其反编译的资料都很少。不过在对一些实验程序做了研究后得到了不少结论。
以下是研究过程:
1)建立一个包含加速表的程序,如下图:
PE文件资源表加速键的解析
PE文件资源表加速键的解析

2)使用自己写的PE工具进行解析,大致思路是根据PE资源表的结构直接定位加速表,这是有难度的。另一种思路是直接使用EnumResourceLanguages()直接定位,再用FindResource(),LoadResource(),LockResource() 获取加速表的数据。两种方法具体实现参考其他文章。这里假设已经获得了加速表数据。

3)由于没有现成的资料,只能自己想。上文提到加速表的创建可以用CreateAcceleratorTable() 来创建,那么我们以此往下找,发现参数有个指针指向一个名叫ACCEL的结构体。继续看这个结构体,再上msdn查,发现其各项的值都与上图所示的内容相对应,那么我们猜想PE资源表里就是这个结构体的数组。
#pragma pack(push,2) //按2字节对齐,于是此结构体占6字节
typedef struct tagACCEL {
BYTE fVirt; //实际占2字节
WORD key;
WORD cmd; //资源编辑器里的ID值
} ACCEL, *LPACCEL;

4)写上后续的代码进行解析,代码如下:
// pData 是指向资源数据的指针,nSize是数据块字节大小,
//可用SizeofResource()或加速表的IMAGE_RESOURCE_DATA_ENTRY的Size字段获取
void AnalyzeAccTable(void* pData, UINT nSize)
{
//获取数组元素个数
UINT nNum = nSize / sizeof(ACCEL);
LPACCEL pAcc = (LPACCEL)pData;
TCHAR buffer[64] = {0};
int i;
for(i = 0; i < nNum; i++)
{
wsprintf(buffer, _T( “ID: %u\n”), pData->cmd);
//其他解析代码。。。
}
}

5)下断点调试,i = 0时,buffer = L"ID: 32771",与第二张图相符;
i = 1时,问题就来了:buffer = L"ID : 13;与图二中的32772不符。一开始怀疑这思路的正确性,但如果思路不正确的话也不会i = 0时也正确了。而且查看nNum = 25, 实际上算nSize / 6 = 25.33,与实际值19不符。于是查看内存情况。首先看pData所指向的内容,如下图:
PE文件资源表加速键的解析
按ACCEL里定义的比较,发现第一项的cmd值为0x8003(little-endian),即32771,而第二项的cmd值为0x000d,即13,按理来说这里应该是32772,即0x8004才对。再往后看时,发现 04 80,即0x8004,是我们想要的值。通过对比发现与我们想要的位置相差2字节,联想到nNum = 25,再通过观察发现每隔6字节出现00 00,于是我们可以大胆推测PE资源表的ACCEL结构占8个字节而非6字节。我们定义一个新的结构体,代码如下:
typedef struct ACCEL_IN_PE
{
WORD fVirt;
WORD key;
DWORD cmd;//补齐后面的0000
}*PACCEL_IN_PE;
最后应用到上述解析代码中,再运行一次,发现i在各个取值下cmd值都准确无误,再用ResourceHacker查看,如下图:
PE文件资源表加速键的解析
程序的运行结果也与ResourceHacker一致,证明了上述猜测是正确的。