выполнить двоичный машинный код из C

Следуя этим инструкциям, мне удалось создать только 528 байтов размером a .out (когда gcc main.c изначально дал мне большой файл размером 8539 байт).

main.c был:

int main(int argc, char** argv) {

    return 42;
}

но вместо этого я построил a.out из этого файла сборки:

основной.с:

; tiny.asm
  BITS 64
  GLOBAL _start
  SECTION .text
  _start:
                mov     eax, 1
                mov     ebx, 42  
                int     0x80

с участием:

me@comp# nasm -f elf64 tiny.s
me@comp# gcc -Wall -s -nostartfiles -nostdlib tiny.o
me@comp# ./a.out ; echo $?
42
me@comp# wc -c a.out
528 a.out

потому что мне нужен машинный код, я делаю:

objdump -d a.out

a.out:     file format elf64-x86-64


Disassembly of section .text:

00000000004000e0 <.text>:
  4000e0:   b8 01 00 00 00          mov    $0x1,%eax
  4000e5:   bb 2a 00 00 00          mov    $0x2a,%ebx
  4000ea:   cd 80                   int    $0x80

># objdump -hrt a.out

a.out:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
 0 .note.gnu.build-id 00000024  00000000004000b0  00000000004000b0  000000b0 2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 1 .text         0000000c  00000000004000e0  00000000004000e0  000000e0 2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
SYMBOL TABLE:
no symbols

файл находится в соглашении с прямым порядком байтов:

me@comp# readelf -a a.out
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4000e0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          272 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         2
  Size of section headers:           64 (bytes)
  Number of section headers:         4
  Section header string table index: 3

теперь я хочу выполнить это так:

#include <unistd.h>
 // which version is (more) correct?
 // this might be related to endiannes (???)
char code[] = "\x01\xb8\x00\x00\xbb\x00\x00\x2a\x00\x00\x80\xcd\x00";
char code_v1[] = "\xb8\x01\x00\x00\x00\xbb\x2a\x00\x00\x00\xcd\x80\x00";

int main(int argc, char **argv)
{
/*creating a function pointer*/
int (*func)();
func = (int (*)()) code;
(int)(*func)();

return 0;
}

однако я получаю ошибку сегментации. Мой вопрос: это часть текста?

  4000e0:   b8 01 00 00 00          mov    $0x1,%eax
  4000e5:   bb 2a 00 00 00          mov    $0x2a,%ebx
  4000ea:   cd 80                   int    $0x80

(этот машинный код) все, что мне действительно нужно? Что я делаю неправильно (endiannes??), может быть, мне просто нужно называть это по-другому, начиная с SIGSEGV?


person 4pie0    schedule 27.08.2013    source источник
comment
Вы не можете просто рассматривать несколько случайных байтов как функцию. Вы должны соблюдать соглашения о вызовах компилятора и предоставлять подходящие прологи и эпилоги функций.   -  person Kerrek SB    schedule 28.08.2013
comment
Конечно, эти коды операций генерируются одним и тем же компилятором, а не случайным образом, так что все должно быть в порядке, знаете ли вы, что именно я должен делать? почему я могу запустить его с терминала?   -  person 4pie0    schedule 28.08.2013
comment
Во-первых, вам нужно убедиться, что код находится в исполняемой памяти. Попробуйте добавить что-то вроде __attribute__((section, ".text")) или подобное (см. руководство). И, как я уже сказал, убедитесь, что реализованы правильные соглашения о вызовах.   -  person Kerrek SB    schedule 28.08.2013
comment
спасибо, я попытаюсь взять коды операций из a.out, сгенерированных gcc, обычным способом, но затем поместить коды операций в asm и собрать a.out, как раньше, чтобы избежать накладных расходов на библиотеки во время выполнения. вы считаете это хорошей идеей? нет, я просто буду использовать коды операций из a.out, так как здесь я не использую никаких библиотек   -  person 4pie0    schedule 28.08.2013
comment
У меня есть это: * 00000000004004B4 <Главная> 55 Push% RBP 00000000004004B5
48 89 E5 MOV% RSP,% RBP 00000000004004B8
89 7D FC MOV% EDI, -0x4 (% RBP) 00000000004004BB +0x7› 48 89 75 f0 mov %rsi,-0x10(%rbp) /NetBeansProjects/examples/tiny_c/tiny.c:15 return 42; 00000000004004bf ‹main+0xb› b8 2a 00 00 00 mov $0x2a,%eax /NetBeansProjects/examples/tiny_c/tiny.c:16 } 00000000004004c4 ‹main+0x10› c9 leaveq 000000000004004c›   -  person 4pie0    schedule 28.08.2013
comment
Я извлек из этого char code_v3[] = \x55\x48\x89\xe5\x89\x7d\xfc\x48\x89\x75\xf0\xb8\x2a\x00\x00\x00\xc9\xc3, снова SIGSEGV   -  person 4pie0    schedule 28.08.2013


Ответы (2)


Код должен находиться на странице с разрешением на выполнение. По умолчанию статические данные стека и чтения-записи (например, неконстантные глобальные переменные) находятся на страницах, сопоставленных без разрешения exec по соображениям безопасности.

Самый простой способ — скомпилировать с помощью gcc -z execstack, который связывает вашу программу таким образом, что стек и глобальные переменные (статическое хранилище) отображаются на исполняемых страницах, и то же самое происходит с malloc.


Другой способ сделать это, не делая все исполняемым, — скопировать этот двоичный машинный код в исполняемый буфер.

#include <unistd.h>
#include <sys/mman.h>
#include <string.h>

char code[] = {0x55,0x48,0x89,0xe5,0x89,0x7d,0xfc,0x48,
    0x89,0x75,0xf0,0xb8,0x2a,0x00,0x00,0x00,0xc9,0xc3,0x00};
/*
00000000004004b4 <main> 55                          push   %rbp
00000000004004b5 <main+0x1>  48 89 e5               mov    %rsp,%rbp
00000000004004b8 <main+0x4>  89 7d fc               mov    %edi,-0x4(%rbp)
00000000004004bb <main+0x7>  48 89 75 f0            mov    %rsi,-0x10(%rbp)
'return 42;'
00000000004004bf <main+0xb>  b8 2a 00 00 00         mov    $0x2a,%eax
'}'
00000000004004c4 <main+0x10> c9                     leaveq 
00000000004004c5 <main+0x11> c3                     retq 
*/

int main(int argc, char **argv) { 
    void *buf;

    /* copy code to executable buffer */    
    buf = mmap (0,sizeof(code),PROT_READ|PROT_WRITE|PROT_EXEC,
                MAP_PRIVATE|MAP_ANON,-1,0);
    memcpy (buf, code, sizeof(code));
    __builtin___clear_cache(buf, buf+sizeof(code)-1);  // on x86 this just stops memcpy from optimizing away as a dead store

    /* run code */
    int i = ((int (*) (void))buf)();
    printf("get this done. returned: %d", i);

    return 0;
}

выход:

сделать это. возвращено: 42

ЗАПУСК УСПЕШНЫЙ (общее время: 57 мс)

Без __builtin___clear_cache это может сломаться при включенной оптимизации, поскольку gcc будет думать, что memcpy был мёртвым магазином и оптимизировал его. При компиляции для x86 __builtin___clear_cache фактически не очищает кеш; нет лишних инструкций; он просто помечает память как «использованную», поэтому хранилища в ней не считаются «мертвыми». (См. руководство по gcc.)


Другим вариантом может быть mprotect страница, содержащая массив char code[], присвоив ей PROT_READ|PROT_WRITE|PROT_EXEC. Это работает независимо от того, является ли это локальным массивом (в стеке) или глобальным в .data.

Или, если это const char code[] в разделе .rodata, вы можете просто дать ему PROT_READ|PROT_EXEC.

(В версиях binutils ld примерно до 2019 года .rodata был связан как часть того же сегмента, что и .text, и уже был сопоставлен с исполняемым файлом. Но недавняя ld выделяет ему отдельный сегмент, поэтому его можно сопоставить без разрешения exec, поэтому const char code[] этого не делает. больше не дает вам исполняемый массив, но раньше вы могли использовать этот старый совет в других местах.)

person 4pie0    schedule 27.08.2013
comment
Хорошо, но с большими знаниями приходит большая ответственность, подумайте об этом, прежде чем злоупотреблять этим.... - person Raymond Nijland; 29.08.2013
comment
вы можете использовать этот код с некоторыми небольшими изменениями в качестве исполнителя шелл-кода, когда есть утечка памяти, это программное обеспечение, но гораздо сложнее сделать это правильно ... но я думаю, что вы создаете что-то вроде виртуальной машины? - person Raymond Nijland; 29.08.2013
comment
нет, я просто хотел выполнить из программы C/C++ машинный код, написанный мной. - person 4pie0; 29.08.2013
comment
Затем рассмотрите возможность использования некоторой библиотеки генератора машинного кода JIT: LLVM или GNU Lightning или libjit . Или просто используйте инструкцию asm в C. - person Basile Starynkevitch; 30.08.2013
comment
В качестве альтернативы (в Linux) сгенерируйте код C во время выполнения, разветвите компиляцию в общий объект и dlopen этот общий объект... - person Basile Starynkevitch; 30.08.2013
comment
гораздо безопаснее определять типы функций и динамически загружать их... но это тоже круто, поскольку обеспечивает прямой способ реализации самомодифицирующегося кода, а в защищенной памяти вы вряд ли заставите вселенную взорваться. - person Grady Player; 19.05.2014
comment
Используйте const char code[] = ..., чтобы поместить массив в раздел .rodata, который связан как часть сегмента TEXT в Linux. Затем вы можете просто привести code к указателю на функцию и вызвать его, даже не нуждаясь в gcc -z execstack. (Но эта опция делает исполняемой кучу и статические данные, а также фактический стек, поэтому это хороший выбор для возни с вещами.) - person Peter Cordes; 13.12.2018
comment
Я немного расширил ваш ответ, чтобы сделать его лучшим каноническим ответом, дубликатом которого могут быть другие, и сделать пример кода пуленепробиваемым даже при включенной оптимизации. - person Peter Cordes; 29.05.2019

Дело в том, что DEP-защита включена! вы можете перейти в «Конфигурации» -> «Компоновщик» -> «Дополнительно» -> «Отключить DEP», теперь все в порядке.

void main(){
int i = 11;
//The following is the method to generate the machine code directly!
//mov eax, 1; ret;
const char *code = "\xB8\x10\x00\x00\x00\xc3";
    __asm call code;  //test successful~..vs 2017
    __asm mov i ,eax;
printf("i=%d", i);
}
person Alex_xtra    schedule 12.12.2018
comment
Это не имеет значения, OP использовал компилятор GCC в Linux (как видно из используемых команд), Linux не использует DEP для процессов пользовательского пространства (афаик), а GCC не имеет графического интерфейса, который делает MSVC. - person Expolarity; 02.03.2021