如果可以用等效的操作码替换汇编指令。
是的,您可以编译操作码,生成的机器代码将是相同的。
例如 x86-32 简短的无用汇编代码:
uselessFunc:
xor eax,eax
ret
Run Code Online (Sandbox Code Playgroud)
也可以用操作码编写:
uselessFunc:
db 0x31, 0xC0 ; opcode "xor eax,eax"
db 0xC3 ; opcode "ret"
Run Code Online (Sandbox Code Playgroud)
两个来源都将产生相同的三个字节的机器代码:31 C0 C3.
是否可以在运行时操作这些操作码
这与来源的形式完全无关。在运行时,您可以操作任何具有写访问权限(理想情况下为读+写访问)的内存。但是在修改操作码之后,如果要运行它们,还需要执行对该内存的访问。
在具有现代操作系统(如 linux)的现代 x86 机器上,这不是默认配置,默认情况下,代码段是只读 + 可执行的,而数据段是读+写,但不可执行,因此如果您尝试修改代码的操作码,你会在写入期间因无效内存访问而崩溃,如果你试图在数据段中执行操作码,你会触发 no-exec 错误。
因此,像 Java VM 和类似的应用程序,它们在运行时生成代码,然后执行它(“JIT”即时编译器.class在运行时将文件中的 java 操作码编译为本机机器代码,以获得更好的性能。重复执行)不仅生成/修改操作码,还管理与其他系统调用的目标内存页,使它们首先可写,然后将它们更改为 no-read+exec 代码内存页。即通常这是可能的,但在许多目标环境中,您必须使用额外的系统服务才能使其正常工作。
请记住,自修改代码在现代被认为是不好的做法,不仅因为它更难调试,而且如果以天真的方式使用,它可能会产生巨大的性能影响(再次例如在 x86 CPU 上只修改很少的操作码执行前的字节将使 CPU 中所有可能的缓存/预取行无效,使其暂停一段时间以重新读取/解码指令)。并且在某些 CPU 上,内存/缓存模型比在 x86 上弱,因此过晚修改操作码可能会被 CPU 忽略,因为它已经对旧内容进行了解码并将执行该内容。
但是只要您知道自己在做什么,就可以生成/修改操作码。它只是不以任何方式取决于源代码的形式,无论您如何生成原始二进制文件,无论您是使用汇编还是 C 语言源代码编写这些操作码,还是将它们直接作为字节值在六进制编辑器中编写。
使用上面的两个示例,在这两种情况下,您都可以执行以下操作:
mov byte [uselessFunc+1],0xD8 ; modify xor eax,eax to xor eax,ebx
Run Code Online (Sandbox Code Playgroud)
如果你会得到的目标内存区域的写访问,并能持续执行的权利,那么这将转xor eax,eax成xor eax,ebx在两种情况下。