我有一个应用程序可以创建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
函数?
答案 0 :(得分:6)
将块分组为函数的最简单算法是:
call some_address
说明进行调用的所有地址ret
结尾,则表示您已完成该功能,否则问题:
ret
而不是call [some_address]
call some_address
而不是jump some_address
后紧跟call some_address
ret
可以使用call some_address
+ push some_address
或ret
+ push some_address
您可以使用一些启发式方法通过查找最常见的prolog指令序列来确定函数的起始位置:
jmp some_other_address
同样,如果在禁止帧指针的情况下编译函数(即他们使用push ebp
mov ebp, esp
而不是esp
来访问堆栈中的参数,这可能不起作用,这是可能的)。
编译器(例如MSVC ++)也可以使用ebp
指令填充功能间空间,这也可以作为即将开始的函数的提示。
至于区分各种调用约定,它可能是最简单的查看符号(当然,如果你有它们)。 MSVC++ generates different name prefixes and suffixes, e.g.:
如果无法从符号中提取此信息,则必须分析代码以查看参数如何传递给函数以及函数或其调用者是否将其从堆栈中删除。
答案 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这样的软件。 call
和ret
操作表示函数调用。但是,代码不会按顺序运行,并且可以在整个地方进行跳转。 call dword ptr [edx]
取决于edx寄存器,因此除非您进行运行时调试,否则您无法知道它的去向。
要识别fastcall功能,您必须查看参数的传递方式。 Fastcall会将前两个指针大小的参数放在edx和ecx寄存器中,其中stdcall会将它们压入堆栈。有关说明,请参阅this article。