这是用于idapro 4.9插件IdaHTML的脚本,完整代码可从
http://dreaman.haolinju.net/IdaHTML/ClassGraph.htm另存。
因为是脚本,不知道发在这里有没有违规,呵呵,不过内容确实是与逆向工程相关的,而且主要用于比较大的C++程序(有内部对象模型的那种了,比如游戏,不多说了,不清楚有用没用呢)。
脚本代码如下:(又改了一下)
<html xmlns:v>
<head>
<title>分析.rdata段的VTBL并据此构造类继承图</title>
<STYLE>
v\:*{behavior:url(#default#VML);}
</STYLE>
</head>
<body scroll="no" leftmargin="0" topmargin="0" rightmargin="0" bottommargin="0">
<table style="width:100%;height:100%">
<tr>
<td colspan="2">
<div style="width:100%;height:100%;overflow:scroll;" onmouseup="zoomGraph()">
<v:group id="flowGraph" style="position:relative;width:100%;height:100%" coordsize="500,300">
</v:group>
</div>
</td>
</tr>
<tr style="height:80px">
<td width="80px">
<select id="caList" style="width:100%;height:100%;" size="2" ondblclick="drawGraph()">
</select>
</td>
<td>
<textarea id="scpArea" style="width:100%;height:100%" WRAP="off">
</textarea>
</td>
</tr>
<tr style="height:16px">
<td colspan="2">
<input type="button" value="Run" onclick="eval(scpArea.value)">
<input type="button" value="Analysis" onclick="startAnalysis()">
<input type="button" value="LoadGraph" onclick="loadTxtGraph()">
</td>
</tr>
</table>
<script src="res://IdaHTML.plw/const.js"></script>
<script>
function ClassInfo()
{
this.level=0;
this.index=0;
this.funcs=new Array();
this.subClasses=new Object();
this.addFunc=function(f)
{
this.funcs.push(f);
}
this.addSubClass=function(c)
{
this.subClasses[c]=true;
}
this.delSubClass=function(c)
{
delete this.subClasses[c];
}
this.after=new Object();//被依赖类
this.addAfter=function(c)
{
this.after[c]=true;
}
this.haveAfter=function(c)
{
if(this.after[c])
return true;
return false;
}
}
function FuncInfo()
{
this.classes=new Array();
this.refCount=0;
this.callFuncs=new Array();
this.addClass=function(c)
{
this.classes.push(c);
this.refCount++;
}
this.addCallFunc=function(f)
{
this.callFuncs.push(f);
}
}
var classes=new Object();
var funcs=new Object();
//继承图相关的数据
var maxLevel=0,maxIndex=0;//当前图的最大层及最大索引,也就是图在二维平面上的高与宽的度量
var layerIndex=null;//图上每一层的当前最大索引,一个数组
var dx=0,dy=0;
var txtGraphs=new Object();
var curW=500,curH=300;
</script>
<script>
function window.onload()
{
scpArea.value="";
}
function getRoundRect(x,y)
{
return document.createElement("<v:roundrect style='position:relative;z-index:1;LEFT:"+(x-32)+";TOP:"+(y-9)+";width:64;height:18;' fillcolor='yellow'/>");
}
function getLine(x1,y1,x2,y2,rx,ry)
{
if(Math.abs((y2-y1)/(x2-x1))>ry/rx)
{
x1-=(x2-x1)*ry/(y2-y1);
y1-=ry;
x2+=(x2-x1)*ry/(y2-y1);
y2+=ry;
}
else if(x1<x2)
{
x1+=rx;
y1+=(y2-y1)*rx/(x2-x1);
x2-=rx;
y2-=(y2-y1)*rx/(x2-x1);
}
else
{
x1-=rx;
y1-=(y2-y1)*rx/(x2-x1);
x2+=rx;
y2+=(y2-y1)*rx/(x2-x1);
}
x1=Math.floor(x1);
y1=Math.floor(y1);
x2=Math.floor(x2);
y2=Math.floor(y2);
return document.createElement("<v:line style='position:relative;z-index:1;' from='"+x1+","+y1+"' to='"+x2+","+y2+"' />");
}
function getStroke()
{
return document.createElement("<v:stroke dashstyle='solid' endarrow='None' opacity='0.5' />");
}
function getText(x,y,text)
{
var r=document.createElement("<v:roundrect style='position:relative;z-index:1;left:"+(x-30)+";top:"+(y-7)+";width:60;height:14;font-size:14px' stroked='f' />");
var f=document.createElement("<v:fill type='frame' opacity='0.5' />");
r.appendChild(f);
var t=document.createElement("<v:textbox inset='1,1,1,1' />");
t.innerText=text;
r.appendChild(t);
return r;
}
function getX(p)
{
var lvl=classes
.level;
var maxIx=layerIndex[lvl];
var m=Math.floor(maxIx/2);
return Math.floor((maxIndex+1)/2*dx+(classes
.index-m)*dx);
}
function getY(p)
{
return Math.floor(dy*classes
.level)+10;
}
function zoomGraph()
{
if(!event.altKey)return;
if(event.button==1)
{
curW/=2;
curH/=2;
}
else if(event.button==2)
{
curW*=2;
curH*=2;
}
else
{
return;
}
flowGraph.coordSize=""+curW+","+curH;
}
function window.drawGraph()
{
if(caList.selectedIndex<0)
return;
var firstClass=window.external.HexToInt(caList.options[caList.selectedIndex].value);
var w=500;
var h=300;
//初始化流程绘制相关的数据
maxLevel=0;
maxIndex=0;
layerIndex=new Array();
flowGraph.innerHTML="";
scpArea.value=txtGraphs[firstClass];
//广度优先遍历确定节点层次与层序号
var line=new Array();//当前层
line.push(firstClass);
layerIndex.push(0);//第一层就一个节点,即首过程结点
maxLevel=0;
for(;;)
{
var tline=new Array();//下一层
for(var i=0;i<line.length;i++)
{
var a=line[i];
if(classes[a])
{
var ns=classes[a].subClasses;
var pix=classes[a].index;
var ct=0;
for(var j in ns)ct++;
var m=Math.floor(ct/2);
var ix=0;
for(var j in ns)
{
var tx=pix+ix-m;
if(maxIndex<tx)
maxIndex=tx;
ix++;
tline.push(j);
var cx=tline.length-1;
classes[j].index=(cx>tx ? cx : tx);
classes[j].level=maxLevel+1;
}
}
}
if(tline.length==0)
{
break;
}
else
{
if(maxIndex<tline.length-1)
maxIndex=tline.length-1;
layerIndex.push(maxIndex);//压入新发现的一层的最大层索引
maxLevel++;
}
line=tline;
}
//
dx=Math.floor(w/(maxIndex+2));
dy=Math.floor(h/(maxLevel+2));
//再次广度优先遍历绘制流程图
line=new Array();//当前层
line.push(firstClass);
//绘制初始节点
flowGraph.appendChild(getRoundRect(getX(firstClass),getY(firstClass)));
flowGraph.appendChild(getText(getX(firstClass),getY(firstClass),window.external.IntToHex(firstClass)));
for(;;)
{
var tline=new Array();//下一层
for(var i=0;i<line.length;i++)
{
var a=line[i];
if(classes[a])
{
var ns=classes[a].subClasses;
for(var j in ns)
{
tline.push(j);
//绘制节点
flowGraph.appendChild(getRoundRect(getX(j),getY(j)));
flowGraph.appendChild(getText(getX(j),getY(j),window.external.IntToHex(j)));
//绘制连线
var l=getLine(getX(j),getY(j),getX(a),getY(a),20,9);
l.appendChild(getStroke());
flowGraph.appendChild(l);
}
}
}
if(tline.length==0)
{
break;
}
line=tline;
}
}
function compare(a,b)
{
var al=classes[a].funcs.length;
var bl=classes.funcs.length;
if(al<bl)
return -1;
else if(al==bl)
{
if(classes[a].haveAfter(b))
return 1;
else if(classes.haveAfter(a))
return -1;
else
return 0;
}
else
return 1;
}
function decideSubClass(carray,ix)
{
var len=carray.length;
if(ix>=len-1)
return;
var c=carray[0];
var c1=carray[ix];
for(var ii=ix+1;ii<len;ii++)
{
if(carray[ii]==0)
continue;//空项目表明已经被添加为子类了
var c2=carray[ii];
if(classes[c1].haveAfter(c2))
{
classes[c].delSubClass(c2);
classes[c1].addSubClass(c2);
decideSubClass(carray,ii);
carray[ii]=0;
}
}
}
function iterateGraph(fp,c0,c,lvl)
{
c=parseInt(""+c,10);
for(var i=0;i<lvl;i++)
{
app.IdcApi.writestr(fp,"\t");
txtGraphs[c0]+="\t";
}
var info=app.IdcApi.form("%8.8X\r\n",c);
app.IdcApi.writestr(fp,info);
txtGraphs[c0]+=info;
for(var sc in classes[c].subClasses)
{
iterateGraph(fp,c0,sc,lvl+1);
}
}
function startAnalysis()
{
var path=app.IdcApi.AskFile(1,"classes.txt","请指定一个文件用于存储类分析的结果:");
if(!path)
return;
var a=app.IdcApi.FirstSeg();
var s=app.IdcApi.SegName(a);
while(s!=".rdata")
{
a=app.IdcApi.NextSeg(a);
s=app.IdcApi.SegName(a);
}
var e=app.IdcApi.NextSeg(a);
app.IdcApi.Message("找到.rdata段:%8.8X-%8.8X,现在开始分析可能的VTBL...\r\n",a,e);
var fp=app.IdcApi.fopen(path,"w");
app.IdcApi.writestr(fp,".classmember--------------------------------------------------\r\n");
var curClass=null;
for(var i=a;i<e;)
{
s=app.IdcApi.GetDisasm(i);
if(app.IdcApi.strstr(s,"dd offset")>=0)
{
//取出VTBL中的一项,查看是否函数
var v=app.IdcApi.Dword(i);
var f=app.IdcApi.GetFlags(v);
//查看是否有名称,有名称的表明有被引用,可能是VTBL的开始
var label=app.IdcApi.Name(i);
if(typeof(label)=="string" && label.length>0)//如果是RTTI或MFC动态类,则第一项可能不是函数
{
curClass=i;
classes[curClass]=new ClassInfo();
app.IdcApi.writestr(fp,app.IdcApi.form("class:%8.8X\r\n",curClass));
classes[curClass].addFunc(v);
if(!funcs[v])
{
funcs[v]=new FuncInfo();
}
funcs[v].addClass(curClass);
app.IdcApi.writestr(fp,app.IdcApi.form("=>member:%8.8X\r\n",v));
}
else if((f & MS_CLS)==FF_CODE && curClass)
{
classes[curClass].addFunc(v);
if(!funcs[v])
{
funcs[v]=new FuncInfo();
}
funcs[v].addClass(curClass);
app.IdcApi.writestr(fp,app.IdcApi.form("=>member:%8.8X\r\n",v));
}
else//不是VTBL成员
{
curClass=null;
}
}
i=app.IdcApi.ItemEnd(i);
}
//删除只有一个虚函数的类(通常这可能不是类)
var nullClasses=new Array();
for(var c in classes)
{
if(classes[c].funcs.length<=1)
nullClasses.push(c);
}
for(var i=0;i<nullClasses.length;i++)
{
delete classes[nullClasses[i]];
}
app.IdcApi.writestr(fp,".classarray---------------------------------------------------\r\n");
app.IdcApi.Message("VTBL分析完毕,现在分析可能的类簇...\r\n");
//分析类簇
var classesArray=new Array();
var accessed=new Object();
for(var c in classes)
{
c=parseInt(""+c,10);
if(!accessed[c])//发现一个新的类簇,并构造出此类簇
{
accessed[c]=true;
app.IdcApi.writestr(fp,app.IdcApi.form("class Array:\r\n"));
app.IdcApi.writestr(fp,app.IdcApi.form("=>%8.8X\t(起始类)\r\n",c));
var curClasses=new Array();
curClasses.push(c);
classesArray.push(curClasses);
var queue=new Array();
queue.push(c);
while(queue.length>0)
{
var cc=queue[0];
for(var i=0;i<classes[cc].funcs.length;i++)
{
var f=classes[cc].funcs[i];
for(var ii=0;ii<funcs[f].classes.length;ii++)
{
var fc=funcs[f].classes[ii];
if(!accessed[fc] && classes[fc])
{
app.IdcApi.writestr(fp,app.IdcApi.form("=>%8.8X\t(共用:%8.8X[%8.8X])\r\n",fc,cc,f));
accessed[fc]=true;
curClasses.push(fc);
queue.push(fc);
}
}
}
queue.shift();
}
}
}
app.IdcApi.writestr(fp,".callvfunc----------------------------------------------------\r\n");
app.IdcApi.Message("类簇分析完毕,现在开始分析虚函数调用关系...\r\n");
var fcount=0;
for(var f in funcs)
{
/*
if(fcount>=100)
{
break;
}
fcount++;
*/
f=parseInt(""+f,10);
var st=f;
var ed=app.IdcApi.FindFuncEnd(f);
for(var i=st;i<ed;)
{
for(var x=app.IdcApi.Rfirst(i);x!=BADADDR;x=app.IdcApi.Rnext(i,x))
{
var xt=app.IdcApi.XrefType();
if(xt == fl_CN && funcs[x])
{
funcs[f].addCallFunc(x);
app.IdcApi.writestr(fp,app.IdcApi.form("%8.8X=>%8.8X\r\n",f,x));
}
}
i=app.IdcApi.ItemEnd(i);
}
}
app.IdcApi.Message("函数调用关系分析完毕,现在据此构造类的依赖关系...\r\n");
for(var f in funcs)
{
for(var i=0;i<funcs[f].callFuncs.length;i++)
{
var cf=funcs[f].callFuncs[i];
for(var ii=0;ii<funcs[cf].classes.length;ii++)
{
var cc=funcs[cf].classes[ii];
for(var iii=0;iii<funcs[f].classes.length;iii++)
{
var c=funcs[f].classes[iii];
classes[cc].addAfter(c);
}
}
}
}
app.IdcApi.writestr(fp,".classorder---------------------------------------------------\r\n");
app.IdcApi.Message("类簇分析完毕,挑选VTBL尺寸最小的类作基类...\r\n");
//对每个类簇的类按VTBL的大小排序
for(var i=0;i<classesArray.length;i++)
{
var ca=classesArray[i];
ca=ca.sort(compare);
classesArray[i]=ca;
app.IdcApi.writestr(fp,"class inherit order:\r\n");
for(var ii=0;ii<ca.length;ii++)
{
app.IdcApi.writestr(fp,app.IdcApi.form("=>%8.8X size:%d\r\n",ca[ii],classes[ca[ii]].funcs.length));
}
}
//每簇类以最小尺寸的类作基类
for(var i=0;i<classesArray.length;i++)
{
var ca=classesArray[i];
for(var ii=1;ii<ca.length;ii++)
{
classes[ca[0]].addSubClass(ca[ii]);
}
}
app.IdcApi.Message("类簇排序完毕,依据依赖细化继承关系...\r\n");
for(var i=0;i<classesArray.length;i++)
{
var ca=classesArray[i];
decideSubClass(ca,1);
}
app.IdcApi.writestr(fp,".graph--------------------------------------------------------\r\n");
app.IdcApi.Message("分析完毕,现在输出继承关系描述...\r\n");
caList.size=classesArray.length;
for(var i=0;i<classesArray.length;i++)
{
var ca=classesArray[i];
var c=ca[0];
if(ca.length>=2)
{
var opt=document.createElement("option");
opt.text=window.external.IntToHex(c);
opt.value=window.external.IntToHex(c);
caList.add(opt);
}
txtGraphs[c]="";
iterateGraph(fp,c,c,0);
}
app.IdcApi.Message("全部分析完成!\r\n");
app.IdcApi.fclose(fp);
}
function loadClass(c,addr,lines,ix,lvl)
{
for(var ii=ix;ii<lines.length;)
{
var line=lines[ii];
for(var i=0;i<line.length;i++)
{
if(line.charAt(i)!="\t")
break;
}
if(i<=lvl)
return ii;
else
{
var ca=window.external.HexToInt(line.substr(i));
classes[ca]=new ClassInfo();
classes[addr].addSubClass(ca);
txtGraphs[c]+=line;
ii=loadClass(c,ca,lines,ii+1,i);
}
}
}
function loadTxtGraph()
{
//目前只装入绘制类继承图相关的信息
var path=app.IdcApi.AskFile(0,"classes.txt","请指定存储了类分析的结果的文本文件");
if(!path)
return;
classes=new Object();//清空类信息
txtGraphs=new Object();
while(caList.options.length>0)
caList.options.remove(0);
app.IdcApi.Message("装入开始...\r\n");
var content=window.external.Setup.ReadTxtFile(path);//将文本文件内容读到字符串中
var lines=content.split("\r\n");
for(var i=lines.length-1;i>=0;i--)
{
if(lines[i].indexOf(".graph")==0)
break;
}
for(var j=i+1;j<lines.length;)
{
var addr=window.external.HexToInt(lines[j]);
classes[addr]=new ClassInfo();
txtGraphs[addr]=lines[j];
var nj=loadClass(addr,addr,lines,j+1,0);
if(nj>j+1)
{
var opt=document.createElement("option");
opt.text=lines[j];
opt.value=lines[j];
caList.add(opt);
}
j=nj;
}
app.IdcApi.Message("装入完成!\r\n");
}
</script>
</body>
</html>
主要分析思路:
1、在.rdata段中查找可能的VTBL,并构造相应的类与成员函数的对应关系;
2、VTBL有公共函数的认为是同一簇的类(不一定正确);
3、分析虚函数间的调用关系,据此构造类的依赖;
4、对2中得到的同一簇的类按VTBL大小与依赖关系排序;
5、根据4中顺序与依赖关系构造继承图(只是参考,不完全正确);
6、用VML绘制类继承图(双击分析结果列表中的某行即绘制)。
[课程]FART 脱壳王!加量不加价!FART作者讲授!