有运行时代码修改的聪明案例吗?

发布于 2024-10-29 08:49:07 字数 147 浏览 9 评论 0原文

您能想到运行时代码修改(程序在运行时修改自己的代码)的任何合法(智能)用途吗?

现代操作系统似乎不赞成执行此操作的程序,因为病毒已使用此技术来避免检测。

我能想到的就是某种运行时优化,通过在运行时了解一些在编译时无法知道的东西来删除或添加一些代码。

Can you think of any legitimate (smart) uses for runtime code modification (program modifying it's own code at runtime)?

Modern operating systems seem to frown upon programs that do this since this technique has been used by viruses to avoid detection.

All I can think of is some kind of runtime optimization that would remove or add some code by knowing something at runtime which cannot be known at compile time.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

梦醒时光 2024-11-05 08:49:07

代码修改有很多有效的案例。在运行时生成代码可用于:

  • 某些虚拟机使用JIT 编译来提高性能。
  • 动态生成专用函数在计算机图形学中早已很常见。例如,请参见 Rob Pike、Bart Locanthi 和 John Reiser Blit 上位图图形的硬件软件权衡 (1984)或者 Chris Lattner 的 帖子 (2006) 关于 Apple 在运行时使用 LLVM OpenGL 堆栈中的代码专业化。
  • 在某些情况下,软件会采用一种称为“蹦床”的技术,该技术涉及在堆栈(或其他位置)上动态创建代码。例如 GCC 的嵌套函数一些Unices的信号机制

有时代码会在运行时转换为代码(这称为动态二进制翻译):

  • 模拟器,例如Apple 的 Rosetta 使用此技术来加速仿真。另一个例子是 Transmeta 的代码变形软件
  • 复杂的调试器和分析器,例如ValgrindPin 使用它在执行时检测您的代码。
  • 在对 x86 指令集进行扩展之前,像 VMWare 这样的虚拟化软件无法直接在虚拟机内运行特权 x86 代码。相反,它必须即时将任何有问题的指令翻译为更合适的自定义代码。

代码修改可用于解决指令集的限制:

  • 曾经有一段时间(我知道很久以前),计算机没有指令从子例程返回或间接寻址内存。自修改代码是实现子例程、指针和数组的唯一方法。

更多代码修改情况:

  • 许多调试器替换指令来实现断点
  • 一些动态链接器在运行时修改代码。 本文提供了有关 Windows DLL 运行时重定位的一些背景知识,Windows DLL 实际上是一种代码形式修改。

There are many valid cases for code modification. Generating code at run time can be useful for:

  • Some virtual machines use JIT compilation to improve performance.
  • Generating specialized functions on the fly has long been common in computer graphics. See e.g. Rob Pike and Bart Locanthi and John Reiser Hardware Software Tradeoffs for Bitmap Graphics on the Blit (1984) or this posting (2006) by Chris Lattner on Apple's use of LLVM for runtime code specialization in their OpenGL stack.
  • In some cases software resorts to a technique known as trampoline which involves the dynamic creation of code on the stack (or another place). Examples are GCC's nested functions and the signal mechanism of some Unices.

Sometimes code is translated into code at runtime (this is called dynamic binary translation):

  • Emulators like Apple's Rosetta use this technique to speed up emulation. Another example is Transmeta's code morphing software.
  • Sophisticated debuggers and profilers like Valgrind or Pin use it to instrument your code while it is being executed.
  • Before extensions were made to the x86 instruction set, virtualization software like VMWare could not directly run privileged x86 code inside virtual machines. Instead it had to translate any problematic instructions on the fly into more appropriate custom code.

Code modification can be used to work around limitations of the instruction set:

  • There was a time (long ago, I know), when computers had no instructions to return from a subroutine or to indirectly address memory. Self modifying code was the only way to implement subroutines, pointers and arrays.

More cases of code modification:

  • Many debuggers replace instructions to implement breakpoints.
  • Some dynamic linkers modify code at runtime. This article provides some background on the runtime relocation of Windows DLLs, which is effectively a form of code modification.
意犹 2024-11-05 08:49:07

这已经在计算机图形学中完成,特别是用于优化目的的软件渲染器。在运行时,会检查许多参数的状态,并生成光栅化器代码的优化版本(可能消除大量条件),这允许更快地渲染图形基元(例如三角形)。

This has been done in computer graphics, specifically software renderers for optimization purposes. At runtime the state of many parameters is examined and an optimized version of the rasterizer code is generated (potentially eliminating a lot of conditionals) which allows one to render graphics primitives e.g. triangles much faster.

秋心╮凉 2024-11-05 08:49:07

一个合理的原因是 asm 指令集缺少一些必要的指令,您可以自己构建这些指令。示例:在 x86 上,无法为寄存器中的变量创建中断(例如,使用 ax 中的中断号创建中断)。仅允许编码到操作码中的常量数字。通过自我修改代码,我们可以模拟这种行为。

One valid reason is because the asm instruction set lack some necessary instruction, which you could build yourself. Example: On x86 there is no way to create an interrupt to a variable in a register (e.g. make interrupt with interrupt number in ax). Only const numbers coded into the opcode were allowed. With selfmodifying code one could emulate this behaviour.

別甾虛僞 2024-11-05 08:49:07

一些编译器过去使用它进行静态变量初始化,避免了后续访问的条件成本。换句话说,他们通过在第一次执行时用空操作覆盖该代码来实现“仅执行该代码一次”。

Some compilers used to use it for static variable initialization, avoiding the cost of a conditional for subsequent accesses. In other words they implement "execute this code only once" by overwriting that code with no-ops the first time it's executed.

如若梦似彩虹 2024-11-05 08:49:07

有很多情况:

  • 病毒通常使用自修改代码在执行之前“反混淆”其代码,但该技术也可用于阻止逆向工程、破解和不需要的黑客攻击
  • 在某些情况下,运行时可能存在特定点(例如,在读取配置文件后立即)当知道 - 在进程的剩余生命周期中 - 将始终或永远不会采用特定分支时:而不是不必要地检查某些变量来确定分支的方式,分支指令本身可以相应修改
    • 例如,我们可能会知道,只会处理一种可能的派生类型,这样虚拟调度就可以替换为特定的调用
    • 检测到哪些硬件可用后,可以对匹配代码的使用进行硬编码
  • 不必要的代码 进行硬编码。用无操作指令替换或跳过它,或者将下一位代码直接移位到位(如果使用与位置无关的操作码则更容易)
  • 为促进其自身调试而编写的代码可能会注入预期的陷阱/信号/中断指令由调试器在战略位置进行。
  • 一些基于用户输入的谓词表达式可能会被库编译为本机代码内
  • 联​​一些直到运行时才可见的简单操作(例如从动态加载的库)...
  • 有条件地添加自我检测/分析步骤
  • 裂缝可以实现为修改加载它们的代码的库(不完全是“自我”修改,但需要相同的技术和权限)。
  • ...

某些操作系统的安全模型意味着自修改代码在没有 root/admin 权限的情况下无法运行,这使得它对于通用用途来说不切实际。

来自维基百科:

在具有严格 W^X 安全性的操作系统下运行的应用程序软件无法执行允许写入的页面中的指令,只有操作系统本身才允许将指令写入内存并随后执行这些指令。

在此类操作系统上,即使是像 Java VM 这样的程序也需要 root/admin 权限才能执行其 JIT 代码。 (有关更多信息,请参阅http://en.wikipedia.org/wiki/W%5EX细节)

There are many cases:

  • Viruses commonly used self-modifying code to "deobfuscate" their code prior to execution, but that technique can also be useful in frustrating reverse engineering, cracking and unwanted hackery
  • In some cases, there can be a particular point during runtime (e.g. immediately after reading the config file) when it is known that - for the rest of the lifetime of the process - a particular branch will always or never be taken: rather than needlessly checking some variable to determine which way to branch, the branch instruction itself could be modified accordingly
    • e.g. It may become known that only one of the possible derived types will be handled, such that virtual dispatch can be replaced with a specific call
    • Having detected which hardware is available, use of a matching code may be hardcoded
  • Unnecessary code can be replaced with no-op instructions or a jump over it, or have the next bit of code shifted directly into place (easier if using position-independent opcodes)
  • Code written to facilitate its own debugging might inject a trap/signal/interrupt instruction expected by the debugger at a strategic location.
  • Some predicate expressions based on user input might be compiled into native code by a library
  • Inlining some simple operations that aren't visible until runtime (e.g. from dynamically loaded library)...
  • Conditionally adding self-instrumentation/profiling steps
  • Cracks may be implemented as libraries that modify the code that loads them (not "self" modifying exactly, but needs the same techniques and permissions).
  • ...

Some OSs' security models mean self-modifying code can't run without root/admin privileges, making it impractical for general-purpose use.

From Wikipedia:

Application software running under an operating system with strict W^X security cannot execute instructions in pages it is allowed to write to—only the operating system itself is allowed to both write instructions to memory and later execute those instructions.

On such OSes, even programs like the Java VM need root/admin privileges to execute their JIT code. (See http://en.wikipedia.org/wiki/W%5EX for more details)

温暖的光 2024-11-05 08:49:07

综合操作系统基本上根据 API 调用部分评估了您的程序,并用结果替换了操作系统代码。主要好处是大量的错误检查消失了(因为如果你的程序不会要求操作系统做一些愚蠢的事情,它就不需要检查)。

是的,这是运行时优化的一个例子。

The Synthesis OS basically partially evaluated your program with respect to API calls, and replaced OS code with the results. The main benefit is that lots of error checking went away (because if your program isn't going to ask the OS to do something stupid, it doesn't need to check).

Yes, that's an example of runtime optimization.

缱绻入梦 2024-11-05 08:49:07

许多年前,我花了一个早上尝试调试一些自修改代码,一条指令改变了下一条指令的目标地址,即我正在计算分支地址。它是用汇编语言编写的,当我一次执行一条指令时,它运行得很好。但是当我运行该程序时它失败了。最终,我意识到机器正在从内存中获取 2 条指令,并且(因为指令已放置在内存中)我正在修改的指令已经被获取,因此机器正在执行该指令的未修改(不正确)版本。当然,当我调试时,一次只执行一条指令。

我的观点是,自修改代码的测试/调试可能非常麻烦,并且通常对机器的行为(无论是硬件还是虚拟)有隐藏的假设。此外,系统永远无法在(现在的)多核机器上执行的各个线程/进程之间共享代码页。这抵消了虚拟内存等的许多好处。它还会使在硬件级别完成的分支优化失效。

(注意 - 我没有将 JIT 包含在自修改代码的类别中。JIT 正在从代码的一种表示形式转换为另一种表示形式,它不会修改代码)

总而言之,这只是一个坏主意 - 真的整洁,确实晦涩,但真的很糟糕。

当然,如果您只有 8080 和 ~512 字节的内存,您可能不得不采取这种做法。

Many years ago i spent a morning trying to debug some self-modifying code, one instruction changed the target address of the following instruction, i.e., i was computing a branch address. It was written in assembly language and worked perfectly when i stepped through the program one instruction at a time. But when i ran the program it failed. Eventually, i realized that the machine was fetching 2 instructions from memory and (as the instructions were laid out in memory) the instruction i was modifying had already been fetched and thus the machine was executing the unmodified (incorrect) version of the instruction. Of course, when i was debugging, it was only doing one instruction at a time.

My point, self-modifying code can be extremely nasty to test/debug and often has hidden assumptions as to the behavior of the machine (be it hardware or virtual). Moreover, the system could never share code pages among the various threads/processes executing on the (now) multi-core machines. This defeats many of the benefits to virtual memory, etc. It also would invalidate branch optimizations done at the hardware level.

(Note - i do not included JIT in the category of self-modifying code. JIT is translating from one representation of the code to an alternate representation, it is not modifying the code)

All, in all, it's just a bad idea - really neat, really obscure, but really bad.

of course - if all you have is an 8080 and ~512 bytes of memory you might have to resort to such practices.

半枫 2024-11-05 08:49:07

从操作系统内核的角度来看,每个即时编译器和链接器运行时都会执行程序文本自我修改。突出的例子是 Google 的 V8 ECMA 脚本解释器。

From the view of an operating system kernel every Just In Time Compiler and Linker Runtime performs program text self modification. Prominent example would be Google's V8 ECMA Script Interpreter.

眼眸里的那抹悲凉 2024-11-05 08:49:07

自修改代码(实际上是“自生成”代码)的另一个原因是为了实现性能的即时编译机制。例如,读取代数表达式并根据一系列输入参数进行计算的程序可以在说明计算之前将表达式转换为机器代码。

Another reason of self-modifying code (actually a "self-generating" code) is to implement a Just-In-time compilation mechanism for performance. E.g. a program that reads an algebric expression and calculates it on a range of input parameters may convert the expression in machine code before stating the calculation.

半枫 2024-11-05 08:49:07

你知道老栗子,硬件和软件之间没有逻辑区别……也可以说代码和数据之间没有逻辑区别。

什么是自修改代码?将值放入执行流中的代码,以便可以将其解释为命令而不是数据。当然,函数式语言中的理论观点认为实际上没有区别。我是说 on e 可以在命令式语言和编译器/解释器中以简单的方式做到这一点,而无需假定平等地位。

我指的是实际意义上的数据可以改变程序执行路径(在某种意义上这是非常明显的)。我正在考虑类似编译器的东西,它创建一个表(数据数组),在解析时遍历该表,从一个状态移动到另一个状态(并且还修改其他变量),就像程序如何从一个命令移动到另一个命令一样,修改过程中的变量。

因此,即使在编译器创建代码空间并引用完全独立的数据空间(堆)的常见情况下,人们仍然可以修改数据以显式更改执行路径。

You know the old chestnut that there is no logical difference between hardware and software...one can also say that there is no logical difference between code and data.

What is self-modifying code? Code that puts values in the execution stream so that it can be imterpreted not as data but as a command. Sure there is the theoretical viewpoint in functional languages that there really is no difference. I'm saying on e can do this in a straightforward manner in imperative languages and compiler/interpreters without the presumption of equal status.

What I'm referring to is in the practical sense that data can alter program execution paths (in some sense this is extremely obvious). I am thinking of something like a compiler-compiler that creates a table (an array of data) that one traverses through in parsing, moving from state to state (and also modifying other variables), just like how a program moves from command to command, modifying variables in the process.

So even in the usual instance of where a compiler creates code space and refers to a fully separate data space (the heap), one can still modify the data to explicitly change the execution path.

墟烟 2024-11-05 08:49:07

我已经实现了一个使用进化来创建最佳算法的程序。它使用自修改代码来修改DNA蓝图。

I have implemented a program using evolution to create the best algorithm. It used self-modifying code to modify the DNA blueprint.

遗心遗梦遗幸福 2024-11-05 08:49:07

一个用例是 EICAR 测试文件,它是一个合法的 DOS 可执行 COM 文件,用于测试防病毒程序。

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

它必须使用自代码修改,因为可执行文件必须仅包含 [21h-60h, 7Bh-7Dh] 范围内的可打印/可键入 ASCII 字符,这显着限制了可编码指令的数量。详细

信息解释


它也用于 floating> floating> floating> floating>在 DOS 中,

某些编译器会发出 CD xx,其中 xx 的范围从 0x34-0x3B 代替 x87 浮点指令。由于cdint指令的操作代码,因此,如果没有X87协处理器,它将跳入中断34h-3bh并在软件中模仿该说明。否则,中断处理程序将用9B DX替换这两个字节,以便以后的执行将通过X87直接处理而无需仿真。

MS-DOS中X87浮点仿真的协议是什么?


另一种用法是在 在期间在期间优化代码Runtime

例如,在没有可变位移动的体系结构上(或者当它们非常慢)可以是模拟常量移位 当移位计数提前已知时,通过在控制到达该指令之前以及加载高速缓存运行之前更改指令中包含移位计数的立即字段,

它也可以用于将函数调用更改为当不同(微)架构有多个版本时,最优化的版本。例如,您有用标量、SSE2、AVX、AVX-512...编写的相同函数,并且根据当前的 CPU,您将选择最好的一个。可以使用Code Dispatcher在启动时设置的功能指针轻松完成,但是您还有一个间接级别,这对CPU不利。一些编译器支持函数多取代 自动编译到不同版本,然后在加载时,链接器将链接器将功能固定在所需的函数地址上那些。但是,如果您没有编译器和链接器支持,并且您也不希望间接呢?只需在启动时修改呼叫指令,而不是更改功能指针。现在呼叫都是静态的,可以通过CPU正确预测

One use case is the EICAR test file which is a legitimate DOS executable COM file for testing antivirus programs.

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

It has to use self code modification because the executable file must contain only printable/typeable ASCII characters in the range [21h-60h, 7Bh-7Dh] which limits the number of encodable instructions significantly

The details are explained here


It's also used for floating-point operation dispatching in DOS

Some compilers will emit CD xx with xx ranging from 0x34-0x3B in places of x87 floating-point instructions. Since CD is the opcode for int instruction, it'll jump into the interrupt 34h-3Bh and emulate that instruction in software if the x87 coprocessor is not available. Otherwise the interrupt handler will replace those 2 bytes with 9B Dx so that later executions will be handled directly by x87 without emulation.

What is the protocol for x87 floating point emulation in MS-DOS?


Another usage is to optimize code during runtime

For example on an architecture without variable bit shifts (or when they're very slow) then they can be emulated using only constant shifts when the shift count is known far in advance by changing the immediate field containing the shift count in the instruction before control reaches that instruction and before the cache is loaded for running

It can also be used to change function calls to the most optimized version when there are multiple versions for different (micro-)architectures. For example you have the same function written in scalar, SSE2, AVX, AVX-512... and depending on the current CPU you'll choose the best one. It can be done easily using function pointers which are set at startup by the code dispatcher, but then you have one more level of indirection which is bad for the CPU. Some compilers support function multiversioning which automatically compiles to different versions, then at load time the linker will fix the function addresses to the desired ones. But what if you don't have compiler and linker support, and you don't want the indirection either? Just modify the call instructions yourself at startup instead of changing the function pointers. Now the calls are all static and can be predicted correctly by the CPU

半葬歌 2024-11-05 08:49:07

我对不断更新的数据库进行统计分析。每次执行代码时,我的统计模型都会被编写和重新编写,以适应可用的新数据。

I run statistical analyses against a continually updated database. My statistical model is written and re-written each time the code is executed to accommodate new data that become available.

祁梦 2024-11-05 08:49:07

Linux 内核具有可加载的内核模块,可以做到这一点。

Emacs也有这个功能,而且我一直在使用它。

任何支持动态插件架构的东西本质上都是在运行时修改它的代码。

The Linux Kernel has Loadable Kernel Modules which do just that.

Emacs also has this ability and I use it all the time.

Anything that supports a dynamic plugin architecture is essentially modifying it code at runtime.

沫尐诺 2024-11-05 08:49:07

可以使用这个的场景是一个学习程序。为了响应用户输入,程序学习一种新算法:

  1. 相似的算法,它会在现有的代码库中查找相似的算法
  2. 如果代码库中没有
  3. ,如果存在相似的算法,程序只会添加一个新算法,程序(也许在用户的一些帮助下)修改现有算法,使其能够同时满足旧目的和新目的

有一个问题如何在 Java 中做到这一点:Java 代码自我修改的可能性有哪些?

The scenario in which this can be used is a learning program. In response to user input the program learns a new algorithm:

  1. it looks up the existing code base for a similar algorithm
  2. if no similar algorithm is in the code base, the program just adds a new algorithm
  3. if a similar algorithm exists, the program (perhaps with some help from the user) modifies the existing algorithm to be able to serve both the old purpose and the new purpose

There is a question how to do that in Java: What are the possibilities for self-modification of Java code?

浮生面具三千个 2024-11-05 08:49:07

最好的版本可能是 Lisp Macros。与 C 宏不同,C 宏只是一个预处理器,Lisp 让您可以随时访问整个编程语言。这是 lisp 中最强大的功能,在任何其他语言中都不存在。

我绝不是专家,但请一位口齿不清的家伙谈论一下!这是有原因的
他们说 Lisp 是最强大的语言,但聪明人不认为他们可能是对的。

The best version of this may be Lisp Macros. Unlike C macros which are just a preprocessor Lisp lets you have access to the entire programming language at all times. This is about the most powerful feature in lisp and does not exist in any other language.

I am by no means an expert but get one of the lisp guys talking about it! There is a reason
that they say that Lisp is the most powerful language around and the smart folks no that they are probably right.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文