Подтвердить что ты не робот

Как вызвать машинный код, хранящийся в массиве char?

Я пытаюсь вызвать собственный код машинного языка. Вот что я до сих пор (он получает ошибку шины):

char prog[] = {'\xc3'}; // x86 ret instruction

int main()
{
    typedef double (*dfunc)();

    dfunc d = (dfunc)(&prog[0]);
    (*d)();
    return 0;
}

Он правильно вызывает функцию и получает ее в инструкцию ret. Но когда он пытается выполнить инструкцию ret, он имеет ошибку SIGBUS. Это потому, что я выполняю код на странице, которая не очищается для выполнения или что-то в этом роде?

Так что я делаю неправильно здесь?

4b9b3361

Ответ 1

Одна из первых проблем может заключаться в том, что место, где хранятся данные prog, не является исполняемым.

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

Вторая проблема может заключаться в том, что вызываемый вами код некорректен. Существует определенная процедура вызова метода в C, называемого соглашение о вызове (например, вы можете использовать "cdecl" ). Это может быть недостаточно для того, чтобы вызываемая функция просто "ret". Также может потребоваться очистка стека и т.д., Иначе программа будет вести себя неожиданно. Это может оказаться проблемой после того, как вы преодолеете первую проблему.

Ответ 2

Вам нужно вызвать memprotect, чтобы страница, на которой prog работает исполняемый файл. Следующий код выполняет этот вызов и может выполнять текст в проге.

#include <unistd.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>

char prog[] = {
   0x55,             // push   %rbp
   0x48, 0x89, 0xe5, // mov    %rsp,%rbp
   0xf2, 0x0f, 0x10, 0x05, 0x00, 0x00, 0x00,
       //movsd  0x0(%rip),%xmm0        # c <x+0xc>
   0x00,
   0x5d,             // pop    %rbp
   0xc3,             // retq
};

int main()
{
    long pagesize = sysconf(_SC_PAGE_SIZE);
    long page_no = (long)prog/pagesize;
    int res = mprotect((void*)(page_no*pagesize), (long)page_no+sizeof(prog), PROT_EXEC|PROT_READ|PROT_WRITE);
    if(res)
    {
        fprintf(stderr, "mprotect error:%d\n", res);
        return 1;
    }
    typedef double (*dfunc)(void);

    dfunc d = (dfunc)(&prog[0]);
    double x = (*d)();
    printf("x=%f\n", x);
    fflush(stdout);
    return 0;
}

Ответ 3

Как все уже сказали, вы должны убедиться, что prog[] является исполняемым, однако правильный способ его выполнения, если вы не пишете JIT-компилятор, заключается в том, чтобы поместить символ в исполняемую область, либо с помощью компоновщика script или указав раздел в коде C, если позволяет компилятор, например:

const char prog[] __attribute__((section(".text"))) = {...}

Ответ 4

Практически все компиляторы C позволят вам сделать это, введя обычный код ассемблера в свой код. Конечно, это нестандартное расширение для C, но авторы компилятора признают, что это часто необходимо. В качестве нестандартного расширения вам нужно будет прочитать свое руководство по компилятору и проверить, как это сделать, но расширение GCC "asm" является довольно стандартным подходом.

 void DoCheck(uint32_t dwSomeValue)
 {
    uint32_t dwRes;

    // Assumes dwSomeValue is not zero.
    asm ("bsfl %1,%0"
      : "=r" (dwRes)
      : "r" (dwSomeValue)
      : "cc");

    assert(dwRes > 3);
 }

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

Если вы пишете код ассемблера самостоятельно, нет никаких оснований для того, чтобы настроить этот ассемблер как массив байтов. Это не просто запах кода - я бы сказал, что это настоящая ошибка, которая может произойти только из-за незнания расширения asm, которое является правильным способом встраивания ассемблера в ваш C.

Ответ 5

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

Ответ 6

Одна очевидная ошибка заключается в том, что \xc3 не возвращает double, который, по вашему утверждению, возвращается.

Ответ 7

Вы можете устранить крах, разрешив компилятору хранить массив в разделе только для чтения вашей памяти процессов (если он известен во время компиляции). Например, объявив массив const.

Пример:

const char prog[] = {'\xc3'}; // x86 ret instruction

int main()
{
    typedef double (*dfunc)();

    dfunc d = (dfunc)(&prog[0]);
    (*d)();
    return 0;
}

В качестве альтернативы вы можете скомпилировать код с отключенной степенной защитой gcc -z execstack.

Похожие вопросы: