首页
社区
课程
招聘
[原创]第一阶段第三题答案
发表于: 2008-10-10 13:03 15513

[原创]第一阶段第三题答案

2008-10-10 13:03
15513

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- TestFloat 分析记录 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

写一个程序和 TestFloat 对照着调试,确定最终出错的函数位置。为了方便,我这里直接调试 TestFloat 本身。

用 WinDbg 载入 TestFloat 程序,设置 crt 源码的位置:

.sympath+ d:\Program Files\Microsoft Visual Studio 8\VC\crt\src

g @$exentry 执行到 TestFloat 入口点位置,在 Crash 按钮函数上下断点:

bp TestFloat!CTestFloatDlg::OnBnClickedBtnCrash

继续执行,点击 Crash 按钮,中断到 WinDbg 后,单步进入 snwprintf 函数内。继续单步慢慢执行,发现在
_woutput_l 函数上程序崩溃,所以重新执行 TestFloat,单步进入 _woutput_l 函数内。继续单步慢慢执行,
发现在 _cfltcvt_l 函数上程序崩溃。

00413de9 8d459c          lea     eax,[ebp-64h]
00413dec 50              push    eax
00413ded ff7594          push    dword ptr [ebp-6Ch]
00413df0 0fbec2          movsx   eax,dl
00413df3 ff75e8          push    dword ptr [ebp-18h]
00413df6 895dd8          mov     dword ptr [ebp-28h],ebx
00413df9 50              push    eax
00413dfa ff75e0          push    dword ptr [ebp-20h]
00413dfd 8d4588          lea     eax,[ebp-78h]
00413e00 56              push    esi
00413e01 50              push    eax
00413e02 ff35c8a04200    push    dword ptr [TestFloat!_cfltcvt_tab+0x18 (0042a0c8)]
00413e08 e83fefffff      call    TestFloat!_decode_pointer (00412d4c)
00413e0d 59              pop     ecx
00413e0e ffd0            call    eax

反汇编 _cfltcvt_l 发现该函数对应成两个函数调用,先通过 _decode_pointer 函数解码出第二个函数的地址,
然后调用第二个函数。执行到第一个函数调用后,查看返回值。

0:000> ln @eax
f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c(46)
(0041ae1f)   TestFloat!_fptrap   |  (0041ae28)   TestFloat!_isdigit_l
Exact matches:
    TestFloat!_fptrap (void)

再往下执行 call eax 就会出错,正常情况下这里解码出来的函数应该是 _cfltcvt_l,而不是 _fptrap。所以
跟进 _decode_pointer 函数看看为啥解码出来的函数不对。

跟进去后发现 _decode_pointer 函数一切正常,估计就是 _cfltcvt_tab 中的值本来就不对。所以下一步跟踪
一下 _cfltcvt_tab 的初始化过程。

查看 cmiscdat.c 源文件,估计 _cfltcvt_tab 是一个指针数组,里面存放着支持浮点运算的一些函数,在初始
化的时候加密起来存放,然后在使用的时候把函数指针解密出来调用。

重新加载 TestFloat,g @$exentry 执行到入口点,显示一下 _cfltcvt_tab 的信息:

0:000> dps TestFloat!_cfltcvt_tab l10
0042a0b0  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0b4  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0b8  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0bc  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0c0  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0c4  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0c8  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0cc  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0d0  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0d4  0041ae1f TestFloat!_fptrap [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0fp.c @ 46]
0042a0d8  00000000

可以看到初始指针都指向 _fptrap 函数。下内存访问断点:

0:000> ba w4 TestFloat!_cfltcvt_tab
0:000> g

中断后查看堆栈:

0:000> kn
# ChildEBP RetAddr  
00 0012fef8 0040f640 TestFloat!_initp_misc_cfltcvt_tab+0x1a [f:\sp\vctools\crt_bld\self_x86\crt\src\cmiscdat.c @ 58]
01 0012fefc 0040ebd8 TestFloat!_cinit+0x28 [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0dat.c @ 283]
02 0012ff88 76074911 TestFloat!__tmainCRTStartup+0x149 [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0.c @ 310]
03 0012ff94 7728e4b6 kernel32!BaseThreadInitThunk+0xe
04 0012ffd4 7728e489 ntdll!__RtlUserThreadStart+0x23
05 0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b

重新加载 TestFloat,在 TestFloat!_cinit 函数上下断点,执行中断后单步跟踪,很快就能看到如下代码:

        if (_FPinit != NULL &&
            _IsNonwritableInCurrentImage((PBYTE)&_FPinit))
        {
            (*_FPinit)(initFloatingPrecision);
        }
        _initp_misc_cfltcvt_tab();

_initp_misc_cfltcvt_tab 函数就是上面用来给 _cfltcvt_tab 中的指针做加密的,在这个函数之前有个 if 判
断,从而决定是否初始化 _cfltcvt_tab 指针表。其中 _IsNonwritableInCurrentImag 函数判断 PE 文件的只读
节是否具有可写属性,如果具有可写属性,则跳过对 _cfltcvt_tab 的初始化过程,_cfltcvt_tab 中保存的就仍
然是最初的 _fptrap 函数指针。往后程序中用到浮点操作时,解密后得到的 _fptrap 函数就会弹出浮点错误的
提示。

TestFloat 程序编译时,我添加了 /section:.rdata,rw 链接选项,所以最终编译出来的 TestFloat 会出错,源
代码上没有任何问题,并不是没有链接浮点库的原因。

这个 bug 是以前调试我们内部游戏加壳时发现的,是个实际的例子。直接从弹出的错误框来看,和网上能搜索到
的答案不一样,觉得有点意思,所以弄出来给大家分析。谢谢大家的积极参与!

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- end -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (27)
雪    币: 383
活跃值: (41)
能力值: ( LV12,RANK:530 )
在线值:
发帖
回帖
粉丝
2
抢个沙发慢慢看了
我直接用OD调试的,map文件不会用...
调试到这个函数处理到f时会跳向

00413e08 e83fefffff      call    TestFloat!_decode_pointer (00412d4c)
00413e0d 59              pop     ecx
00413e0e ffd0            call    eax
接着就出错了,我把f改成大写F就不出错了,但是没有答案了,后来翻了下书,没有F这种格式,改成g也错,就直接放弃了
2008-10-10 13:05
0
雪    币: 231
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qdk
3
我竟然没有注意到map文件。
有时间了慢慢学习一下。
2008-10-10 13:09
0
雪    币: 216
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
座第一排........
标准答案来了
2008-10-10 13:10
0
雪    币: 8209
活跃值: (4518)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
5
大家分析一下,微软为什么增加这个_IsNonwritableInCurrentImage的判断?
以前版本的VC没有这个判断
2008-10-10 13:10
0
雪    币: 216
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
哇,第一排居然没座起,前面两个也太快了
2008-10-10 13:10
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
7
upx分析过了
2008-10-10 13:14
0
雪    币: 8209
活跃值: (4518)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
8
那只是upx从解决自身兼容性考虑的
我们需要进一步了解一下微软增加_IsNonwritableInCurrentImage判断的必要性
没有这个判断会对程序产生什么样负面的影响
2008-10-10 13:24
0
雪    币: 111
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
这话怎么解?
2008-10-10 13:25
0
雪    币: 7309
活跃值: (3788)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
10
强烈抗议把我的帖子移到休闲区
http://bbs.pediy.com/showthread.php?t=74370

楼主都说了,是加壳时候发现的BUG,所以我才发帖,让大家去研究壳的差异

这不是休闲贴,是休闲形式的技术贴
2008-10-10 13:30
0
雪    币: 647
活跃值: (564)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
11
多看一会儿运行时源码可以找到答案,这题可惜了
2008-10-10 13:32
0
雪    币: 8209
活跃值: (4518)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
12
[QUOTE=海风月影;519549]强烈抗议把我的帖子移到休闲区
http://bbs.pediy.com/showthread.php?t=74370

楼主都说了,是加壳时候发现的BUG,所以我才发帖,让大家去研究壳的差异

这不是休闲贴,是休闲形式的技术贴[/QUOTE]

还有这个关系啊
让人联想到也许tx的加壳工具是用kbys改出来的
2008-10-10 13:38
0
雪    币: 7309
活跃值: (3788)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
13
提kbys是因为shoooo第一个提交了,也许有什么关联

其实upx1.25也有这个BUG,大家可以试试
上传的附件:
2008-10-10 13:44
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
14
initialize floating point package, ** if present **
不知对不对 ?
用这个来判断 if present ?   
2008-10-10 13:47
0
雪    币: 8209
活跃值: (4518)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
15
这个高的版本没用过,我用习惯了upx0.70
2008-10-10 13:47
0
雪    币: 111
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
16
少看了上面几行,就离标准答案。。唉
2008-10-10 13:49
0
雪    币: 186
活跃值: (15)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
17
同意,那是为什么呢?
2008-10-10 14:01
0
雪    币: 277
活跃值: (321)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
18
kbys 是啥?

_IsNonwritableInCurrentImage 微软为啥添加这个估计比较难分析,最后只能是猜测。
2008-10-10 14:22
0
雪    币: 407
活跃值: (125)
能力值: ( LV13,RANK:280 )
在线值:
发帖
回帖
粉丝
19
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 */
发段代码,大家看看,是不是和CRTDLL 这个有关呢
2008-10-10 15:00
0
雪    币: 221
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
20
VC6没问题,VC2005有这个问题
2008-10-10 15:01
0
雪    币: 497
活跃值: (63)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
21
捉摸不透的微软
2008-10-10 17:02
0
雪    币: 212
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
收获很多,谢谢评委出题了
2008-10-10 19:08
0
雪    币: 249
活跃值: (35)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
23
为什么 重新导入一个 snwprintf 也能正确执行呢。。 奇怪
2008-10-10 19:51
0
雪    币: 212
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
又自己跟了一下,发现这个题如果有耐心的话还是能找到出错地方的,个人感觉有点被google误导了的感觉,不过只能怪自己不会google了,再次感谢评委出的如此优秀的题目
2008-10-10 19:54
0
雪    币: 212
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
个人猜测是系统自带的snwprintf是没有程序里的那些判断的,调用的函数也是系统已经初始化好的程序,为了给整个系统使用,限制条件就要少些。而程序里的函数是在程序加载的时候初始化的,于是有了那个rdata是否可写的判断,纯猜测,可以无视
2008-10-10 20:06
0
游客
登录 | 注册 方可回帖
返回
//