zoukankan      html  css  js  c++  java
  • 在没有符号和FPO的情况下遍历堆栈(帧指针省略)

    下面是应用程序崩溃转储的调用堆栈。报告的崩溃是名为“HelperLibrary”的模块内的访问冲突,我们没有该模块的符号或源代码。调用堆栈看起来不太可能:

    0:000> kv
    ChildEBP RetAddr  Args to Child             
    WARNING: Stack unwind information not available. Following frames may be wrong.
    0028fcec 74ba339a 7efde000 0028fd38 77479ed2
      HelperLibrary+0x1014
    0028fcf8 77479ed2 7efde000 776a5346 00000000
      kernel32!BaseThreadInitThunk+0xe (FPO: [1,0,0])
    0028fd38 77479ea5 011212b2 7efde000 00000000
      ntdll!__RtlUserThreadStart+0x70 (FPO: [SEH])
    0028fd50 00000000 011212b2 7efde000 00000000
      ntdll!_RtlUserThreadStart+0x1b (FPO: [2,2,0])

    除了HelperLibrary+0x1014之外,这里没有其他真正的帧,但是我们非常确定堆栈上应该还有其他代码,比如应用程序的主函数
    要从这个堆栈重建某些内容,您需要了解谁调用了HelperLibrary+0x1014,即使您没有准确的符号。通常,这是一个遍历EBP引用的问题,但是如果它那么简单,调试器就已经做到了!

    好吧,那么EBP怎么样了?

    0:000> r ebp
    ebp=0034fbfc
    0:000> ln ebp
    0:000> u ebp
    0034fbfc 08fc            or      ah,bh
    0034fbfe 3400            xor     al,0
    0034fc00 9a33ba7400e0fd  call    FDE0:0074BA33
    0034fc07 7e48            jle     0034fc51
    0034fc09 fc              cld
    0034fc0a 3400            xor     al,0
    0034fc0c d29e477700e0    rcr     byte ptr [
      esi-1FFF88B9h],cl
    0034fc12 fd              std

    如果你没有注意到,这不是实际的代码,而是一堆被解释为指令的数据。EBP完全有可能被损坏,指向完全不相关的位置,但也有另一种可能:当前代码正在使用FPO。
    什么是FPO?FPO(帧指针省略)是一种优化技术,而编译器使用EBP寄存器作为暂存值来存储杂项数据,就像任何其他通用寄存器一样。如何处理局部变量和函数参数?直接通过ESP。
    换言之,当使用FPO(您可以使用/Oy编译开关启用它)时,编译器可以自由地避免使用以前的EBP值创建“真实”堆栈帧。没有从当前EBP值开始的堆栈帧的链接列表。如果没有FPO信息(出现在符号文件中,而我们没有这些信息),调试器将无法执行任何操作。
    这使得我们需要反汇编HelperLibrary+0x1014并尝试手动找出它返回的位置。让我们看看HelperLibrary+0x1014(粗体的违规指令)附近:

    0:000> u HelperLibrary+0x1014-0x14 L8
    HelperLibrary+0x1000:
    66951000 56              push    esi
    66951001 ff157c209566    call    dword ptr [6695207c]
    66951007 50              push    eax
    66951008 c60061          mov     byte ptr [eax],61h
    6695100b ff1578209566    call    dword ptr [66952078]
    66951011 83c408          add     esp,8
    66951014 c60661          mov     byte ptr [esi],61h
    66951017 c3              ret

    这看起来,确实,像一个FPO的栈帧-没有EBP被看到。但是,随后的ret指令会转到某个地方,因此我们可以查看ESP并在那里找到返回地址:

    0:000> dps esp L1
    0034fb9c  66951040 HelperLibrary+0x1040

    好的,那么HelperLibrary+0x1040是什么?

    0:000> u HelperLibrary+0x1040-0x20 LC
    HelperLibrary+0x1020:
    66951020 56              push    esi
    66951021 8bf0            mov     esi,eax
    66951023 56              push    esi
    66951024 ff157c209566    call    dword ptr [6695207c]
    6695102a 50              push    eax
    6695102b c60061          mov     byte ptr [eax],61h
    6695102e ff1578209566    call    dword ptr [66952078]
    66951034 03742410        add     esi,dword ptr [esp+10h]
    66951038 83c408          add     esp,8
    6695103b e8c0ffffff      call    HelperLibrary+0x1000
    66951040 5e              pop     esi
    66951041 c3              ret

    有点意思。此帧也不使用EBP,因此我们可以预期返回值为ESP+4(因为返回之前的pop指令)。但在这个函数中,我们如何计算ESP的值呢?好吧,假设上一个函数返回。它已经从堆栈中删除了四个字节(返回地址)。ESP的下一个值是ESP+4,我们需要再添加4个字节来解释“pop esi”指令。

    0:000> dps esp+8 L1
    0034fba4  6695106b HelperLibrary!ImportantFunction+0x1b

    好吧!我们正在取得一些真正的进展,我们有一个非常小的偏移量,即使没有符号,ImportantFunction可能是一个导出函数,因此我们在DLL中有它的位置:

    0:000> u HelperLibrary!ImportantFunction LD
    HelperLibrary!ImportantFunction:
    66951050 8b442404        mov     eax,dword ptr [esp+4]
    66951054 85c0            test    eax,eax
    66951056 7501            jne     HelperLibrary!ImportantFunction+0x9
    66951058 c3              ret
    66951059 837c240c00      cmp     dword ptr [esp+0Ch],0
    6695105e 7e0e            jle     HelperLibrary!ImportantFunction+0x1e
    66951060 8b4c2408        mov     ecx,dword ptr [esp+8]
    66951064 49              dec     ecx
    66951065 51              push    ecx
    66951066 e8b5ffffff      call    HelperLibrary+0x1020
    6695106b 83c404          add     esp,4
    6695106e b801000000      mov     eax,1
    66951073 c3              ret

    这是另一个没有EBP使用痕迹的函数。注意,它有参数,并且它使用来自ESP的直接偏移量来访问这些参数,ESP是FPO的信号。这个函数返回到哪里?好吧,在上一个函数返回后,ESP已经在当前值的ESP+0xC处。ImportantFunction再添加四个字节,然后返回,因此我们需要查看ESP+0x10:

    0:000> dps esp+0x10 L1
    0034fbac  00ed100f MainApp!wmain+0xf

    对!我们离开了动态链接库,回到符号区!因此,重建的堆栈如下所示:

    HelperLibrary!…somefunction…
    HelperLibrary!…someotherfunction…
    HelperLibrary!ImportantFunction
    MainApp!wmain

    这些都不是调试器提供的。作为参考,这里是相同的调用堆栈与符号(其中包含FPO信息)

    0:000> kv
    ChildEBP RetAddr  Args to Child             
    003dfee4 668e1040 00000001 668e106b 0000000f
     
    HelperLibrary!AnotherHelperFunction+0x14 (FPO: [0,0,0])
    003dfeec 668e106b 0000000f 010c100f 010c20f4
      HelperLibrary!HelperFunction+0x20 (FPO: [1,0,4])
    003dfef4 010c100f 010c20f4 00000010 00000001
      HelperLibrary!ImportantFunction+0x1b (FPO: [3,0,0])
    003dff04 010c1191 00000001 00771b78 00771c10
      MainApp!wmain+0xf (FPO: [2,0,0])
    003dff48 74ba339a 7efde000 003dff94 77479ed2
      MainApp!__tmainCRTStartup+0x122 (FPO: [Non-Fpo])
    003dff54 77479ed2 7efde000 777db3e9 00000000
      kernel32!BaseThreadInitThunk+0xe (FPO: [1,0,0])
    003dff94 77479ea5 010c12b2 7efde000 00000000
      ntdll!__RtlUserThreadStart+0x70 (FPO: [SEH])
    003dffac 00000000 010c12b2 7efde000 00000000
      ntdll!_RtlUserThreadStart+0x1b (FPO: [2,2,0])

  • 相关阅读:
    JVM学习笔记之认识JDK(一)
    C#发送邮件异常:根据验证过程,远程证书无效
    windows下使用mysql双机热备功能
    批处理实现mysql的备份
    WebApi FormData+文件长传 异步+同步实现
    Oracle中已知字段名查询所在的表名
    mstsc遇到CredSSP加密Oracle修正
    使用subgit进行svn迁移至git(branch,tags)
    使用guava进行对字符串的加锁
    使用spring-data-solr做solr客户端
  • 原文地址:https://www.cnblogs.com/yilang/p/12012830.html
Copyright © 2011-2022 走看看