我正在浏览 MSIL 并注意到有很多 指示。MSDN 文章称,如果操作码被修补,它们不会采取任何行动,而是用于填充空间。它们在调试版本中的使用比发布版本中的使用要多得多。我知道汇编语言中使用这些类型的语句来确保操作码适合字边界,但为什么 MSIL 中需要它?

有帮助吗?

解决方案

NOP 有多种用途:

  • 它们允许调试器在一行上放置断点,即使它与生成的代码中的其他断点组合在一起。
  • 它允许加载程序使用不同大小的目标偏移量来修补跳转。
  • 它允许代码块在特定边界对齐,这有利于缓存。
  • 它允许增量链接通过调用新部分来覆盖代码块,而不必担心整体函数大小的变化。

其他提示

以下是调试使用nops的方法:

语言编译器(C#,VB等)使用Nops来定义隐式序列点。这些告诉JIT编译器在哪里确保机器指令可以映射回IL指令。

Rick Byer关于 DebuggingModes.IgnoreSymbolStoreSequencePoints 的博客文章,解释了一些细节。

C#还会在呼叫指令后放置Nops,以便源中的返回站点位置是呼出而不是呼叫后的线路。

它为代码中的基于行的标记(例如断点)提供了机会,其中发布版本不会发出任何内容。

在针对特定处理器或体系结构进行优化时,它还可以使代码运行得更快:

处理器长时间使用大致并行工作的多个流水线,因此可以同时完成两个独立的指令。在具有两个流水线的简单处理器上,第一个可以支持所有指令,而第二个仅支持一个子集。此外,当必须等待前一条指令尚未完成的结果时,管道之间会有一些停顿。

在这些情况下,专用的 nop 可能会强制下一条指令进入特定的管道(第一条,或者不是第一条),并改善后续指令的配对,以便< em> nop 超过摊销。

多德! No-op太棒了!这是一条除了消耗时间之外什么也不做的指令。在昏暗的黑暗时代,你会用它在关键循环中进行微调,或者更重要的是作为自修改代码中的填充物。

在我最近工作的一个处理器(四年)中,NOP用于确保先前的操作在下一个操作开始之前完成。例如:

加载值进行注册(需要8个周期) nop 8 将1添加到注册

这确保寄存器在添加操作之前具有正确的值。

另一个用途是填写执行单元,例如必须是一定大小(32字节)的中断向量,因为vector0的地址对于向量1 0x20来说是0,依此类推,因此编译器放置了NOP在那里,如果需要的话。

它们的一个经典用途是,您的调试器始终可以将源代码行与IL指令相关联。

他们可以在调试时使用它们来支持编辑并继续。它为调试器提供了工作空间,可以用新的代码替换旧代码而无需更改偏移等。

Nop在缓冲区溢出漏洞的有效载荷中至关重要。

正如ddaa所说,nops让你考虑堆栈中的差异,这样当你覆盖返回地址时它会跳转到nop sled(连续很多nops)然后正确地命中可执行代码,而不是跳转到不是开头的指令中的某个字节。

50岁太晚了,但是嘿。

如果您手动输入汇编代码,Nop非常有用。 如果你必须删除代码,你可以删除旧的操作码。

类似地,您可以通过覆盖某些操作码来插入新代码并跳转到其他位置。你把覆盖的操作码放在那里,然后插入你的新代码。准备好后,你会跳回来。

有时您必须使用可用的工具。在某些情况下,这只是一个非常基本的机器码编辑器。

现在有了编译器,这些技术再也没有任何意义了。

在软件破解场景中,解锁应用程序的一种经典方法是使用NOP修补检查密钥或注册或时间段的行或什么不做,以便它什么也不做,只需继续启动应用程序就好像它已注册。

我还看到代码中的NOP修改了自己,以模糊它作为占位符的作用(veeery旧版本保护)。

有点非正统的用法是 NOP-Slides ,用于缓冲区溢出攻击。

它们允许链接器用较短的指令(短跳转)替换较长的指令(通常为长跳转)。 NOP占用了额外的空间 - 代码无法移动,因为它会阻止其他跳跃工作。这发生在链接时,因此编译器无法知道长跳或短跳是否合适。

至少,这是他们的传统用途之一。

这不是您具体问题的答案,但在过去,您可以使用NOP来填充分支延迟槽,如果你无法用其他有用的指令填充它。

.NET编译器是否对齐MSIL输出?我认为它可能有助于加快对IL的访问...另外,我的理解是它的设计是可移植的,并且在其他一些硬件平台上需要对齐访问。

我学到的第一个程序集是SPARC所以我熟悉分支延迟槽,如果你不能用另一个指令填充它,通常是你要放在分支指令上面的指令或者在循环中增加一个计数器,你使用NOP。

我不熟悉破解,但我认为使用NOP覆盖堆栈很常见,因此您无需准确计算恶意功能的开始位置。

我使用NOP自动调整进入ISR后累积的延迟。指甲时间非常方便。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top