Как получить код для выполнения шестнадцатеричного байт-кода? - PullRequest
8 голосов
/ 01 апреля 2012

Я хочу, чтобы простой метод C мог запускать шестнадцатеричный байт-код на 64-битной машине Linux. Вот программа на C, которая у меня есть:

char code[] = "\x48\x31\xc0";
#include <stdio.h>
int main(int argc, char **argv)
{
        int (*func) ();
        func = (int (*)()) code;
        (int)(*func)();
        printf("%s\n","DONE");
}

Код, который я пытаюсь запустить ("\x48\x31\xc0"), который я получил, написав эту простую программу сборки (она не должна ничего делать)

.text
.globl _start
_start:
        xorq %rax, %rax

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

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

Ответы [ 6 ]

16 голосов
/ 01 апреля 2012

Вам нужна страница, содержащая машинный код, чтобы иметь разрешение на выполнение. Таблицы страниц x86-64 имеют отдельный бит для выполнения, отдельный от разрешения на чтение, в отличие от устаревших таблиц на 386 страниц.

Вот простой пример:

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

int main ()
{
  char code[] = {
    0x8D, 0x04, 0x37,           //  lea eax,[rdi+rsi]       // retval = a+b;                    
    0xC3                        //  ret                                         
  };

  int (*sum) (int, int) = NULL;

  // copy code to executable buffer                                             
  sum = mmap (0,sizeof(code),PROT_READ|PROT_WRITE|PROT_EXEC,
              MAP_PRIVATE|MAP_ANON,-1,0);
  memcpy (sum, code, sizeof(code));

  // doesn't actually flush cache on x86
  // but is still necessary so memcpy isn't optimized away as a dead store.
  __builtin___clear_cache(sum, sum + sizeof(sum));  // GNU C

  // run code                                                                   
  int a = 2;
  int b = 3;
  int c = sum (a, b);

  printf ("%d + %d = %d\n", a, b, c);

  return 0;
}
4 голосов
/ 01 апреля 2012

Ваш машинный код может быть в порядке, но ваш процессор объекты.

Современные процессоры управляют памятью в сегментах. При нормальной работе операционная система загружает новую программу в сегмент program-text и устанавливает стек в сегменте data . Операционная система говорит ЦП никогда не запускать код в сегменте данных. Ваш код в code[], в сегменте данных. Таким образом, segfault.

3 голосов
/ 01 апреля 2012

Вам необходимо включить сборку в строку через специальную директиву компилятора, чтобы она правильно оказалась в сегменте кода. См. Это руководство, например: http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html

2 голосов
/ 01 апреля 2012

Это потребует некоторых усилий.

Ваша переменная code хранится в разделе .data вашего исполняемого файла:

$ readelf -p .data exploit

String dump of section '.data':
  [    10]  H1À

H1À - это значение вашей переменной.

Раздел .data - это , а не исполняемый файл:

$ readelf -S exploit
There are 30 section headers, starting at offset 0x1150:
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
[...]
  [24] .data             PROGBITS         0000000000601010  00001010
       0000000000000014  0000000000000000  WA       0     0     8

Все 64-разрядные процессоры, с которыми я знаком, изначально поддерживают неисполняемые страницы в таблицах страниц.,Большинство новых 32-разрядных процессоров (те, которые поддерживают PAE) предоставляют достаточное дополнительное пространство в своих таблицах страниц для операционной системы, чтобы эмулировать аппаратные неисполняемые страницы.Вам нужно будет запустить либо древнюю ОС, либо древний процессор, чтобы получить раздел .data, помеченный как исполняемый.

Поскольку это просто флаги в исполняемом файле, вы должны иметь возможность установить Xфлаг через какой-то другой механизм, но я не знаю, как это сделать.А ваша ОС может даже не позволить вам иметь страницы с возможностью записи и исполняемый файл.

0 голосов
/ 28 апреля 2019

Две проблемы:

  • разрешение exec на странице, , поскольку вы использовали массив, который будет помещен в раздел noexec для чтения + записи .data.
  • ваш машинный код не заканчивается ret инструкцией , поэтому даже если он все-таки будет выполняться, выполнение вернется к следующему в памяти, а не к возврату.

И кстати, префикс REX полностью избыточен. "\x31\xc0" xor eax,eax имеет тот же эффект, что и xor rax,rax.


Вам нужна страница с машинным кодом, чтобы иметь разрешение на выполнение . Таблицы страниц x86-64 имеют отдельный бит для выполнения, отличный от разрешения на чтение, в отличие от устаревших таблиц на 386 страниц.

Самый простой способ поместить статические массивы в память read + exec - это скомпилировать с gcc -z execstack. (Делает стек и исполняемым разделом).

До недавнего времени (2018 или 2019) стандартный набор инструментов (binutils ld) помещал секцию .rodata в тот же сегмент ELF, что и .text, так что они оба имели бы разрешение на чтение + exec. Таким образом, использования const char code[] = "..."; было достаточно для выполнения вручную указанных байтов в качестве данных.

Но в моей системе Arch Linux с GNU ld (GNU Binutils) 2.31.1 это уже не так. readelf -a показывает, что секция .rodata перешла в сегмент ELF с .eh_frame_hdr и .eh_frame и имеет только разрешение на чтение. .text идет в сегмент с Read + Exec, а .data идет в сегмент с Read + Write (вместе с .got и .got.plt). ( В чем разница раздела и сегмента в формате файла ELF )

#include <stdio.h>

// can be non-const if you use gcc -z execstack.  static is also optional
static const char code[] = {
  0x8D, 0x04, 0x37,           //  lea eax,[rdi+rsi]       // retval = a+b;                    
  0xC3                        //  ret                                         
};

static const char ret0_code[] = "\x31\xc0\xc3";   // xor eax,eax ;  ret
                     // the compiler will append a 0 byte to terminate the C string,
                     // but that's fine.  It's after the ret.

int main () {
  // void* cast is easier to type than a cast to function pointer,
  // and in C can be assigned to any other pointer type.  (not C++)

  int (*sum) (int, int) = (void*)code;
  int (*ret0)(void) = (void*)ret0_code;

  // run code                                                                   
  int c = sum (2, 3);
  return ret0();
}

В старых системах Linux: gcc -O3 shellcode.c && ./a.out (работает из-за const в глобальных / статических массивах)

В старых и текущих версиях Linux: gcc -O3 -z execstack shellcode.c && ./a.out (работает из-за -zexecstack независимо от того, где хранится ваш машинный код). Также работает с clang -z execstack. gcc позволяет -zexecstack без пробела, но clang - нет.

Они также работают в Windows, где данные только для чтения идут в .rdata вместо .rodata.

Сгенерированный компилятором main выглядит следующим образом (из objdump -drwC -Mintel). Вы можете запустить его внутри gdb и установить точки останова на code и ret0_code

(I actually used   gcc -no-pie -O3 -zexecstack shellcode.c  hence the addresses near 401000
0000000000401020 <main>:
  401020:       48 83 ec 08             sub    rsp,0x8           # stack aligned by 16 before a call
  401024:       be 03 00 00 00          mov    esi,0x3
  401029:       bf 02 00 00 00          mov    edi,0x2           # 2 args
  40102e:       e8 d5 0f 00 00          call   402008 <code>     # note the target address in the next page
  401033:       48 83 c4 08             add    rsp,0x8
  401037:       e9 c8 0f 00 00          jmp    402004 <ret0_code>    # optimized tailcall

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

Вместо компиляции с gcc -zexecstack вы можете использовать mmap(PROT_EXEC) для выделения новых исполняемых страниц или mprotect(PROT_EXEC) для изменения существующих страниц на исполняемые. (Включая страницы, содержащие статические данные.) Вы также обычно хотите по крайней мере PROT_READ, а иногда PROT_WRITE, конечно.

Использование mprotect для статического массива означает, что вы все еще выполняете код из известного местоположения, возможно, упрощая установку точки останова для него.

В Windows вы можете использовать VirtualAlloc или VirtualProtect.

В GNU C вам также необходимо использовать __builtin___clear_cache(buf, buf + len) после записи байтов машинного кода в буфер, потому что оптимизатор не рассматривает разыменование указателя функции как чтение байтов с этого адреса. Удаление мертвого хранилища может удалить хранилища байтов машинного кода в буфер, если компилятор докажет, что хранилище ничего не читается как данные. https://codegolf.stackexchange.com/questions/160100/the-repetitive-byte-counter/160236#160236 и https://godbolt.org/g/pGXn3B имеет пример, где gcc действительно выполняет эту оптимизацию, потому что gcc "знает о" malloc.

(А на архитектурах, отличных от x86, где I-кеш не согласован с D-кешем, он фактически выполняет любую необходимую синхронизацию кеша. На x86 это просто блокировщик оптимизации во время компиляции.)

Мое изменение в ответе @ AntoineMathys добавило это. В настоящее время gcc не знает о mmap, поэтому он не оптимизирует хранилище до указателя, возвращаемого mmap.

Но вам это не нужно после mprotect на странице, содержащей переменные C, доступные только для чтения.

#include <stdio.h>
#include <sys/mman.h>
#include <stdint.h>

// can be non-const if you want, we're using mprotect
static const char code[] = {
  0x8D, 0x04, 0x37,           //  lea eax,[rdi+rsi]       // retval = a+b;                    
  0xC3                        //  ret                                         
};

static const char ret0_code[] = "\x31\xc0\xc3";

int main () {
  // void* cast is easier to type than a cast to function pointer,
  // and in C can be assigned to any other pointer type.  (not C++)
  int (*sum) (int, int) = (void*)code;
  int (*ret0)(void) = (void*)ret0_code;

   // hard-coding x86's 4k page size for simplicity.
   // also assume that `code` doesn't span a page boundary and that ret0_code is in the same page.
  uintptr_t page = (uintptr_t)code & -4095ULL;                  // round down
  mprotect((void*)page, 4096, PROT_READ|PROT_EXEC|PROT_WRITE);  // +write in case the page holds any writeable C vars that would crash later code.

  // run code                                                                   
  int c = sum (2, 3);
  return ret0();
}

Я использовал PROT_READ|PROT_EXEC|PROT_WRITE в этом примере, поэтому он работает независимо от того, где находится ваша переменная. Если он был локальным в стеке и вы пропустили PROT_WRITE, call потерпит неудачу после того, как стек будет доступен только для чтения, когда он попытается отправить адрес возврата.

Кроме того, PROT_WRITE позволяет вам тестировать шелл-код, который изменяется автоматически, например, редактировать нули в свой собственный машинный код или другие байты, которых он избегал.

$ gcc -O3 shellcode.c           # without -z execstack
$ ./a.out 
$ echo $?
0
$ strace ./a.out
...
mprotect(0x55605aa3f000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

ЕслиЯ комментирую mprotect, он делает segfault.

Если бы я сделал что-то вроде ret0_code[2] = 0xc3;, мне понадобится __builtin___clear_cache(ret0_code+2, ret0_code+2) после этого, чтобы убедиться, что магазин не был оптимизированнет, но если я не изменю статические массивы, то после mprotect это не нужно.Это необходимо после mmap + memcpy или ручного сохранения, потому что мы хотим выполнить байты, записанные в C (с memcpy).

0 голосов
/ 05 декабря 2012

Вам может потребоваться установить исполняемый файл страницы, прежде чем вы сможете его вызвать.В MS-Windows см. Функцию VirtualProtect.

URL: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366898%28v=vs.85%29.aspx

...