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

Выполнение машинного кода в памяти

Я пытаюсь выяснить, как выполнить машинный код, хранящийся в памяти.

У меня есть следующий код:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    FILE* f = fopen(argv[1], "rb");

    fseek(f, 0, SEEK_END);
    unsigned int len = ftell(f);
    fseek(f, 0, SEEK_SET);

    char* bin = (char*)malloc(len);
    fread(bin, 1, len, f);

    fclose(f);

    return ((int (*)(int, char *)) bin)(argc-1, argv[1]);
}

Код выше компилируется в GCC, но когда я пытаюсь выполнить программу из командной строки следующим образом:

./my_prog /bin/echo hello

Программа segfaults. Я выяснил, что проблема находится на последней строке, так как комментирование ее останавливает segfault.

Я не думаю, что я делаю это совершенно правильно, так как я все еще склоняюсь к указателям на функции.

Является ли проблема неисправным приложением или что-то еще?

4b9b3361

Ответ 1

Мне кажется, вы загружаете изображение ELF, а затем пытаетесь перейти прямо в заголовок ELF? http://en.wikipedia.org/wiki/Executable_and_Linkable_Format

Если вы пытаетесь выполнить другой двоичный код, почему бы вам не использовать функции создания процесса для какой-либо платформы, которую вы используете?

Ответ 2

Вам нужна страница с правами на выполнение записи. См. Mmap (2) и mprotect (2), если вы находитесь под unix. Вы не должны делать это, используя malloc.

Кроме того, прочитайте, что говорили другие, вы можете запускать только исходный машинный код с помощью вашего загрузчика. Если вы попытаетесь запустить заголовок ELF, он все равно будет segfault.

Относительно содержания ответов и понижений:

1- OP сказал, что он пытался запустить машинный код, поэтому я ответил на это, а не на выполнение исполняемого файла.

2- Узнайте, почему вы не смешиваете функции malloc и mman:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>

int main()
{
    char *a=malloc(10);
    char *b=malloc(10);
    char *c=malloc(10);
    memset (a,'a',4095);
    memset (b,'b',4095);
    memset (c,'c',4095);
    puts (a);
    memset (c,0xc3,10); /* return */

    /* c is not alligned to page boundary so this is NOOP.
     Many implementations include a header to malloc'ed data so it always NOOP. */
    mprotect(c,10,PROT_READ|PROT_EXEC);
    b[0]='H'; /* oops it is still writeable. If you provided an alligned
    address it would segfault */
    char *d=mmap(0,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);
    memset (d,0xc3,4096);
    ((void(*)(void))d)();
    ((void(*)(void))c)(); /* oops it isn't executable */
    return 0;
}

Он отображает именно это поведение в Linux x86_64, другое уродливое поведение обязательно возникнет в других реализациях.

Ответ 3

Использование malloc отлично работает.

ОК, это мой последний ответ, обратите внимание, что я использовал код плаката orignal. Я загружаю с диска, скомпилированную версию этого кода в кучу выделенной области "bin", точно так же, как и код orignal (имя исправлено не с использованием argv, а значение 0x674:

objdump -F -D foo|grep -i hoho
08048674 <hohoho> (File Offset: 0x674):

Это можно найти во время работы с BFD (Binary File Descriptor library) или что-то еще, вы можете вызывать другие двоичные файлы (не только себя), если они статически связаны с одним и тем же набором lib.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

unsigned char *charp;
unsigned char *bin;

void hohoho()
{
   printf("merry mas\n");
   fflush(stdout);
}

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

   charp = malloc(10101);
   memset(charp, 0xc3, 10101);
   mprotect(charp, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   __asm__("leal charp, %eax");
   __asm__("call (%eax)" );

   printf("am I alive?\n");

   char *more = strdup("more heap operations");
   printf("%s\n", more);

   FILE* f = fopen("foo", "rb");

   fseek(f, 0, SEEK_END);
   unsigned int len = ftell(f);
   fseek(f, 0, SEEK_SET);

   bin = (char*)malloc(len);
   printf("read in %d\n", fread(bin, 1, len, f));
   printf("%p\n", bin);

   fclose(f);
   mprotect(&bin, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   asm volatile ("movl %0, %%eax"::"g"(bin));
   __asm__("addl $0x674, %eax");
   __asm__("call %eax" );
   fflush(stdout);

   return 0;
}

работает...

co tmp # ./foo
am I alive?
more heap operations
read in 30180
0x804d910
merry mas

Вы можете использовать UPX для управления загрузкой/изменением/exec файла.

P.S. извините за предыдущую неработающую ссылку: |

Ответ 4

Типичный исполняемый файл имеет:

  • заголовок
  • код ввода, который вызывается до main(int, char **)

Первое означает, что вы обычно не можете ожидать, что байт 0 файла будет исполняемым; intead, информация в заголовке описывает, как загрузить остальную часть файла в памяти и с чего начать его выполнение.

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

Выполнение этого вручную под данной ОС переместилось бы на некоторую глубину, которая находится за пределами меня; но я уверен, что есть намного лучший способ сделать то, что вы пытаетесь сделать. Вы пытаетесь выполнить внешний файл как операцию включения или выгрузки внешнего бинарного файла и рассматривать его функции как часть вашей программы? Оба они удовлетворяются библиотеками C в Unix.

Ответ 5

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

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

Кроме того, если ваш процессор/ОС обеспечивает предотвращение выполнения данных, то попытка, вероятно, обречена. В любом случае, в лучшем случае это не рекомендуется, код загрузки - это то, для чего предназначена ОС.

Ответ 6

То, что вы пытаетесь сделать, - это нечто похожее на то, что делают переводчики. За исключением того, что интерпретатор читает программу, написанную на интерпретируемом языке, таком как Python, компилирует этот код "на лету", помещает исполняемый код в память и затем выполняет его.

Возможно, вы захотите также ознакомиться с компиляцией "точно в срок":

Просто компиляция времени
Java HotSpot JIT runtime

Существуют библиотеки для генерации кода JIT, такие как молния GNU и libJIT, если вы заинтересованы. Однако вам придется делать гораздо больше, чем просто читать из файла и пытаться выполнить код. Пример использования сценария:

  • Прочитайте программу, написанную на языке сценариев (возможно, ваш собственный).
  • Разбор и компиляция источника в промежуточный язык, понятный библиотека JIT.
  • Использование библиотеки JIT для генерации кода для этого промежуточного для вашего целевого платформы CPU.
  • Выполнить код, сгенерированный JIT.

И для выполнения кода вам нужно будет использовать такие методы, как использование mmap() для сопоставления исполняемого кода в адресном пространстве процесса, маркирования этой исполняемой страницы и перехода к этой части памяти. Это сложнее, чем это, но это хороший старт, чтобы понять, что происходит под всеми этими интерпретаторами языков сценариев, таких как Python, Ruby и т.д.

онлайн-версия книги Линкеры и загрузчики "предоставит вам больше информации о форматах объектных файлов, что происходит за кулисами при выполнении программы, роли компоновщиков и загрузчиков и т.д. Это очень хорошо читается.

Ответ 7

Используйте операционную систему для загрузки и выполнения программ.

В unix это можно сделать exec.

Ваш фрагмент в вопросе может быть переписан:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    return execv(argv[1],argv+2);
}

Ответ 8

Вы можете выполнить dlopen() файл, посмотреть символ "main" и называть его аргументами 0, 1, 2 или 3 (все типы char *) с помощью приведения к указателю на функцию-возврат -INT взятие-0,1,2, or3- char *

Ответ 9

Исполняемые файлы содержат гораздо больше, чем просто код. Заголовок, код, данные, больше данных, этот материал разделяется и загружается в разные области памяти ОС и его библиотеками. Вы не можете загрузить файл программы в один кусок памяти и ожидать перехода к нему в первый байт.

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