[转载]COM之ActiveX逆向简单方法
文章作者:剑心信息来源:[url]http://www.loveshell.net/[/url]
原始出处:[url]http://www.loveshell.net/blog/blogview.asp?logID=228[/url]
一.前言.
以下IE中ActiveX控件中对调用的属性,方法等的一些逆向分析纪录文字.
阅读此文你许要一点点的com基础知识.:)
二.认识ActiveX
IE中可以调用几乎所有在HKEY_CLASSES_ROOT\CLSID键下的Object.
调用方式很简单:
<html>
<OBJECT ID="com" CLASSID="CLSID:{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx}"></OBJECT>
</html>
这样会带来一些安全问题. 比如最近很多的 com 创建实例初始化的漏洞公告. 这里不对这些进行研究.
对于这些COM组件的属性(property)和方法(method), 我们可以通过script来调用.
比如, 对上述组件的属性,假设是test,方法(假设是methodtest),参数是BSTR类型的:
<script>
var s;
//赋值(com的IDL解释为propput):
com.test = "AAAAAAAAAAAAA";
//获取值(com的IDL解释为propget):
s = com.test;
com.methodtest(s);
</script>
很简单. 但是也因为script的原因, 对一些复杂的变量,比如COM中的VARIANT类型变量, 就很难调用起来了.
并不是每个com都可以通过script调用. 一般说来,只有com 组件的interface继承了 IDispatch 接口才可以被
script调用(有些COM书籍上称之为动态调用).
IE调用这些com的时候,如果没有被标记"safe call",那么在client浏览的时候,就会有一个提示"acticvex组建
调用是否安全"之类的对话框, 当然,这个和浏览器的安全区域设置有关系.
关于ActiveX安全可以看这里:[url]http://msdn.microsoft.com/workshop/components/activex/security.asp[/url]
三.认识TypeLib
现在假设我们知道有哪些com可以被我们调用了而且client端不会有提示了,比如win2k sp4 IE6 sp1中的
[+] {71650000-E8A8-11D2-9652-00C04FC30871}:
首先我们应该要知道的是它是由哪个文件提供的.
到HKEY_CLASSES_ROOT\CLSID下的{71650000-E8A8-11D2-9652-00C04FC30871}找InprocServer32键的default value:
它所对应的DLL是:G:\WINNT\system32\webvw.dll
这个时候你就可以IDA打开它进行分析了.
然后我们需要知道它的typelib是由谁提供的:
HKEY_CLASSES_ROOT\CLSID\{71650000-E8A8-11D2-9652-00C04FC30871}\TypeLib:
{cd603fc0-1f11-11d1-9e88-00c04fdcab92}
再转到注册表:
HKEY_CLASSES_ROOT\TypeLib\{CD603FC0-1F11-11D1-9E88-00C04FDCAB92}
下就有对应版本的typelib提供者:
HKEY_CLASSES_ROOT\TypeLib\{CD603FC0-1F11-11D1-9E88-00C04FDCAB92}\1.0\0\win32
default value:
G:\WINNT\system32\webvw.dll
四. 获取注册类
下一步就是得到接口描述了.
用oleview打开该DLL(使用File->View TypeLib打开,也可以使用LoadTypeLib()获得一个ITypeLib指针自己获取).
得到三个coclass:
[
uuid(71650000-E8A8-11D2-9652-00C04FC30871),
helpstring("ThumbCtl Class")
]
coclass ThumbCtl {
[default] interface IThumbCtl;
[default, source] dispinterface DThumbCtlEvents;
};
[
uuid(BCFD624E-705A-11D2-A2AF-00C04FC30871),
helpstring("WebView Class")
]
coclass WebView {
[default] interface IWebView;
};
[
uuid(844F4806-E8A8-11D2-9652-00C04FC30871),
helpstring("WebViewFolderIcon Class")
]
coclass WebViewFolderIcon {
[default] interface IWebViewFolderIcon2;
};
我们得到我们所需要的ThumbCtl 注册类, 它有两个接口, 注意的是,我们用script只能
调用默认的一个Interface.
我们列举一下IThumbCtl接口:
[
uuid(E8ACCAE0-23E6-11D1-9E88-00C04FDCAB92),
helpstring("IThumbCtl Interface"),
dual
]
dispinterface IThumbCtl {
properties:
methods:
[id(0x00000001), helpstring("method displayFile")]
VARIANT_BOOL displayFile(BSTR bsFileName);
[id(0x00000002), helpstring("method haveThumbnail")]
VARIANT_BOOL haveThumbnail();
[id(0x00000003), propget, helpstring("property freeSpace")]
BSTR freeSpace();
[id(0x00000004), propget, helpstring("property usedSpace")]
BSTR usedSpace();
[id(0x00000005), propget, helpstring("property totalSpace")]
BSTR totalSpace();
};
可以看到的是, IThumbCtl 接口提供两个方法(displayFile(),haveThumbnail()),两个属性:
freeSpace(),usedSpace(),totalSpace();
好,我们已经知道在script中可以调用该控件的方法和属性了. 下一步我们需要的就是将
这些函数和我们的反汇编结果联系起来, 我们需要知道哪个函数是负责处理displayFile,
哪些函数是负责其他方法和属性的.
根据com的规则,对于赋值的属性处理函数,名称默认前面都要加上get_的名称,而对于取值的属性处理函数来说,
名称默认都要加上put_的名称,也就是说,我们需要为我们的汇编代码重新指定:
displayFile(),haveThumbnail(),get_freeSpace(),get_usedSpace(),get_totalSpace()函数名称.
因为 提供接口的类是继承 IDispatch 的,所以还要指定如下的名称:
HRESULT _stdcall GetTypeInfoCount([out] unsigned int* pctinfo);
HRESULT _stdcall GetTypeInfo(
[in] unsigned int itinfo,
[in] unsigned long lcid,
[out] void** pptinfo);
HRESULT _stdcall GetIDsOfNames(
[in] GUID* riid,
[in] char** rgszNames,
[in] unsigned int cNames,
[in] unsigned long lcid,
[out] long* rgdispid);
HRESULT _stdcall Invoke(
[in] long dispidMember,
[in] GUID* riid,
[in] unsigned long lcid,
[in] unsigned short wFlags,
[in] DISPPARAMS* pdispparams,
[out] VARIANT* pvarResult,
[out] EXCEPINFO* pexcepinfo,
[out] unsigned int* puArgErr);
同时,IDispatch又是继承 IUnknown 的,所以还有:
HRESULT _stdcall QueryInterface([in] GUID* riid,[out] void** ppvObj);
unsigned long _stdcall AddRef();
unsigned long _stdcall Release();
计算一下,我们共有 5 + 4 + 3 = 12个函数名称需要指定.
这里有一个关键的数字 12 , 同时也意味着我们的vftable的大小也是12个.
接口取完了,剩下就是处理我们的汇编代码了.
五. 麻烦的vftable
当IE调用一个object的时候,首先要调用实现object的dll/ocx 输出的 DllGetClassObject 函数.
该函数原形如下:
STDAPI DllGetClassObject(REFCLSID rclsid,REFIID riid,LPVOID * ppv);
ppv是一个指向IClassFactory的指针. 通过调用IClassFactory::createInstance()来获取所需要的
类.
下面就是createInstance调用原形:
HRESULT createInstance(IUnknown * pUnkOuter,REFIID riid,void ** ppvObject);
这次返回的ppvObject就是一个指向包含我们前面所说的12个函数的vftable的指针.
这句话比较难理解,图示如下.
[ecx]
ppvObject -> class -> [ecx] -> vftable1
[ecx+4] -> vftable2
[ecx+8] -> vftable3
.....
现在我们有了这个ppvObject,那么我们怎么确定我们所要找的vftable是vftable1还是vftable2或者是vftable3呢?
我们有两个方法:
(1).这个时候我们就要看汇编中返回 ppvObject 后,立即调用的是谁的 QueryInterface().
比如,webvw.dll中:
.text:658FDCC3 call ??0?$CComObject@VCThumbCtl@@@ATL@@QAE@PAX@Z ; ATL::CComObject<CThumbCtl>::CComObject<CThumbCtl>(void *)
.text:658FDCC8 mov esi, eax
.text:658FDCCA jmp short loc_658FDCCE
.text:658FDCCC ; ----------------------------------------------------------------------------
.text:658FDCCC
.text:658FDCCC loc_658FDCCC: ; CODE XREF: ATL::CComCreator<ATL::CComObject<CThumbCtl>>::createInstance(void *,_GUID const &,void * *)+14j
.text:658FDCCC xor esi, esi
.text:658FDCCE
.text:658FDCCE loc_658FDCCE: ; CODE XREF: ATL::CComCreator<ATL::CComObject<CThumbCtl>>::createInstance(void *,_GUID const &,void * *)+23j
.text:658FDCCE test esi, esi
.text:658FDCD0 jz short loc_658FDCF7
.text:658FDCD2 push [esp+arg_8]
.text:658FDCD6 mov ecx, [esi+60h]
.text:658FDCD9 lea eax, [esi+60h]
.text:658FDCDC push [esp+4+arg_4]
.text:658FDCE0 push eax
.text:658FDCE1 call dword ptr [ecx]
看到这个[esi+60h]吗? 这里的地址就是我们要找的vftable.
(2). 数个数.
看看每个vftable中的个数是否相等. 这个方法比较笨. 相等就认为是我们要找的,如果刚好有多个相等,那么就
需要一一分析.
好,现在我们已经得到了我们所要的vftable,我们给他们一一更名吧.
顺便说一句, oleview返回的接口定义里面的函数名称是按顺序的,也就是说displayFile()排在haveThumbnail()
前面,那么在vtable中,displayFile函数地址同样写在haveThumbnail所在位置前面.
按顺序我们得到:
VARIANT_BOOL displayFile(BSTR bsFileName);
VARIANT_BOOL haveThumbnail();
BSTR get_freeSpace();
BSTR get_usedSpace();
BSTR get_totalSpace();
HRESULT _stdcall GetTypeInfoCount([out] unsigned int* pctinfo);
HRESULT _stdcall GetTypeInfo(
[in] unsigned int itinfo,
[in] unsigned long lcid,
[out] void** pptinfo);
HRESULT _stdcall GetIDsOfNames(
[in] GUID* riid,
[in] char** rgszNames,
[in] unsigned int cNames,
[in] unsigned long lcid,
[out] long* rgdispid);
HRESULT _stdcall Invoke(
[in] long dispidMember,
[in] GUID* riid,
[in] unsigned long lcid,
[in] unsigned short wFlags,
[in] DISPPARAMS* pdispparams,
[out] VARIANT* pvarResult,
[out] EXCEPINFO* pexcepinfo,
[out] unsigned int* puArgErr);
HRESULT _stdcall QueryInterface([in] GUID* riid,[out] void** ppvObj);
unsigned long _stdcall AddRef();
unsigned long _stdcall Release();
写个idc脚本来处理吧:)
六. 参考:
1. MSDN
2. O'Reilly <<dcom 入门>>
七.附件
附上一个perl自动生成idc的程序:
#!/usr/bin/perl
$argc=@ARGV;
if($argc<1)
{
print "Usage:$0 <idl file>\n";
exit;
}
$sfile = $ARGV[0];
$dfileprv = "webvw"; #idc脚本文件名前缀,可以留空
$StartAddr = 0x658F1F08; #第一个函数的地址
$EndAddr = 0x658F1F34; #最后一个函数的地址
open(FILE,"<$sfile") || die "Cannot open $sfile erorr:$!\n";
my @FileContent = <FILE>;
close(FILE);
my $FileLineNumber = @FileContent;
my ($FunctionName,$InterFaceName,$dfilename,$i,$startline,$endline,$FunctionPrevName,$FunctionDescribe,$FunctionString);
my $FunctionNumber = 0;
my @FunctionNameArray;
my $addr = 0;
$subfunction="static MakeAddrName(ea,str)\n";
$subfunction.="{\n\t auto addr;\n\n";
$subfunction.="\t addr = Dword(ea);\n";
$subfunction.="\t if(MakeNameEx(addr,str,SN_NON_PUBLIC | SN_NOWARN) == 0)\n";
$subfunction.="\t {\n\t\t Message(\"MakeName value(0x%x):0x%x to %s Failed.\",ea,addr,str);\n";
$subfunction.="\t }\n}";
for($i=0;$i<$FileLineNumber;$i++)
{
$tvar = $FileContent[$i];
chomp $tvar;
#print "$tvar\n";
if($tvar=~/interface\s+(\w+)/i)
{
#interface IActiveMovie : IDispatch {
$InterFaceName = $1;
print "Found interface $InterFaceName\n";
$dfilename = $dfileprv."_$InterFaceName.idc";
#last;
for($startline = $i+1;$startline < $FileLineNumber;$startline ++)
{
$svar = $FileContent[$startline];
chomp $svar;
#interface描述结束符号
if($svar=~/^\s{0,}\}\s{0,}\;/)
{
last;
}
#print "$svar\n";
#$svar=" [id(0xfffffdd8)]";
if($svar=~/^\s{0,}\[(.*?)\]\s{0,}(.*)\s{0,}$/)
{
$FunctionDescribe = $1;
my $StrAfterFunctionDescribe = $2;
#print "FunctionDescribe:$FunctionDescribe,StrAfterFunctionDescribe:$StrAfterFunctionDescribe\n";
#属性函数名称分辨
if($FunctionDescribe=~/propget/)
{
$FunctionPrevName="get_";
}
elsif($FunctionDescribe=~/propput/)
{
$FunctionPrevName="put_";
}
else
{
$FunctionPrevName="";
}
#看是一行就写完的吗
if($StrAfterFunctionDescribe=~/\;/) #开始寻找函数名称
{
$FunctionString = $StrAfterFunctionDescribe;
}
else
{
#函数名字在第二行
$startline++;
#print "$FileContent[$startline]\n";
$FunctionString = $StrAfterFunctionDescribe.$FileContent[$startline];
}
chomp $FunctionString;
#print "FunctionString:$FunctionString\n";
if($FunctionString=~/\s{0,}(\w+?)\(/)
{
$FunctionName = $1;
$FunctionName = $FunctionPrevName.$FunctionName;
push @FunctionNameArray,$FunctionName;
#print "Found Function:$FunctionName\n";
$FunctionNumber++;
}
#print "found id:$1\n";
}
#last;
}
}
}
printf "Total %d+7=%d Functions\n",$FunctionNumber,$FunctionNumber+7;
#检查要修改的和得出的函数个数是否符合
$WantNamedTableCount = ($EndAddr - $StartAddr)/4 + 1;
if($WantNamedTableCount != ($FunctionNumber + 7))
{
print "Wanted $WantNamedTableCount functions,NOT MATCH...\n";
print "Continue...?(y/n)";
$choice = <STDIN>;
chomp $choice;
if($choice ne "y")
{
exit;
}
}
$addr = $StartAddr;
#success MakeNameEx (long ea,string name,long flags);
open(DFILE,">$dfilename") || die "Cannot open $dfilename For write:$!\n";
print DFILE "//By bkbll from ActiveX reverse\r\n";
print DFILE "#include <idc.idc>\n\n";
print DFILE $subfunction;
print DFILE "\n";
print DFILE "static main()\n{\n";
printf DFILE " MakeAddrName(0x%x,\"%s::QueryInterFace\");\n",$addr,${InterFaceName};
$addr+=4;
printf DFILE " MakeAddrName(0x%x,\"%s::Addref\");\n",$addr,${InterFaceName};
$addr+=4;
printf DFILE " MakeAddrName(0x%x,\"%s::Release\");\n",$addr,${InterFaceName};
$addr+=4;
printf DFILE " MakeAddrName(0x%x,\"%s::GetTypeInfoCount\");\n",$addr,${InterFaceName};
$addr+=4;
printf DFILE " MakeAddrName(0x%x,\"%s::GetTypeInfo\");\n",$addr,${InterFaceName};
$addr+=4;
printf DFILE " MakeAddrName(0x%x,\"%s::GetIDsOfNames\");\n",$addr,${InterFaceName};
$addr+=4;
printf DFILE " MakeAddrName(0x%x,\"%s::Invoke\");\n",$addr,${InterFaceName};
$addr+=4;
for($addr,$i= 0 ;$addr<=$EndAddr;$addr+=4,$i++)
{
printf DFILE " MakeAddrName(0x%x,\"%s::$FunctionNameArray[$i]\");\n",$addr,${InterFaceName};
}
print DFILE "}\n";
close DFILE;
页:
[1]
