利用监视点定位导致溢出的代码点


/*
* For x86/EWindows XP SP1 & VC 7
* cl vulnerable_0.c /nologo /Os /G6 /W3 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#pragma comment( linker, "/INCREMENTAL:NO"    )
#pragma comment( linker, "/subsystem:console" )

int __cdecl main ( int argc, char * argv[] )
{
    unsigned char buf[8];

    if ( 2 != argc )
    {
        fprintf( stderr, "Usage: %s <any string>/n", argv[0] );
        return( EXIT_FAILURE );
    }
    strcpy( buf, argv[1] );
    printf( "%s/n", buf );
    return( EXIT_SUCCESS );
}  /* end of main */

前面讨论了如何尽早中断在vulnerable_0.exe进程空间中,这次介绍如何利用监视点
定位导致溢出的代码点。

在<<SMB系列(11)--TRANSACT2_OPEN处理过程存在远程缓冲区溢出漏洞>>中介绍过GDB
调试技巧,利用watch功能定位导致溢出的代码点。windbg的ba不能在用户级调试中
使用,很奇怪为什么是这样。SoftICE的bpm断点等价于gdb的watch命令。

打开faults on,让EIP等于0x41414141时SoftICE弹出:

Break due to BP 00: BPX #001B:00401289  (ET=4.41 seconds)
:bl
00)   BPX #001B:00401289
:faults on
:g
Break due to UnhandledException NTSTATUS=STATUS_ACCESS_VIOLATION

在当前栈顶(ESP)以低位置搜索特征字节,试图定位ret指令用过的那个栈帧:

:s -a esp&ffff0000 L esp&0000ffff "AAAAAAAAAAAAAAAA"
Pattern found at 0023:0012F948 (0000F948)
Pattern found at 0023:0012FA60 (0000FA60)
0000000002 occurances found
:db 0012F948 L 20
0023:0012F948 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0023:0012F958 0D 0A 00 00 A8 03 00 00-00 00 00 00 01 00 00 00  ................
:db 0012FA60 L 20
0023:0012FA60 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0023:0012FA70 0D 0A 02 03 02 03 02 03-02 03 10 02 10 02 10 02  ................
:faults off
:g

注意到这两处结尾都有"/r/n",从源代码可知这两处必不是buf[]所在,像printf()
所用缓冲区。一般没有源代码可供参考,只能试着先在这两外下写监视点,,找出向
此写入"AA..AA"的代码点。重新执行vulnerable_0.exe,中断在总入口点:

:bd 0
:bpmd 0012F948 w if *0012F948==41414141
:bpmd 0012FA60 w if *0012FA60==41414141
:bl
00) * BPX #001B:00401289
01)   BPMD #0023:0012F948 W DR3  IF (*(0x12F948)==0x41414141)
02)   BPMD #0023:0012FA60 W DR2  IF (*(0x12FA60)==0x41414141)

再次提醒,bpm断点随进程空间消失而消失,因此应该尽早中断在vulnerable_0.exe
进程空间中,然后设置bpm断点,而不是其它时刻。g继续执行,其中一个写监视点命
中:

:g
Break due to BP 02: BPMD #0023:0012FA60 W DR2  IF (*(0x12FA60)==0x41414141)
:d 0012FA60 L 20
0023:0012FA60 41 41 41 41 02 03 02 03-02 03 02 03 02 03 02 03  AAAA............
0023:0012FA70 02 03 02 03 02 03 02 03-02 03 10 02 10 02 10 02  ................
:d 0012F948 L 20
0023:0012F948 00 01 00 00 84 FD 12 00-00 01 00 00 84 FC 12 00  ................
0023:0012F958 00 01 00 00 A8 03 00 00-00 00 00 00 01 00 00 00  ................
:

有代码在向0x0012FA60写入"AA..AA",在这段代码附近F10单步跟几步,就发现源串
跟如下代码相关:

001B:00403C11  8B55F8              MOV       EDX,[EBP-08]
001B:00403C14  FF45F8              INC       DWORD PTR [EBP-08]
001B:00403C17  8A12                MOV       DL,[EDX]

:dd ebp-8 L 4
0023:0012FE74 003718BC  C0BDC4AA  0012FEE4  0040217A      ..7.........z!@.
:db 003718BC-20 L 40
0023:0037189C 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0023:003718AC 00 00 00 00 01 02 01 01-00 01 08 00 41 41 41 41  ............AAAA
0023:003718BC 41 41 41 41 41 41 41 41-41 41 41 41 0A 00 00 00  AAAAAAAAAAAA....
0023:003718CC 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

这回发现0x003718B8处也有16个'A',后面还有一个'/n'。再次搜索内存:

:s -a 0 L 7fffffff "AAAAAAAAAAAAAAAA"
Pattern found at 0023:0012FEDC (0012FEDC)
Pattern found at 0023:00142371 (00142371)
Pattern found at 0023:00142FD9 (00142FD9)
Pattern found at 0023:00370B3D (00370B3D)
Pattern found at 0023:003718B8 (003718B8)
0000000005 occurances found

不必搜索0x80000000开始的空间,那是内核空间。

:stack
FrameEBP  RetEIP     Symbol
0012FE7C  0040217A   vulnerable_0!.text+2C11
0012FEE4  41414141   vulnerable_0!.text+117A

查看调用栈回溯时意外发现某个返回地址已经被覆盖成0x41414141,与内存搜索结果
对照,显然我们应该在0x0012FEDC下写监视点:

:be 0
:bc 2
:bpe 1
:BPMD #0023:0012FEDC W DR3  IF (*(0x0012FEDC)==0x41414141)
:bl
00)   BPX #001B:00401289
01)   BPMD #0023:0012FEDC W DR3  IF (*(0x12FEDC)==0x41414141)
:g

不过这次bpm断点已经无效,0x0012FEDC已经被写过了,而且进程空间消失后bpm断点
随之消失。我用bpe命令先设置一次,留在命令缓冲中。下次因bpx中断后,只需用上
键头找出bpm命令重新设置即可,不必输入很长的一串。重新执行vulnerable_0.exe:

Break due to BP 00: BPX #001B:00401289  (ET=14.80 seconds)
:bl
00)   BPX #001B:00401289
:BPMD #0023:0012FEDC W DR3  IF (*(0x0012FEDC)==0x41414141)
:bl
00)   BPX #001B:00401289
01)   BPMD #0023:0012FEDC W DR3  IF (*(0x12FEDC)==0x41414141)
:db 0012FEDC L 20
0023:0012FEDC 00 00 00 00 31 00 00 00-44 9F 59 80 02 00 00 00  ....1...D.Y.....
0023:0012FEEC FF FF FF FF 00 08 00 00-10 8C 18 FD B1 77 4F 80  .............wO.

g继续执行,写监视点命中:

:g
Break due to BP 01: BPMD #0023:0012FEDC W DR3  IF (*(0x12FEDC)==0x41414141)
:db 0012FEDC L 20
0023:0012FEDC 41 41 41 41 41 41 41 00-C0 FF 12 00 F9 13 40 00  AAAAAAA.......@.
0023:0012FEEC 02 00 00 00 20 0B 37 00-58 0B 37 00 94 00 00 00  .... .7.X.7.....
:stack
FrameEBP  RetEIP     Symbol
0012FEE4  004013F9   vulnerable_0!.text+011B
0012FFC0  77E814C7   vulnerable_0!.text+03F9
0012FFF0  00000000   kernel32!_BaseProcessStart+0023

在栈帧0x0012FEE4(EBP)所对应的函数中,有代码导致自己的栈帧被覆盖,这是最典
型的栈溢出。而我们已经找到导致溢出的代码点:

EAX=7EFEFEFE   EBX=7FFDF000   ECX=00370B44   EDX=41414141   ESI=00000A28
EDI=0012FEDF   EBP=0012FEE4   ESP=0012FECC   EIP=0040111B   o d I s Z a P c
CS=001B   DS=0023   SS=0023   ES=0023   FS=0038   GS=0000
─────────────────────────────────PROT32─
001B:0040111B  83C704              ADD       EDI,04
001B:0040111E  BAFFFEFE7E          MOV       EDX,7EFEFEFF
001B:00401123  8B01                MOV       EAX,[ECX]
001B:00401125  03D0                ADD       EDX,EAX
... ...
001B:00401150  EBC7                JMP       00401119
001B:00401152  8917                MOV       [EDI],EDX
(PASSIVE)-KTEB(8106B240)-TID(0164)──vulnerable_0!.text+011B──────

总结一下全过程:

a. 执行问题程序,EIP为0x41414141时SoftICE弹出,在内存中搜索特征字节,记录
   地址,准备下次设置写监视点用。

   用stack命令查看此刻调用栈回溯,获取更多有用信息。

   立即根据PE头计算出程序总入口点,在程序总入口点设置bpx断点。

b. 重新执行问题程序,SoftICE因bpx断点而弹出。根据步骤a记录下来的地址设置写
   监视点。

   g命令继续执行问题程序。

c. SoftICE因写监视点命中而弹出。再次在内存中搜索特征字节,配合stack命令确
   定被覆盖的栈帧以及相应的缓冲区起始地址。

   清空那些无用的写监视点,保持bpx断点。

   g命令结束本次执行。

d. 重新执行问题程序,SoftICE因bpx断点而弹出。根据步骤c记录下来的地址设置写
   监视点。

   g命令继续执行问题程序。

e. SoftICE因写监视点命中而弹出。此刻EIP附近的代码就是我们要找的代码。

这是一个逐步逼近的调试过程。vulnerable_0.c相当简单,因此逼近过程快。如果碰
上没有源代码可供参考的复杂应用程序,就远不是这样轻松了。不过整体抽象思路依
旧。

czy后来提到,在EIP等于0x41414141时,检查ESP-4、ESP-8两个位置,他认为这就是
RetAddr、EBP所在。现在来讨论一下这个观点。

函数有__cdecl、__stdcall、__fastcall之分,不能保证ret时是简单的ret,还是弹
出形参的"ret n"。在不清楚是何种调用风格之前,不能假设ESP-4对应RetAddr,C调
用风格有此结论,后两种调用风格则在无形参时有此结论。没有源码时只能"在当前
栈顶(ESP)以低位置搜索特征字节,试图定位ret指令用过的那个栈帧"。

顺便讨论另一个问题。VC编译出来的程序有bp-based frame、non bp-based frame之
分,就是说函数入口处是否有显式的"push ebp/mov ebp, esp"或者enter指令存在。
如果两个push之后是"call __SEH_prolog"指令,就属于non bp-based frame情形。
据我逆向经验,XP SP1/ntdll中涉及SEH/__try块的基本都是non bp-based frame情
形,此时stack布局如下:

--------------------------------------------------------------------------
内存高址方向

[EBP+0x004] RetAddr
[EBP-0x000] _ebp
[EBP-0x004] trylevel
[EBP-0x008] scopetable
[EBP-0x00C] handler
[EBP-0x010] prev
[EBP-0x014] PEXCEPTION_POINTERS/GetExceptionInformation()
[EBP-0x018] Hold the final ESP after all the prologue code has executed
[EBP-0x01C] 第001个局部变量

内存低址方向
--------------------------------------------------------------------------

hume指出2000/ntdll是bp-based frame情形,此时第001个局部变量是[EBP-0x004]。
IDA Pro逆向后很容易区分这两种情形。

引发异常后SEH机制会进行全局展开。即使没有全局展开,流程到达某个
scopetable[n].lpfnHandler时,必然有一系列堆栈操作,这将破坏stack中原有数据。
如果在这之后搜索内存中的特征字节,往往找不到所期望的那个区域(发生溢出的缓
冲区、被覆盖的栈帧)。所以前面我说需要逐步逼近。

不过今天讨论czy这个想法时又仔细想了想全过程。应该下这样一个断点:

:bpx ntdll!KiUserExceptionDispatcher do "dd (*esp)+c L10;dd (*(esp+4))+8c L40"

然后搜索内存中的特征字节,似乎可以尽力保持堆栈原貌,此时最接近ESP的特征字
节所在区域可能就是所期望的区域。我现在没时间测试验证这点,czy有时间可以试
试。

至此又想起另一个问题。通过覆盖_EXCEPTION_REGISTRATION结构的_except_handler
使流程转向,当流程到达_except_handler时,前面已经有一些堆栈操作。构造攻击
模板时要意识到这个问题。"利用SEH机制使流程转向"小节最后成功的那个攻击模板
符合要求。

谢谢czy的讨论,现在对这个问题的理解更深了一些。

以前只是在KiUserExceptionDispatcher入口处下断点定位ExceptionAddress,现在
看来,还应该包括在KiUserExceptionDispatcher入口处下断点搜索内存中的特征字
节。

czy还提到,一般VC写的程序运行之初都要调用GetCommandLineA(),对它下个断点,
多按几下F12,回到我们感兴趣的地方。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值