首页
社区
课程
招聘
[原创]第一阶段◇第三题
发表于: 2008-10-8 17:06 2820

[原创]第一阶段◇第三题

2008-10-8 17:06
2820
用OD加载程序,加载Map文件,运行程序,点击"Crash",弹出一个错误对话框,
点确定,程序终止,此时看Alt+K,查看调用堆栈:

调用堆栈:     主线程
地址       堆栈       函数过程 / 参数                       调用来自                        结构
0012F1A4   6F029C6F   包含ntdll.KiFastSystemCallRet           SYSFER.6F029C6D                 0012F2A0
0012F1A8   7C81CD96   包含SYSFER.6F029C6F                     kernel32.7C81CD94               0012F2A0
0012F2A4   7C81CDEE   ? kernel32.7C81CD34                   kernel32.7C81CDE9               0012F2A0
0012F2B8   0040F55A   ? kernel32.ExitProcess                TestFloa.___crtExitProcess+0E   0012F2B4
0012F2BC   000000FF     ExitCode = FF
0012F2C0   0040F774   ? <TestFloa.___crtExitProcess>        TestFloa.0040F76F
0012F2FC   0040F7AA   ? TestFloa.0040F6AA                   TestFloa.__exit+8
0012F30C   0040F51C   可能 <TestFloa.__exit>                  TestFloa.__amsg_exit+1E         0012F3D0
0012F310   000000FF     status = FF (255.)
0012F31C   0041AE26   <TestFloa.__amsg_exit>                TestFloa.0041AE21               0012F3D0
0012F324   00413E10   包含TestFloa.0041AE26                   TestFloa.__woutput_l+59D        0012F3D0
0012F7C8   0040EE90   ? <TestFloa.__woutput_l>              TestFloa.__snwprintf+89         0012F3D0
0012F80C   00420D86   <TestFloa.__snwprintf>                TestFloa.?OnBnClickedBtnCrash@  0012F808
0012F8BC   00402802   <TestFloa.?OnBnClickedBtnCrash@CTest  TestFloa.?_AfxDispatchCmdMsg@@  0012F8B8
0012F8CC   00402A0F   <TestFloa.?_AfxDispatchCmdMsg@@YGHPA  TestFloa.?OnCmdMsg@CCmdTarget@  0012F8C8
0012F8FC   0040300C   <TestFloa.?OnCmdMsg@CCmdTarget@@UAEH  TestFloa.?OnCmdMsg@CDialog@@UA  0012F8F8
0012F920   00407F23   <TestFloa.?OnCmdMsg@CDialog@@UAEHIHP  TestFloa.?OnCommand@CWnd@@MAEH  0012F91C
0012F970   00408937   可能 <TestFloa.?OnCommand@CWnd@@MAEH    TestFloa.?OnWndMsg@CWnd@@MAEHI  0012F96C


由调用栈可知程序是在TestFloa.__woutput_l+59D处挂掉的

重新打开程序,在_woutput_l程序中进行跟踪:

00413DDB  |> \8B03          |mov     eax, dword ptr [ebx]
00413DDD  |.  83C3 08       |add     ebx, 8
00413DE0  |.  8945 88       |mov     dword ptr [ebp-78], eax
00413DE3  |.  8B43 FC       |mov     eax, dword ptr [ebx-4]
00413DE6  |.  8945 8C       |mov     dword ptr [ebp-74], eax
00413DE9  |.  8D45 9C       |lea     eax, dword ptr [ebp-64]
00413DEC  |.  50            |push    eax
00413DED  |.  FF75 94       |push    dword ptr [ebp-6C]
00413DF0  |.  0FBEC2        |movsx   eax, dl
00413DF3  |.  FF75 E8       |push    dword ptr [ebp-18]
00413DF6  |.  895D D8       |mov     dword ptr [ebp-28], ebx
00413DF9  |.  50            |push    eax
00413DFA  |.  FF75 E0       |push    dword ptr [ebp-20]
00413DFD  |.  8D45 88       |lea     eax, dword ptr [ebp-78]
00413E00  |.  56            |push    esi
00413E01  |.  50            |push    eax
00413E02  |.  FF35 C8A04200 |push    dword ptr [42A0C8]
00413E08  |.  E8 3FEFFFFF   |call    <__decode_pointer>
00413E0D  |.  59            |pop     ecx
00413E0E  |.  FFD0          |call    eax
00413E10  |.  8B5D EC       |mov     ebx, dword ptr [ebp-14]
00413E13  |.  83C4 1C       |add     esp, 1C


发现当调用__decode_pointer的后得到一个函数指针_fptrap,然后call eax会调用这个函数,而这
个函数是一个陷阱函数,会报出相应的错误,我们打开VC的crt代码来看看

先看看_woutput_l函数,找到处理格式化“%f”的代码,如下:

            case _T('e'):
            case _T('f'):
            case _T('g'):
            case _T('a'): {
                /* floating point conversion -- we call cfltcvt routines */
                /* to do the work for us.                                */
                flags |= FL_SIGNED;         /* floating point is signed conversion */
#ifdef POSITIONAL_PARAMETERS
                if((format_type == FMT_TYPE_POSITIONAL) && (pass == FORMAT_POSSCAN_PASS))
                {
                    _VALIDATE_RETURN(((type_pos>=0) && (type_pos<_ARGMAX)), EINVAL, -1);

#if !LONGDOUBLE_IS_DOUBLE

                    if (flags & FL_LONGDOUBLE)
                    {
                        STORE_ARGPTR(pos_value, e_longdouble_arg, type_pos, ch, flags)
                    }
                    else
#endif  /* !LONGDOUBLE_IS_DOUBLE */
                    {
                        STORE_ARGPTR(pos_value, e_double_arg, type_pos, ch, flags)
                    }

                    break;
                }
#endif  /* POSITIONAL_PARAMETERS */
                text.sz = buffer.sz;        /* put result in buffer */
                buffersize = BUFFERSIZE;

                /* compute the precision value */
                if (precision < 0)
                    precision = 6;          /* default precision: 6 */

由于precision的初始值为-1,所以程序会把它设为一个默认精度6,然后再往下执行

                    tmp=va_arg(argptr, _CRT_DOUBLE);
00406B77  mov         ecx,dword ptr [ebp+14h] 
00406B7A  add         ecx,8 
00406B7D  mov         dword ptr [ebp+14h],ecx 
00406B80  mov         edx,dword ptr [ebp+14h] 
00406B83  mov         eax,dword ptr [edx-8] 
00406B86  mov         ecx,dword ptr [edx-4] 
00406B89  mov         dword ptr [tmp],eax 
00406B8F  mov         dword ptr [ebp-48Ch],ecx 
#ifdef POSITIONAL_PARAMETERS
                    }
                    else
                    {
                        /* Will get here only for pass == FORMAT_OUTPUT_PASS because
                        pass == FORMAT_POSSCAN_PASS has a break Above */
                        va_list tmp_arg;

                        _VALIDATE_RETURN(((type_pos>=0) && (type_pos<_ARGMAX)), EINVAL, -1);

                                                _ASSERTE(pass == FORMAT_OUTPUT_PASS);
                        tmp_arg = pos_value[type_pos].arg_ptr;
                        tmp=va_arg(tmp_arg, _CRT_DOUBLE);
                    }
#endif  /* POSITIONAL_PARAMETERS */
                    /* Note: assumes ch is in ASCII range */
                    /* In safecrt, we provide a special version of _cfltcvt which internally calls printf (see safecrt_output_s.c) */
#ifndef _SAFECRT_IMPL
                    _cfltcvt_l(&tmp.x, text.sz, buffersize, (char)ch, precision, capexp, _loc_update.GetLocaleT());
00406B95  lea         ecx,[ebp-40h] 
00406B98  call        _LocaleUpdate::GetLocaleT (404F60h) 
00406B9D  push        eax  
00406B9E  mov         edx,dword ptr [ebp-2Ch] 
00406BA1  push        edx  
00406BA2  mov         eax,dword ptr [ebp-30h] 
00406BA5  push        eax  
00406BA6  movsx       ecx,byte ptr [ebp-454h] 
00406BAD  push        ecx  
00406BAE  mov         edx,dword ptr [ebp-44h] 
00406BB1  push        edx  
00406BB2  mov         eax,dword ptr [ebp-4] 
00406BB5  push        eax  
00406BB6  lea         ecx,[tmp] 
00406BBC  push        ecx  
00406BBD  mov         edx,dword ptr [__cfltcvt_tab+18h (457274h)] 
00406BC3  push        edx  
00406BC4  call        _decode_pointer (40D1A0h) 
00406BC9  add         esp,4 
00406BCC  call        eax  
00406BCE  add         esp,1Ch 


可以看到在源代码中调用_cfltcvt_l函数的时候其实调用一个宏,展开就是
00406BBD  mov         edx,dword ptr [__cfltcvt_tab+18h (457274h)] 
00406BC3  push        edx  
00406BC4  call        _decode_pointer (40D1A0h) 
00406BC9  add         esp,4 
00406BCC  call        eax  
00406BCE  add         esp,1Ch 


意味着_cfltcvt_l函数指针其实是存放在一个叫做__cfltcvt_tab的表中。。。

我们再打开__cfltcvt_tab表看看:
/*-
 *  ... table of (model-dependent) code pointers ...
 *
 *  Entries all point to _fptrap by default,
 *  but are changed to point to the appropriate
 *  routine if the _fltused initializer (_cfltcvt_init)
 *  is linked in.
 *
 *  if the _fltused modules are linked in, then the
 *  _cfltcvt_init initializer sets the entries of
 *  _cfltcvt_tab.
-*/

PFV _cfltcvt_tab[10] = {
    _fptrap,    /*  _cfltcvt */
    _fptrap,    /*  _cropzeros */
    _fptrap,    /*  _fassign */
    _fptrap,    /*  _forcdecpt */
    _fptrap,    /*  _positive */
    _fptrap,    /*  _cldcvt */
    _fptrap,    /*  _cfltcvt_l */
    _fptrap,    /*  _fassign_l */
    _fptrap,    /*  _cropzeros_l */
    _fptrap     /*  _forcdecpt_l */
};

void __cdecl _initp_misc_cfltcvt_tab()
{
    int i;
    for (i = 0; i < _countof(_cfltcvt_tab); ++i)
    {
        _cfltcvt_tab[i] = (PFV)_encode_pointer(_cfltcvt_tab[i]);
    }
}

源码中的注释说得很清楚了。。。Entries all point to _fptrap by default,
but are changed to point to the appropriate,
routine if the _fltused initializer (_cfltcvt_init) is linked in.

我们从源代码得到提示,肯定是因为这个_cfltcvt_tab没有得到初始化,所以TestFloat.exe执行的时候会执行到
_fptrap,如果初始化了就肯定能正确执行_cfltcvt_l函数。

我们用ollydbg 查看内存地址42A0C8(__decode_pointer的函数值)
0042A0B0 >1E6E1EF5
0042A0B4  1E6E1EF5
0042A0B8  1E6E1EF5
0042A0BC  1E6E1EF5
0042A0C0  1E6E1EF5
0042A0C4  1E6E1EF5
0042A0C8  1E6E1EF5
0042A0CC  1E6E1EF5
0042A0D0  1E6E1EF5
0042A0D4  1E6E1EF5

10个地址全部相同,可以肯定这就是经过 _encode_pointer 编码过的 _fptrap 函数地址。

知道了发生了什么事,那我们就要找出为什么会这样,是什么原因导致没有初始化这个 _cfltcvt_tab 表,
我们搜索CRT代码,找到初始化_cfltcvt_tab表的代码在

int __cdecl _cinit (
        int initFloatingPrecision
        )
{
        int initret;

        /*
         * initialize floating point package, if present
         */
#ifdef CRTDLL
        _fpmath(initFloatingPrecision);
#else  /* CRTDLL */
        if (_FPinit != NULL &&
            _IsNonwritableInCurrentImage((PBYTE)&_FPinit))
        {
            (*_FPinit)(initFloatingPrecision);
        }
        _initp_misc_cfltcvt_tab();
#endif  /* CRTDLL */

        /*
         * do initializations
         */
        initret = _initterm_e( __xi_a, __xi_z );
        if ( initret != 0 )
            return initret;

#ifdef _RTC
        atexit(_RTC_Terminate);
#endif  /* _RTC */
        /*
         * do C++ initializations
         */
        _initterm( __xc_a, __xc_z );

#ifndef CRTDLL
        /*
         * If we have any dynamically initialized __declspec(thread)
         * variables, then invoke their initialization for the thread on
         * which the DLL is being loaded, by calling __dyn_tls_init through
         * a callback defined in tlsdyn.obj.  We can't rely on the OS
         * calling __dyn_tls_init with DLL_PROCESS_ATTACH because, on
         * Win2K3 and before, that call happens before the CRT is
         * initialized.
         */
        if (__dyn_tls_init_callback != NULL &&
            _IsNonwritableInCurrentImage((PBYTE)&__dyn_tls_init_callback))
        {
            __dyn_tls_init_callback(NULL, DLL_THREAD_ATTACH, NULL);
        }
#endif  /* CRTDLL */

        return 0;
}


关键是这里:
        if (_FPinit != NULL &&
            _IsNonwritableInCurrentImage((PBYTE)&_FPinit))
        {
            (*_FPinit)(initFloatingPrecision);
        }


FPInit会初始化这个表。。。于是我们又跟踪TestFloat.exe中的cinit的地方,
发现由于 _IsNonwritableInCurrentImage 的返回值为FALSE,所以导致了不初始化 _cfltcvt_tab 表。

_IsNonwritableInCurrentImage函数的原型是:

BOOL __cdecl _IsNonwritableInCurrentImage(
    PBYTE pTarget
    )

功能是:判断目标地址pTarget是否在当前PE的Image范围内,并且判断其所属的Section是否为不可写,
        如果为不可写则返回 TRUE,否则返回 FALSE

_FPinit在.rdata段中,这个段默认情况下就是只读段,不允许写的。。。其中包含了一些函数指针数据,我想
MS可能是出于安全性的考虑,所以会调用_IsNonwritableInCurrentImage进行判断吧。

致此,我们程序的根本错误原因就很明朗了,就是因为.rdata的段属性包含 Writeable属性

我们用LordPE打开TestFloat.exe看.rdata的属性,果然其含有 Writeable属性 ,验证了我的想法是正确的。

我们将其 Writeable属性 去掉,程序就不会Crash了。。。

===================================华丽的分割线===================================

我们还可以写一个小程序来验证我的结论:
#include "stdafx.h"
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
	float	f;
	wchar_t	buf[256];

	f = 3.14159;

	_snwprintf(buf, sizeof(buf), L"PI * 2 = %f", f + f);

	return 0;
}


这个代码可以正确运行,当我们加入一句设置.rdata段函数为可写时程序就会出错,如下所示:
#include "stdafx.h"
#include <stdio.h>

#pragma comment(linker, "/Section:.rdata,RW")

int _tmain(int argc, _TCHAR* argv[])
{
	float	f;
	wchar_t	buf[256];

	f = 3.14159;

	_snwprintf(buf, sizeof(buf), L"PI * 2 = %f", f + f);

	return 0;
}


附上一个修复的exe

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (2)
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
打错一个字__decode_pointer的函数值应该改为__decode_pointer的参数值
2008-10-8 17:12
0
雪    币: 264
活跃值: (30)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
3
结果提交时间 5 小时 6 分钟
结果提交时间长度 = 306 分钟
结果提交次数 = 1
结果提交为根本原因
得分 = [(2880 - 306)/2880]^1/5 x 1.0 x 100 - (1 -1 ) x 5 = 97.78
2008-10-14 16:00
0
游客
登录 | 注册 方可回帖
返回
//