分类 调试 下的文章

如何确保程序中的崩溃不可利用?简而言之,答案很简单:假设每个崩溃都是可利用的,然后修复它!至少,这是一个质量问题,在产品交付给客户之前解决这个问题通常更便宜、更实用。执行确定可利用性所需的分析可能会相当昂贵。
分析与内存损坏相关的程序故障,以了解安全后果可能是一项复杂且容易出错的任务。必须考虑几个因素,包括缓冲区在内存中的位置、覆盖的可能目标、覆盖的大小、对覆盖期间可以使用的数据的限制、运行时执行环境的状态以及绕过任何现有缓解机制的能力。简而言之,您必须了解失败的根本原因,才能彻底回答这些问题。
记住,并非每一个失败都会以可观察的方式显现出来。其中一个例子是微软安全公告MS07-017中讨论的GDI远程代码执行问题。负责调用易受攻击的解析代码的软件使用异常处理程序来从几乎所有可能生成的异常中恢复,并像没有发生任何异常一样继续运行。另一个不太明显的例子可以在某些类型的堆栈和堆内存损坏中找到,可能发生了故障,但程序的当前状态及其执行环境没有显示任何明显的迹象。

本文提供了有关如何分析程序崩溃的指导,以考虑可能的安全隐患,例如启用任意代码执行的内存损坏或至少拒绝服务的情况。我们将列举您在查看这些类型的问题时可能遇到的常见硬件和软件异常。我们也会提供一些一般性的指导,你可以在这样的调查中使用。例如,下图给出了调查过程的图形路径,以帮助您确定特定崩溃是否可利用。重要的是要记住,这些只是指导原则,只有全面的根本原因分析才能确保您已正确诊断为不可利用的崩溃。新技术或现有攻击技术的变种一直在被发现。

 

最常见的崩溃原因是硬件或软件异常。典型的现代处理器可以生成许多不同类型的硬件异常,但在Windows环境中,只有其中一些会产生与软件安全相关的问题。最常见的硬件异常是访问冲突。我们将首先介绍如何分析硬件异常,然后是软件异常。

访问冲突

当指令或程序执行导致的内存访问不满足处理器体系结构或内存管理单元结构定义的某些条件时,现代处理器会生成访问冲突异常(0xc000005=状态访问冲突)。
虽然纯崩溃只能导致拒绝服务条件,但不能安全地假设崩溃不能用于实现更危险的效果,包括代码执行。在分析崩溃时,您应该假设整个内存体(除了一些小的异常)都处于潜在攻击者的控制之下;因此,在大多数情况下,访问冲突可能导致由攻击者控制的数据。此语句适用于指令读取或写入数据时发生的异常。
如果访问冲突会导致您的数据受到攻击者的控制,则从内存读取导致的每个访问冲突都会转化为加载攻击者控制的数据。这种行动的安全效果并不总是容易确定的。您可以对二进制或源代码执行完整的数据流分析,以找到源地址控制的范围以及在某些执行点向程序提供随机数据的结果。这是一项耗时且富有挑战性的任务。作为回应,我们开发了简单的启发式方法来快速分析代码执行潜力的读取访问冲突崩溃。
如下例所示,寄存器eax中的无效内存指针导致崩溃。在这种情况下,对内存内容的控制使攻击者能够完全控制程序流:

Application!Function+0x133:
3036a384 eb32            call     [eax]           ds:0023:6c7d890d=??
0:000> ub
mov    eax, [ebx]  ->  eax = invalid memory pointer
...                         (instructions not affecting register eax)
call        [eax]  ->  crash

如果攻击者无法充分控制正在读取的地址,则可以将其视为拒绝服务条件。例如,在典型的Windows用户模式环境中,在初始化的空指针处发生的、不受攻击者影响的崩溃本身不会导致代码执行。
在下面的示例中,您可以看到崩溃是由引用寄存器eax中的地址0值引起的:

Application!Function+0x133:
3036a384 8b14            mov     ecx, [eax]           ds:0023:00000000=??
0:000> ub
xor    eax, eax
...                         (instructions not affecting register eax)
cmp    ebx, 2
jne        label123
mov    ecx, [eax]  ->  crash, eax = 0 (NULL)

通过反汇编(使用Visual Studio命令行调试器中的ub命令),我们可以跟踪此寄存器中的数据流,直到确认寄存器中的值不会受到恶意输入的影响。事实上,在这个例子中,寄存器是通过自身的异或运算来调零的,直到到达崩溃指令时才使用它。有时,从崩溃的指令看不到缺陷的可利用性。例如,在分解以下指令之后,您可以看到,当失败的指令(在上一段中描述)后面紧跟着一个关键指令时,这是控制流的结果:

!handle

简介

!handle扩展显示有关目标系统中一个或所有进程拥有的一个或多个句柄的信息。

使用形式

  • 用户模式
    !handle[Handle [UMFlags [TypeName]]]
    !handle -?
  • 内核模式
    !handle[Handle [KMFlags [Process [TypeName]]]]

参数

  • Handle
    指定要显示的句柄的索引。如果Handle为-1或省略此参数,调试器将显示与当前进程关联的所有句柄的数据。如果句柄为0,调试器将显示所有句柄的数据。
  • UMFlags
    (仅限用户模式)指定显示内容。此参数可以是以下任何位值的总和。(默认值为0x1。)|
    位 0 (0x1)
    显示句柄类型信息。

    位 1 (0x2)
    显示基本句柄信息。

    位 2 (0x4)
    显示句柄名称信息。

    位 3 (0x8)
    显示特定于对象的句柄信息 (如果可用)。

  • KMFlags
    (仅限内核模式)指定显示内容。此参数可以是以下任何位值的总和。(默认值为0x3。)
    位 0 (0x1)
    显示基本句柄信息。

    位 1 (0x2)
    显示有关对象的信息。

    位 2 (0x4)
    显示可用的句柄项。 如果未设置此位并且省略了句柄或将其设置为零, 则显示的句柄列表不包含可用的句柄。 如果handle指定单个自由句柄, 则即使未设置此位, 也会显示该句柄。

    位 4 (0x10)
    显示来自内核句柄表而不是当前进程的句柄。

    位 5 (0x20)
    将句柄解释为线程 ID 或进程 ID, 并显示有关相应内核对象的信息。

  • Process
    (仅限内核模式)指定进程。您可以使用进程ID或进程对象的十六进制地址。此参数必须引用目标系统上当前正在运行的进程。如果此参数为-1或忽略此参数,则使用当前进程。如果此参数为0,则显示所有进程的句柄信息。
  • TypeName
    指定要检查的句柄的类型。 只显示匹配此类型的句柄。 TypeName区分大小写。 有效类型包括事件、节、文件、端口、目录、SymbolicLink、变化、Windowstation 时出错、信号量、密钥、令牌、进程、线程、桌面、IoCompletion、计时器、作业和 WaitablePort。
  • -?
    (仅用户模式)在调试器命令窗口中显示此扩展的一些帮助文本。

支持环境

Windows 2000

Kdextx86 Ntsdexts Uext. .dll

Windows XP 和更高版本

Kdexts Ntsdexts Uext. .dll

备注

你可以在用户模式和内核模式实时调试过程中使用!handle扩展。 你还可以在内核模式转储文件中使用此扩展。 但是, 你不能对用户模式转储文件使用此扩展, 除非你专门使用处理信息创建它们。 (可以使用dump/mh (创建转储文件) 命令创建此类转储文件。)在实时用户模式调试过程中, 可以使用closehandle (关闭句柄) 命令关闭一个或多个句柄。

0:000> !handle
Handle 4
  Type          Section
Handle 8
  Type          Event
Handle c
  Type          Event
Handle 10
  Type          Event
Handle 14
  Type          Directory
Handle 5c
  Type          File
6 Handles
Type            Count
Event           3
Section         1
File            1
Directory       1

以下命令显示有关句柄0x8 的详细信息

0:000> !handle 8 f
Handle 8
  Type          Event
  Attributes    0
  GrantedAccess 0x100003:
         Synch
         QueryState,ModifyState
  HandleCount   2
  PointerCount  3
  Name          <none>
  Object Specific Information
    Event Type Auto Reset
    Event is Waiting

以下示例是 ! handle的内核模式示例。 以下命令将列出所有句柄, 包括自由句柄。

kd> !handle 0 4
processor number 0
PROCESS 80559800  SessionId: 0  Cid: 0000    Peb: 00000000  ParentCid: 0000
    DirBase: 00039000  ObjectTable: e1000d60  TableSize: 380.
    Image: Idle

New version of handle table at e1002000 with 380 Entries in use

0000: free handle, Entry address e1002000, Next Entry fffffffe
0004: Object: 80ed5238  GrantedAccess: 001f0fff
0008: Object: 80ed46b8  GrantedAccess: 00000000
000c: Object: e1281d00  GrantedAccess: 000f003f
0010: Object: e1013658  GrantedAccess: 00000000
......
0168: Object: ffb6c748  GrantedAccess: 00000003 (Protected)
016c: Object: ff811f90  GrantedAccess: 0012008b
0170: free handle, Entry address e10022e0, Next Entry 00000458
0174: Object: 80dfd5c8  GrantedAccess: 001f01ff
......

以下命令显示有关内核句柄表中的句柄0x14 的详细信息。

kd> !handle 14 13
processor number 0
PROCESS 80559800  SessionId: 0  Cid: 0000    Peb: 00000000  ParentCid: 0000
    DirBase: 00039000  ObjectTable: e1000d60  TableSize: 380.
    Image: Idle

Kernel New version of handle table at e1002000 with 380 Entries in use
0014: Object: e12751d0  GrantedAccess: 0002001f
Object: e12751d0  Type: (80ec8db8) Key
    ObjectHeader: e12751b8
        HandleCount: 1  PointerCount: 1
        Directory Object: 00000000  Name: \REGISTRY\MACHINE\SYSTEM\CONTROLSET001\CONTROL\SESSION MANAGER\EXECUTIVE

以下命令显示有关所有进程中的 Section 对象的所有句柄的信息。

!handle 0 3 0 Section
...
PROCESS fffffa8004f48940
    SessionId: none  Cid: 0138    Peb: 7f6639bf000  ParentCid: 0004
    DirBase: 10cb74000  ObjectTable: fffff8a00066f700  HandleCount:  39.
    Image: smss.exe

Handle table at fffff8a00066f700 with 39 entries in use

0040: Object: fffff8a000633f00  GrantedAccess: 00000006 (Inherit) Entry: fffff8a000670100
Object: fffff8a000633f00  Type: (fffffa80035fef20) Section
    ObjectHeader: fffff8a000633ed0 (new version)
        HandleCount: 1  PointerCount: 262144

简介

STATUS_STACK_BUFFER_OVERRUN,值为0xC0000409,又称栈缓冲区溢出异常,其定义如下:

/
// MessageId: STATUS_STACK_BUFFER_OVERRUN
//
// MessageText:
//
// The system detected an overrun of a stack-based buffer in this application. This overrun could potentially allow a malicious user to gain control of this application.
//
#define STATUS_STACK_BUFFER_OVERRUN      ((NTSTATUS)0xC0000409L)    // winnt

说明

系统在此应用程序中检测到基于堆栈的缓冲区溢出。此溢出可能允许恶意用户获得此应用程序的控制权。同时现在的Windows系统上也不仅仅用来表达着个异常,也用来做一些会导致致命错误的安全检测,而引发进程快速失败。与所有其他异常代码不同,Fail Fast异常绕过所有异常处理程序(基于帧或向量)。如果启用了Windows错误报告,引发此异常将终止应用程序并调用Windows错误报告。本异常代码最初设计用于引发安全检查失败。具体来说,是违反警戒线(/GS)。随着时间的推移,出于非安全原因,应用程序利用了立即终止功能的愿望。这些应用程序利用第一个参数来指定场景(子代码)。原始的“安全检查失败”用例保留值为0。由于每个应用程序的性质,当前未定义异常参数值。

异常填充结构

ExceptionAddress: 0f2846a9 (msvcr120!_invoke_watson+0x0000000e)
ExceptionCode: c0000409 (Security check failure or stack buffer overrun)
ExceptionFlags: 00000001
NumberParameters: 1
   Parameter[0]: 00000005//异常子代码
Subcode: 0x5 FAST_FAIL_INVALID_ARG

!dlls

简介

!dlls扩展显示所有加载模块或指定线程或进程正在使用的所有模块的表条目。

使用形式

!dlls[Options][LoaderEntryAddress]

!dlls -h

参数

  • Options 指定输出的级别。
    此参数可以是下列值中的任意组合:

    -f
    显示文件标头。

    -s
    显示部分标头。

    -a
    显示完成模块信息。 (此选项相当于-f-s。)

    -c **** ModuleAddress
    显示包含的模块ModuleAddress

    -i
    对显示的初始化顺序进行排序。

    -l
    对显示按加载顺序进行排序。 默认值为这种情况。

    -m
    对显示的内存顺序进行排序。

    -v
    显示版本信息。 此信息将由每个模块的资源部分。

    -h
    显示此扩展中的一些帮助文本调试器命令窗口。

  • LoaderEntryAddress
    指定模块的加载程序条目的地址。 如果包括此参数时,调试器将显示仅此特定模块。

支持环境

Windows 2000

Kdextx86.dll Ntsdexts.dll

Windows XP 及更高版本

Exts.dll

备注

模块列表包括每个模块的所有入口点。!dlls扩展仅在实时调试中工作(不在崩溃转储分析中)。在内核模式下,此扩展显示当前进程上下文的模块。你不能使用!dlls与系统进程或空闲进程一起。

如果您花了任何时间编写代码,很可能您不得不处理异常处理。在Visual Studio中,当异常被抛出或最终未被处理时,调试器可以帮助您通过中断来调试这些异常,就像在遇到断点时中断一样。下面我们将讨论异常的不同分类,以及如何配置调试器何时为这些异常中断。

异常分类

我们将从调试器中异常的分类类型开始。调试器按以下方式对异常进行分类:

First Chance Exceptions:

当应用程序中首次抛出异常时,这被归类为“First Chance”异常。此时,调试器不知道应用程序是否会捕获(处理)异常。所有异常都以第一次机会异常开始。

  • 每次引发异常时都会通知调试器。您可以在输出窗口和IntelliTrace中看到这些通知。
  • 您可以告诉调试器要中断的首次机会异常,就像启用断点一样。
  • 一旦由于首次出现异常而中断,可以通过单步执行或按“继续”继续调试。当您继续时,您的代码有机会处理此异常,如果没有,则该异常现在属于下面列出的分类之一。

User-unhandled Exceptions:

当用户代码中没有捕获(处理)第一次机会异常,并且在调用堆栈的“外部代码”中捕获时,这被归类为“用户未处理”异常。此分类仅适用于仅启用我的代码调试托管或JavaScript应用程序时。

  • 默认情况下,调试器将为所有用户未处理的异常而中断。
  • 一旦由于用户未处理的异常而中断,可以通过单步执行或按“继续”继续调试。异常可以在调用堆栈的“外部代码”中的某个位置处理,如果不是,则它将成为未处理的异常。
  • 您可以更改默认设置,但在大多数情况下,您可能不需要更改此设置。大多数框架,比如ASP.NET,都实现了全局异常处理程序,这样应用程序就不会崩溃,但是异常并没有得到正确的处理。调试器为用户未处理的异常提供中断功能,以便在这些情况下通知您。

Unhandled Exceptions:

当应用程序未捕获(处理)第一次机会异常并到达系统默认处理程序时,这被归类为“未处理”异常。