2010-11-19 10 views
4

有没有一种或多或少的可靠方法来判断内存中某个位置的数据是处理器指令还是某些其他数据的开始?从数据中告诉指令的x86方式

例如,E8 3F BD 6A 00可以是call相对的0x6ABD3F偏移指令(E8),或者它可能是三个字节属于一些其它的指令数据,接着push 06A 00)。

我知道这个问题听起来很愚蠢,可能没有简单的方法,但也许指令集的设计是考虑到这个问题,也许一些简单的代码检查位置周围的+ -100字节可以给出很可能的答案正确。

我想知道这一点,因为我扫描程序的代码并将所有调用替换为调用我的替换函数。它工作到目前为止,但在某些时候,并不是不可能的,因为我增加了我要替换的函数的数量,一些数据看起来就像是对该确切地址的函数调用,并且将被替换,并且这会导致程序以最意想不到的方式打破。我想减少这种可能性。

+1

让我想起我曾经使用的旧拆装机。它通过机器代码进行了9次不同的分析,试图从数据中分离出代码。而且经常犯错。 – 2010-11-19 14:33:31

回答

1

这是不可能区分数据与一般的指令,这是因为von Neumann architecture。分析代码很有帮助,反汇编工具可以做到这一点。 (This可能会有所帮助,如果您不能使用IDA Pro /它是商业/,请使用其他反汇编工具。)

1

纯代码具有非常特定的熵,因此很容易从大多数数据中提取。但是,这是一种概率方法,但是可以识别足够大的普通代码缓冲区(特别是编译器输出,当您也可以识别模式时,例如函数的开始)。另外,有些操作码是为未来保留的,其他操作码只能在内核模式下使用。在这种情况下,通过了解它们并知道如何计算指令长度(您可以尝试一下由Z0mbie编写的例程),您可以做到这一点。

5

如果是你的代码(或者另一个保留链接和调试信息的代码),最好的方法是扫描目标文件中的符号/重定位表。否则,没有可靠的方法来确定某个字节是否是导致数据丢失。

可能最有效的方法来限定数据是递归反汇编。 I. e。从实体点和发现的所有跳转目标中分解代码。但是这不是完全可靠的,因为它不会遍历跳转表(你可以尝试使用一些启发式的方法,但这也不完全可靠)。

解决您的问题的方法是将补丁函数替换为自身:使用跳转指令覆盖其开头的函数。

2

不幸的是,没有100%可靠的方法来区分代码和数据。从CPU的角度来看,只有当某些跳转操作码诱使处理器尝试执行字节,就好像它们是代码一样,代码才是代码。您可以尝试从程序入口点开始进行控制流分析,并遵循所有可能的执行路径,但是在存在指向函数的指针时可能会失败。

对于您的特定问题:我收集你想用自己的替换替换现有的功能。我建议你修补被替换的函数本身。即,而不是定位到foo()函数的所有调用,并用bar()的调用替换它们,只需将foo()的前几个字节替换为bar()jmp,而不是call:您不想混乱堆栈)。由于双跳,这不太令人满意,但它是可靠的。

+0

jmp技巧+1。 IIRC,这是Visual Studio实现导入库的方式。 – 2010-11-19 13:16:09

+0

我需要一个电话,而不是跳转,而且我确实想要堆叠。我想我的替换函数摆弄参数,调用原始函数,摆弄结果,并返回。 jmp会覆盖原始函数的一部分,我想保持它完整无缺。 虽然这是一种可能性,我可以在原始函数开始时将jmp放置到我的函数中,然后在我的函数更改代码的原始位置返回到原来的位置,调用它,并在返回时再次将jmp放在那里。 – AUTOMATIC 2010-11-19 13:39:27

+1

@AUTOMATIC - 在代码中使用JMP意味着您可以轻松地摆放堆栈。例如。代码是'CALL foo',foo的第一个字节被修补到'JMP bar',所以'bar()'中的堆栈与* foo()完全相同,当bar()返回时它不需要通过'foo()'直接返回给调用者。 – Roddy 2010-11-19 13:44:05

0

托马斯建议正确的想法。要正确实施它,你需要反汇编前几条指令(你会用JMP覆盖的部分)并生成一个简单的蹦床函数,执行它们然后跳转到原始函数的其余部分。

有图书馆为你做这个。众所周知的是Detours,但它的许可条件有些尴尬。用更宽松的许可证很好地实现相同的想法是Mhook