首页
社区
课程
招聘
OllyHTML脚本详解(二)基本原理
发表于: 2006-9-6 01:06 6520

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期)

收藏
免费 7
支持
分享
最新回复 (3)
雪    币: 465
活跃值: (667)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
2
不错,辛苦了!如果能配上图,那就更好了。

个人感觉,是否和odbgscript完成的功能一样?

如果是的话,感觉好像没有odbgscript简洁。
如果不完全相同,那么ollyhtml的特色是什么啊?

另外执行速度如何?

个人所见
2006-9-6 06:49
0
雪    币: 1316
活跃值: (512)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
3
调试功能方面应该与odbgscript差不多,
特点应该主要是使用DHTML作脚本,可以有自己的界面以及一个灵活的javascript语言,在需要表达较复杂的算法时应该会更适用一些,
速度上不好说,我没有比较过:)
2006-9-6 09:42
0
雪    币: 257
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
编程功底好厉害,学习
2006-9-6 10:24
0
游客
登录 | 注册 方可回帖
返回
//