5

我有一个创建.textwin32 进程段转储的应用程序。然后它将代码划分为基本块。基本块是一组指令,它们总是一个接一个地执行(跳转总是这些基本块的最后一条指令)。这是一个例子:

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功能?

4

3 回答 3

6

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

  1. 记下所有呼叫的地址,并附有call some_address说明
  2. 如果这样的地址之后的第一个块以 结尾ret,则您已完成该功能,否则
  3. 跟随块中的跳转到另一个块,依此类推,直到您遵循所有可能的执行路径(请记住条件跳转,每个路径都将路径分成两部分)并且所有路径都以ret. 您需要识别组织循环的跳转,这样您的程序本身就不会因进入无限循环而挂起

问题:

  1. 可以通过从内存中读取函数指针来间接进行许多调用,例如,您将拥有call [some_address]而不是call some_address
  2. 可以对计算的地址进行一些间接调用
  3. 在返回之前调用其他函数的函数可能有jump some_address而不是call some_address紧随其后ret
  4. call some_address可以用push some_address+ retOR push some_address+的组合来模拟jmp some_other_address
  5. 一些函数可能在它们的末尾共享代码(例如,它们有不同的入口点,但一个或多个出口点是相同的)

您可以通过寻找最常见的 prolog 指令序列来使用一些启发式方法来确定函数从哪里开始:

push ebp
mov ebp, esp

esp同样,如果函数在编译时抑制了帧指针(即它们会使用而不是ebp访问堆栈上的参数,这是可能的),这可能不起作用。

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

至于区分各种调用约定,查看符号可能是最容易的(当然,如果你有它们的话)。MSVC++ 生成不同的名称前缀和后缀,例如

  • _function - cdecl
  • _function@number - 标准调用
  • @function@number - 快速调用

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

于 2013-02-07T16:53:59.937 回答
3

您可以使用存在enter来表示函数的开始,或者使用某些设置框架的代码

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

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

mov esp, ebp
pop ebp

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

识别thiscall,fastcall等将在 s 之前对call使用初始位置的代码进行一些分析,并对使用/清理的寄存器进行评估。

于 2013-02-07T16:30:27.663 回答
1

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

要识别 fastcall 函数,您必须查看参数是如何传递的。Fastcall 会将前两个指针大小的参数放在 edx 和 ecx 寄存器中,stdcall 会将它们压入堆栈。请参阅这篇文章以获得解释。

于 2013-02-07T16:39:27.280 回答