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

时间:2010-10-10 02:55:50

标签: c metaprogramming self-modifying

我手上有太多时间,开始想知道我是否可以编写一个自我修改程序。为此,我在C中编写了一个“Hello World”,然后使用十六进制编辑器在已编译的可执行文件中查找“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中。因此,对于所有意图和目的,硬盘驱动器上的可执行文件是一个副本。为什么修改自己会有问题?

有解决方法吗?

由于

9 个答案:

答案 0 :(得分:30)

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

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

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

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

答案 1 :(得分:6)

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

分步示例:

  1. 使用VirtualProtect()保护功能,在您要修改的代码页上调用PAGE_WRITECOPY
  2. 修改代码页。
  3. 使用VirtualProtect()保护功能,在修改过的代码页上调用PAGE_EXECUTE
  4. 致电FlushInstructionCache()
  5. 有关详细信息,请参阅How to Modify Executable Code in Memory(已存档:2010年8月)

答案 2 :(得分:4)

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

其他操作系统对文件进行安全检查,例如iPhone,所以写它将是很多工作,加上它作为只读文件。

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

答案 3 :(得分:4)

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

但是,如果您真的希望看到自己的自我修改代码,您可以使用以下方法:

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

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

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

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

答案 4 :(得分:2)

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

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

  1. 创建一个新的空选择器(&#34;高&#34;远指针的一部分)
  2. 将其属性设置为XO区域的属性
  3. 如果您只想查看其中的内容,则必须将新选择器的访问属性设置为RO DATA
  4. 如果要修改数据,则必须将访问属性设置为RW DATA
  5. 所以问题的答案就在最后一步。如果您希望能够插入调试器所执行的断点指令,则RW是必需的。比80286更现代的处理器具有内部调试寄存器,以启用非侵入式监视功能,这可能导致发出断点。

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


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

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

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

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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

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

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

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

2)表现,像JIT。

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

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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