为什么这段代码能够检测调试器?

16

下面的汇编代码为什么是一种反调试工具?

l1:
call l3
l2:
;some code
l3:
mov al, 0c3h
mov edi, offset l3
or ecx, -1
rep stosb
我知道 C3hRETN,而且我知道 stobs 会根据 edi 中的偏移量将 al 中的值写成操作码,并重复执行 ecx 次,因为有 rep 前缀。

我还知道如果在英特尔架构上预提取了 stobsstosw,它们将会运行其原始格式。

如果我们以调试模式运行程序,则预提取就不相关了,并且将运行 l2 标签(因为它是单步骤)。否则,如果没有调试器,它将在 l1 和 l3 之间来回切换,对吗?


1
这不还取决于它是否设置为单步吗?因为我真的看不出来调试器为什么会在其他情况下遇到问题。单步的规则有些不同。 - 0xC0000022L
http://stackoverflow.com/questions/12633599/anti-debug-using-prefetch-queue-doesnt-work-with-my-cpu - 0x90
2个回答

14
程序调试时(即单步执行),每一步都会清空预取队列(当中断发生时)。但是,正常执行时,对于rep stosb而言,这种情况不会发生。旧的处理器甚至在缓存区域有内存写操作时也不会清空预取队列,以支持可自修改代码,除了rep movsrep stosb。(如果我没记错,在 i7 处理器中最终修复了这个问题。)
这就是为什么如果有调试器(单步执行),代码将正确执行;当rep stosb被替换为ret后,将执行。当没有调试器时,rep stosb将继续执行,因为ecx是可能的最大值,它最终会写入不应该写入的位置并引发异常。
这种反调试技术在论文中有描述。

2
调试器在这里唯一的作用就是增加时间延迟。这可能是这种方法奏效的关键。英特尔(我猜想 AMD 也一样)手册明确表示,除非程序向 CPU 发出信号表明包含修改指令的缓存行已更改,否则自修改代码不能保证“正常工作”。这是为了使预取逻辑变得便宜;芯片设计师不希望硬件不断测试指令缓存行的每个字节是否仍然有效。
所以我猜调试器会导致l1调用l3,后者在rep stosb之后存储一个返回地址,由于单步调试器引入了长时间的延迟,强制缓存行包含l3被更改后重新获取,因此该返回地址得到执行。
如果没有调试器,我猜测 stosb 后面的指令(未显示)将被执行。如果它跳转到“没有调试器”的位置,那么跳转的成功将证明未使用单步调试器。
如果我在一个应用程序中发现这段代码,我将拒绝运行它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接