C-указатель функции: могу ли я перейти на код ассемблера кучи памяти?

Можно ли создать динамическую функцию, выделив динамическую память, написав ей некоторые коды операций ассемблера (например, 0x90 0xC2 для NOP RET), создав указатель функции, который указывает на эту динамическую память и выполняет ее как обычную функцию изнутри программа C?

Цель должна быть обычной системой x86 Linux.

Ответы

Ответ 1

В этой памяти нет памяти кучи (см. примечание ниже). Более того, я думаю, что вы не можете изменить разрешение кучи памяти.

В Linux вы можете использовать следующее:

#include <sys/mman.h>
size_t size = 0x1000; // 1 page
// this will be mapped somewhere between /lib/x86_64-linux-gnu/ld-2.15.so
// and stack (see note and memory map below)
void *code = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS, -1, 0);
// use `code` to write your instructions
// then store location at which you want to jump to in `fp`
void *fp = ...;
mprotect(code, size, PROT_READ | PROT_EXEC);
// use some inline assembly to jump to fp

Примечание. В Linux память, отображаемая пользователем, находится в отдельной области (что-то вроде 400000000000 и до стека на x86 Linux и, возможно, 7f0000000000 на x64 one). Куча расположена после сегментов ELF программы и до области, доступных для mmap. Сама куча может быть выделена непосредственно с помощью системного вызова brk (заменяется на malloc). См. Этот пример (полученный на моем Ubuntu 12.10 x64):

➜  ~ ps
  PID TTY          TIME CMD
 9429 pts/3    00:00:07 zsh
20069 pts/3    00:00:00 git-credential-
22626 pts/3    00:00:00 ps
➜  ~ cat /proc/9429/maps 
00400000-004a2000 r-xp 00000000 08:01 6291468                            /bin/zsh5
006a1000-006a2000 r--p 000a1000 08:01 6291468                            /bin/zsh5
006a2000-006a8000 rw-p 000a2000 08:01 6291468                            /bin/zsh5
006a8000-006bc000 rw-p 00000000 00:00 0 
01a51000-01fd8000 rw-p 00000000 00:00 0                                  [heap]
...
7f6529d61000-7f6529d91000 rw-p 00000000 00:00 0 
...
7f652d0d3000-7f652d0d5000 rw-p 00023000 08:01 44833271                   /lib/x86_64-linux-gnu/ld-2.15.so
7fffd7c7f000-7fffd7cae000 rw-p 00000000 00:00 0                          [stack]
7fffd7dff000-7fffd7e00000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Как вы можете видеть, heap не является исполняемым (и это правильно), поэтому вы не можете использовать malloc для получения исполняемой памяти.

Ответ 2

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

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

В Linux вы можете использовать mmap(), чтобы попросить ядро ​​отобразить вам некоторую память и указав флаг PROT_EXEC в вызов вы можете попросить его сделать исполняемый файл памяти тоже.

Ответ 3

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

Если вы хотите сделать это, вам нужно использовать определенные функции ОС и, возможно, придется запускать программу как "администратор" или "root", чтобы это было возможно, хотя это, по-видимому, не требуется.