本节我们将通过两个示例程序,演示对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头部后即可。