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

时间:2011-09-16 15:31:12

标签: c self-modifying

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

例如,可能像

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中使用相同的块时,翻译器将使用先前翻译的结果。现在,如果代码被动态修改,那么翻译人员会注意到这一点,并将其先前的翻译标记为陈旧。因此迫使重新翻译相同的代码。这就是我想要实现的目标,迫使翻译人员进行多次翻译。通常,这些块是分支指令(例如跳转指令)之间的指令。我只是认为自修改代码是实现这一目标的绝佳方法。

11 个答案:

答案 0 :(得分:12)

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

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

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

答案 1 :(得分:9)

这是可能的,但它很可能无法移植,您可能不得不与正在运行的代码的只读内存段以及操作系统实施的其他障碍进行竞争。

答案 2 :(得分:7)

对不起,我的回答有点晚了,但我想我找到了你要找的东西: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;
}

答案 3 :(得分:5)

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

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

答案 4 :(得分:5)

根据您需要的自由度,您可以使用函数指针完成所需的操作。使用伪代码作为起点,考虑我们希望在循环索引x发生变化时以不同方式修改变量i的情况。我们可以这样做:

#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进行有限数量的事情时,这才有效。为了使更改持久化(这是“自我修改”所需的一部分),您可能希望将函数指针变量设置为全局变量或静态变量。我不确定我是否真的可以推荐这种方法,因为通常有更简单,更清晰的方法来完成这类工作。

答案 5 :(得分:3)

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

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

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

答案 6 :(得分:2)

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

答案 7 :(得分:1)

在标准C11(读为n1570)中,您不能编写自修改代码(至少没有undefined behavior时)。至少从概念上讲,code segment是只读的。

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

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

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

答案 8 :(得分: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

这些示例在使用C ++的Windows上进行,但是仅使用跨平台和C语言应该很容易。

答案 9 :(得分:0)

这是在使用c ++的Windows上执行此操作的方法。您必须VirtualAlloc一个具有读/写保护的字节数组,在其中复制代码,并用读/执行保护VirtualProtect。动态创建不执行任何操作并返回的函数的方法如下。

#include <cstdio>
#include <Memoryapi.h>
#include <windows.h>
using namespace std;
typedef unsigned char byte;

int main(int argc, char** argv){
    byte bytes [] = { 0x48, 0x31, 0xC0, 0x48, 0x83, 0xC0, 0x0F, 0xC3 }; //put code here
    //xor %rax, %rax
    //add %rax, 15
    //ret
    int size = sizeof(bytes);
    DWORD protect = PAGE_READWRITE;
    void* meth = VirtualAlloc(NULL, size, MEM_COMMIT, protect);
    byte* write = (byte*) meth;
    for(int i = 0; i < size; i++){
        write[i] = bytes[i];
    }
    if(VirtualProtect(meth, size, PAGE_EXECUTE_READ, &protect)){
        typedef int (*fptr)();
        fptr my_fptr = reinterpret_cast<fptr>(reinterpret_cast<long>(meth));
        int number = my_fptr();
        for(int i = 0; i < number; i++){
            printf("I will say this 15 times!\n");
        }
        return 0;
    } else{
        printf("Unable to VirtualProtect code with execute protection!\n");
        return 1;
    }
}

您使用Table column drag and drop sortable and resizable汇编代码。

答案 10 :(得分:0)

虽然不可能用C进行“真正的”自修改代码(汇编方式感觉像是在作弊,因为在这一点上,我们正在用汇编而不是用C编写自修改代码,这是原来的问题),可能是一种纯粹的C方法,使声明的相似效果自相矛盾地没有按照您认为的去做。我自相矛盾地说,因为ASM自修改代码和以下C代码片段可能不是表面上/直觉上都有意义,但如果您将直觉放在一边并进行逻辑分析,则是合乎逻辑的,这就是使悖论成为悖论的差异。 >

C2:C
#include <stdio.h>
#include <string.h>

int main()
{
    struct Foo
    {
        char a;
        char b[4];
    } foo;

    foo.a = 42;
    strncpy(foo.b, "foo", 3);
    printf("foo.a=%i, foo.b=\"%s\"\n", foo.a, foo.b);

    *(int*)&foo.a = 1918984746;
    printf("foo.a=%i, foo.b=\"%s\"\n", foo.a, foo.b);

    return 0;
}

首先,我们更改$ gcc -o foo foo.c && ./foo foo.a=42, foo.b="foo" foo.a=42, foo.b="bar" foo.a的值并打印结构。然后,我们仅更改foo.b的值,但观察输出。