首页 文章

如何在C中编写自修改代码?

提问于
浏览
25

我想编写一段不断变化的代码,即使变化微不足道 .

例如,可能是类似的东西

``

for i in 1 to  100, do 
begin
   x := 200
   for j in 200 downto 1, do
    begin
       do something
    end
end

``

假设我希望我的代码应该在第一次迭代之后将行 x := 200 更改为某个其他行 x := 199 然后在下一次迭代后将其更改为 x := 198 ,依此类推 .

写这样的代码可能吗?我需要使用内联汇编吗?

编辑:这就是我想用C做的原因:

该程序将在实验操作系统上运行,我不能/不知道如何使用从其他语言编译的程序 . 我需要这样一个代码的真正原因是因为这个代码是在虚拟机上的客户操作系统上运行的 . 管理程序是一个翻译代码块的二进制翻译器 . 翻译做了一些优化 . 它只翻译一次代码块 . 下次在guest中使用相同的块时,翻译器将使用先前翻译的结果 . 现在,如果代码被动态修改,那么翻译人员会注意到这一点,并将其先前的翻译标记为陈旧 . 因此迫使重新翻译相同的代码 . 这就是我想要实现的目标,迫使翻译人员进行多次翻译 . 通常,这些块是分支指令(例如跳转指令)之间的指令 . 我只是认为自修改代码将是实现这一目标的绝佳方式 .

9 回答

  • 11

    您可能需要考虑在C中编写虚拟机,您可以在其中构建自己的自修改代码 .

    如果您希望编写自修改的可执行文件,很大程度上取决于您所针对的操作系统 . 您可以通过修改内存中的程序映像来实现所需的解决方案 . 为此,您将获得程序代码字节的内存地址 . 然后,您可以在此内存范围上操作操作系统保护,允许您修改字节而不会遇到访问冲突或'''SIG_SEGV''' . 最后,您将使用指针(可能是'''unsigned char *'''指针,可能'''在RISC机器上'''unsigned long *'''来修改已编译程序的操作码) .

    关键是您将修改目标体系结构的机器代码 . C代码在运行时没有规范格式 - C是编译器的文本输入文件的规范 .

  • 9

    这是可能的,但它很可能无法移植,您可能不得不与运行代码的只读内存段以及操作系统实施的其他障碍相抗衡 .

  • 0

    对不起,我的回答有点晚了,但我想我找到了你要找的东西:https://shanetully.com/2013/12/writing-a-self-mutating-x86_64-c-program/

    在本文中,它们通过在堆栈中注入程序集来更改常量的值 . 然后他们通过修改堆栈上函数的内存来执行shellcode .

    下面是第一个代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/mman.h>
    
    void foo(void);
    int change_page_permissions_of_address(void *addr);
    
    int main(void) {
        void *foo_addr = (void*)foo;
    
        // Change the permissions of the page that contains foo() to read, write, and execute
        // This assumes that foo() is fully contained by a single page
        if(change_page_permissions_of_address(foo_addr) == -1) {
            fprintf(stderr, "Error while changing page permissions of foo(): %s\n", strerror(errno));
            return 1;
        }
    
        // Call the unmodified foo()
        puts("Calling foo...");
        foo();
    
        // Change the immediate value in the addl instruction in foo() to 42
        unsigned char *instruction = (unsigned char*)foo_addr + 18;
        *instruction = 0x2A;
    
        // Call the modified foo()
        puts("Calling foo...");
        foo();
    
        return 0;
    }
    
    void foo(void) {
        int i=0;
        i++;
        printf("i: %d\n", i);
    }
    
    int change_page_permissions_of_address(void *addr) {
        // Move the pointer to the page boundary
        int page_size = getpagesize();
        addr -= (unsigned long)addr % page_size;
    
        if(mprotect(addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
            return -1;
        }
    
        return 0;
    }
    
  • 6

    这将是一个良好的开端 . C中基本上是Lisp的功能:

    http://nakkaya.com/2010/08/24/a-micro-manual-for-lisp-implemented-in-c/

  • 5

    根据您需要的自由度,您可以通过使用函数指针来完成您想要的任务 . 使用您的伪代码作为起点,考虑我们想要以循环索引 i 更改的不同方式修改该变量 x 的情况 . 我们可以这样做:

    #include <stdio.h>
    
    void multiply_x (int * x, int multiplier)
    {
        *x *= multiplier;
    }
    
    void add_to_x (int * x, int increment)
    {
        *x += increment;
    }
    
    int main (void)
    {
        int x = 0;
        int i;
    
        void (*fp)(int *, int);
    
        for (i = 1; i < 6; ++i) {
                fp = (i % 2) ? add_to_x : multiply_x;
    
                fp(&x, i);
    
                printf("%d\n", x);
        }
    
        return 0;
    }
    

    编译和运行程序时的输出是:

    1
    2
    5
    20
    25
    

    显然,只有在每次运行时你想用 x 做有限数量的事情时,这才有效 . 为了使更改持久化(这是"self-modification"所需的一部分),您可能希望将函数指针变量设置为全局变量或静态变量 . 我不确定我是否真的可以推荐这种方法,因为通常有更简单,更清晰的方法来完成这种事情 .

  • 1

    由于可移植性问题,关于在C中实施LISP然后使用它的建议是可靠的 . 但是如果你真的想要,也可以在许多系统上以另一个方向实现,方法是将程序的字节码加载到内存中,然后返回到内存中 .

    有几种方法可以尝试这样做 . 一种方法是通过缓冲区溢出漏洞利用 . 另一种方法是使用mprotect()使代码段可写,然后修改编译器创建的函数 .

    这样的技术对于编程挑战和混淆的竞争很有趣,但考虑到你的代码是多么难以理解结合您正在利用C认为未定义行为的事实,在 生产环境 环境中最好避免使用它们 .

  • 3

    一种自我解释的语言(不像C那样硬编译和链接)可能更好 . Perl,javascript,PHP具有可能适合您的目的的邪恶 eval() 功能 . 通过它,您可以拥有一串代码,您可以不断修改,然后通过 eval() 执行 .

  • 0

    我的朋友和我在处理自我修改代码的游戏时遇到了这个问题 . 我们允许用户重写x86程序集中的代码片段 .

    这只需要利用两个库 - 汇编程序和反汇编程序:

    FASM汇编程序https://github.com/ZenLulz/Fasm.NET

    UDIS86反汇编程序:https://github.com/vmt/udis86

    我们使用反汇编程序读取指令,让用户编辑它们,使用汇编程序将新指令转换为字节,然后将它们写回内存 . 回写需要在Windows上使用 VirtualProtect 来更改页面权限以允许编辑代码 . 在Unix上,你必须使用 mprotect .

    我在这里发表了一篇关于我们如何做到的文章:

    https://medium.com/squallygame/how-we-wrote-a-self-hacking-game-in-c-d8b9f97bfa99

    以及示例代码:

    https://github.com/Squalr/SelfHackingApp

    这些示例在Windows上使用C,但它应该非常容易制作跨平台和C .

  • 5

    在标准C11(读n1570),_ you cannot write self modifying code (至少没有undefined behavior) . 从概念上讲,code segment是只读的 .

    您可以考虑使用dynamic linker使用plugins扩展程序代码 . 这需要特定于操作系统的功能 . 在POSIX上,使用dlopen(可能dlsym来获取新加载的函数指针) . 然后,您可以使用新的地址覆盖函数指针 .

    也许您可以使用一些JIT-compiling库(如libgccjitasmjit)来实现您的目标 . 您将获得新的函数地址并将它们放入函数指针中 .

    请记住,C编译器可以为给定的函数调用或跳转生成各种大小的代码,因此即使以机器特定的方式覆盖它也很脆弱 .

相关问题