一、概述
想要获取一个可执行文件(PE文件)里包含的资源文件,首先要解析可执行文件,得到资源存储的地址及大小,可参考 https://blog.csdn.net/zhyulo/article/details/85717711 。然后,根据资源存储方式,得到各资源的数据内容及其大小,可参考 https://blog.csdn.net/zhyulo/article/details/85930045 。
PE文件的资源中,位图、图标与光标的存储格式与bmp位图、ico图标与cur光标的文件的存储格式不太一样。具体表现在文件头PE资源中的缺失、·分离。但是具体的图片颜色数据,则没有变化。bmp位图、ico图标与cur光标文件格式,可参考 https://blog.csdn.net/zhyulo/article/details/85934728 。PE资源与独立文件的差别,具体见下面内容:
二、位图资源与bmp文件在数据结构上的差别
位图资源类型ID=2。位图资源与bmp文件在数据结构上的唯一差别,表现在bmp文件的BITMAPFILEHEADER文件头结构,在位图资源中的缺失。而信息头、调色板、位图点阵数据,则没有差别。所以,在位图资源的数据前,补加BITMAPFILEHEADER文件头,另存为bmp文件,就可以做到PE文件位图资源的文件提取了。
首先回顾一下BITMAPFILEHEADER文件头的定义:
typedef struct tagBITMAPFILEHEADER {
WORD bfType; //文件标识,规定为0x4D42,字符显示就是'BM'
DWORD bfSize; //文件大小
WORD bfReserved1; //保留,必须设置为0
WORD bfReserved2; //保留,必须设置为0
DWORD bfOffBits; //从头到点阵数据的偏移
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
可以看到,BITMAPFILEHEADER文件头并没有什么重要的信息(这也是资源中该结构被丢弃的原因吧)。
bfType为固定内容0x4D42;bfSize为整个文件大小,可以通过"sizeof(BITMAPFILEHEADER)+位图资源大小"得到;bfReserved1、bfReserved2没有使用,填0即可;bfOffBits是点阵数据的偏移地址,可以通过"sizeof(BITMAPFILEHEADER)+BITMAPINFOHEADER.biSize+(sizeof(RGBQUAD)*调色板数)"得到。
由于bfOffBits需要得到BITMAPINFOHEADER.biSize与调色板数,所以需要首先得到BITMAPINFOHEADER结构内容。
PE文件位图资源的文件提取的一般步骤为:得到BITMAPINFOHEADER信息头->构造BITMAPFILEHEADER文件头->输出BITMAPFILEHEADER文件头->输出位图资源。通过以下代码可以实现:
void GetBitmap(void *file, void *buf, int len)
{
BITMAPFILEHEADER BmpHead = {0x4d42,0};//位图信息头
BITMAPINFOHEADER *BmpInfo = (BITMAPINFOHEADER*)buf;//位图信息头
BmpHead.bfSize = sizeof(BITMAPFILEHEADER) + len;
if(BmpInfo->biBitCount < 16)
{
BmpHead.bfOffBits = sizeof(RGBQUAD);
if(BmpInfo->biClrUsed)
BmpHead.bfOffBits *= BmpInfo->biClrUsed;
else BmpHead.bfOffBits *= (1 << BmpInfo->biBitCount);
}
else BmpHead.bfOffBits = 0;
BmpHead.bfOffBits += sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
WriteFile(file, &BmpHead, sizeof(BITMAPFILEHEADER));
WriteFile(file, BmpInfo, len);
}
三、图标资源与ico文件在数据结构说的差别
在PE资源中,图标资源的文件头与图片数据分开保存。文件头被另存为一个叫图标组的资源,图标组资源类型ID=14。图片数据分为一个个图片,分开保存为图标资源,图标资源ID=3。
图标组资源的ICONDIR文件头结构,定义如下:
typedef struct
{
BYTE bWidth; //图像宽度,单位:像素
BYTE bHeight; //图像高度,单位:像素
BYTE bColorCount; //颜色数
BYTE bReserved; //保留,为0
WORD wPlanes; //平面数,一般为1
WORD wBitCount; //每像素比特数
DWORD dwBytesInRes; //数据块大小
WORD dwID;//数据块ID
} ICONDIRENTRY, *LPICONDIRENTRY;
typedef struct
{
WORD idReserved; //保留,为0
WORD idType; //文件类型,图标为1,光标为2
WORD idCount; //图象个数
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;
可以看到,图标组资源的ICONDIRENTRY结构与ico文件中的ICONDIRENTRY结构大致相同,但最后一个成员由dwImageOffset(数据块偏移量)变为了dwID(数据块ID)。这也可以理解,毕竟图标资源中是通过ID区分的。
PE文件图标资源的文件提取一般步骤为:得到图标组资源->输出ICONDIR文件头(不包括ICONDIRENTRY数组)并输出->输出文件头中的ICONDIRENTRY数组,注意数据块偏移量的累加->根据dwID(数据块ID)得到图标资源并输出。通过以下代码可以实现:
void GetIcons(void *file, char *buf)
{
DWORD Offset = sizeof(ICON_DIR);
ICON_DIR *IcoHead = (ICON_DIR*)buf;//图标文件头
WriteFile(file, IcoHead,Offset);
buf += Offset;
Offset += sizeof(ICONDIRENTRY) * IcoHead->wCount;
ICONDIRENTRY *IcoInfo;
int i;
for(i=0; i<IcoHead->wCount; i++)
{
IcoInfo = (ICONDIRENTRY*)(buf +
(sizeof(ICONDIRENTRY) - 2) * i);
WriteFile(file, IcoInfo, sizeof(ICONDIRENTRY) - 4);
WriteFile(file, &Offset, 4);
Offset += IcoInfo->dwBytesInRes;
}
for(i=0; i<IcoHead->wCount; i++)
{
IcoInfo = (ICONDIRENTRY*)(buf + (sizeof(ICONDIRENTRY) - 2) * i);
void *buf = FindResource(3, (WORD)IcoInfo->dwImageOffset);
if(!buf) return;
WriteFile(file, buf,IcoInfo->dwBytesInRes);
}
}
代码中的ICONDIRENTRY不是图标组中的结构,而是ico文件中的结构。
四、光标资源与cur文件在数据结构说的差别
PE文件资源中,光标资源与图标资源及其类似,文件头与图片分开保存。文件头被另存为一个叫光标组的资源,光标组资源类型ID=12。图片数据分为一个个图片,分开保存为光标资源,光标资源ID=1。
图标组资源的ICONDIR文件头结构,定义如下:
typedef struct
{
WORD bWidth; //图像宽度,单位:像素
WORD bHeight; //图像高度,单位:像素
WORD wPlanes; //平面数,一般为1
WORD wBitCount; //每像素比特数
DWORD dwBytesInRes; //数据块大小
WORD dwID;//数据块ID
} ICONDIRENTRY, *LPICONDIRENTRY;
typedef struct
{
WORD idReserved; //保留,为0
WORD idType; //文件类型,图标为1,光标为2
WORD idCount; //图象个数
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;
注意,这里的ICONDIRENTRY结构又发生了变化。
光标资源保存的图片数据也有点不一样。数据在首先是热点数据,保存格式如下。然后才是真正的图片数据。
struct CursorComponent {//热点信息
WORD xHotspot;
WORD yHotspot;
};
PE文件光标资源的文件提取步骤与图标资源大致相同,通过以下代码可以实现文件提取:
void GetCursors(void *file, char *buf)
{
DWORD Offset = sizeof(ICON_DIR);
ICON_DIR *CurHead = (ICON_DIR*)buf;//光标文件头
WriteFile(file, CurHead,Offset);
struct CursorsInfo
{
WORD wWidth;//图像宽度,单位:像素
WORD wHeight;//图像高度,单位:像素
WORD wPlanes;//平面数,一般为1
WORD wBitCount;//每像素比特数
DWORD lBytesInRes;//数据块大小
WORD wID;//图像ID
WORD Others;//不包括在本结构中
} *CurInfo;
buf += Offset;
Offset += sizeof(ICONDIRENTRY) * CurHead->wCount;
int i;
for(i=0; i<CurHead->wCount; i++)
{
CurInfo = (CursorsInfo*)(buf + (sizeof(CursorsInfo) - 2) * i);
CursorComponent *Hotspot = (CursorComponent*)FindResource(1, CurInfo->wID);
if(!Hotspot) return;
ICONDIRENTRY CurEntry = {(BYTE)CurInfo->wWidth, (BYTE)CurInfo->wWidth,
0, 0, Hotspot->xHotspot, Hotspot->yHotspot,
CurInfo->lBytesInRes - sizeof(CursorComponent), Offset};
WriteFile(file, &CurEntry, sizeof(ICONDIRENTRY));
Offset += CurInfo->lBytesInRes - sizeof(CursorComponent);
}
for(i=0; i<CurHead->wCount; i++)
{
CurInfo = (CursorsInfo*)(buf + (sizeof(CursorsInfo) - 2) * i);
CursorComponent *Hotspot = (CursorComponent*)FindResource(1, CurInfo->wID);
WriteFile(file, Hotspot+1, CurInfo->lBytesInRes-sizeof(CursorComponent));
}
}
代码中的ICONDIRENTRY不是光标组中的结构,而是ico文件中的结构。