Internet Explorer 完全解析 [1]
Internet Explorer 是一个广泛使用的浏览器,囿于微软的闭源策略,Internet Explorer的许多特性无法被人深入理解,现在,我要大概使用40章的篇幅来完全解析一下Internet Explorer的方方面面,最终我会给出修改好的pdf,40章我是随读随写的,所以不免会有问题,请以最终的pdf为准。
历史等介绍前略
文章来源 www.nul.pw
第1章 intro
1.1 Internet Explorer的结构
关于这部分,网上有一个很经典的图,大致描述起来就是 宿主-shdocvw.dll-mshtml.dll-html文档 的四层结构
mshtml是一个活动文档解释器,或者更通俗的说,mshtml负责解析html。既然是浏览器,无论是平时的分析还是漏洞测试,页面解析这块必然是重头戏,因此mshtml.dll是我这次首先开始解析的部分。
1.2 初识CBase
mshtml.dll中,可以作为“基类”存在的类为CBase
,CBase实现了IDispatchEx、IProvideMultipleClassInfo、ISupportErrorInfo、IOleCommandTarget、ISpecifyPropertyPages这些基本的自动化接口,还有IObjectIdentity接口,用于判断两个对象是否相同。CBaseCF类实现了类厂(Class Factory)功能。
CDispParams
类用来协助CBase的实现,它用来对DISPPARAMS进行二次封装(因此无疑它继承了DISPPARAMS),方便Invoke和InvokeEx这两个函数的调用(它们有一个参数是DISPPARAMS类型)。
CDispParams的成员变量都继承自父类DISPPARAMS,为rgvarg、rgdispidNamedArgs、cArgs、cNamedArgs。
关于DISPPARAMS的定义可以参见微软MSDN的介绍*1
。其结构如下:
typedef struct FARSTRUCT tagDISPPARAMS {
VARIANTARG FAR* rgvarg; //参数数组
DISPID FAR* rgdispidNamedArgs; //已命名参数的DISPID(Dispatch identifiers)
unsigned int cArgs; //参数个数
unsigned int cNamedArgs; //已命名的参数的个数
} DISPPARAMS;
其成员函数及作用列举如下:
【1】HRESULT CDispParams::Create(DISPPARAMS*)
这个函数即会从DISPPARMS*中获取对应参数信息,参数数组的初始化值是VT_NULL,命名参数数组的初始化值是DISPID_UNKNOWN。
【2】HRESULT CDispParams::MoveArgsToDispParams (DISPPARAMS *pOutDispParams, UINT cNumArgs, BOOL fFromEnd)
这个函数的作用是把参数数组的内容给移动到pOutDispParams中。其实它虽然调用了memcpy,但是事实上只是把参数列表(CDispParams的成员变量:rgvarg[])的指针给复制到了pOutDispParams->rgvarg里,所以现在pOutDispParams和CDispParams事实上拥有了同样的对象(当然,释放的话只能释放一次)。最后一个参数指定了复制时是从最后(index: cArgs - cNumArgs)开始复制还是从最开始(index: 0)开始复制。
1.3 CBase的_pAA,大家的_pAA
在介绍CBase之前,也许非常有必要去解释一下常见的_pAA
参数,这个参数实际上是“样式属性数组的指针
”的缩写(pointer of attribute array),它是CAttrArray
类的一个实例,之所以将它提前介绍,是因为_pAA就是元素的样式表
,因此它的身影几乎贯穿在所有的类中间。CAttrArray
继承于CAttrValue
。
CAttrValue
的flags
数据结构如下:
31 24 23 16 15 8 7 0
-----------------------------------------------------------------------
| | | | |
| VT_TYPE | AAExtraBits | AATYPE |
| | | | |
-----------------------------------------------------------------------
struct AttrFlags {
BYTE _aaType;
WORD _aaExtraBits;
BYTE _aaVTType;
};
_aaType
指定了这个AA是什么类型的,_aaExtraBits
指定了这个AA的具体类型,例如important类型(“important”)、隐含式(“background: none”)之类的,最后一个_aaVTType
指定VT_TYPE
。这个类具体来说就是样式表的解析器。
CAttrArray
继承自CAttrValue(CDataAry
1.4 CBase的成员函数
- CBase::CBase。构造类中初始化了所有变量。
- CBase::CBaseCheckThread。
如果CBase被线程A创建,那么只有A能访问这个创建的CBase对象。这个函数的用途是检查引用CBase对象的线程是否是创建这个被引用的CBase对象的线程。 - CBase::Init。检查是否正确初始化。
- CBase::Passivate。通过释放对其他对象的引用、清理资源使用来释放main对象。主对象引用数为0的时候会调用这个函数。主对象和所有子对象的引用为0时会调用析构函数。在析构时,Passivate还会删除_pAA对象。
- CBase::IsEqualObject。实现了IObjectIdentity,判断一个对象是否和自身相同,具体方式:自己和待比较对象分别获取IID_IUnknown接口,比较最终结果是否相同。
- CBase::PrivateQueryInterface。IPrivateUnknown中的实现,和每个IIDTable中的值相比较,实现查询指定栈接口功能。如果传递的是非栈接口,那么代表着很可能所有派生类都需要在各自的PrivateQueryInterface中处理这些消息。
- CBase::PrivateAddRef/CBase::PrivateRelease。IPrivateUnknown中的实现,AddRef/ReleaseRef用。
- CBase::SubAddRef/CBase::SubRelease。SubBase的Add/Release Ref用。
- CBase::CLock::CLock/CBase::CLock::~CLock。增加/释放CBase对象中资源锁用的函数。
- CBase::QueryService。空函数,需要由派生类重载。目的是为了查询Service,传入的参数分别是:service的GUID、service interface的IID、service的pointer of pointer。
- CBase::GetPages。ISpecifyPropertyPages中的实现,用来指定这个对象可以接受的属性页,传来的参数指向一个UUID(CLSID)的数组。CLSID分为两个部分,每组都以NULL结尾,第一组用作浏览模式(
run-mode
),第二组用于编辑模式(design mode
)。 - CBase::HasPages。返回这个对象是否支持任何属性页,返回值是bool类型。其实就是检查property pages中是否有元素。
- CBase:GetClassInfo。IProvideMultipleClassInfo的实现,返回控件的coclass 信息。
- CBase:GetGUID。IProvideMultipleClassInfo的实现,接受两个参数,第一个是guid的类型,第二个是接受guid的缓冲区的指针,根据第一个guid类型返回对应的guid。
- CBase::GetMultiTypeInfoCount。IProvideMultipleClassInfo的实现,返回这个对象上type info的数量。
- CBase:GetAggMultiTypeInfoCount。IProvideMultipleClassInfo的聚合体(无构造函数、无非公开的成员函数、无基类、无虚函数的数组、类或者结构体
*2
)的帮助函数。函数接受聚合体的IUnkown接口,通过读取其IID_IProvideMultipleClassInfo接口获得一个IProvideMultipleClassInfo实例,然后调用其GetMultiTypeInfoCount获取其type info 的数量。 - CBase:GetInfoOfIndex。IProvideMultipleClassInfo的实现,从index的type info中获取信息。
- CBase::GetAggInfoOfIndex。在一个聚合体中,根据index获取一些type info。
- CBase:InterfaceSupportsErrorInfo。ISupportErrorInfo的实现,当传入的接口(传入的是IID)支持错误信息的时候返回true。
- CBase::GetClassID。 IPersist的实现。
- CBase::InvokeDispatchWithThis。 它的作用是,接受IDispatch*的变量,通过把这个Dispatch的IUnknown加到参数列表里面来引用(invokes)它。它的动作为:
- 获取传入的pDisp的IDispatchEx(/IDispatch)接口;
- 将其信息保存到DISPPARAMS的rgvarg中去;
- 调用其InvokeEx(/Invoke)函数 (DISPID_VALUE);
*3
- 如果需要传递这个参数,而且参数数量>1时,用原始参数替代现在的参数。
其实,主要的就是在第三步 。
- CBase::InvokeDispatchExtraParam。依然是invoke pDispatch,多了一个额外参数。
- CBase::Invoke。 IDispatch中的标准实现。
- CBase::GetIDsOfNames。 IDispatch中的标准实现。
- CBase::InvokeAA。 辅助函数。
类型为PROPERTY_GET、实际参数为0个的时候,从dispidMembers中找到对应的aaType,返回这个VARIANT。
类型为PROPERTY_PUT、实际参数为1个的时候,dispMembers中找到对应的aaType,把已知(未知)的aaType替换掉(加到)dispMembers的ArgList里面的对应位置,通知dispidMember有OnPropertyChange。
flags为GET/METHOD但是执行失败时,试着invoke一次。具体做法:dispMembers找到对应的aaType(返回AAINDEX),如果是已知的AAINDEX,获取该位置上对应的dispatch对象,然后调用InvokeDispatchWithThis来invoke。 - CBase::ContextInvokeEx。真的InvokeEx实现,用传入的参数来调用函数。
~
- CBase::GetDispID。IDispatchEx中的实现。
- CBase::DeleteMemberByName/CBase::DeleteMemberByDispID。 IDispatchEx中的实现。
- CBase::GetMemberProperties/CBase::GetMemberName。IDispatchEx中的实现。后者返回ExpandoName或者ExposedName或者Name。
- CBase::GetNextDispID。IDispatchEx中的实现。从ITypeInfo中遍历所有项目的值,然后枚举所有expando的属性。这个函数会枚举所有普通的property和所有expando。
- CBase::GetNameSpaceParent。IDispatchEx的实现。
- CBase::GetInternalDispID。 GetIDsOfName的封装。
- CBase::GetExpandoDispID。 获取Expando的DispID。
- CBase::GetInternalNextDispID。 获取NextDispID。
- CBase::NextTypeInfoProperty。 获取下一个TypeInfo Property(通过INVOKE_PROPERTYGET/PUT来区分)。
- CBase::AddExpando。 辅助函数, 将一个expando property添加到attr array中。
- CBase::SetExpando。 辅助函数,顾名思义,Set Expando的property。
- CBase::GetNextDispIDExpando。 获取dispid的下一个expando。
~
- CBase::GetExpandoName。 获取expando的名字。
- CBase::NextProperty。 辅助函数,遍历用。遍历范围和GetNextDispID一样。
- CBase::FindNextAttach。 遍历找到下一个attach event的attr array。
- CBase::GetTheDocument。 从IID_IHTMLDocument2获取接口,然后从接口中获取IID_IDispatchEx,再获取"document"的dispID,然后Invoke,并从中返回一个IHTMLDocument2*,写入
*ppDoc
(传入时类型是IHTMLDocument2** ppDoc)。 - CBase::FindEventName。 从event table中找到对应的Event。
- CBase::FireEvent。 从主dispatch中触发一个与之关联的事件。仔细看一下这个函数。
1.首先触发任何会导致这个event的property (除了OnError);
2.触发任何附加的事件;
3.触发任何通过普通连接点监听着的property,它们通过一个内部的dispid存在AA里;这一步里,遍历所有AA,然后存储起来,最后通过ITridentEventSink的FireEvent调用它们; - CBase::FireAttachEvents。触发所有通过attachEvent(脚本)附加的事件。
- CBase::FireAttachEventV。FireAttachEvents的封装。根据arrayType将va_list转为DISPPARAMS。
- CBase::FireEventV。跟上面的类似,FireEvent的封装。
- CBase::FirePropertyNotify。 触发Property OnChanged或者OnRequestEdit事件。
- CBase::FireCancelableEvent。试图触发事件,然后返回脚本的返回值。封装的是FireEventV。
- CBase::GetEnabled/CBase::GetValid。 辅助函数,多数函数都会重载它。
- CBase::FindAAType。 AttrArray的辅助函数。_pAA中查找AAIndex并返回查找到的序数。
- CBase::DidFindAAIndexAndDelete。 查找到AA之后把它删除,对FindAAType的封装。
- CBase::FindNextAAIndex。 通过给定的dispid和AAType查找AAIndex。
- CBase::GetStringAt。 获取指定AAINDEX上的字符串。
- CBase::GetIntoBSTRAt/CBase::GetIntoStringAt。 获取指定AAINDEX上的字符串到BSTR/LPCTSTR变量中。
- CBase::GetPointerAt/CBase::GetVariantAt。获取指定AAINDEX上的指针/VARIANT。
- CBase::FetchObject。 找到AttrValue对象后,如果类型是VT_UNKNOWN/VT_DISPATCH给其IUnknown/IDispatch接口增加1个计数。
- CBase::GetObjectAt。 找到对象后调用FetchObject。
- CBase::GetCookieAt/CBase::SetCookieAt。 找到对象后获取/设置其Cookie,Cookie是一个DWORD类型的变量。
- CBase::GetAAtypeAt/CBase::GetVariantTypeAt。 找到对象后获取其AAType/VariantType。
- CBase::Add*****。 各种添加函数,封装了Set函数。
- CBase::AddUnknownObjectMultiple/CBase::AddDispatchObjectMultiple/CBase::AddDispatchObject。 把一个对象加入attr array,表明这个对象在指定的DISPID下允许有多个入口。(VT_UNKNOWN/VT_DISPATCH的区别)
- CBase::StoreEventsToHook。 对CAttrValue的StoreEventsToHook的封装。
1.先调用CAttrArray::EnsureHeader,这个函数的作用是将this转为CAttrValue*类型,然后获取自己的GetAVType (AttrValue Type),如果确认类型是CAttrValue::VT_AAHEADER,则返回转换后的CAttrValue*,否则,调用SetHeader()设置一个头,然后返回转换后的CAttrValue*,失败的话返回NULL。
2.调用CAttrValue*的SetEventsToHook函数,传入这个函数的参数,类型为InlineEvts*
。CAttrValue::SetEventsToHook其实就做了一件事:GetAAHeader()->_pEventsToHook = pEventsToHook;。 - CBase::GetEventsToHook。与Set一样,最后获取到_pEventsToHook的值,返回。
- CBase::ChangeSimpleAt。 修改指定AAIndex位置的VARIANT为一个VT_I4类型的值,值为long类型的dwSimple(由参数传入)。
- CBase::ChangeStringAt/CBase::ChangeUnknownObjectAt/CBase::ChangeDispatchObjectAt/CBase::ChangeVariantAt/CBase::ChangeAATypeAt。 同上,换成了VT_LPWSTR/VT_UNKNOWN/VT_DISPATCH/some variant/some aatype。
- CBase::FindVTableEntryForName。 从vtable中找到对应名字的项目并返回。返回VTABLEDESC*类型,这是一个结构体。
- CBase::getAttribute。获取指定的DISPID,然后FindPropDescFromDispID从中找到Property返回。
~
- CBase::removeAttributeDispid/CBase::setAttribute。 从指定的DISPID删除/设置属性。
- CBase::IsExpandoDISPID。 判断某个DISPID是否是一个Expando DISPID。
- CBase::toString。将DISPID_VALUE获取到的内容转为 BSTR。
- CBase::FindPropDescFromDispID。通过DISPID找到PROPERTYDESC。 先获取vtable array(
struct VTABLEDESC*
),然后从property desc中找到对应的dispid。 - CBase::DefaultMembers。 调用Default将所有prop desc list的所有类成员设置为成员。
- CBase::GetEnumDescFromDispID。IPerPropertyBrowsing的辅助函数,从给定的dispid(property)中获取enumdesc。
- CBase::GetDisplayString。 IPerPropertyBrowsing::GetDisplayString,获取指定的dispid(property)对应的字符串值。
- CBase::MapPropertyToPage。IPerPropertyBrowsing::MapPropertyToPage的实现。
- CBase::GetPredefinedStrings。 IPerPropertyBrowsing::GetPredefinedStrings的实现。返回一个带字符数量的string array(也即LPOLESTRS指针),里面是支持的所有可用的dispid(property)。
- CBase::GetPredefinedValue。 IPerPropertyBrowsing::GetPredefinedValue的实现。从某个dispid返回关联到某个预定义字符串的variant。
- CBase::attachEvent。把一个AA_AttachEvent添加到attrarray里面,以支持多播onXXXX事件。
~
- CBase::detachEvent。遍历attrarray中的AA_AttachEvent,移除第一个发现的COM标识和传入的pDisp一样的项目。
- STDAPI MatchExactGetIDsOfNames。遍历ITypeInfo里面的所有property,导出函数,shdocvw.dll会使用。
- LONG GetArgsActual。 辅助函数,从dispparms的cArgs中获取准确的参数数量;如果有DISPID_THIS的话,数量减一。
1.5 总结:CBase
CBase类是mshtml.dll中提供的一个基础类,提供了许多功能函数的支持,同时也支持重要的事件操作(例如支持javascript中的addEventListener) ,之后还会看到很多与之有关联的类,本节到此结束。
引用
1
http://msdn.microsoft.com/en-us/library/aa912051.aspx
2
http://msdn.microsoft.com/en-us/library/sdwe79a4.aspx
3
http://msdn.microsoft.com/en-us/library/asd22sd4(v=vs.94).aspx
引申
expando
http://msdn.microsoft.com/en-us/library/ie/ms533747(v=vs.85).aspx
attachEvent
http://msdn.microsoft.com/en-us/library/ie/ms536343(v=vs.85).aspx
标记
~
wait for further analysis
property
可以简单的理解为dispid