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

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

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

#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.

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

Действительно ли проблемой является дефектный бросок или что-то еще?

28
задан anonymous coward 7 January 2010 в 11:35
поделиться

9 ответов

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

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

12
ответ дан 28 November 2019 в 02:52
поделиться

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

Также, прочитайте, что сказали другие, Вы можете запускать только сырой машинный код с помощью загрузчика. Если вы попытаетесь запустить ELF-заголовок, то, скорее всего, он все равно сегментируется.

Что касается содержимого ответов и даунмодов:

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's 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 и другое уродливое поведение, которое, несомненно, может возникнуть на других реализациях.

.
29
ответ дан 28 November 2019 в 02:52
поделиться

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

OK - это мой окончательный ответ, пожалуйста, обратите внимание, что я использовал восточный плакатный код. Я загружаю с диска скомпилированную версию этого кода в кучу выделенной области "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 для управления загрузкой/модификацией/исполнением файла.

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

.
12
ответ дан 28 November 2019 в 02:52
поделиться

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

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

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

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

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

.
4
ответ дан 28 November 2019 в 02:52
поделиться

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

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

Более того, если ваш процессор/OS позволяет предотвратить выполнение данных, то эта попытка, вероятно, обречена. В любом случае, это в лучшем случае плохо продумано, код загрузки - это то, для чего нужна операционная система.

.
3
ответ дан 28 November 2019 в 02:52
поделиться

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

.
1
ответ дан 28 November 2019 в 02:52
поделиться

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

В unix это могут сделать вызовы exec.

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

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

int main(int argc, char* argv[])
{
    return execv(argv[1],argv+2);
}
1
ответ дан 28 November 2019 в 02:52
поделиться

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

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

.
0
ответ дан 28 November 2019 в 02:52
поделиться

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

Вы также можете захотеть прочитать о компиляции Just-in-time:

Just in time compilation
Java HotSpot JIT runtime

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

  1. Чтение программы, написанной на языке сценариев (возможно свой собственный).
  2. Разобрать и скомпилировать источник в язык-посредник, понятный в библиотеке JIT.
  3. Используйте библиотеку JIT для генерации кода. для этого посредника представление, для процессора вашей целевой платформы.
  4. Выполните сгенерированный JIT код.

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

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

2
ответ дан 28 November 2019 в 02:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: