一、概述
想要获取一个可执行文件(PE文件)里包含的资源文件,首先要解析可执行文件,得到资源存储的地址及大小,可参考 https://blog.csdn.net/zhyulo/article/details/85717711 。然后,根据资源存储方式,得到各资源的数据内容及其大小,可参考 https://blog.csdn.net/zhyulo/article/details/85930045 。
PE文件的资源中,对话框的资源类型ID=5。对话框不仅可以通过ID区别,也可以通过对话框名区别,也就是说对话框在资源树的第二层可能是数字,也可能是字符串,这点需要注意。
对话框可以说是资源中最重要、最复杂的一种。它在结构上分为2种:基本对话框和扩展对话框。而由于对于一些属性,既可能是字符串ID,又可能直接就是字符串,所以在这里又把它们的存储设计了一种新的方式。而由于对话框上有不同控件,不同控件所需的参数又不尽相同,所以解析对话框在资源中的数据流一直是一个难点。在博主的不断的努力下,才揭开了它的神秘面纱。
在RC文件中,基本对话框和扩展对话框的定义如下所示:
IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 235, 55
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "关于 test"
FONT 9, "宋体"
BEGIN
ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20
LTEXT "test 1.0 版",IDC_STATIC,40,10,119,8,
SS_NOPREFIX
LTEXT "版权所有 (C) 2019",IDC_STATIC,40,25,119,8
DEFPUSHBUTTON "确定",IDOK,178,7, 50,14,WS_GROUP
END
IDD_TEST_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "test"
FONT 9, "宋体"
BEGIN
DEFPUSHBUTTON "确定",IDOK,260,7,50,14
PUSHBUTTON "取消",IDCANCEL,260,23,50,14
LTEXT "TODO: 在这里设置对话控制。",IDC_STATIC,50,90,200,8
END
二、ID与字符串组合结构
由于对于一些属性,既可能是字符串ID,又可能直接就是字符串,所以在这里又把它们的存储设计了一种新的方式。注意,这里的字符串都是Unicode字符串。如果该结构的第一个字是0xFFFF,说明该结构是一个ID,ID值为第二个字。否则,该结构是一个字符串,以'\0'结束。我们这里把这个结构定义为IDStr。
三、基本对话框的资源结构
基本对话框包括对话框头和子控件2部分组成。对话框头与每一个子控件4字节对齐。
1.对话框头
对话框头的结构如下所示:
struct DialogBoxHeader
{
DWORD Style;//标准窗口样式
DWORD ExStyle;//扩展窗口样式
WORD DlgItems;//控件数量
WORD x;//起点坐标
WORD y;
WORD cx;//宽
WORD cy;//高
IDStr menuName;//菜单名
IDStr ClassName;//类名
IDStr szCaption;//对话框标题
};
如果Style的DS_SETFONT位为1,那么在DialogBoxHeader的结构后还有字体结构,字体结构如下:
struct DialogFont
{
WORD wPointSize;//字体大小
WCHAR FontName[1];
};
2.子控件
基本对话框的子控件结构如下:
struct ControlData
{
DWORD Style;//标准窗口样式
DWORD ExStyle;//扩展窗口样式
WORD x;//左上角坐标
WORD y;
WORD cx;//长
WORD cy;//高
WORD id;//控件ID
IDStr ClassID;//控件类型
IDStr Text;//控件名称
WORD ExtraStuff;
};
四、扩展对话框的资源结构
与基本对话框一样,扩展对话框也由扩展对话框头和子控件2部分组成。扩展对话框头与每一个子控件4字节对齐。
1.扩展对话框头
扩展对话框头的结构如下所示:
struct DialogBoxHeaderEx
{
DWORD SignEx;//0xFFFF0001
DWORD Version;
DWORD ExStyle;//扩展窗口样式
DWORD Style;//标准窗口样式
WORD DlgItems;//控件数量
WORD x;//起点坐标
WORD y;
WORD cx;//宽
WORD cy;//高
IDStr menuName;//菜单名
IDStr ClassName;//类名
IDStr szCaption;//对话框标题
};
如果Style的DS_SETFONT位为1,那么在DialogBoxHeaderEx的结构后还有字体结构,字体结构如下:
struct DialogFontEx
{
WORD wPointSize;//字体大小
WORD Weight;
BYTE Italic;
BYTE CharSet;
WCHAR FontName[1];
};
2.子控件
扩展对话框的子控件结构如下:
struct ControlDataEx
{
DWORD helpID;//帮助ID
DWORD ExStyle;//扩展窗口样式
DWORD Style;//标准窗口样式
WORD x;//左上角坐标
WORD y;
WORD cx;//长
WORD cy;//高
WORD id;//控件ID
WORD ;
IDStr ClassID;//控件类型
IDStr Text;//控件名称
WORD ExtraStuffLen;
BYTE ExtraBuf[1];
};
五、实例代码
以下程序展示了如何从PE文件的对话框资源流中读取并输出成RC文件形式的代码。
char* GetDialogItem(char *buf, bool isExtra)
{
DWORD Style, ExStyle, NotStyle;
struct ControlData//标准对话框组件结构
{
DWORD Style;//标准窗口样式
DWORD ExStyle;//扩展窗口样式
WORD x;//左上角坐标
WORD y;
WORD cx;//长
WORD cy;//高
WORD id;//控件ID
} *Item;
DWORD *helpID = NULL;//只有扩展对话框才有帮助ID
WCHAR *ClassID, *Text, *ExtraStuff;
if(isExtra)
{
helpID = (DWORD*)buf;
Item = (ControlData*)(helpID + 1);
}
else Item = (ControlData*)buf;
Style = isExtra ? Item->ExStyle : Item->Style;
ExStyle = isExtra ? Item->Style : Item->ExStyle;
Puts("\t");
//控件类型
ClassID = (WCHAR*)&Item->id + 1;
if(isExtra) ClassID++;
const char *pStyle[] = {"", "button", "static"};
int isControl = 0;
if(*ClassID == 0xFFFF)
{
Text = ClassID + 2;
switch(ClassID[1])
{
case 0x0080:
switch(Style & 0x0FL)
{
case BS_PUSHBUTTON: Puts("PUSHBUTTON"); NotStyle = BS_PUSHBUTTON | WS_TABSTOP; break;
case BS_DEFPUSHBUTTON: Puts("DEFPUSHBUTTON"); NotStyle = BS_DEFPUSHBUTTON | WS_TABSTOP; break;
case BS_CHECKBOX: Puts("CHECKBOX"); NotStyle = BS_CHECKBOX | WS_TABSTOP; break;
case BS_AUTOCHECKBOX: Puts("AUTOCHECKBOX"); NotStyle = BS_AUTOCHECKBOX; break;
case BS_RADIOBUTTON: Puts("RADIOBUTTON"); NotStyle = BS_RADIOBUTTON; break;
case BS_3STATE: Puts("STATE3\t"); NotStyle = BS_3STATE; break;
case BS_AUTO3STATE: Puts("AUTO3STATE"); NotStyle = BS_AUTO3STATE; break;
case BS_GROUPBOX: Puts("GROUPBOX"); NotStyle = BS_GROUPBOX; break;
case BS_AUTORADIOBUTTON: Puts("AUTORADIOBUTTON"); NotStyle = BS_AUTORADIOBUTTON; break;
default: Puts("CONTROL\t"); NotStyle = 0; isControl = 1;
}
break;
case 0x0081: Puts("EDITTEXT"); NotStyle = ES_LEFT | WS_BORDER | WS_TABSTOP; break;
case 0x0082:
switch(Style & 0x0FL)
{
case SS_LEFT: Puts("LTEXT\t"); NotStyle = SS_LEFT | WS_GROUP; break;
case SS_RIGHT: Puts("RTEXT\t"); NotStyle = SS_RIGHT | WS_GROUP; break;
case SS_CENTER: Puts("CTEXT\t"); NotStyle = SS_CENTER | WS_GROUP; break;
case SS_ICON: Puts("ICON\t"); NotStyle = SS_ICON; break;
default: Puts("CONTROL\t"); NotStyle = 0; isControl = 2;
}
break;
case 0x0083: Puts("LISTBOX\t"); NotStyle = WS_BORDER | LBS_NOTIFY; break;
case 0x0084: Puts("SCROLLBAR"); NotStyle = 0; break;
case 0x0085: Puts("COMBOBOX"); NotStyle = 0; break;
default: Puts("CONTROL\t"); NotStyle = 0; isControl = -2;
}
}
else
{
Text = ClassID;
while(*Text) Text++;
Text++;
Puts("CONTROL\t");
NotStyle = 0;
isControl = -1;
}
Puts("\t");
NotStyle |= WS_CHILD | WS_VISIBLE;
Style &= ~NotStyle;
NotStyle &= ~(isExtra ? Item->ExStyle : Item->Style);
//控件名称
if(*Text == 0xFFFF)
{
PutIDName(Text[1]);//控件ID
Puts(", ");
ExtraStuff = Text + 2;
}
else
{
if(!isControl && (ClassID[1] == 0x0081 || ClassID[1] == 0x0083//EDITTEXT,LISTBOX无需此项
|| ClassID[1] == 0x0084 || ClassID[1] == 0x0085))
{
ExtraStuff = Text;
while(*ExtraStuff) ExtraStuff++;
ExtraStuff++;
}
else
{
Puts("\"");
ExtraStuff = (WCHAR*)WPuts(Text);
Puts("\", ");
}
}
//控件ID
PutIDName(Item->id, "IDC_");
//扩展控件类型
if(isControl)
{
Puts(", ");
if(isControl == -1)
{
Puts("\"");
WPuts(ClassID);
Puts("\"");
}
else if(isControl > 0) Puts(pStyle[isControl]);
else Print("%d", ClassID[1]);
//标准窗口样式
if(Style || !NotStyle) Print(", %#x", Style);
if(NotStyle)
{
if(Style) Puts(" |");
else Puts(",");
Print(" NOT %#x", NotStyle);
}
Style = NotStyle = 0;
}
Print(", %d,%d,%d,%d",Item->x,Item->y,Item->cx,Item->cy);
if(Style || NotStyle || ExStyle || (isExtra && *helpID))
{
if(Style || (!NotStyle && !isControl)) Print(", %#x", Style);
if(NotStyle)
{
if(Style) Puts(" |");
else Puts(",");
Print(" NOT %#x", NotStyle);
}
if(ExStyle || (isExtra && *helpID))
{
if(ExStyle == WS_EX_STATICEDGE)
Puts(", WS_EX_STATICEDGE");
else Print(", %#x", ExStyle);
if(isExtra && *helpID)
{
Puts(", ");
PutIDName(Item->id, "HIDC_");
}
}
}
Puts("\n");
buf = (char*)(ExtraStuff + 1);
if(isExtra) buf += *ExtraStuff;
return buf;
}
void GetDialog(char *buf, CStrID id)
{
if(*(DWORD*)buf == 0xFFFF0001)
{
GetDialogEx(buf, id);
return;
}
if(id.IsID()) PutIDName(id.GetID(), "IDD_DIALOG");
else Puts(id.GetStr());
Puts(" DIALOG");
struct DialogBoxHeader
{
DWORD Style;//标准窗口样式
DWORD ExStyle;//扩展窗口样式
WORD DlgItems;//控件数量
WORD x;//起点坐标
WORD y;
WORD cx;//宽
WORD cy;//高
WCHAR menuName[1];//菜单名
} *Header = (DialogBoxHeader*)buf;
Print(" DISCARDABLE %d, %d, %d, %d\n",//对话框坐标
Header->x,Header->y,Header->cx,Header->cy);
Print("STYLE 0x%.8X\n", Header->Style & (~DS_SETFONT));//标准窗口样式
if(Header->ExStyle) Print("EXSTYLE 0x%.8X\n", Header->ExStyle);//扩展窗口样式
WCHAR *ClassName,*szCaption;
char *ItemData;
//菜单名
if(Header->menuName[0] == 0xFFFF)
{
Print("MENU %d\n",Header->menuName[1]);
ClassName = Header->menuName + 2;
}
else if(Header->menuName[0])
{
Puts("MENU \"");
ClassName = (WCHAR*)WPuts(Header->menuName);
Puts("\"\n");
}
else ClassName = Header->menuName + 1;
//类名
if(*ClassName == 0xFFFF)
{
Print("CLASS %d\n",ClassName[1]);
szCaption = ClassName + 2;
}
else if(*ClassName)
{
Puts("CLASS \"");
szCaption = (WCHAR*)WPuts(ClassName);
Puts("\"\n");
}
else szCaption = ClassName + 1;
//对话框标题
if(*szCaption)
{
Puts("CAPTION \"");
ItemData = (char*)WPuts(szCaption);
Puts("\"\n");
}
else ItemData = (char*)(szCaption + 1);
if(Header->Style & DS_SETFONT)//字体结构
{
struct DialogFont
{
WORD wPointSize;//字体大小
WCHAR FontName[1];
} *Font = (DialogFont*)ItemData;
Print("FONT %d, \"", Font->wPointSize);
ItemData = (char*)WPuts(Font->FontName);
Puts("\"\n");
}
Puts("BEGIN\n");
for(int i=0; i<Header->DlgItems; i++)
{
ItemData = (ItemData - buf + 3) /4*4 + buf;
ItemData = GetDialogItem(ItemData, false);
}
Puts("END\n");
}
void GetDialogEx(char *buf, CStrID id)
{
if(id.IsID()) PutIDName(id.GetID(), "IDD_DIALOG");
else Puts(id.GetStr());
Puts(" DIALOGEX");
struct DialogBoxHeaderEx
{
DWORD SignEx;//0xFFFF0001
DWORD Version;
DWORD ExStyle;//扩展窗口样式
DWORD Style;//标准窗口样式
WORD DlgItems;//控件数量
WORD x;//起点坐标
WORD y;
WORD cx;//宽
WORD cy;//高
WCHAR menuName[1];//菜单名
} *Header = (DialogBoxHeaderEx*)buf;
Print(" DISCARDABLE %d, %d, %d, %d\n",//对话框坐标
Header->x,Header->y,Header->cx,Header->cy);
Print("STYLE 0x%.8X\n", Header->Style & (~DS_SETFONT));//标准窗口样式
if(Header->ExStyle) Print("EXSTYLE 0x%.8X\n", Header->ExStyle);//扩展窗口样式
WCHAR *ClassName,*szCaption;
char *ItemData;
//菜单名
if(Header->menuName[0] == 0xFFFF)
{
Print("MENU %d\n",Header->menuName[1]);
ClassName = Header->menuName + 2;
}
else if(Header->menuName[0])
{
Puts("MENU \"");
ClassName = (WCHAR*)WPuts(Header->menuName);
Puts("\"\n");
}
else ClassName = Header->menuName + 1;
//类名
if(*ClassName == 0xFFFF)
{
Print("CLASS %d\n",ClassName[1]);
szCaption = ClassName + 2;
}
else if(*ClassName)
{
Puts("CLASS \"");
szCaption = (WCHAR*)WPuts(ClassName);
Puts("\"\n");
}
else szCaption = ClassName + 1;
//对话框标题
if(*szCaption)
{
Puts("CAPTION \"");
ItemData = (char*)WPuts(szCaption);
Puts("\"\n");
}
else ItemData = (char*)(szCaption + 1);
if(Header->Style & DS_SETFONT)//字体结构
{
struct DialogFontEx
{
WORD wPointSize;//字体大小
WORD Weight;
BYTE Italic;
BYTE CharSet;
WCHAR FontName[1];
} *Font = (DialogFontEx*)ItemData;
Print("FONT %d, \"", Font->wPointSize);
ItemData = (char*)WPuts(Font->FontName);
Print("\", %d, %d, %d\n", Font->Weight, Font->Italic, Font->CharSet);
}
Puts("BEGIN\n");
for(int i=0; i<Header->DlgItems; i++)
{
ItemData = (ItemData - buf + 3) /4*4 + buf;
ItemData = GetDialogItem(ItemData, true);
}
Puts("END\n");
}