《Windows PE》7.4 资源表应用

本节我们将通过两个示例程序,演示对PE文件内图标资源的置换与提取。

本节必须掌握的知识点:

更改图标

提取图标资源

7.4.1 更改图标

让我们来做一个实验,替换PE文件中现有的图标。如果手工替换,一定是先找到资源表,然后分别替换图标资源和图标组资源就可以了。当然这个过程稍微有点繁复,可能会涉及到其他资源的重定位。还好,Windows操作系统为开发者提供了一组更新PE文件中资源的API函数:BeginUpdateResource、UpdateResource、EndUpdateResource。用来枚举 PE 文件中资源的函数有:EnumResourceTypes、EnumResourceNames、EnumResourceLanguages。具体的使用方法可以参见MSDN,下面我们将使用这些函数实现图标资源的替换。

实验五十:更改图标

●模块1:resource.h(略)

●模块2:peinfo.rc(略)

●模块3:info.h

cpp 复制代码
#pragma once
#ifndef INFO_H_
#define INFO_H_

#include <windows.h>
#include <richedit.h>	//CHARFORMAT富文本结构定义
#include <commctrl.h>	//通用控件
#pragma comment(lib,"comctl32.lib")
#include <strsafe.h>	//StringCchCopy
#include <stdlib.h>

// 文件中的ICO头部
typedef struct
{
	byte bWidth;				//宽度
	byte bHeight;				//高度
	byte bColorCount;			//颜色数
	byte bReserved;			//保留字,必须为0
	WORD wPlanes;			//调色板
	WORD wBitCount;			//每个像素的位数
	DWORD dwBytesInRes;		//资源长度
	DWORD dwImageOffset;	//资源在文件偏移
}ICON_DIR_ENTRY;

typedef struct
{
	WORD idReserved;			//保留字,必须为0
	WORD idType;				//资源类别,如果是1表示为ICO文件
	WORD idCount;				//图标数量
	//ICON_DIR_ENTRY idEntries;	//图标项,一个图标一项
}ICON_DIR;

//PE中ICO头部
typedef struct
{
	byte bWidth;			//宽度
	byte bHeight;			//高度
	byte bColorCount;		//颜色数
	byte bReserved;		//保留字,必须为0
	WORD wPlanes;		//调色板
	WORD wBitCount;		//每个像素的位数
	DWORD dwBytesInRes;	//资源长度
	WORD nID;			//资源在文件序号
}PE_ICON_DIR_ENTRY;
typedef struct
{
	WORD idReserved;	//保留字,必须为0
	WORD idType;		//资源类别,如果是1表示为ICO文件
	WORD idCount;		//图标数量
	PE_ICON_DIR_ENTRY idEntries;	//图标项,一个图标一项
}PE_ICON_DIR;

//函数声明
BOOL CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void Exception(void);
void init(); //初始化
void  _OpenFile();//打开PE文件并处理
int  CALLBACK _Handler(EXCEPTION_POINTERS * lpExceptionPoint);
void ShowErrMsg();
void _AppendInfo(const TCHAR * _lpsz);//往文本框中追加文本
//将boy.ico图标替换指定PE程序的图标
BOOL _doUpdate(TCHAR* lpszFile, TCHAR* lpszExeFile);

#endif

●模块4:PEUpdateIcon.c

cpp 复制代码
/*------------------------------------------------------------------------
 FileName: PEUpdateIcon.c
 实验50:更改程序图标实例(支持32位和64位PE文件)
 (c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <strsafe.h>	//StringCchCopy
#include "resource.h"
#include "info.h"

HANDLE hInstance;
HWND hWinMain, hWinEdit;
HMODULE hRichEdit;
TCHAR szFileName[MAX_PATH];
HANDLE hFileDump;
HANDLE hFile;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{
	TCHAR	szDllEdit[] = TEXT("RichEd20.dll");
	TCHAR	szClassEdit[] = TEXT("RichEdit20W");//peinfo.rc中定义
	hRichEdit = LoadLibrary((LPCWSTR)&szDllEdit);
	hInstance = GetModuleHandle(NULL);
	DialogBoxParam(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, 
(DLGPROC)DlgProc, (LPARAM)0);
	FreeLibrary(hRichEdit);
	return 0;
}

//初始化窗口函数
void init()
{
	CHARFORMAT stCf;
	TCHAR	szClassEdit[] = TEXT("RichEdit20A");
	TCHAR	szFont[] = TEXT("宋体");
	HINSTANCE hInstance;

	hWinEdit = GetDlgItem(hWinMain, IDC_INFO);
	//为窗口程序设置图标
	hInstance = GetModuleHandle(NULL);
	HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ICO_MAIN));
	//HICON hIcon = LoadIcon(hInstance, TEXT("#111"));
	SendMessage(hWinMain, WM_SETICON, ICON_BIG, (LPARAM)hIcon);

	//设置编辑控件
	SendMessage(hWinEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0);
	RtlZeroMemory(&stCf, sizeof(stCf));
	stCf.cbSize = sizeof(stCf);
	stCf.yHeight = 9 * 20;
	stCf.dwMask = CFM_FACE | CFM_SIZE | CFM_BOLD;
	StringCchCopy((LPTSTR)&stCf.szFaceName, lstrlen(szFont) + 1, (LPCTSTR)&szFont);
	SendMessage(hWinEdit, EM_SETCHARFORMAT, 0, (LPARAM)&stCf);
	SendMessage(hWinEdit, EM_EXLIMITTEXT, 0, -1);//设为-1表示无限制
}

//富文本窗口回调函数
BOOL CALLBACK DlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
	const TCHAR szErr[] = TEXT("文件格式错误!");
	const TCHAR szErrFormat[] = TEXT("这个文件不是PE格式的文件!");
	switch (wMsg)
	{
	case WM_CLOSE:
		EndDialog(hWnd, 0);
		return TRUE;

	case WM_INITDIALOG:
		hWinMain = hWnd;
		init();	//初始化
		return TRUE;

	case WM_COMMAND:
		switch (wParam)
		{
		case IDM_EXIT:
			EndDialog(hWnd, 0);
			return TRUE;

		case IDM_OPEN:
			_OpenFile();
			return TRUE;
		case IDM_1:
			MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);
			return TRUE;
		case IDM_2:
			MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);
			return TRUE;
		case IDM_3:
			MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);
			return TRUE;
		}
	}
	return FALSE;
}

//执行比对PE文件
void _OpenFile()
{
	OPENFILENAME stOF;
	HANDLE hFile = NULL;
	HANDLE hMapFile = NULL;
	PBYTE lpMemory = NULL;	//PE文件内存映射文件地址
	int dwFileSize;
	const TCHAR szDefaultExt[] = TEXT("exe");
	const TCHAR szFilter[] = TEXT("PE Files (*.exe)\0*.exe\0")\
		TEXT("DLL Files(*.dll)\0*.dll\0")\
		TEXT("SCR Files(*.scr)\0*.scr\0")\
		TEXT("FON Files(*.fon)\0*.fon\0")\
		TEXT("DRV Files(*.drv)\0*.drv\0")\
		TEXT("All Files(*.*)\0*.*\0\0");
	const TCHAR szErr[] = TEXT("文件格式错误!");
	const TCHAR szErrFormat[] = TEXT("操作文件时出现错误!");
	const TCHAR szOut1[] = TEXT("----------------------------------------------------------------\r\n")
		TEXT("待处理的PE文件:%s\r\n");
	static TCHAR lpszBoyIcon[] = 
TEXT("D:\\code\\winpe\\ch07\\PEUpdateIcon\\boy.ico");
	const TCHAR szFailure[] = TEXT("执行图标修改失败。");
	const TCHAR szSuccess[] = TEXT("恭喜你,图标修改成功成功的。");

	IMAGE_DOS_HEADER *lpstDOS;	//DOS块地址
	IMAGE_NT_HEADERS *lpstNT;	//PE文件头地址

	//显示打开文件对话框
	RtlZeroMemory(&stOF, sizeof(stOF));
	stOF.lStructSize = sizeof(stOF);
	stOF.hwndOwner = hWinMain;
	stOF.lpstrFilter = szFilter;
	stOF.lpstrFile = szFileName;
	stOF.nMaxFile = MAX_PATH;
	stOF.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
	stOF.lpstrDefExt = szDefaultExt;
	if (!GetOpenFileName(&stOF))
		return;
	//打开PE文件
	hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ | 
FILE_SHARE_WRITE, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		MessageBox(NULL, TEXT("打开文件失败!"), NULL, MB_ICONWARNING);
	else
	{
		//获取文件大小
		dwFileSize = GetFileSize(hFile, NULL);
		//创建内存映射文件
		if (dwFileSize)
		{
			if (hMapFile = CreateFileMapping(hFile, 
NULL, PAGE_READONLY, 0, 0, NULL))
			{
				//获得文件在内存的映象起始位置
				lpMemory = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
				//异常处理方法2:
				if (!lpMemory)
				{
					atexit(Exception);
					goto _ErrFormat;
					exit(EXIT_FAILURE);
				}

				//检查PE文件是否有效
				lpstDOS = (IMAGE_DOS_HEADER *)lpMemory;
				if (lpstDOS->e_magic != IMAGE_DOS_SIGNATURE)
				{
					//如果非PE文件-异常处理
					atexit(Exception);
					goto _ErrFormat;
					exit(EXIT_FAILURE);
				}
				lpstNT = (IMAGE_NT_HEADERS *)(lpMemory + lpstDOS->e_lfanew);
				if (lpstNT->Signature != IMAGE_NT_SIGNATURE)
				{
					//如果非PE文件-异常处理
					atexit(Exception);
					goto _ErrFormat;
					exit(EXIT_FAILURE);
				}
				//将boy.ico的图标数据写入PE文件
				if (_doUpdate(lpszBoyIcon, szFileName))
					_AppendInfo(szSuccess);
				else
					_AppendInfo(szFailure);

				goto _ErrorExit;
			_ErrFormat:
				MessageBox(hWinMain, szErrFormat, NULL, MB_OK);
			_ErrorExit:
				if (lpMemory)
					UnmapViewOfFile(lpMemory);
			}
			if (hMapFile)
				CloseHandle(hMapFile);
		}
		if (hFile)
			CloseHandle(hFile);
	}
	return;
}

//RITCH控件添加文本信息--以 null 结尾的字符串
void _AppendInfo(const TCHAR * _lpsz)
{
	CHARRANGE stCR;
	//检索文本控件内文本的长度
	stCR.cpMin = GetWindowTextLength(hWinEdit);
	stCR.cpMax = stCR.cpMin;
	//择并替换文本控件的选定内容
	SendMessage(hWinEdit, EM_EXSETSEL, 0, (LPARAM)&stCR);
	//EM_REPLACESEL以 null 结尾的字符串的指针替换
	SendMessage(hWinEdit, EM_REPLACESEL, FALSE, (LPARAM)_lpsz);
}

//异常处理
void Exception(void)
{
	MessageBox(hWinMain, TEXT("获得文件在内存的映象起始位置失败!"), NULL, MB_OK);
}

/*
;-----------------------------------
;将boy.ico图标替换指定PE程序的图标
;使用win32 api函数UpdateResource实现此功能
;-----------------------------------
*/
BOOL _doUpdate(TCHAR* lpszFile, TCHAR* lpszExeFile)
{
	ICON_DIR stID;
	ICON_DIR_ENTRY stIDE;
	PE_ICON_DIR stGID;
	HANDLE hFile, hUpdate;
	DWORD dwReserved, nSize, nGSize;
	BOOL flag = FALSE;
	LPVOID pIcon, pGrpIcon;

	hFile = CreateFile(lpszFile,GENERIC_READ,0,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return 0;

	RtlZeroMemory(&stID,sizeof(stID));
	ReadFile(hFile,&stID,sizeof(stID),&dwReserved,NULL);
	RtlZeroMemory(&stIDE, sizeof(stIDE));
	ReadFile(hFile, &stIDE, sizeof(stIDE), &dwReserved, NULL);

	nSize = stIDE.dwBytesInRes;
	pIcon = GlobalAlloc(GPTR, nSize);
	SetFilePointer(hFile, stIDE.dwImageOffset, NULL, FILE_BEGIN);
	if (!ReadFile(hFile, pIcon, nSize, &dwReserved, NULL))
		goto _ret;

	RtlZeroMemory(&stGID,sizeof(stGID));
	stGID.idCount = stID.idCount;
	stGID.idReserved = 0;
	stGID.idType = 1;
	RtlMoveMemory(&stGID.idEntries,&stIDE,12);
	stGID.idEntries.nID = 0;
	nGSize = sizeof(stGID);
	pGrpIcon = GlobalAlloc(GPTR, nGSize);
	RtlMoveMemory(pGrpIcon,&stGID,nGSize);

	//开始修改
	hUpdate = BeginUpdateResource(lpszExeFile,FALSE); //检索句柄,可使用该句柄在二进制模块中添加,删除或替换资源
	UpdateResource(hUpdate,RT_GROUP_ICON,(LPCWSTR)1,LANG_CHINESE,pGrpIcon,nGSize);
	flag = UpdateResource(hUpdate,RT_ICON, (LPCWSTR)1,LANG_CHINESE,pIcon,nSize);
	EndUpdateResource(hUpdate,FALSE);
	if (flag == FALSE)
		MessageBox(NULL, L"替换资源失败!", NULL, MB_OK);
	else
		goto over;
_ret:
	GlobalFree(pIcon);
	CloseHandle(hFile);
	return flag;
over:
	CloseHandle(hFile);
	return flag;
}

运行:

图7-7 替换图标

总结

上述示例选择打开一个PE文件,然后_doUpdate函数实现图标资源的替换。具体实现步骤如下:

1.首先调用CreateFile函数打开boy.ico图标文件。

2.然后调用ReadFile函数,分别将图标文件的头部信息和图标项读取到缓冲区中。

3.接着分配一个和图标资源大小一样的内存空间,并将整个图标文件读取到该内存中。

4.然后再将初始化后的PE_ICON_DIR结构变量stGID复制到一个新分配的内存块中。

5.最后调用一组资源替换的API函数BeginUpdateResource、UpdateResource和EndUpdateResource完成替换。

7.4.2 提取图标资源

一个ICO文件由三大部分组成:第一部分是图标头,第二部分是图标项,第三部分为图标数据。PE资源表中,第一部分和第二部分组合成图标组资源,第三部分的每个图标数据对应一个图标资源。接下来我们给出一个提取图标的示例程序。

实验五十一:提取图标资源

●模块1:resource.h(略)

●模块2:peinfo.rc(略)

●模块3:info.h

cpp 复制代码
#pragma once
#ifndef INFO_H_
#define INFO_H_

#include <windows.h>
#include <richedit.h>	//CHARFORMAT富文本结构定义
#include <commctrl.h>	//通用控件
#pragma comment(lib,"comctl32.lib")
#include <strsafe.h>	//StringCchCopy
#include <stdlib.h>

//函数声明
BOOL CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
//void Exception(void);
void init(); //初始化
void  _OpenFile();//打开PE文件并处理
DWORD RVAToOffset(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);// 将内存偏移量RVA转换为文件偏移
DWORD GetRVASection(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);//查找 RVA 所在的节区
int  CALLBACK _Handler(EXCEPTION_POINTERS * lpExceptionPoint);
//void ShowErrMsg();
void _AppendInfo(const TCHAR * _lpsz);//往文本框中追加文本
//PE文件处理模块
void _getResource(PBYTE, IMAGE_NT_HEADERS *, int);//获取PE文件的资源信息
void ProcessRes(PBYTE lpFile, PBYTE lpRes, IMAGE_RESOURCE_DIRECTORY * lpResDir, DWORD dwLevel);
void _getIcoData(PBYTE lpFile, PBYTE lpRes, DWORD number, DWORD off, DWORD size);//处理单个ICO文件
int _getFinnalData(PBYTE lpFile, PBYTE lpRes, DWORD number);//将图标数据写入文件
#endif

●模块4:RvaToFileOffset.c(略)

●模块5:PEDumpIcon.c

cpp 复制代码
/*------------------------------------------------------------------------
 FileName:PEDumpIcon.c
 实验51:提取程序图标实例(支持32位和64位PE文件)
 (c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <strsafe.h>	//StringCchCopy
#include "resource.h"
#include "info.h"

HANDLE hInstance;
HWND hWinMain, hWinEdit;
HMODULE hRichEdit;
TCHAR szFileName[MAX_PATH];
HANDLE hFileDump;
HANDLE hFile;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{
	TCHAR	szDllEdit[] = TEXT("RichEd20.dll");
	TCHAR	szClassEdit[] = TEXT("RichEdit20W");//peinfo.rc中定义
	hRichEdit = LoadLibrary((LPCWSTR)&szDllEdit);
	hInstance = GetModuleHandle(NULL);
	DialogBoxParam(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgProc, (LPARAM)0);
	FreeLibrary(hRichEdit);
	return 0;
}

//初始化窗口函数
void init()
{
	CHARFORMAT stCf;
	TCHAR	szClassEdit[] = TEXT("RichEdit20A");
	TCHAR	szFont[] = TEXT("宋体");
	HINSTANCE hInstance;

	hWinEdit = GetDlgItem(hWinMain, IDC_INFO);
	//为窗口程序设置图标
	hInstance = GetModuleHandle(NULL);
	HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ICO_MAIN));
	//HICON hIcon = LoadIcon(hInstance, TEXT("#111"));
	SendMessage(hWinMain, WM_SETICON, ICON_BIG, (LPARAM)hIcon);

	//设置编辑控件
	SendMessage(hWinEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0);
	RtlZeroMemory(&stCf, sizeof(stCf));
	stCf.cbSize = sizeof(stCf);
	stCf.yHeight = 9 * 20;
	stCf.dwMask = CFM_FACE | CFM_SIZE | CFM_BOLD;
	StringCchCopy((LPTSTR)&stCf.szFaceName, lstrlen(szFont) + 1, (LPCTSTR)&szFont);
	SendMessage(hWinEdit, EM_SETCHARFORMAT, 0, (LPARAM)&stCf);
	SendMessage(hWinEdit, EM_EXLIMITTEXT, 0, -1);//设为-1表示无限制
}

//富文本窗口回调函数
BOOL CALLBACK DlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
	const TCHAR szErr[] = TEXT("文件格式错误!");
	const TCHAR szErrFormat[] = TEXT("这个文件不是PE格式的文件!");
	switch (wMsg)
	{
	case WM_CLOSE:
		EndDialog(hWnd, 0);
		return TRUE;

	case WM_INITDIALOG:
		hWinMain = hWnd;
		init();	//初始化
		return TRUE;

	case WM_COMMAND:
		switch (wParam)
		{
		case IDM_EXIT:
			EndDialog(hWnd, 0);
			return TRUE;

		case IDM_OPEN:
			_OpenFile();
			return TRUE;
		case IDM_1:
			MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);
			return TRUE;
		case IDM_2:
			MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);
			return TRUE;
		case IDM_3:
			MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);
			return TRUE;
		}
	}
	return FALSE;
}

//执行比对PE文件
void _OpenFile()
{
	OPENFILENAME stOF;
	HANDLE hFile = NULL;
	HANDLE hMapFile = NULL;
	PBYTE lpMemory = NULL;	//PE文件内存映射文件地址
	int dwFileSize;
	const TCHAR szDefaultExt[] = TEXT("exe");
	const TCHAR szFilter[] = TEXT("PE Files (*.exe)\0*.exe\0")\
		TEXT("DLL Files(*.dll)\0*.dll\0")\
		TEXT("SCR Files(*.scr)\0*.scr\0")\
		TEXT("FON Files(*.fon)\0*.fon\0")\
		TEXT("DRV Files(*.drv)\0*.drv\0")\
		TEXT("All Files(*.*)\0*.*\0\0");
	const TCHAR szErr[] = TEXT("文件格式错误!");
	const TCHAR szErrFormat[] = TEXT("操作文件时出现错误!");
	const TCHAR szOut1[] = TEXT("----------------------------------------------------------------\r\n")
		TEXT("待处理的PE文件:%s\r\n");
	static TCHAR szBuffer[256];
	IMAGE_DOS_HEADER *lpstDOS;	//DOS块地址
	IMAGE_NT_HEADERS *lpstNT;	//PE文件头地址

	//显示打开文件对话框
	RtlZeroMemory(&stOF, sizeof(stOF));
	stOF.lStructSize = sizeof(stOF);
	stOF.hwndOwner = hWinMain;
	stOF.lpstrFilter = szFilter;
	stOF.lpstrFile = szFileName;
	stOF.nMaxFile = MAX_PATH;
	stOF.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
	stOF.lpstrDefExt = szDefaultExt;
	if (!GetOpenFileName(&stOF))
		return;

	//打开PE文件
	hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		MessageBox(NULL, TEXT("打开文件失败!"), NULL, MB_ICONWARNING);
	else
	{
		//获取文件大小
		dwFileSize = GetFileSize(hFile, NULL);
		//创建内存映射文件
		if (dwFileSize)
		{
			if (hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL))
			{
				//获得文件在内存的映象起始位置
				lpMemory = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
				//异常处理方法2:
				if (!lpMemory)
				{
					MessageBox(hWinMain, szErrFormat, NULL, MB_OK);
				}
				//检查PE文件是否有效
				lpstDOS = (IMAGE_DOS_HEADER *)lpMemory;
				if (lpstDOS->e_magic != IMAGE_DOS_SIGNATURE)
				{
					//如果非PE文件-异常处理
					MessageBox(hWinMain, szErrFormat, NULL, MB_OK);
				}
				lpstNT = (IMAGE_NT_HEADERS *)(lpMemory + lpstDOS->e_lfanew);
				if (lpstNT->Signature != IMAGE_NT_SIGNATURE)
				{
					MessageBox(hWinMain, szErrFormat, NULL, MB_OK);
				}
				//到此为止,该文件的验证已经完成。为PE结构文件
				wsprintf(szBuffer, szOut1, szFileName);
				_AppendInfo(szBuffer);
				//显示资源信息
				_getResource(lpMemory, lpstNT, dwFileSize);
			
				if (lpMemory)
					UnmapViewOfFile(lpMemory);
			}
			if (hMapFile)
				CloseHandle(hMapFile);
		}
		if (hFile)
			CloseHandle(hFile);
	}
	return;
}

//RITCH控件添加文本信息--以 null 结尾的字符串
void _AppendInfo(const TCHAR * _lpsz)
{
	CHARRANGE stCR;
	//检索文本控件内文本的长度
	stCR.cpMin = GetWindowTextLength(hWinEdit);
	stCR.cpMax = stCR.cpMin;
	//择并替换文本控件的选定内容
	SendMessage(hWinEdit, EM_EXSETSEL, 0, (LPARAM)&stCR);
	//EM_REPLACESEL以 null 结尾的字符串的指针替换
	SendMessage(hWinEdit, EM_REPLACESEL, FALSE, (LPARAM)_lpsz);
}

●模块6:GetResource.c

cpp 复制代码
/*
;遍历资源表项的图标组资源
;_lpFile:文件地址
;_lpRes:资源表地址
*/
#include <windows.h>
#include "info.h"

extern TCHAR szFileName[MAX_PATH];	//pemian.c模块中定义
extern HANDLE hWinEdit,hFileDump;
extern HWND hWinMain;

void _AppendInfo(const TCHAR * _lpsz);
DWORD RVAToOffset(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);
DWORD GetRVASection(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);

//遍历资源表项的图标组资源:参数1:PE文件地址;参数2:资源块起始地址;
void ProcessRes(PBYTE lpFile, PBYTE lpRes, IMAGE_RESOURCE_DIRECTORY * lpResDir, DWORD dwLevel)
{
	const TCHAR szOut10[] = TEXT("资源表中有图标组%d个。\r\n")
			TEXT("----------------------------------------------------------------\r\n\r\n");
	const TCHAR szLevel3[] = TEXT("图标组%4d所在文件位置:0x%08x  资源长度:%d\r\n");
	const TCHAR szNoIconArray[] = TEXT("资源表中没有发现图标组!");
	static TCHAR szBuffer[1024];
	static TCHAR szResName[256];
	IMAGE_RESOURCE_DIRECTORY * lpstRES_DIR;//资源目录
	IMAGE_RESOURCE_DIRECTORY_ENTRY * lpstRES_DIR_ENT;//目录项
	IMAGE_RESOURCE_DATA_ENTRY * lpstRES_DATA_ENT;
	int number;	//资源数量
	DWORD dwNextLevel;
	DWORD lpAddr, lpAddr2;
	DWORD IDname, dwTemp1, dwTemp2, dwTemp3;
	DWORD dwICO = 0 ; //ICO文件的个数

	dwNextLevel = dwLevel + 1;
	//检查资源目录表,得到资源目录项的数量
	lpstRES_DIR = lpResDir;
	number = lpstRES_DIR->NumberOfIdEntries + lpstRES_DIR->NumberOfNamedEntries;
//IMAGE_RESOURCE_DIRECTORY结构后面紧跟着是IMAGE_RESOURCE_DIRECTORY_ENTRY结构
	lpstRES_DIR_ENT = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)((PBYTE)lpstRES_DIR + sizeof(IMAGE_RESOURCE_DIRECTORY));

	//循环处理每个资源目录项
	while (number--)
	{
		RtlZeroMemory(szBuffer, sizeof(szBuffer));
		//OffsetToData字段最高位为1,后七位指向下层目录块的起始地址IMAGE_RESOURCE_DIRECTORY结构
		lpAddr = lpstRES_DIR_ENT->OffsetToData;
		if (lpAddr & 0x80000000)
		{
			lpAddr &= 0x7fffffff;
			lpAddr += (DWORD)lpRes;
			//第一层:资源类型
			IDname = lpstRES_DIR_ENT->Name;//目录项名称字符串或ID
			//最高位为1时,低7位值作为指向UNICODE编码的资源名IAMGE_RESOURCE_STRING_U结构
			//如果是按名称定义的资源类型,跳过 
			if (IDname & 0x80000000)
			{
				goto _next;
			}
			//高位为0时,表示字段的值作为ID使用
			else
			{
				if (IDname == 0x0e)	//判断编号是否为图标组
				{
					//移动到第二级目录
					//计算目录项的个数
					lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;
					dwICO = lpstRES_DIR->NumberOfNamedEntries + 
lpstRES_DIR->NumberOfIdEntries;
					wsprintf(szBuffer, szOut10, dwICO);
					_AppendInfo(szBuffer);

					//跳过第二级目录头定位到第二级目录项
					lpAddr2 = lpAddr + sizeof(IMAGE_RESOURCE_DIRECTORY);

					dwTemp1 = 0;
					while (dwICO--)
					{
						lpstRES_DIR_ENT = 
(IMAGE_RESOURCE_DIRECTORY_ENTRY*)lpAddr2;
						//直接访问到数据,获取数据在文件的偏移及大小
						dwTemp1++;
						lpAddr = lpstRES_DIR_ENT->OffsetToData;
						if (lpAddr & 0x80000000)
						{
							lpAddr &= 0x7fffffff;	//最高位为1
							lpAddr += (DWORD)lpRes;	//第三级
							lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;
							//移动到第三级目录,假设目录项数量都为1
							lpAddr += sizeof(IMAGE_RESOURCE_DIRECTORY);
							lpstRES_DIR_ENT = 
(IMAGE_RESOURCE_DIRECTORY_ENTRY*)lpAddr;
							//地址指向数据项
							lpAddr = lpstRES_DIR_ENT->OffsetToData + 
(DWORD)lpRes;
							lpstRES_DATA_ENT = 
(IMAGE_RESOURCE_DATA_ENTRY*)lpAddr;
							dwTemp3 = lpstRES_DATA_ENT->Size;
							dwTemp2 = RVAToOffset((IMAGE_DOS_HEADER *)lpFile, 
lpstRES_DATA_ENT->OffsetToData);
							wsprintf(szBuffer, szLevel3, dwTemp1, dwTemp2,
 dwTemp3);
							_AppendInfo(szBuffer);

						//处理单个ICO文件,参数1:文件开始,参数2:资源表开始
						//参数3:PE ICO头开始,参数4:编号,参数5:PE ICO头大小
							_getIcoData(lpFile, lpRes, dwTemp1, dwTemp2, 
dwTemp3);

						}
						lpAddr2 += sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY);
					}
					goto _next;
				}
				else
					goto _next;
			}
		}
	_next:
		lpstRES_DIR_ENT++;
	}
	if (dwICO == 0)
		_AppendInfo(szNoIconArray);
}
//获取PE文件的资源信息
void _getResource(PBYTE lpFile, IMAGE_NT_HEADERS * _lpPeHead, int _dwSize)
{
	const TCHAR szNoResource[] = TEXT("\r\n未发现资源表!");

	static TCHAR szBuffer[256];
	static TCHAR szSectionName[16];
	static TCHAR szNameB[256];
	static TCHAR szNameW[256];
	IMAGE_NT_HEADERS32 * lpstNT32;		//PE32文件头
	IMAGE_NT_HEADERS64 * lpstNT64;		//PE64文件头
	IMAGE_RESOURCE_DIRECTORY * lpstRES_DIR;//资源目录
	DWORD rva, address;

	lpstNT32 = _lpPeHead;
	lpstNT64 = (IMAGE_NT_HEADERS64 *)_lpPeHead;
	//检测是否存在资源--数据目录第2项
	if (lpstNT64->OptionalHeader.Magic == 0x020B) //64位PE文件
	{//数据目录项的第3项
		rva = lpstNT64->OptionalHeader.DataDirectory[2].VirtualAddress; 
	}
	else
		rva = lpstNT32->OptionalHeader.DataDirectory[2].VirtualAddress;
	if (!rva)
	{
		_AppendInfo(szNoResource);
		return;
	}
	//求资源表在文件的偏移
	address = RVAToOffset((IMAGE_DOS_HEADER *)lpFile, rva);
	if (!address)
		return;
//资源目录的实际地址
	lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY *)(address + (DWORD)lpFile); 

	/*显示所有资源目录块的信息
	; 传入的两个参数分别表示
	; 1、文件头位置
	; 2、资源表位置*/
	ProcessRes(lpFile, (PBYTE)lpstRES_DIR, lpstRES_DIR, 1);
}

●模块7:GetIconData.c

cpp 复制代码
/*
;通过PE ICO头获取ICO数据
;参数1:文件开始
;参数2:资源表开始
;参数3:PE ICO头开始
;参数4:编号(由此构造磁盘文件名g12.ico)
;参数5:PE ICO头大小
*/
#include <windows.h>
#include "info.h"

extern TCHAR szFileName[MAX_PATH];	//pemian.c模块中定义
extern HANDLE hFile;
extern HWND hWinMain;

// 文件中的ICO头部
typedef struct  
{
	byte bWidth ;			//宽度
	byte bHeight ;			//高度
	byte bColorCount ;		//颜色数
	byte bReserved ;		//保留字,必须为0
	WORD wPlanes ;			//调色板
	WORD wBitCount ;		//每个像素的位数
	DWORD dwBytesInRes ;	//资源长度
	DWORD dwImageOffset ;	//资源在文件偏移
}ICON_DIR_ENTRY;

//PE中ICO头部
typedef struct  
{
	byte bWidth;		//宽度
	byte bHeight;		//高度
	byte bColorCount;	//颜色数
	byte bReserved;		//保留字,必须为0
	WORD wPlanes;		//调色板
	WORD wBitCount;		//每个像素的位数
	DWORD dwBytesInRes;	//资源长度
	WORD dwImageOffset;	//资源在文件偏移
}PE_ICON_DIR_ENTRY;

//处理单个ICO文件
void _getIcoData(PBYTE lpFile, PBYTE lpRes, DWORD number, DWORD off, DWORD size)
{
	const TCHAR szFile[] = TEXT("  >>新建文件%s\r\n");
	const TCHAR szOut11[] = TEXT("g%d.ico");
	const TCHAR szOut13[] = TEXT("  >>图标组%4d中共有图标:%d个。\r\n");
	const TCHAR szICOHeader[] = TEXT("  >> 完成ICO头部信息 \r\n");
	TCHAR szFileName1[MAX_PATH];
	static TCHAR szBuffer[256];
	DWORD dwTemp, dwCount, dwTemp1;
	DWORD dwForward,lpMemory;
	DWORD dwIcoDataSize;	//图标数据的大小
	PE_ICON_DIR_ENTRY * lpPE_ICON_DIR_ENTRY;
	ICON_DIR_ENTRY lpIconDE;
	//PE_ICON_DIR_ENTRY lpPEIconDE;
	WORD order;
	DWORD lpAddr;

	wsprintf(szFileName1,szOut11,number);
	wsprintf(szBuffer,szFile,szFileName1);
	_AppendInfo(szBuffer);
	//将内存写入文件以供检查
	hFile = CreateFile(szFileName1,GENERIC_WRITE,FILE_SHARE_READ,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);

	//定位文件指针
	lpMemory = (DWORD)lpFile + off;
	//写入6个字节文件头
	WriteFile(hFile, (PBYTE)lpMemory, 6, &dwTemp, NULL);
	//求出图标组包含图标的个数
	dwCount = *(PWORD)((PBYTE)lpMemory + 4);
	wsprintf(szBuffer,szOut13,number,dwCount);
	_AppendInfo(szBuffer);

	//求第一个图标数据在文件中的偏移
	dwForward = dwCount * 2 + size;//每一个记录多2个字节
	//定位到ICO图标项起始
	lpPE_ICON_DIR_ENTRY = (PE_ICON_DIR_ENTRY *)((PBYTE)lpMemory + 6);
	dwIcoDataSize = 0;
	dwTemp1 = dwCount;
	while (dwTemp1--)
	{
		//将PE_ICON_DIR_ENTRY结构中的大部分字段赋值,除最后一个字段外
		lpIconDE.bWidth = lpPE_ICON_DIR_ENTRY->bWidth;
		lpIconDE.bHeight = lpPE_ICON_DIR_ENTRY->bHeight;
		lpIconDE.bColorCount = lpPE_ICON_DIR_ENTRY->bColorCount;
		lpIconDE.bReserved = lpPE_ICON_DIR_ENTRY->bReserved;
		lpIconDE.wPlanes = lpPE_ICON_DIR_ENTRY->wPlanes;
		lpIconDE.wBitCount = lpPE_ICON_DIR_ENTRY->wBitCount;
		lpIconDE.dwBytesInRes = lpPE_ICON_DIR_ENTRY->dwBytesInRes;
		//该值需要修正,记录图标数据在文件偏移。
		//第一个图标的该值是文件ICO头大小

		//以后的图标的该值是上一个加上数据长度
		lpIconDE.dwImageOffset = dwForward + dwIcoDataSize;

		WriteFile(hFile, &lpIconDE, sizeof(ICON_DIR_ENTRY),&dwTemp,NULL);
		//为下一次计算地址做准备
		dwIcoDataSize = lpPE_ICON_DIR_ENTRY->dwBytesInRes;
		lpPE_ICON_DIR_ENTRY++;
	}//该循环结束,所有的头部信息已经完毕
	_AppendInfo(szICOHeader);

	//开始下一个循环,将所有图标数据写入文件
	lpAddr = (DWORD)lpMemory + 6;
	dwTemp1 = dwCount;
	while (dwTemp1--)
	{
		lpPE_ICON_DIR_ENTRY = (PE_ICON_DIR_ENTRY *)lpAddr;
		//写入文件图标数据
		order = lpPE_ICON_DIR_ENTRY->dwImageOffset;//取得图标的顺号
		//返回eax为图标数据大小
		_getFinnalData(lpFile, lpRes, order);

		//lpAddr += sizeof(PE_ICON_DIR_ENTRY);//16个字节--错误
		lpAddr += 14;
	}
	CloseHandle(hFile);
}
//将图标数据写入文件
/*
;参数:lpFile 文件内存起始地址
;参数: lpRes 资源表起始地址
;参数:nubmer为图标顺号
*/
int _getFinnalData(PBYTE lpFile, PBYTE lpRes, DWORD number)
{
	static TCHAR szBuffer[1024];
	static TCHAR szResName[256];
	DWORD lpMem, dwTemp,dwTemp1, dwTemp2, dwTemp3;
	DWORD dwICO = 0; //ICO文件的个数
	int count = 0;	//图标数据大小
	int counter;	//资源数量
	DWORD lpAddr, IDname;
	IMAGE_RESOURCE_DIRECTORY * lpstRES_DIR;//资源目录
	IMAGE_RESOURCE_DIRECTORY_ENTRY * lpstRES_DIR_ENT;//目录项
	IMAGE_RESOURCE_DATA_ENTRY * lpstRES_DATA_ENT;
	const TCHAR szLevel31[] = TEXT("  >> 图标%4d所在文件位置:0x%08x  资源长度:%d\r\n");
	const TCHAR szFinished[] = TEXT("  >> 完成写入。\r\n\r\n");
	const TCHAR szNoIconArray[] = TEXT("资源表中没有发现图标组!");

	//计算目录项的个数
	lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY *)lpRes;
	counter = lpstRES_DIR->NumberOfIdEntries + lpstRES_DIR->NumberOfNamedEntries;
//IMAGE_RESOURCE_DIRECTORY结构后面紧跟着是IMAGE_RESOURCE_DIRECTORY_ENTRY结构
	lpstRES_DIR_ENT = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)((PBYTE)lpstRES_DIR + sizeof(IMAGE_RESOURCE_DIRECTORY));

	//循环处理每个资源目录项
	while (counter--)
	{
		RtlZeroMemory(szBuffer, sizeof(szBuffer));
//OffsetToData字段最高位为1,后七位指向下层目录块的起始地址IMAGE_RESOURCE_DIRECTORY结构
		lpAddr = lpstRES_DIR_ENT->OffsetToData;
		if (lpAddr & 0x80000000)
		{
			lpAddr &= 0x7fffffff;
			lpAddr += (DWORD)lpRes;
			//第一层:资源类型
			IDname = lpstRES_DIR_ENT->Name;//目录项名称字符串或ID
//最高位为1时,低7位值作为指向UNICODE编码的资源名IAMGE_RESOURCE_STRING_U结构
			//如果是按名称定义的资源类型,跳过 
			if (IDname & 0x80000000)
			{
				goto _next;
			}
			//高位为0时,表示按编号定义的资源类型
			else
			{
				//第一层指向了资源类别
				if (IDname == 0x03)	//判断编号是否为图标组
				{
					//移动到第二级目录
					//计算目录项的个数
					lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;
					dwICO = lpstRES_DIR->NumberOfNamedEntries + 
lpstRES_DIR->NumberOfIdEntries;

					//跳过第二级目录头定位到第二级目录项
					lpAddr += sizeof(IMAGE_RESOURCE_DIRECTORY);
					lpstRES_DIR_ENT = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)lpAddr;
					dwTemp1 = 0;
					while (dwICO--)
					{
						//直接访问到数据,获取数据在文件的偏移及大小
						dwTemp1++;
						//判断序号是否和指定的一致
						if (dwTemp1 != number)
							goto _loop;

						//如果一致,则继续查找数据
						lpAddr = lpstRES_DIR_ENT->OffsetToData;
						if (lpAddr & 0x80000000)
						{
							lpAddr &= 0x7fffffff;	//最高位为1
							lpAddr += (DWORD)lpRes;	//第三级
							lpstRES_DIR = (IMAGE_RESOURCE_DIRECTORY*)lpAddr;
							//移动到第三级目录,假设目录项数量都为1
							lpAddr += sizeof(IMAGE_RESOURCE_DIRECTORY);
							lpstRES_DIR_ENT = 
(IMAGE_RESOURCE_DIRECTORY_ENTRY*)lpAddr;
							//地址指向数据项
							lpAddr = lpstRES_DIR_ENT->OffsetToData + 
(DWORD)lpRes;
							lpstRES_DATA_ENT = 
(IMAGE_RESOURCE_DATA_ENTRY*)lpAddr;
							dwTemp3 = lpstRES_DATA_ENT->Size;
							dwTemp2 = RVAToOffset((IMAGE_DOS_HEADER *)lpFile,
 lpstRES_DATA_ENT->OffsetToData);
							wsprintf(szBuffer, szLevel31, dwTemp1, dwTemp2, 
dwTemp3);
							_AppendInfo(szBuffer);

							//将dwTemp2开始的dwTemp3个字节写入文件
							lpMem = (DWORD)lpFile + dwTemp2;
							WriteFile(hFile, (PBYTE)lpMem, dwTemp3, &dwTemp, 
NULL);
							_AppendInfo(szFinished);
							count = 1;
							return count;
						}
					_loop:
						lpstRES_DIR_ENT++;
					}
					goto _next;
				}
				else
					goto _next;
			}
		}
	_next:
		lpstRES_DIR_ENT++;
	}
	if (dwICO == 0)
		_AppendInfo(szNoIconArray);
	return count;
}

运行:

图7-8 提取图标

总结

上述程序假设图标是由多个图标数据组成,即资源表中一定存在图标组。

ICO头部分+ICO项描述在资源图标组里定义,每一部分数据则在资源图标里定义。程序 首先检测是否存在图标组资源,如果不存在则退出;如果存在,就定位到图标组资源数据,通过将字段编号(1个字)修改为偏移(2个字),重新组合ICO头部使其符合ICO文件头部要求。最后,分別读取各部分图标资源数据,顺序连接到ICO头部后即可。

相关推荐
꧁坚持很酷꧂9 分钟前
Winddows11官网下载安装VMware Workstation Pro17(图文详解)
windows
心随_风动22 分钟前
主流操作系统对比分析(macOS、Linux、Windows、Unix)
linux·windows·macos
兔子蟹子1 小时前
Java集合框架解析
java·windows·python
chuxinweihui1 小时前
数据结构——二叉树,堆
c语言·开发语言·数据结构·学习·算法·链表
周而复始 否极泰来1 小时前
深入浅出学会函数(上)
c语言·学习
看到我,请让我去学习2 小时前
C语言基础(day0424)
c语言·开发语言·数据结构
猫猫头有亿点炸2 小时前
C语言斐波拉契数列2.0
c语言·开发语言·算法
努力写代码的熊大2 小时前
c语言中文件操作详解
c语言·开发语言
明月醉窗台3 小时前
Qt 入门 6 之布局管理
c语言·开发语言·c++·qt
YuforiaCode4 小时前
第十四届蓝桥杯 2023 C/C++组 冶炼金属
c语言·c++·蓝桥杯