一、概述
想要获取一个可执行文件(PE文件)里包含的资源文件,首先要解析可执行文件,得到资源存储的地址及大小,可参考 https://blog.csdn.net/zhyulo/article/details/85717711 。然后,根据资源存储方式,得到各资源的数据内容及其大小,可参考 https://blog.csdn.net/zhyulo/article/details/85930045 。
PE文件的资源中,菜单的资源类型ID=4,菜单可能通过字符串命名,但大部分都是通过ID区分命名,而菜单中的子菜单通过ID区别,菜单名只做信息提示及快捷键,不是区分子菜单的元素。菜单资源的存放实质上是一种顺序储存的多叉树式数据结构,用标志位来记录树节点间的关系。

图1 深度优先存储的多叉树结构
二、菜单资源结构
1.菜单头结构
菜单资源的指针指向的起始位置一般有4个字节的0,定义如下:
struct MenuHeader
{
WORD wVersion;//版本号,为0
WORD cbHeaderSize;//头大小,为0
};
菜单头结构结构后紧跟着的是菜单项。
2.菜单项结构
菜单项有两种:弹出菜单(POPUP)和普通菜单项(MENUITEM),注意,特殊的菜单项 -- 分隔符也是一个普通菜单项。弹出菜单项结构体PopupMenuItem与普通菜单项结构体NormalMenuItem的定义如下:
typedef struct
{
WORD fItemFlag; //菜单项标志
WCHAR[] szItemText;//菜单名
} PopupMenuItem;
typedef struct
{
WORD fItemFlag; //菜单项标志
WORD wMenuID; //菜单ID
WCHAR[] szItemText;//菜单名
} NormalMenuItem;
1、fItemFlag是描述菜单项的标志集合,常见标志及对应的值如下(数据来源:WinUser.h):
#define MF_ENABLED 0x00000000L
#define MF_GRAYED 0x00000001L
#define MF_DISABLED 0x00000002L
#define MF_UNCHECKED 0x00000000L
#define MF_CHECKED 0x00000008L
#define MF_USECHECKBITMAPS 0x00000200L
#define MF_STRING 0x00000000L
#define MF_BITMAP 0x00000004L
#define MF_OWNERDRAW 0x00000100L
#define MF_POPUP 0x00000010L
#define MF_MENUBARBREAK 0x00000020L
#define MF_MENUBREAK 0x00000040L
#define MF_END 0x00000080L
以上这些常量定义是windows惯常使用的方式,以二进制不同的标志位代表不同的含义,通过逻辑运算可以很方便的做出不同的参数设置。搞过Windows编程的,对这种方法早已烂熟于心。简单举例,一个选中的(Checked)、非活动的(Disabled)、分栏断开(MenuBarBreak)的普通菜单项的fItemFlag:
MF_DISABLED | MF_CHECKED | MF_MENUBARBREAK = 0x0002| 0x0008 | 0x0020 = 0x002A
2、wMenuID用于唯一标识菜单。只有普通菜单项有,弹出菜单没有该结构。
3、szItemText指向一个宽字符的Unicode字符串,'\0'结束。比如"新建\t(&N)",其中(&N)代表出现该菜单时,按下快捷键N即可选中该菜单。如果该项直接为0,则代表该菜单项为分隔符。
三、示例程序
PE文件的菜单资源提取,可以使用递归方式进行。
首先取出一个字fItemFlag,判断是否是弹出菜单(MF_POPUP位置1),不是再取出一个子wMenuID。接下来就是菜单名字符串szItemText。再判断fItemFlag,如果MF_POPUP位置1,进入下一层递归;如果MF_END置1,结束当前一层递归;否则,调到第一步,循环执行。
以下程序展示了如何从PE文件的菜单资源流中取并输出成RC文件形式的代码。
void CopyOut(const char *str, int n)
{
for(int i=0; i<n; i++) Puts(str);
}
WORD* GetMenuItem(WORD *ItemFlags, int lay)
{
WORD *MenuID=NULL, *ItemText;
CopyOut("\t", lay++);
Puts("BEGIN\n");//菜单开始
while(1)
{
CopyOut("\t", lay);
if(*ItemFlags & MF_POPUP)//弹出式菜单项
{
Puts("POPUP");
ItemText = ItemFlags + 1;
}
else
{
Puts("MENUITEM");
MenuID = ItemFlags + 1;
ItemText = MenuID + 1;
}
if(!*ItemText)//菜单分隔符
{
Puts(" SEPARATOR");
ItemText++;
}
else
{
Puts(" \"");
ItemText = (WORD*)WPuts((WCHAR*)ItemText);
Puts("\"");
if(!(*ItemFlags & MF_POPUP))//ID号
{
Puts(", ");
PutIDName(*MenuID, "ID_MENUITEM");
}
if(*ItemFlags & MF_GRAYED) Puts(", GRAYED");//灰色
if(*ItemFlags & MF_DISABLED) Puts(", INACTIVE");//未激活
if(*ItemFlags & MF_BITMAP) Puts(", BITMAP");//位图
if(*ItemFlags & MF_OWNERDRAW) Puts(", OWNERDRAW");//自画
if(*ItemFlags & MF_CHECKED) Puts(", CHECKED");//已选中
}
Puts("\n");
if(*ItemFlags & MF_POPUP) ItemText = GetMenuItem(ItemText, lay);
if(*ItemFlags & MF_END) break;//菜单结束
ItemFlags = ItemText;
}
CopyOut("\t", lay-1);
Puts("END\n");//菜单结束
return ItemText;
}
void GetMenu(void *buf, WORD id)
{
struct MenuHeader
{
WORD wVersion;//版本号,为0
WORD cbHeaderSize;//头大小,为0
} *head = (MenuHeader*)buf;
PutIDName(id, "IDR_MENU");
Puts(" MENU DISCARDABLE\n");
GetMenuItem((WORD*)(head + 1), 0);
}
本文详细介绍了PE文件中的菜单资源结构,包括菜单头结构和菜单项结构,探讨了菜单资源作为顺序储存的多叉树式数据结构,并提供了示例程序展示如何解析和提取菜单资源。

878

被折叠的 条评论
为什么被折叠?



