51

自我修改代码有什么实际用途吗?

我知道它们可用于构建蠕虫/病毒,但我想知道程序员可能不得不使用自我修改代码是否有充分的理由。

有任何想法吗?假设的情况也是受欢迎的。

4

15 回答 15

51

事实证明,关于“自我修改代码”的维基百科条目有一个很棒的列表:

  1. 状态相关循环的半自动优化
  2. 运行时代码生成,或在运行时或加载时(例如,在实时图形领域很流行)中的算法的专门化,例如准备代码以执行特定调用中描述的键比较的通用排序实用程序。
  3. 更改对象的内联状态,或模拟闭包的高级构造。
  4. 修补子程序地址调用,通常在加载动态库时进行,或者在每次调用时修补子程序对其参数的内部引用,以便使用它们的实际地址。这是否被视为“自我修改代码”是一个术语的例子。
  5. 进化计算系统,例如遗传编程。
  6. 隐藏代码以防止逆向工程,例如通过使用反汇编器或调试器。
  7. 隐藏代码以逃避病毒/间谍软件扫描软件等的检测。
  8. 使用重复操作码的滚动模式填充 100% 的内存(在某些架构中),以擦除所有程序和数据,或烧毁硬件
  9. 压缩要在运行时解压缩和执行的代码,例如,当内存或磁盘空间有限时。
  10. 一些非常有限的指令集别无选择,只能使用自修改代码来实现某些功能。例如,仅使用减法和分支如果负“指令”的“单指令集计算机”机器不能进行间接复制(类似于 C 编程中的“*a = **b”语言)而不使用自修改代码。
  11. 更改容错指令

关于使用自我修改代码阻止黑客的观点:

在几次固件更新过程中,DirectTV 在他们的智能卡上慢慢组装了一个程序,以销毁被黑客入侵以非法接收未付费频道的卡。有关更多信息,请参阅 Jeff 关于黑色星期天黑客的 Coding Horror 文章。

于 2009-02-05T16:37:41.827 回答
15

我见过自修改代码用于:

  1. 速度优化,通过让程序在运行中为自己编写更多代码

  2. 混淆,使逆向工程更加困难

于 2009-02-05T16:37:56.943 回答
11

在以前 RAM 有限的时代,使用自我修改代码来节省内存。现在,例如像UPX这样的应用程序压缩实用程序用于在加载应用程序的压缩图像后解压缩/修改自己的代码。

于 2009-02-05T16:40:10.183 回答
7

因为 Commodore 64 没有很多寄存器并且有一个 1Mhz 的处理器。当您需要读取一个内存地址偏移量时,修改源更容易。

@Reader:
LDA $C000
STA $D020
INC Reader+1
JMP Reader

无论如何,那是我最后一次编写自修改代码 :-)

于 2009-02-05T17:02:17.843 回答
7

人工智能?

于 2009-02-05T20:09:59.043 回答
6

因为它真的很酷,有时这就是足够的理由。

于 2010-01-06T13:36:49.457 回答
6

1960 年代的汇编语言使用自修改代码来实现没有堆栈的函数调用。

Knuth,v1,第 1 版,第 182 页:

MAX100  STJ   EXIT   ;Subroutine linkage
        ENT3  100    ;M1. Initialize
        JMP   2F
1H      CMPA  X,3    ;M3. Compare
        JGE   *+3
2H      ENT2  0,3    ;M4. Change m
        LDA   X,3    ;(New maximum found)
        DEC3  1      ;M5. Decrease k
        J3P   1B     ;M2. All tested?
EXIT    JMP   *      ;Return to main program

在包含此编码作为子程序的较大程序中,单条指令“JMP MAX100”将导致寄存器 A 设置为位置 X + 1 到 X + 100 的当前最大值,最大值的位置将出现在 rI2 . 在这种情况下,子程序链接是通过指令“MAX100 STJ EXIT”和随后的“EXIT JMP *”实现的。由于 J 寄存器的操作方式,exit 指令将跳转到最初引用 MAX100 的位置之后的位置。

编辑:即使在这里有简短的解释,也可能很难看到发生了什么。在行MAX100 STJ EXIT中,MAX100是指令的标签(因此对于整个过程而言),STJ表示 STORE 跳转寄存器(我们刚刚来自的地方),EXIT表示标记为“EXIT”的内存位置是 STORE 的目标。EXIT,我们稍后看到的是最后一条指令的标签。所以它正在覆盖代码!但是,许多指令(包括STJ这里)隐式地仅覆盖指令字的操作数部分。所以JMP保持不变,并且*是一个虚拟令牌,因为放在那里真的没有任何意义,它只会被覆盖。


自修改代码也用于寄存器间接寻址不可用的地方,但您需要的地址就在寄存器中。PDP-1 语言:

dap .+1  ;deposit address part of accumulator in (IP+1)
lac xy   ;load accumulator with (ADDRESS) [xy is a dummy symbol, just like * above]

这两条指令ACC := (ACC)通过修改加载指令的操作数来执行。

像这样的修改是相对安全的,在古董建筑上,它们是必要的。

于 2011-09-17T05:53:18.670 回答
5

很多原因。在我的头顶上:

  • 运行时类构建和元编程。例如,拥有一个与 SQL 表建立连接并生成专门用于该表的客户端类的类工厂(具有列的访问器、查找方法等)。

  • 然后当然还有著名的 bitblt 示例和 regexp 类似物。

  • 基于 RT 信息的动态优化(a la tracking JITs)

  • 增值环境中 ada 样式泛型函数的子类型特化。

——马库斯

于 2009-02-05T17:37:09.803 回答
4

动态链接是一种自我修改(修补绝对和/或相对跳转位置)......不过,这通常由 O/S 的程序加载器完成。

于 2009-02-05T16:37:15.493 回答
3

神经网络是一种自修改代码。

然后是进化算法,它们会自我修改。

于 2009-02-05T16:38:18.907 回答
2

Mike Abrash 不久前为 Dobb 博士的日记描述了 Pixomatic 代码生成器:http ://www.ddj.com/architect/184405807 。那是一个软件 3d dx7(?) 兼容的光栅化器。

于 2009-02-05T16:51:02.703 回答
2

大声笑 - 我曾两次编写过自修改代码:

  1. 第一次学习汇编语言时,在我理解间接索引访问之前
  2. 意外地,作为汇编语言和 C 中的指针错误

我可以想象在某些情况下,自修改代码会比替代代码更有效,但没有什么明显的飞跃。一般来说,这是要避免的——调试噩梦等——除非你故意像上面提到的那样试图混淆。

于 2009-02-05T16:54:39.077 回答
1

实现自己的脚本语言的应用程序经常这样做。例如,数据库服务器经常以这种方式编译存储过程(或查询)。

于 2009-02-05T16:49:19.617 回答
0

SwiftShader 中的动态代码生成是一种自修改代码形式,使其能够在 CPU 上高效地实现 Direct3D 9。

于 2009-02-06T13:58:54.267 回答
0

现代示例:我有一个需要 JWT 令牌才能工作的脚本。要请求令牌,需要进行交互式登录,或者使用与新 JWT 令牌一起发布的刷新令牌。将刷新令牌存储在脚本中并在每次执行时更新它会很好

于 2020-01-23T08:05:54.053 回答