C程序可以修改其可执行文件吗?

我手上有点太多时间,开始想知道我是否可以写一个自修改程序。 为此,我在C中编写了一个“Hello World”,然后使用hex编辑器在已编译的可执行文件中查找“Hello World”字符串的位置。 是否可以修改此程序以打开自身并覆盖“Hello World”字符串?

char* str = "Hello World\n"; int main(int argc, char* argv) { printf(str); FILE * file = fopen(argv, "r+"); fseek(file, 0x1000, SEEK_SET); fputs("Goodbyewrld\n", file); fclose(file); return 0; } 

这不起作用,我假设有一些东西阻止它打开自己,因为我可以将它分成两个单独的程序(一个“Hello World”和一些修改它),它工作正常。

编辑:我的理解是,当程序运行时,它被完全加载到ram中。 因此,对于所有意图和目的,硬盘驱动器上的可执行文件都是副本。 为什么修改自己会有问题?

有解决方法吗?

谢谢

在Windows上,当程序运行时,整个*.exe文件使用Windows中的内存映射文件函数 映射到内存中 。 这意味着该文件不一定全部被加载,而是在访问文件的页面时按需加载。

以这种方式映射文件时,另一个应用程序(包括其自身)无法写入同一文件以在其运行时更改它。 (此外,在Windows上,无法重命名正在运行的可执行文件,但它可以在Linux和其他具有基于inode的文件系统的Unix系统上重命名)。

可以更改映射到内存中的位,但是如果这样做,操作系统会使用“写时复制”语义来执行此操作,这意味着底层文件不会在磁盘上更改,而是页面的副本( s)在内存中进行修改。 在被允许这样做之前,你通常必须在有问题的存储器上摆弄保护位(例如VirtualProtect )。

曾经有一段时间,在非常有限的内存环境中使用自修改代码的低级汇编程序常见。 然而,没有人这样做了,因为我们不是在相同的受限环境中运行,而现代处理器有很长的管道,如果你开始从它们下面改变代码就会非常沮丧。

如果您使用的是Windows,则可以执行以下操作:

分步示例:

  1. 使用PAGE_WRITECOPY保护在要修改的代码页上调用VirtualProtect()
  2. 修改代码页。
  3. 使用PAGE_EXECUTE保护在已修改的代码页上调用VirtualProtect()
  4. 调用FlushInstructionCache()

有关更多信息,请参阅如何修改内存中的可执行代码 (已存档:2010年8月)

它非常依赖于操作系统。 有些操作系统会锁定文件,因此您可以尝试通过在某处创建新副本来作弊,但是您只是运行该程序的另一个组合。

其他操作系统对文件进行安全检查,例如iPhone,因此编写它将需要大量工作,并且它作为只读文件存在。

对于其他系统,您甚至可能不知道文件的位置。

所有现在的答案或多或少都围绕着这样一个事实,即今天你不能轻易地进行自修改机器代码。 我同意这对今天的PC来说基本上是正确的。

但是,如果您真的想要看到自己的自修改代码,那么您有一些可能的可用性:

  • 试用微控制器,较简单的微控制器没有高级流水线。 我找到的最便宜和最快的选择是MSP430 USB-Stick

  • 如果您可以使用仿真,则可以为较旧的非流水线平台运行仿真器。

  • 如果你想要自我修改代码只是为了它的乐趣,你可以在Corewars上使用自毁代码(更确切地说是敌人摧毁)获得更多乐趣。

  • 如果你愿意从C转到说Lisp方言,编写代码的代码就很自然了。 我建议有意保持小的方案。

如果我们谈论在x86环境中这样做,那应该不是不可能的。 但应谨慎使用,因为x86指令是可变长度的。 长指令可能会覆盖以下指令,较短的指令会留下应该被禁止的覆盖指令的残留数据(NOP指令)。

当x86首次受到保护时,intel参考手册建议使用以下方法来调试对XO(仅执行)区域的访问:

  1. 创建一个新的空选择器(远指针的“高”部分)
  2. 将其属性设置为XO区域的属性
  3. 如果您只想查看其中的内容,则必须将新选择器的访问属性设置为RO DATA
  4. 如果要修改数据,则必须将访问属性设置为RW DATA

所以问题的答案就在最后一步。 如果您希望能够插入调试器所执行的断点指令,则RW是必需的。 比80286更现代的处理器具有内部调试寄存器,以启用非侵入式监视function,这可能导致发出断点。

从Win16开始,Windows提供了构建块。 它们可能仍然存在。 我认为微软将这类指针操作称为“thunking”。


我曾经用PL / M-86为DOS编写了一个非常快速的16位数据库引擎。 当Windows 3.1到达(在80386s上运行)时,我将其移植到Win16环境。 我想利用可用的32位内存,但没有PL / M-32可用(或Win32)。

解决问题我的程序以下列方式使用thunking

  1. 使用结构定义了32位远指针(sel_16:offs_32)
  2. 使用全局内存分配32位数据区(<=>> 64KB大小)并以16位远指针(sel_16:offs_16)格式接收它们
  3. 通过复制选择器填充结构中的数据,然后使用具有32位结果的16位乘法计算偏移量。
  4. 使用指令大小覆盖前缀将指针/结构加载到es:ebx中
  5. 使用指令大小和操作数大小前缀的组合来访问数据

一旦该机制无bug,它就可以顺利运行。 我的程序使用的最大内存区域是2304 * 2304双精度,大约40MB。 即使在今天,我也称之为“大”内存块。 1995年,它是典型SDRAM棒(128 MB PC100)的30%。

在许多平台上有不可移植的方法。 例如,在Windows中,您可以使用WriteProcessMemory()执行此操作。 然而,在2010年,这通常是一个非常糟糕的主意。 这不是你在汇编代码的DOS时代,为了节省空间这样做。 这很难做到,你基本上要求稳定性和安全性问题。 除非你像调试器那样做一些非常低级的事情,我会说不要为此烦恼,你所引入的问题并不值得你获得任何好处。

自修改代码用于内存中的修改,而不是文件中的修改(如UPX那样的运行时解包器)。 此外,程序的文件表示更难以操作,因为相对虚拟地址,可能的重定位和对大多数更新所需的头的修改(例如,通过将Hello world!更改为longer Hello World您需要扩展文件中的数据段)。

我建议你先学会记忆中的。 对于文件更新,最简单和更通用的方法是运行程序的副本,以便它可以修改原始程序。

编辑:不要忘记使用自修改代码的主要原因:

1)混淆,以便实际执行的代码不是您将通过文件的简单静态分析看到的代码。

2)性能,像JIT。

它们都不会从修改可执行文件中受益。

如果您在Windows上运行,我相信它会锁定该文件,以防止它在运行时被修改。 这就是为什么你经常需要退出程序才能安装更新。 在Linux系统上也是如此。

在应用程序在用户空间中运行的较新版本的Windows CE(至少5.x更新版本)(与所有应用程序以管理员模式运行的早期版本相比),应用程序甚至无法读取它自己的可执行文件。