欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]PE文件菜单资源的格式分析

[原创]PE文件菜单资源的格式分析

  前些日子做了一些PE文件分析,查阅了包括《Windows PE 权威指南》在内的不少文章及源码。可能出于实用的需要,大家分析的目标通常集中在区段、输入输出表方面的缘故,很少有文章对资源部分做很详细的介绍,即便是《Windows PE 权威指南》,在菜单资源部分说的也很简练,只分析了第一个简单菜单项,其他的一带而过。为了正确读取菜单资源的内容,不得已查了些资料,做了些分析推测,记录一下。当然,这些分析没有什么实际价值,内容非常初级,并且不带来任何效益,菜鸟以上级别请自行闪退。
  菜单资源的存放实质上是一种顺序储存的多叉树式数据结构,用标志位来记录树节点间的关系。
  
  图1 深度优先存储的多叉树结构
  菜单项有两种:弹出菜单(POPUP)和普通菜单项(MENUITEM)。
弹出菜单项

typedef struct

{

WORD		fItemFlag; //菜单项标志

WCHAR[]	szItemText;//菜单名

}PopupMenuItem;

普通菜单项
typedef struct

{

WORD		fItemFlag; //菜单项标志

WORD		wMenuID;   //菜单ID

WCHAR[]	szItemText;//菜单名

}NormalMenuItem;

  fItemFlag是描述菜单项的标志集合,常见标志及对应的值如下(数据
#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
  以上定义的中文名参考了ResScope中的翻译。你可以用ResScope或者ExeScope很轻松的用修改菜单项,然后用WinHex 或者UltraEdit验证。
  以实例来分析相对会比较简单,因此自己搞了个菜单来分析。

  图2 实验用的菜单资源
  WinHex打开PE文件或者直接在VS里直接读取都没问题。如何定位到菜单资源是PE文件分析中另一层面的内容,同时也是很简单的问题,在此不做分析。图2中的菜单资源Hex视图显示如图3。

图3 资源文件的Hex视图
  从弹出菜单项说起(图3中的红色部分):
  如何才能知道,一个菜单项是弹出菜单还是普通菜单项呢?WinUser.h给出了说明。弹出菜单项的fItemFlag为0x0010,如果弹出菜单项是本层级菜单的最后一个菜单项,则:
  MF_POPUP | MF_END = 0x0010 | 0x0080 = 0x0090,如图3中的菜单项test5。
  同理,菜单项 test3 即使不查看图2,也可知该菜单项为 MF_POPUP | MF_GRAYED,也即一个灰色弹出菜单项。ResScope 查看到的菜单资源样式也是用这种方法分析得来的。
  菜单项的名称为Unicode字符串,长度是不固定的,但无论C语言程序还是Delphi程序,菜单资源中的字符串定义方式都是C方式,字符串的结束符为标准的0x0000。
  普通菜单项:
  图3中的蓝色部分为普通菜单项的fItemFlag,0x0080、0x0081之类的一目了然,0x0000的没有设置标志位,为“一般”普通菜单项。绿色部分为每个普通菜单项的ID,和Resourse.h 中的菜单ID一致。
  需要说明的是菜单项test13,资源设置的时候设定了PopUp属性,因此它的资源ID为不可编辑状态,但因为没有给它下一层级的菜单项,程序编译的时候认为它不是一个弹出菜单项,但又没有资源ID,编译器把它的资源ID定义为 -1(0xFFFF)。
  特殊的菜单项 -- 分隔符也是一个普通菜单项,十六进制代码如图3中紫色部分,只是它的fItemFlag、wMenuID均为0,字符串为Unicode的‘\0’,因此分隔符菜单项共占6个字节。
  由以上分析可知,每级菜单的结束条件均为 fItemFlag 低八位的最高位为1,可以很简练的用递归方法遍历。这里没有统计菜单的层级,也没有写修改菜单的函数,因为多叉树写起来比较麻烦。
void PEResourse::GetMenu(PBYTE *begin,int level)

{

	WORD fItemFlag = **(WORD **)begin;

	if (level < 0)

		return;

	if (fItemFlag & 0x10)//弹出菜单项

	{

		GetPopMenuItem(begin);

		++ level;

	}

	else

		GetNormalMenuItem(begin);//普通菜单项

	if (fItemFlag & 0x80)

		-- level;

	GetMenu(begin,level);

}

  两个读取菜单项的函数:
void PEResourse::GetNormalMenuItem( PBYTE *begin)//**为移动地址的指针

{

	CString out_str,name_str;

	*begin += sizeof(WORD);

	if (!(**(DWORD **)begin))//分隔符,跳过

		*begin += sizeof(WORD);

	else

	{

		out_str.Format(_T("MENU : %04X\t"),**(WORD **)begin);

		*begin += sizeof(WORD);

		name_str = (PWCHAR)(*begin);

		out_str += name_str;

		m_menu_str.push_back(out_str);	//类成员变量vector <CString> m_menu_str

	}

	*begin += (name_str.GetLength() + 1 )* sizeof(WCHAR);	

}

void PEResourse::GetPopMenuItem( PBYTE *begin)

{

	CString out_str,name_str;

	out_str = _T("POPUP:\t\t");

	*begin += sizeof(WORD);

	name_str = (PWCHAR)(*begin);

	out_str += name_str;

	m_menu_str.push_back(out_str);

	*begin += (name_str.GetLength() + 1 )* sizeof(WCHAR);

}

  结语:其实PE文件的资源组织方式都差不多,而且和Windows资源管理器的数据组织方式大同小异,窥一斑可见全豹吧。文中错误之处欢迎指正。
参考资料:
1. 《Windows PE 权威指南》
2. Msdn
3. 《C++基于顺序储存的多叉树实现》
4. internet

上传的附件 未标题-2.gif
Snap3副本.gif
Snap4.gif

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功