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

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

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

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

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

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

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

7 ответов


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

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

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


вам нужно вызвать memprotect, чтобы сделать страницу, где prog живет исполняемый файл. Следующий код делает этот вызов и может выполнить текст в 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;
}

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

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

практически все компиляторы 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.


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


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


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

пример:

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

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

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

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

вопрос: