我正在处理的可执行转储中有很多

... code ...
call sub_...
nop
... code ...


模式。它们出现在子例程的中间,我认为它们不能用于对齐目的。我对此构造的起源感到好奇。

该程序已经打包,所以我不确定调用-nop对是最初存在还是在解压缩后出现。

评论

它可能是反调试器方案的一部分-该函数可以检查其返回地址中的内容。如果不是nop,则可能是调试器断点。这使调试器无法通过使用除断点之外的常规int 3指令以外的其他方式隐藏。
有趣的技巧,但这里似乎没有用到它-因为我欺骗了返回地址,将其作为挂钩技术的一部分,并且程序运行正常。
由于您已经接受了其他答案,因此我认为反调试技巧可能不适用于您。但是我想添加它,以防有人在一两年内用谷歌搜索该问题。

这是关于堆栈溢出的类似问题。

#1 楼

加壳程序可能已将对导入功能的间接调用替换为对另一个函数的直接(相对)调用。这会使指令缩短一个字节,从而需要使用一个NOP进行填充:

FF 15 ?? ?? ?? ??  call cs:[__imp_foo] ; RIP-relative offs32 indirect
E8 ?? ?? ?? ??     call foo            ; RIP-relative offs32


评论


但是该调用在同一模块内。为什么要使用import?

–uranix
15年1月16日在14:15

一些打包程序(“保护程序”)保持常驻,并为打包的可执行文件提供API。该可执行文件是针对DLL生成的,该DLL通过IAT中的地址槽导出包装API,因此由编译器发出了间接调用。但是包装程序可以选择在加载/拆包过程中将那些导入解析为直接调用。这样,加载的过程就不会有一个令人生畏的IAT讲述故事。 ``最小重建''调试版本也倾向于使用奇怪的重击/保留机制,但是调试版本很少被打包和交付...

– DarthGizka
15年1月16日在14:28

在32位模式下,可以将对固定目标的所有间接调用都转换为直接调用(再次取消IAT),但在64位模式下,如果距离超过2 ^ 31字节,则需要蹦床重击。

– DarthGizka
2015年1月16日14:32

可能只是链接程序,而不是打包程序。 blogs.msdn.microsoft.com/russellk/2005/03/20/lnk4217

–伊戈尔·斯科钦斯基♦
18年5月12日在11:38

#2 楼

NOP之后的第一条指令很可能是其他地方不同分支/跳转的目标。为了更好地利用i-cache和更好地进行BTB预测,通常最好跳转到对齐的目标:


11.5代码对齐

大多数微处理器都以对齐方式获取代码16字节或32字节块。

如果重要的子例程条目或跳转
标签恰好在16字节块的末尾附近,则
微处理器在获取时将仅获得一些有用的代码字节
那段代码。它可能还必须提取下一个16个字节,然后才能解码标签后的第一条指令。通过
16对齐重要的子例程条目和循环条目,可以避免这种情况。

8对齐将确保第一条指令可以加载至少8个字节的代码提取,如果指令较小的话就足够了。

如果子例程是关键热点的一部分,并且不太可能执行前面的代码,则可以按
缓存行大小(通常为64个字节)对齐子例程条目。在相同的上下文中。


http://agner.org/optimize/optimizing_assembly.pdf#page=86

NOP只是一个填充,用于对齐以下说明。如在其他地方指出的那样,为此必须谨慎地添加填充,因为盲目添加填充可能会导致i-cache使用率降低,从而降低性能。注意:在其他体系结构(即非x86 / x86-64)中,有时需要在调用后进行NOP;由于问题是关于x86-64的,因此不适用。