如何将反汇编的C代码划分为函数?

时间:2013-02-07 16:24:46

标签: c windows assembly x86

我有一个应用程序可以创建win32进程的.text段转储。然后它将基本块上的代码分开。基本块是一组一个接一个地执行的指令(跳转始终是这些基本块的最后指令)。这是一个例子:

Basic block 1
    mov ecx, dword ptr [ecx]
    test ecx, ecx
    je 00401013h

Basic block 2
    mov eax, dword ptr [ecx]
    call dword ptr [eax+08h]

Basic block 3
    test eax, eax
    je 0040100Ah

Basic block 4
    mov edx, dword ptr [eax]
    push 00000001h
    mov ecx, eax
    call dword ptr [edx]

Basic block 5
    ret 000008h

现在我想在函数中对这些基本块进行分组 - 比如哪些基本块构成一个函数。什么是算法?我必须记住,一个函数中可能有许多ret指令。如何检测fast_call函数?

3 个答案:

答案 0 :(得分:6)

将块分组为函数的最简单算法是:

  1. 记下使用call some_address说明进行调用的所有地址
  2. 如果此地址后面的第一个块以ret结尾,则表示您已完成该功能,否则
  3. 跟随块中的跳转到另一个块,依此类推,直到你遵循所有可能的执行路径(记住条件跳转,每个路径将路径分成两个),所有路径都以{{1}完成}。你需要识别组织循环的跳转,这样你的程序本身就不会因为进入无限循环而挂起
  4. 问题:

    1. 可以通过从存储器中读取函数指针来间接地进行多次调用,例如,你有ret而不是call [some_address]
    2. 可以对计算的地址进行一些间接调用
    3. 在返回之前调用其他函数的函数可能会call some_address而不是jump some_address后紧跟call some_address
    4. ret可以使用call some_address + push some_addressret + push some_address
    5. 的组合进行模拟
    6. 某些功能可能会在其末尾共享代码(例如,它们有不同的入口点,但一个或多个出口点是相同的)
    7. 您可以使用一些启发式方法通过查找最常见的prolog指令序列来确定函数的起始位置:

      jmp some_other_address

      同样,如果在禁止帧指针的情况下编译函数(即他们使用push ebp mov ebp, esp 而不是esp来访问堆栈中的参数,这可能不起作用,这是可能的)。

      编译器(例如MSVC ++)也可以使用ebp指令填充功能间空间,这也可以作为即将开始的函数的提示。

      至于区分各种调用约定,它可能是最简单的查看符号(当然,如果你有它们)。 MSVC++ generates different name prefixes and suffixes, e.g.

      • _function - cdecl
      • _function @ number - stdcall
      • @ function @ number - fastcall

      如果无法从符号中提取此信息,则必须分析代码以查看参数如何传递给函数以及函数或其调用者是否将其从堆栈中删除。

答案 1 :(得分:3)

您可以使用enter来表示函数的开头,或certain code which sets up a frame

push ebp
mov  ebp, esp
sub  esp, (bytes for "local" stack space)

稍后您会在调用leave之前找到相反的代码(或ret):

mov esp, ebp
pop ebp

您还可以使用本地堆栈空间的字节数来标识局部变量。

Identifying thiscall, fastcall等会对call之前的代码进行一些分析,这些代码使用初始位置和对使用/清理的寄存器的评估。

答案 2 :(得分:1)

看看像windasm或ollydbg这样的软件。 callret操作表示函数调用。但是,代码不会按顺序运行,并且可以在整个地方进行跳转。 call dword ptr [edx]取决于edx寄存器,因此除非您进行运行时调试,否则您无法知道它的去向。

要识别fastcall功能,您必须查看参数的传递方式。 Fastcall会将前两个指针大小的参数放在edx和ecx寄存器中,其中stdcall会将它们压入堆栈。有关说明,请参阅this article