PE文件解析-资源中的菜单结构

本文详细介绍了PE文件中的菜单资源结构,包括菜单头结构和菜单项结构,探讨了菜单资源作为顺序储存的多叉树式数据结构,并提供了示例程序展示如何解析和提取菜单资源。

一、概述

    想要获取一个可执行文件(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);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值