Как написать самоизменяющийся код на C? - PullRequest
27 голосов
/ 16 сентября 2011

Я хочу написать фрагмент кода, который постоянно меняется, даже если это изменение незначительно.

Например, может быть что-то вроде

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:

Эта программа будет работать в экспериментальной операционной системе, и я не могу / не знаю, как использовать скомпилированные программыс других языков.Настоящая причина, по которой мне нужен такой код, заключается в том, что этот код выполняется в гостевой операционной системе на виртуальной машине.Гипервизор - это двоичный переводчик, который переводит куски кода.Переводчик делает некоторые оптимизации.Он переводит куски кода только один раз.В следующий раз, когда этот же чанк будет использован в гостевой системе, переводчик будет использовать ранее переведенный результат.Теперь, если код изменяется на лету, переводчик замечает это и помечает свой предыдущий перевод как устаревший.Таким образом вынуждает повторный перевод того же кода.Это то, чего я хочу добиться, заставить переводчика делать много переводов.Обычно эти фрагменты являются инструкциями между инструкциями перехода (такими как инструкции перехода).Я просто думаю, что самоизменяющийся код был бы фантастическим способом достичь этого.

Ответы [ 9 ]

12 голосов
/ 16 сентября 2011

Возможно, вы захотите написать виртуальную машину на C, где вы можете создать свой собственный самоизменяющийся код.

Если вы хотите писать самоизменяемые исполняемые файлы, многое зависит от операционной системы, на которую вы ориентируетесь. Вы можете приблизиться к желаемому решению, изменив образ программы в памяти. Для этого вы получите адрес в памяти байтов кода вашей программы. Затем вы можете манипулировать защитой операционной системы в этом диапазоне памяти, позволяя изменять байты, не сталкиваясь с нарушением прав доступа или '' 'SIG_SEGV' ''. Наконец, вы должны использовать указатели (возможно, указатели unsigned char *, указатели unsigned long *, как на машинах RISC), чтобы изменить коды операций скомпилированной программы.

Ключевым моментом является то, что вы будете изменять машинный код целевой архитектуры. Канонического формата для кода C во время его работы не существует - C представляет собой спецификацию текстового входного файла для компилятора.

9 голосов
/ 16 сентября 2011

Это возможно, но, скорее всего, это не всегда возможно, и вам, возможно, придется бороться с сегментами памяти только для чтения для работающего кода и других препятствий, создаваемых вашей ОС.

7 голосов
/ 02 апреля 2017

Извините, я отвечаю немного поздно, но я думаю, что нашел именно то, что вы ищете: https://shanetully.com/2013/12/writing-a-self-mutating-x86_64-c-program/

В этой статье они изменяют значение константы, вставляя сборку в стек. Затем они выполняют шелл-код, изменяя память функции в стеке.

Ниже первый код:

#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;
}
5 голосов
/ 16 сентября 2011

В зависимости от того, сколько свободы вам нужно, вы можете достичь желаемого, используя указатели функций.Используя ваш псевдокод в качестве отправной точки, рассмотрим случай, когда мы хотим изменить эту переменную 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 голосов
/ 16 сентября 2011

Это было бы хорошим началом.По существу функциональность Lisp в C:

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

3 голосов
/ 07 мая 2013

Предложение о внедрении LISP в C и последующем его использовании вполне обосновано из-за проблем с переносимостью.Но если вы действительно хотите, это также может быть реализовано в другом направлении во многих системах, загрузив байт-код вашей программы в память и затем вернувшись к нему.

Есть несколько способов, которыми вы можете попытаться сделать это,Одним из способов является использование эксплойта переполнения буфера.Другой вариант - использовать mprotect (), чтобы сделать раздел кода доступным для записи, а затем изменить функции, созданные компилятором.

Подобные методы полезны для задач программирования и запутанных соревнований, но с учетом того, насколько нечитабельным будет сочетаться ваш кодс тем фактом, что вы используете то, что С считает неопределенным поведением, их лучше избегать в производственных средах.

2 голосов
/ 16 сентября 2011

Для этого может быть лучше использовать интерпретируемый сам язык (не скомпилированный и не связанный как C).Perl, javascript, PHP имеют злую функцию eval(), которая может соответствовать вашим целям.Таким образом, вы можете получить строку кода, которую вы постоянно изменяете и затем выполняете через eval().

1 голос
/ 31 августа 2018

В стандартном C11 (читается n1570 ), вы не можете написать самоизменяющийся код (по крайней мере, без неопределенное поведение ). Концептуально, по крайней мере, сегмент кода доступен только для чтения.

Возможно, вы захотите расширить код своей программы с помощью плагинов , используя динамический компоновщик . Это требует определенных функций операционной системы. В POSIX используйте dlopen (и, вероятно, dlsym , чтобы получить вновь загруженные указатели функций). Затем вы можете перезаписать указатели на функции новыми адресами.

Возможно, вы могли бы использовать некоторую библиотеку JIT (например, libgccjit или asmjit ) для достижения ваших целей. Вы получите свежие адреса функций и поместите их в свои указатели функций.

Помните, что компилятор C может генерировать код различного размера для заданного вызова функции или перехода, поэтому даже перезапись, которая определенным для машины способом является хрупкой.

0 голосов
/ 31 августа 2018

Мы с другом столкнулись с этой проблемой, работая над игрой, которая самостоятельно изменяет свой код.Мы разрешаем пользователю переписывать фрагменты кода в сборке x86.

Это просто требует использования двух библиотек - ассемблера и дизассемблера:

FASM-ассемблер https://github.com/ZenLulz/Fasm.NET

Дизассемблер UDIS86: https://github.com/vmt/udis86

Мы читаем инструкции, используя дизассемблер, позволяем пользователю редактировать их, преобразовывать новые инструкции в байты с помощью ассемблера и записывать их обратно в память.Обратная запись требует использования 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...