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

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

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

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

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

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

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

53

Решение

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

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

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

51

Другие решения

Вам нужно вызвать 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;
}
48

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

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

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

28

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

9

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

5

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

Пример:

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

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

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

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

Связанный вопрос:

4
По вопросам рекламы [email protected]