《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头部后即可。

相关推荐
九鼎科技-Leo24 分钟前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
搬砖的小码农_Sky35 分钟前
C语言:数组
c语言·数据结构
Yang.993 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
我不瘦但很逗3 小时前
Windows下使用DBeaver连接云数据库(MySQL)
数据库·windows
ashane13144 小时前
Java list
java·windows·list
万里沧海寄云帆4 小时前
Word 插入分节符页码更新问题
windows·microsoft·word
ahadee5 小时前
蓝桥杯每日真题 - 第19天
c语言·vscode·算法·蓝桥杯
Theliars5 小时前
C语言之字符串
c语言·开发语言
Reese_Cool5 小时前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法
dot.Net安全矩阵5 小时前
.NET 通过模块和驱动收集本地EDR的工具
windows·安全·web安全·.net·交互