吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4146|回复: 4
收起左侧

[原创] OD V1.10版本调试32位Uncoide程序无法下消息断点Bug原理分析和解决方案

[复制链接]
summon 发表于 2016-1-20 20:25
在kerui学软件逆向分析大半年了,我来发一份技术分析贴,为啥吾爱发个图这么难呢!!!
注:修复源码在帖子后面,附件当中也有一份
第一部分: 问题描述和简要分析
ODV1.10给32位Unicode版本的程序下消息断点时,弹出如下框

先提三个问题   
1、产生这个问题的原因是什么?
2、如何利用工具和实用的策略去分析它?
3、如何修复这个问题?
下面将围绕着这三个问题,展开分析和验证

如何做呢?基本策略如下
1、根据已有的事实和现象,提出假设和猜想
2、通过现有的跟踪技术,找到问题源,验证提出的假设和猜想
3、写程序修复这个问题
先来简单分析一下原因:
为了找到问题原因,我们先列一些事实(使用OD打开不同版本的程序,有Unicode版本的程序也和Ansi版本的程序)
1、Unicode版本程序的下消息断点会出错,而Ansi版本的程序下消息断点不会有问题。
2、OD软件是本身也是程序员写的,难免程序也会有BUG
通过以上两点信息,说明消息断点功能是完整的,至于为什么Unicode版本程序下消息断点出错,还不得而知,很有可能是程序BUG。另外注意到Unicode版本程序的下消息断点会出错,并弹出错误对话框,那么接下来能否通过这个错误对话框,追本求源,找到触发问题的事件源,就是揭开迷题的关键了。
如何根据触发错误点,找到一些关键的信息?
这里我们采用的策略是:

在弹框这个点下断点,利用栈回溯,找到弹出ffff05C9这个信息的来源函数


第二部分验证猜想的,找到问题源

1、当错误点触发时,使用spy++,查看弹框类型,可以看到注册窗口类型是#32770(Dialog),猜测是MessageBox弹出的框。
接下来将以MessageBox为分析的入口点,查找获得ffff05C9这个信息的源函数

利用OD函数返回和的栈回溯,始终跟踪存放ffff05C9值,我们很容易跟到上面的位置如下条指令,但还是未找到哪个函数给这个地址赋的值
00498D33  |. 8B43 24        MOV EAX,DWORD PTR DS:[EBX+24]            ;// 在这个关键代码处下软件写入断点([EBX+24]当中存放的值是ffff05C9)

说明还未到关键的位置,继续跟我们会发现,EBX是在Getsortedbyselection当中赋的值,并且是通过[004DE928 +11c]这个堆空间的值通过计算处到的,
细心的读者会发现,其实004DE928 是一个全局地址,如下代码所示:
004989A5  |. 50             PUSH EAX                                                                       ; /Arg2 => 00000000
004989A6  |. 68 28E94D00    PUSH OllyDbg.004DE928                             ; |Arg1 = 004DE928 ASCII "Table of windows"
004989AB  |. E8 2CCEFBFF    CALL OLLYDBG._Getsortedbyselection       ; \_Getsortedbyselection
分析Getsortedbyselection函数代码后,我们发现004DE928 +11c这个地址是个堆空间的地址,
我在这里并未想到其他的办法继续追踪,学过PE的朋友知道,全局地址是在exe加载时从文件当中读取的,并赋值到内存地址当中。
如果我们在OD刚刚加载应用程序未调试之前,到004DE928 地址下断点,就会有新的发现。


现在的问题变成了,跟踪004DE928 +11c这个堆地址什么时候初始化的问题了,我在这里的策略是:抢先在初始化堆之前下断点
断点已经命中,如下代码所示::[EBX+11C] = EAX
00454E11  |. 51             PUSH ECX                                              ; |Size
00454E12  |. 6A 00          PUSH 0                                                ; |Address = NULL
00454E14  |. E8 DFA30500    CALL <JMP.&KERNEL32.VirtualAlloc>                     ; \VirtualAlloc
00454E19  |. 8983 1C010000 MOV DWORD PTR DS:[EBX+11C],EAX
00454E1F  |. 85F6           TEST ESI,ESI                                          ;  OLLYDBG.00497D80
接下来通过在[EBX+11C] VirtualAlloc分配的区域的下访问断点
仔细查看初始化的堆空间,已经存在重要数据了,说明最关键的初始化call已经获得了ffff05C9,接下来继续通过栈回溯跟踪

通过层层返回,通过系统函数,到达用户层代码:最终发现是GetClassLongA 这个API获得的ffff05C9
查阅MSDN可以知道GetClassLong返回是注册窗口类的回调函数地址。分析不难发现ffff05C9这个回调函数地址是错误的。




为了增加说服力,我们拿Ansi版本的应用程序来做试验:
OD窗口当中列出的ClassProc地址是全是7xxx打头的如下图所示:


数据窗口查看其中一个75b1af93地址,确实是可执行代码,如下图所示:


于是我们得到这个样一个实事:当使用GetClassLongA获得Unicode版本的窗口回调函数地址时,返回值是ffff0xxx,其值是错误的
当使用GetClassLongA获得Ansi版本的窗口回调函数地址时,返回值是7xxx,其值是正常的。另外我们知道微软为API提供了Ansi版本的和Unicode版本的函数接口。
由上分析可得知,OD当中使用GetClassLongA获得去Unicode版本窗口至少不正常的,应该使用GetClassLongW去获得Unicode版本窗口。



为了证实上面的猜想接下来在OD当中手动修改00497B23这条代码,为将CALL GetClassLongA编辑为 CALL GetClassLongW。如下图所示:
为啥发个图这么难呢!!!
然后重新打开或者刷新OD的window窗口
如下所示:notepad的claproc地址不再是ffffxxxx打头的了。


OK,到此为止我们已经证实我们的猜想,并且基本上确认问题由于OD未检查应用程序窗口是Ansi版本还是Unicode版本,直接调用的GetClassLongA,导致不兼容Unicode版本的窗口应用程序


第三部分:解决方案
解决问题步骤:
1、程序当中需要判断窗口程序是Unicode版本还是Ansi版本的
2、然后根据返回值,调用GetWindowLongA或者GetWindowLongW
由上面步骤可知,该解决方案,需要添加代码较多,我们首先想到的是让程序在执行原GetWindowLongA函数之前,先跳(JMP)到我们修复程序当中,然后再确定是Ansi版本还是Unicode版本,再调用GetWindowLongW或者GetWindowLongA。
基于上面的思想,修复代码基本上确定了。


接下来是考虑注入方案了。
考虑通用性,首先可以排除使用另外一个注入程序来操作;其次加壳、修改PE之类注入也可以排除太麻烦了,这里我们选择OD的插件功能,完成注入。


要写OD插件,就要遵守 OllyDbg 所提供给插件开发者的可用接口规范
简单起见,这里只完成必须的两个接口函数就OK了。
1、ODBG_Plugindata(必需)仅返回一个字符串指针,该字符串就用于标识该插件的唯一名称。
2、ODBG_Plugininit(必需)该函数检查插件版本是否与当前OllyDbg对应,如果对应就执行函数中的其它操作,并执行其它回调函数,表示插件安装成功。





接下来创建简单版本动态链接库工程MessageInterFix:
MessageInterFix要完成如下两项工作:
1)、在DLL被加载时,执行对原程序的调整工作,让程序在执行原GetWindowLongA函数之前,先跳(JMP)到我们修复程序当中,然后再确定是Ansi版本还是Unicode版本,再调用GetWindowLongW或者GetWindowLongA。
2)、在DLL被卸载时,执行对原程序的恢复工作,保证DLL被卸载之后,主程序仍能正常运行。
本程序选择在IAT表挂勾,当然其他地方也可以。
说了那么多,该上代码了:

完整修复代码如下:

#include "stdafx.h"
#include "Plugin.h"
#pragma comment(lib,"OLLYDBG")
DWORD g_nOldpfn = 0;


extc int  _export cdecl ODBG_Plugindata(char shortname[32])
{
        strcpy(shortname,"MessageInterFix");
        return PLUGIN_VERSION;
}
extc int  _export cdecl ODBG_Plugininit(int ollydbgversion,HWND hw,
                                                                                ulong *features)
{
        if(ollydbgversion > PLUGIN_VERSION)
        {
                return -1;
        }
        return 0;
}


//************************************************************
// 函数名称:        FixClassLongProc
// 函数说明:        回调函数
// 作        者:        unsummon
// 时        间:        2016/1/19
// 返 回        值:        DWORD
//************************************************************
DWORD __stdcall FixClassLongProc(HWND hWnd,int nIndex)
{


        int nRetAddr;


        if (IsWindowUnicode(hWnd) == TRUE)
        {
                nRetAddr = GetClassLongW(hWnd,nIndex);
        }
        else
        {
                nRetAddr = GetClassLongA(hWnd,nIndex);
        }


        if (nRetAddr < 0)
        {
                nRetAddr = GetClassLongW(hWnd,nIndex);
        }


        return nRetAddr;
}


//************************************************************
// 函数名称:        SetHookClassLongA
// 函数说明:        挂勾函数
// 作        者:        unsummon
// 时        间:        2016/1/19
// 返 回        值:        BOOL
//************************************************************
BOOL SetHookClassLongA()
{
        DWORD nNewPfn = 0;
        DWORD nOriginalOffset = 0;
        DWORD OldProtect = 0;
        DWORD nTargetPfnAddr = *(DWORD *)(0x004af420 + 2);//nTargetPfnAddr 位置存放的是函数指针
        //保存原来函数指针
        memcpy(&g_nOldpfn,(DWORD *)nTargetPfnAddr, 4);
        nNewPfn = (DWORD)FixClassLongProc;
        //挂勾新的函数指针
        if(VirtualProtect((void*)nTargetPfnAddr,6,PAGE_EXECUTE_READWRITE,&OldProtect))
        {
                *(DWORD *)(nTargetPfnAddr) = nNewPfn;
                return VirtualProtect((void *)nTargetPfnAddr,6,OldProtect,&OldProtect);
        }


        return FALSE;
}


//************************************************************
// 函数名称:        UnSetHookClassLongA
// 函数说明:       
// 作        者:        unsummon
// 时        间:        2016/1/19
// 返 回        值:        BOOL
//************************************************************
BOOL UnSetHookClassLongA()
{
        //恢复原来函数指针
        DWORD OldProtect = 0;
        DWORD nTargetPfnAddr = *(DWORD *)(0x004af420 + 2);
        if(VirtualProtect((void*)nTargetPfnAddr,6,PAGE_EXECUTE_READWRITE,&OldProtect))
        {
                *(DWORD *)(nTargetPfnAddr) = g_nOldpfn;
                return VirtualProtect((void *)nTargetPfnAddr,6,OldProtect,&OldProtect);
        }


        return TRUE;
}
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                                         )
{
        switch(ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
                {
                        SetHookClassLongA();
                }
                break;
        case DLL_PROCESS_DETACH:
                {
                        UnSetHookClassLongA();
                }
                break;
        }
    return TRUE;
}







file:///D:/Users/Administrator/Documents/My%20Knowledge/temp/af53ed06-ce67-4bf6-becd-1bf1c2ae75a3.pngfile:///D:/Users/Administrator/Documents/My%20Knowledge/temp/af53ed06-ce67-4bf6-becd-1bf1c2ae75a3.pngfile:///D:/Users/Administrator/Documents/My%20Knowledge/temp/af53ed06-ce67-4bf6-becd-1bf1c2ae75a3.pngfile:///D:/Users/Administrator/Documents/My%20Knowledge/temp/af53ed06-ce67-4bf6-becd-1bf1c2ae75a3.png
file:///D:/Users/Administrator/Documents/My%20Knowledge/temp/af53ed06-ce67-4bf6-becd-1bf1c2ae75a3.png


发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

guoguo 发表于 2016-1-20 20:47
昨天在看雪也看了关于消息断点的问题,谢谢贴主
xiawan 发表于 2016-1-20 20:49
枫恋蓝点 发表于 2016-1-20 21:04
 楼主| summon 发表于 2016-1-20 21:05
我自己也是乱的,图片也没有贴上来,第一次发贴,没经验,望大家见谅。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-17 15:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表