Delphi Pascal Try..Except..Finally 块

逆向工程 拆卸 反编译
2021-06-12 04:24:27

我在对特定的 Delphi Pascal .exe 进行逆向工程时遇到了麻烦(旧的 vsn.,1995 年之前,所以可能是 v.3)。从系统调用中我知道这可能是一个try..except..finally块,但我无法通过代码找到“正常”路由,except以及(可能)finally块是什么。

程序集如下所示:

782CFC  33 C0                   xor    eax, eax
782CFE  55                      push   ebp
782CFF  68 (782E37)             push   _FINALLY_A_0_782E37
782D04  64 FF 30                push   dword ptr fs:[eax]
782D07  64 89 20                mov    dword ptr fs:[eax], esp

            _try_0_782D0A:
782D0A  8B D3                   mov    edx, ebx
782D0C  8B C6                   mov    eax, esi
782D0E  E8 D1 F3 FF FF          call   ...unrelated...
782D13  8D 56 1C                lea    edx, [esi+1Ch]
.. lots of regular code here ..
.. ending with ..
782E17  8B 18                   mov    ebx, dword ptr [eax]
782E19  FF 53 20                call   dword ptr [ebx+20h]

        finally_1_782E1C:
782E1C  33 C0                   xor    eax, eax
782E1E  5A                      pop    edx
782E1F  59                      pop    ecx
782E20  59                      pop    ecx
782E21  64 89 10                mov    dword ptr fs:[eax], edx
782E24  68 (782E3E)             push   _end_1_782E3E

                @block_L:
782E29  8D 45 F4                lea    eax, [ebp + local_0C]
782E2C  BA 02 00 00 00          mov    edx, 2
782E31  E8 12 E3 F7 FF          call   System.@LStrArrayClr
782E36  C3                      retn

            _FINALLY_A_0_782E37:
782E37  E9 B4 E2 F7 FF          jmp    System.@HandleFinally

            _FINALLY_B_0_782E3C:
782E3C  EB EB                   jmp    @block_L
                ; -------

            _end_1_782E3E:
782E3E  5F                      pop    edi
782E3F  5E                      pop    esi
782E40  5B                      pop    ebx
782E41  8B E5                   mov    esp, ebp
782E43  5D                      pop    ebp
782E44  C3                      retn

-- 这是我自己的反汇编器的输出,但我认为其中没有错误。标签已自动命名,但我仍然无法遵循从一个块​​到下一个块的“逻辑”(如果有)。特别是下半部分,就在函数结语之前,让我感到困惑。

这些片段是否足以重建原始的try..finally块?


阅读伊戈尔的回答后:是的。考虑这些流程图:左图,在特殊处理 try/finally 块之前的原始流程图,右图,之后。

流程图

在最初的流程图中,我把从一个基本块到另一个基本块的每一次跳转都看作一个链接,代码流在每个retn. if(E-(F)-K) 和if-else(GH/IJ) 结构可以清楚地辨别。然而,推送返回地址和异常处理的其他“技巧”,打败了这一点,正如悬空块 N 和 O 所见——它们从无处“进入”——以及一个单独的块“M”,它来了不知从何而来。

在右边,我将异常块初始化与主代码(添加一个新块 B)分开,并将finalize结构连接成一个新块(M),它最终跳转到一个 AFTER_TRY(这恰好是最后一个退出块)。现在很明显

  1. 在序言之后, atry被启动;
  2. 所有代码都在finally块 M处结束,其中
  3. 那么代码总是存在于一个固定点。
1个回答

Delphi的工具try/ except/finally通过使用Win32结构化异常处理程序(SEH)。Matt Pietrek经典文章中解释了 SEH 的基础知识,因此我将跳到仅与 Delphi 相关的详细信息。

1.try进入

try块的入口,或保护需要在退出时销毁的自动变量的块(例如字符串)如下所示:

xor     eax, eax
push    ebp
push    offset SEH_HANDLER
push    dword ptr fs:[eax]
mov     fs:[eax], esp

这是设置 SEH 帧的典型方式。运行后,堆栈顶部将如下所示:

       +-----------+
ESP+00 |    next   | <- fs:[0] points here
       +-----------+
ESP+04 |  handler  |
       +-----------+
ESP+08 | saved_ebp |
       +-----------+

指向此结构的指针将传递给 SEH 处理程序。

2.try退出

try的末尾,SEH帧被拆除:

    xor     eax, eax
    pop     edx               ; pop 'next' into edx
    pop     ecx               ; pop handler
    pop     ecx               ; pop saved_ebp
    mov     fs:[eax], edx     ; move 'next' into fs:[0]

如果有finally处理程序或自动析构函数,那么它会像这样继续:

    push    offset AFTER_TRY  ; make it so the 'ret' will jump to AFTER_TRY
FINALLY_HANDLER:
    <destruct automatic variables created in the try block>
    <finally handler body>
    ret                       ; jumps to AFTER_TRY

否则有一个简单的跳转:

    jmp AFTER_TRY

3.finally处理程序

在程序使用finally语句的情况下,或者在try..finally编译器添加来保护自动变量的情况下,SEH 处理程序如下所示:

SEH_HANDLER:
    jmp     _HandleFinally
    jmp     FINALLY_HANDLER

4.except处理程序

如果程序使用except处理程序来捕获所有异常,则代码看起来有点不同:

SEH_HANDLER:
    jmp     _HandleAnyException
    <handler code>
    call    _DoneExcept

5.except on处理程序

如果程序用于except on...匹配被捕获的异常,编译器会生成一个包含一个或多个可能的异常类以及相应处理程序的表:

SEH_HANDLER:
    jmp     _HandleOnException
    dd <numExceptions>
    dd offset ExceptionClass1
    dd offset OnException1_handler
    dd offset ExceptionClass2
    dd offset OnException2_handler
    <...>

OnException1_handler:
    <handler code>
    call    _DoneExcept

OnException2_handler:
    <handler code>
    call    _DoneExcept

可能会有一些变化,但我想我已经涵盖了大部分。

的源代码_HandleFinally_HandleAnyException_HandleOnException_DoneExcept和其他一些例外相关的功能可以在发现system.pas在VCL源。