VB可执行体内部数据结构浅析① VB头结构

第一次分析VB可执行体内部数据结构是几年前的事了,当时代码写的很混乱,现在刚好了解相关知识的时候,发现之前的代码可读性太差,所以一边重构代码,一边写篇文章复习下以前的成果。

一般使用反汇编工具来分析VB程序的入口点,结果都是以下形式:

push    0x0040XXXX
call    MSVBVM60.ThunRTMain

首先push一个地址到栈里,紧接着调用MSVBVM60库里的ThunRTMain函数,并且不会立即返回。

这个地址指向的数据结构,我就管它叫做VB_HEADER,该数据结构是VB程序运行时至关重要的。

根据该结构,VB运行时(MSVBVM60)可以枚举该项目有多少个对象,对象有多少控件,控件又有多少的事件。程序该从何处执行,代码是否为P-Code等等。

下图简示了从VB_HEADER开始枚举每个对象的流程:

vb_01

这里说的’对象‘就是指原VB项目中的每个编译单元(frm/bas/ctl/cls/…),在编译完成后,都会以’对象‘的形式存在于最终二进制文件中。

而窗体对象进而会指出一个数据结构来描述所拥有的控件表,控件又会指出一个数据结构来描述所拥有的事件表。

下面是用C语言写的上图相关的数据结构:

//VB 初始化头部结构
typedef struct _VB_HEADER
{
	union {
		char        crSign[4];    // 四个字节的签名符号,和PEHEADER里的那个signature是类似性质的东西,VB文件都是"VB5!"
		uint32_t    dwSign;
	} Sign;

	uint16_t      wRuntimeBuild;          // 运行时创立的变量(类似编译的时间)
	char          szLangDll[14];          // 语言DLL文件的名字(如果是0x2A的话就代表是空或者是默认的)
	char          szSecLangDll[14];       // 备份DLL语言文件的名字(如果是0x7F的话就代表是空或者是默认的,改变这个值堆EXE文件的运行没有作用)
	uint16_t      wRuntimeRevision;       // 运行时DLL文件的版本
	LCID          dwLCID;                 // 语言的ID
	LCID          dwSecLCID;              // 备份语言的ID(只有当语言ID存在时它才存在)
	PVOID         lpSubMain;              // VA sub main过程的地址指针(3.)(如果是0则代表这个EXE时从Form窗体文件开始运行的)
	PVBPI         lpProjInfo;             // VA 工程信息的地址指针,指向一个VB_PROJECT_INFO结构(2.)
	uint32_t      fMdlIntCtls;            // ?详细见"MDL 内部组建的标志表"
	uint32_t      fMdlIntCtls2;           // ?详细见"MDL 内部组建的标志表"
	uint32_t      dwThreadFlags;          // 线程的标志
	uint32_t      dwThreadCount;          // 线程个数
	uint16_t      wFormCount;             // 窗体个数
	uint16_t      wExtComponentCount;     // VA 外部引用个数例如WINSOCK组件的引用
	uint32_t      dwThunkCount;           // ?大概是内存对齐相关的东西
	PVBGT         lpGuiTable;             // VA GUI元素表的地址指针(指向一个GUITable_t结构(4.四))
	PVBEXTCOMP    lpExtComponentTable;    // VA 外部引用表的地址指针
	PVBCRD        lpComRegData;           // VA COM注册数据的地址指针
	uint32_t      oSZProjectDescription;  // Offset 指向工程EXE名字的字符串
	uint32_t      oSZProjectExeName;      // Offset 指向工程标题的字符串
	uint32_t      oSZProjectHelpFile;     // Offset 指向帮助文件的字符串
	uint32_t      oSZProjectName;         // Offset 指向工程名的字符串
} VB_HEADER, VBHDR, *PVB_HEADER, *PVBHDR;

//VB工程信息
typedef struct _VB_PROJECT_INFO
{ //sizeof() = 0x23c
	union {
		char                      chSign[4];                  // 结构的签名特性,和魔术字符类似
		uint32_t                  dwSign;
	} Sign;

	PVBOT                       lpObjectTable;              // VA 结构指向的组件列表的地址指针(很重要的!(7.))  
	uint32_t                    dwNull;                     // 没有用的东西  
	PVOID                       lpCodeStart;                // VA 代码开始点,类似PEHEAD->EntryPoint这里告诉了VB代码实际的开始点  
	PVOID                       lpCodeEnd;                  // VA 代码结束点  
	uint32_t                    dwDataSize;                 // 标志1  
	PVOID                       lpThreadSpace;              // 多线程的空间??
	func__vbaExceptHandler      lpVBAExceptHandler;         // VA VBA异常处理器(SEH)地址指针  
	PVOID                       lpNativeCode;               // VA 本地机器码开始位置的地址指针  
	WCHAR                       szPathInformation[264];     // 原文件地址,一个字符串,长度最长为264
	PVBEXT                      lpExternalTable;            // VA 引用表的地址  
	uint32_t                    dwExternalCount;            // 引用表大小(个数)  
} VB_PROJECT_INFO, VBPI, *PVB_PROJECT_INFO, *PVBPI;

//VB对象表
typedef struct _VB_OBJECT_TABLE //这个是OBJECT 的总表,可以索引以后的每个OBJECT
{ //sizeof() = 0x54
	LPVOID          lpHeapLink;                             //没有用的填充东西
	LPVOID          lpExecProj;                             //指向VB工程Exec COM对象
	struct _VB_PROJECT_INFO2 * lpProjectInfo2;              //VA指向Project Info 2
	uint32_t        dwReserved;                             //编译后总为-1,未使用
	uint32_t        dwNull;                                 //编译后未使用
	LPVOID          lpProjectObject;                        //指向工程数据
	GUID            uuidObject;                             //对象表的GUID
	uint16_t        fCompileState;                          //在编译过程中使用的内部标志
	uint16_t        wTotalObjects;                          //目前在工程总计对象数量
	uint16_t        wCompiledObjects;                       //已编译对象数量
	uint16_t        wObjectsInUse;                          //通常等于上面的编译后的对象数量
	PVBPUBOBJ       lpObjectArray;                          //VA指向第一个VB公共对象描述符,很重要
	uint32_t        fIdeFlag;                               //标志/指针,仅在IDE模式下使用
	uint32_t        lpIdeData;                              //标志/指针,仅在IDE模式下使用
	uint32_t        lpIdeData2;                             //标志/指针,仅在IDE模式下使用
	PCHAR           lpProjectName;                          //指向工程名字
	LCID            dwLcid;                                 //language ID1
	LCID            dwLcid2;                                //language ID2
	LPVOID          lpIdeData3;                             //标志/指针,仅在IDE模式下使用
	uint32_t        dwIdentifier;                           //结构的模板版本
} VB_OBJECT_TABLE, VBOBJTAB, *PVB_OBJECT_TABLE, *PVBOT;

//VB公共对象描述符
typedef struct _VB_PUBLIC_OBJECT_DESCRIPTOR   //这个就是每个对象描述符的结构 
{ // sizeof() = 0x30
	struct _VB_OBJECT_INFO  * lpObjectInfo;               //VA 指向一个ObjectInfo_t类型,来显示这个OBJECT的数据
	uint32_t                  dwReserved;                 //保留未使用,编译后总为-1
	PSHORT                    lpPublicBytes;              //VA 指向外部可见变量表大小 (integers)
	PSHORT                    lpStaticBytes;              //VA 指向外部不可见变量表大小 (integers)
	LPVOID                    lpModulePublic;             //VA 指向外部可见变量表
	LPVOID                    lpModuleStatic;             //VA 指向外部不可见变量表
	PCHAR                     lpObjectName;               //VA 字符串,这个OBJECT的名字
	uint32_t                  dwMethodCount;              //events, funcs, subs(事件\函数\过程)数目
	PCHAR *                   lpMethodNamesArray;         //指向方法名称指针数组
	uint32_t                  oStaticVars;                //OFFSET  从pModuleStatic指向的静态变量表偏移
	uint32_t                  fObjectType;                //比较重要显示了这个OBJECT的实现,具体见下表
	uint32_t                  dwNull;                     //编译后无效
} VB_PUBLIC_OBJECT_DESCRIPTOR, VBPUBOBJDESCR, VBPUBOBJ, *PVB_PUB_OBJ_DESCR, *PVBPUBOBJ;

因为VB内部的数据结构众多且较为复杂,所以本文只摘取相关的数据结构来讲解。

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据