PE文件解析-资源中的位图、图标与光标结构

一、概述

    想要获取一个可执行文件(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文件中的结构。

猜你喜欢

转载自blog.csdn.net/zhyulo/article/details/86476308