是否有一种或多或少可靠的方法来判断内存中某个位置的数据是处理器指令的开头还是其他一些数据?
例如,E8 3F BD 6A 00
可能是call
指令(E8
),相对偏移量为0x6ABD3F
,或者可能是属于某个其他指令的三个字节的数据,然后是push 0
(6A 00
)。
我知道这个问题听起来很愚蠢而且可能没有简单的方法,但也许指令集的设计考虑到了这个问题,也许一些简单的代码检查位置周围的+ -100字节可以给出一个非常可能正确的答案
我想知道这一点,因为我扫描程序的代码并用对替换的调用替换对某些函数的所有调用。它正在发挥作用,但是在某些时候,当我增加我正在替换的函数数量时,一些数据看起来就像是对该确切地址的函数调用,并且将被替换,并且这将导致程序以一种最意想不到的方式打破。我希望降低这种可能性。
答案 0 :(得分:5)
如果是您的代码(或另一个保留链接和调试信息的代码),最好的方法是扫描目标文件中的符号/重定位表。否则,没有可靠的方法来确定某个字节是否是构造或数据。
可能最有效的数据鉴定方法是递归分解。 I. e。从enty点和所有跳转目的地中分解代码。但这并不完全可靠,因为它不会遍历跳转表(您可以尝试使用一些启发式算法,但这也不完全可靠)。
你的问题的解决方案就是补丁功能被自己替换:用你的函数的跳转结构覆盖它的开头。
答案 1 :(得分:2)
不幸的是,没有100%可靠的方法来区分代码和数据。从CPU的角度来看,只有当某些跳转操作码诱导处理器尝试执行字节时,代码才是代码,就好像它们是代码一样。您可以尝试从程序入口点开始并遵循所有可能的执行路径进行控制流分析,但是如果存在指向函数的指针,则可能会失败。
针对您的具体问题:我认为您想要替换现有的功能并替换自己的功能。我建议你修补被替换的功能本身。即,不是找到对foo()
函数的所有调用,而是通过调用bar()
替换它们,只需将foo()
的第一个字节替换为bar()
(a jmp
,而不是call
:你不想弄乱堆栈)。由于双跳,这不太令人满意,但它是可靠的。
答案 2 :(得分:1)
通常不可能将数据与指令区分开来,这是因为von Neumann architecture 。分析代码是有帮助的,反汇编工具可以做到这一点。 (This可能会有所帮助。如果您无法使用IDA Pro /它是商业/,请使用其他反汇编工具。)
答案 3 :(得分:1)
普通代码具有非常特定的熵,因此很容易从大多数数据中删除它。但是,它是一种概率方法,但可以识别普通代码的足够大的缓冲区(尤其是编译器输出,当您还可以识别模式时,如函数的开头)。
此外,某些操作码是为将来保留的,其他操作码仅在内核模式下可用。在这种情况下,通过了解它们并知道如何计算指令长度(您可以尝试Z0mbie为此编写的例程),您可以这样做。
答案 4 :(得分:0)