37

我想写一段不断改变自己的代码,即使改变是微不足道的。

例如,也许像

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 中这样做:

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

4

11 回答 11

15

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

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

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

于 2011-09-16T15:54:40.087 回答
9

这是可能的,但它很可能不是可移植的,并且您可能不得不为运行代码和操作系统设置的其他障碍与只读内存段抗衡。

于 2011-09-16T15:44:00.027 回答
9

抱歉,我回答得有点晚了,但我想我找到了你正在寻找的东西: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;
}
于 2017-04-01T22:22:43.643 回答
5

This would be a good start. Essentially Lisp functionality in C:

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

于 2011-09-16T15:58:05.383 回答
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显然,这只有在每次运行时要处理的事情数量有限时才有效。为了使更改持久化(这是您希望“自我修改”的一部分),您需要将函数指针变量设为全局或静态。我不确定我是否真的可以推荐这种方法,因为通常有更简单、更清晰的方法来完成这类事情。

于 2011-09-16T16:03:28.837 回答
4

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

于 2011-09-16T15:34:41.167 回答
3

由于可移植性问题,有关在 C 中实现 LISP 然后使用它的建议是可靠的。但是,如果您真的想这样做,也可以在许多系统上以相反的方式实现,将程序的字节码加载到内存中,然后返回到它。

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

像这样的技术对于编程挑战和混淆竞赛来说很有趣,但考虑到代码的不可读性与您正在利用 C 认为未定义行为的事实相结合,最好在生产环境中避免使用它们。

于 2013-05-07T03:29:19.930 回答
2

在标准 C11(阅读n1570)中,您不能编写自我修改代码(至少没有未定义的行为)。至少在概念上,代码段是只读的。

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

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

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

于 2018-08-31T04:52:58.187 回答
1

这是使用 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;
    }
}

您可以使用此工具汇编代码。

于 2020-09-06T06:07:07.777 回答
1

My friend and I encountered this problem while working on a game that self-modifies its code. We allow the user to rewrite code snippets in x86 assembly.

This just requires leveraging two libraries -- an assembler, and a disassembler:

FASM assembler: https://github.com/ZenLulz/Fasm.NET

Udis86 disassembler: https://github.com/vmt/udis86

We read instructions using the disassembler, let the user edit them, convert the new instructions to bytes with the assembler, and write them back to memory. The write-back requires using VirtualProtect on windows to change page permissions to allow editing the code. On Unix you have to use mprotect instead.

I posted an article on how we did it, as well as the sample code.

These examples are on Windows using C++, but it should be very easy to make cross-platform and C only.

于 2018-08-31T04:48:05.577 回答
0

虽然在 C 中“真正的”自我修改代码是不可能的(汇编方式感觉像是轻微的作弊,因为此时,我们是在汇编中而不是在 C 中编写自我修改代码,这是最初的问题),可能有一个纯 C 方法使语句的类似效果矛盾地不做你认为应该做的事情。我说自相矛盾,因为 ASM 自我修改代码和下面的 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"

首先,我们改变 and 的值foo.afoo.b打印结构。然后我们只改变 的值foo.a,但观察输出。

于 2020-10-07T19:42:40.757 回答