-
-
OllyHTML脚本详解(二)基本原理
-
发表于:
2006-9-6 01:06
6520
-
【文章标题】: OllyHTML脚本详解(二)基本原理
【文章作者】: dreaman
【作者邮箱】: dreaman_163@163.com
【作者主页】: http://dreaman.haolinju.net
【软件名称】: OllyHTML
【软件大小】: 800KB
【下载地址】: http://dreaman.haolinju.net
【加壳方式】: 无
【保护方式】: 无
【编写语言】: c++
【使用工具】: vc7.1
【操作平台】: win2000以上系统
【软件介绍】: Ollydbg的插件,用以支持用DHTML作脚本
【作者声明】: 个人觉得用DHTML作脚本是能做一些比较复杂的事情的,希望大家能用用:)
OllyHTML插件实际上是一个IE浏览器,我们利用IE对象模型的扩展能力扩充了DOM(文档对象模型),也就是加入了许多Ollydbg提供的功能,这样一来,
我们可以用网页里的脚本操作扩展DOM,也就是可以用脚本调用Ollydbg的功能了。
IE对象模型的扩充主要是通过window.external来引入,所以OllyHTML提供的功能都通过window.external来引入,我们将主要的功能
封装成对象,并使用一个根对象作为这些对象的容器,这个根对象通过window.external.Application来访问。在脚本的包含文件
const.js中我们定义了一个全局变量app:
var app=window.external.Application;
这样只要我们包含了这个文件,我们在脚本中就可以用app来访问插件提供的功能了。
这便是插件如何为脚本提供API的基本原理,至于每个具体功能对象,这是我们后面各部分要详细讲述的内容,这里就不提了。
一般说来,我们写的OllyHTML脚本有两种工作情形:
一、一种情形是脚本提供一个UI,当用户点击上面的功能按钮时执行对应的脚本,脚
本再调用插件提供的API,操纵Ollydbg执行某种行为或者从Ollydbg读取信息,这种方式一般用于静态分析;
二、另一种情形是Ollydbg调试程序时产生了调试事件,由插件调用脚本里的事件处理,在事件处理中操纵Ollydbg执行某种行为或者从Ollydbg读取信息
,这种方式用于动态调试,此时脚本所做的就像我们用快捷键或菜单命令控制Ollydbg设断点、单步执行、单步跟进等等,而调试事件的产生相当于Ollydbg
执行完了我们前面的一个操作。
下面我们分别就两种情形各看一个例子。
例1(静态分析):模糊查找反汇编指令
<html>
<head>
<title>模糊查找反汇编指令</title>
<script src="res://OllyHTML.dll/const.js"></script>
</head>
<body>
<table style="font-size:14px;width:100%;height:100%;border-collapse:collapse" cellspacing="0" cellpadding="0" align="center">
<tr>
<td>
<textarea id="resultArea" style="width:100%;height:100%;" rows="10" ondblclick="gotoAddr()" WRAP="off"></textarea>
</td>
</tr>
<tr style="height:24px">
<td align="center">
ADDR:<input type="text" id="addrId" value="00401000">
ASM CODE:<input type="text" id="asmCode" value="">
<input type="button" value="run" onclick="findCode()">
</td>
</tr>
</table>
<script>
//在用户双击结果编辑框里的地址时让Ollydbg跳到相应反汇编地址
function gotoAddr()
{
var txtRange=document.selection.createRange();//获取当前选中的文本范围
var val=parseInt(txtRange.text,16);//将选中的文本字符串转成整数
if(val>0x01000)
app.Analyser.Cpu(val,val,0);//让Ollydbg的CPU窗口的反汇编与DUMP窗格显示选中地址的内容
}
</script>
<script>
var addr=0;
//这个函数是DHTML window对象的事件onload的处理,这是DHTML中一种事件处理函数的写法(大多数
//OllyHTML脚本都需要使用这个事件处理)。
function window.onload()
{
var tinfo=app.Analyser.GetThreadInfo(app.CpuThreadId);//读取当前CPU窗口代码所属线程的线程信息
if(!tinfo)
{
return;
}
var minfo=app.Analyser.GetModuleInfo(tinfo.Entry);//读取当前线程入口点所在的模块的信息
var mi=app.Analyser.GetMemoryInfo(minfo.Base);//读取指定模块基地址所属内存段的信息(通常这是第一个段,也就是PE文件头)
mi=app.Analyser.GetMemoryInfo(mi.Base+mi.Size+1);//获取紧随第一个内存段的内存段的信息
addrId.value=window.external.IntToHex(mi.Base);//将第二个内存段的起始地址作为搜索的起始地址,初始化UI上的起始地址编辑框
}
//这个函数当用户点击UI上的run按钮时被调用
function findCode()
{
addr=window.external.HexToInt(addrId.value);//将16进制字符串转为整数,作为搜索的起始地址
var cond=app.StringFields.Disasm.Like(asmCode.value);//构造一个搜索条件,这是OllyHTML提供的用来构造搜索条件的
//Lambda表达式系列对象在后面的部分我们会详细介始它,现在我们不用深究
var lines=new Array();
for(;;)
{
var dasm=app.Analyser.FindDisasm2(addr,cond,true);//从指定地址开始搜索符合指定条件的汇编代码,忽略大小写(第三个参数是0.7.0.1版本的插件加入的,之前的版本没有这个参数)
if(!dasm)//找不到,退出
{
break;
}
lines.push(window.external.IntToHex(dasm.IP)+":"+dasm.Disasm);//将结果字符串添加到数组中,以供稍后一次性显示
addr=app.Analyser.GetNextOPAddr(dasm.IP,1);//取下一条指令地址
window.external.Title=""+lines.length;//用窗口标题栏显示进度信息,这只是个取巧的用法,也可以自己在UI提供一个
//状态显示控件
}
addrId.value=window.external.IntToHex(addr);
resultArea.value=lines.join("\r\n");//将所有搜索到的信息用回车换行分隔,然后显示到结果编辑框中
}
</script>
</body>
</html>
这个脚本比我们(一)中的Hello world要复杂一些,不过结构上是一样的,HTML HEAD里除标题不一样,内容是一样的,就是引用我们的脚本
常量资源文件。
HTML BODY里照例是UI与功能脚本两大块,UI部分仍然使用TABLE来布局,二行一列,第一行是我们用来显示结果的多行文本编辑框,第二行
我们放了两个文本框,一个按钮,分别用于输入起始地址、输入搜索代码以及执行搜索功能。
注:我们在多行文本框上添加了一个双击事件处理,用于当用户双击某个地址时让Ollydbg跳到相应地址以便于观察上下文代码,gotoAddr()这个函数
在我们的许多脚本里可以用上。
这个脚本在执行前我们应该先装入被调试程序,并且最好已经分析过代码,让CPU窗口显示主程序代码,这几乎是所有静态分析脚本在执行前的要求。
例2(动态调试):二次内存断点找OEP入口
动态调试代码与静态分析代码的主要差别有三处:
1、需要有一个允许调试事件的启动过程,这个步骤通过两个API调用实现:
app.EnableDebug();//允许脚本接收调试事件,也就是说之后插件会在产生调试事件时调用相应的window.OnPaused
//[或window.OnStep(这个一般不用,Ollydbg会周期性调用它),
//window.OnReset(打开新被调试程序或重启被调试程序时调用)]
app.Execution.StepInto();//单步跟进,这个主要是启动调试,也可以用app.Execution.StepOver()或app.Execution.ExceptionContinue()
//、app.Execution.Run();
2、提供一个调试事件处理,并在其中调用与Ollydbg进行调试会话的逻辑(就是判断当前是什么事件,然后依据不同情形让Ollydbg做点什么):
function window.OnPaused(reason,extdata,reg,debugEvent)
{
...
return 1;
}
这是事件处理的写法,其中的参数:
reason --- 产生事件的原因,将它与PP_MAIN相与可得:
PP_EVENT 表明是调试事件,此常量值为0
PP_PAUSE 表明是用户操作请求的暂停
PP_TERMINATED 表明是程序结束
对于调试事件,我们可以直接用下面常量值判断其类型:
PP_BYPROGRAM --- 由于程序倒致的调试事件,比如程序里的int3指令(不是我们设置的断点)
PP_INT3BREAK --- 我们设置的int3
PP_MEMBREAK --- 内存断点
PP_HWBREAK --- 硬件断点
PP_SINGLESTEP --- 单步
PP_EXCEPTION --- 异常,就是我们通常想要异常继续或看异常处理的那种异常
PP_ACCESS --- 访问异常,经常在C++程序里看到的"内存不可读,0xc0000005"应该就是此类了
PP_GUARDED --- 这个很特别,偶跟了一个Ollydbg,它的访问断点就是产生这个事件,我们可以用VirtualProtectEx改变一个内存段属性,加上
PAGE_GUARD|PAGE_EXECUTE_READWRITE标志,就会在访问到该内存时产生这个事件,而且这个事件是一个OneShot型的,只触发
一次,之后除非再次设定内存段属性,否则就不产生这个事件了(这个做法与OD访问断点是一样的实现方法)。
extdata --- 这个参数没有用处,不用理它
reg --- 产生调试事件时的寄存器情况,这是一个对象,我们在其它部分详细说明
debugEvent --- 调试事件信息,这也是一个对象,我们在其它部分详细说明
调试事件处理中只要我们处理了就都要返回1,否则返回0
3、在脚本结束时,禁用调试事件,就是调用API:
app.DisableDebug();//之后插件就不会再调用2中的处理了,这个是必须要执行的,否则,后面我们手工调试也会调2中的处理,会很难受的:)
好了,现在来看具体的脚本是什么样的:
<html>
<head>
<title>模拟手动跟踪</title>
<script src="res://OllyHTML.dll/const.js"></script>
</head>
<body>
<table style="font-size:14px;width:100%;height:100%;border-collapse:collapse" cellspacing="0" cellpadding="0" align="center">
<tr style="height:160px">
<td style="color:red" align="center">(执行脚本前请隐藏OD、清除已经设置的全部断点并重新启动被调试程序CTRL+F2)
</td>
</tr>
<tr style="height:24px">
<td align="center">
<input type="button" value="run" onclick="findOEP()">
</td>
</tr>
</table>
<script>
var codeAddr=0x00401000;
var codeSize=0x1000;
var dataAddr=0x00402000;
var dataSize=0x1000;
var bpCount=0;//当前遇到的内存断点次数
function findOEP()
{
var tinfo=app.Analyser.GetThreadInfo(app.CpuThreadId);
if(!tinfo)
{
alert("请先打开要调试的程序!");
return;
}
var minfo=app.Analyser.GetModuleInfo(tinfo.Entry);
var mbase=minfo.Base;
var mi=app.Analyser.GetMemoryInfo(minfo.Base);
mi=app.Analyser.GetMemoryInfo(mi.Base+mi.Size);//除PE头外第一个段,脚本认定这是目标代码段
codeAddr=mi.Base;
codeSize=mi.Size;
mi=app.Analyser.GetMemoryInfo(mi.Base+mi.Size);//第二个段,脚本认定这是目标数据段
dataAddr=mi.Base;
dataSize=mi.Size;
app.BreakPoint.OnMemoryWrite(dataAddr,dataSize);//设置数据段内存写断点
bpCount=0;
app.EnableDebug();//允许调试事件
app.Execution.ExceptionContinue();//异常继续,加壳程序用这个方法启动调试比较好一些,对停在异常的程序也能正常调试
}
//这个便是事件处理了
function window.OnPaused(reason,extdata,reg,debugEvent)
{
if(reason==PP_TERMINATED)//是程序终止吗?
{
app.BreakPoint.MemoryClear();//清除内存断点
app.DisableDebug();//禁止调试事件
alert("程序已经结束!");
return 1;
}
if(reason==PP_MEMBREAK)//是内存断点吗?
{
if(bpCount<1)//是第一次吧?
{
bpCount++;
app.BreakPoint.MemoryClear();//清除内存断点
app.BreakPoint.OnMemoryAccess(codeAddr,codeSize);//设置代码段内存读写断点
app.Execution.Run();//继续执行被调试程序
}
else//已经是第二次了!
{
app.BreakPoint.MemoryClear();//清除内存断点
app.DisableDebug();//禁止调试事件
alert("已经到达二次内存断点,OEP:"+window.external.IntToHex(reg.Eip)+"!");
}
}
else
{
app.Execution.ExceptionContinue();
}
return 1;
}
</script>
</body>
</html>
脚本的总体结构仍然是Hello world那样的结构,HTML HEAD部分还是引用一下常量资源文件,UI部分我们是二行一列的布局,第一行显示提示信息,
第二行是一个按钮,用于在用户点击时开始用二次内存断点法搜索OEP。
写教程真的好累啊,在此向长期以来一直为大家写作精彩教程的CCDebuger致敬!
--------------------------------------------------------------------------------
2006年09月06日 0:38:39
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)