Введение в эксплуатацию двоичных файлов x64 Linux (часть 1)
Введение в бинарную эксплуатацию x64 Linux (часть 2)
Давайте применим на практике то, что мы видели до сих пор, на простом примере. В приведенной ниже программе у нас есть два выделения размером 12 и 30 байт соответственно. Затем мы копируем значения LoremIpsum1 и LoremIpsum2 в переменные val1 и val2 и, наконец, освобождаем выделенную память с помощью функции free:
еперь скомпилируем и загрузим программу в gdb, установив одну точку останова после вызова malloc и еще одну после вызова функции free:
b *main+89, b *main+109
Before Free
Нажмите run в gdb и введите heap arenas в gef, чтобы отследить структуру malloc_state. Вспомните изображение, которое мы использовали во вступительном сообщении, помня, что у нас один поток:
Мы запросили 12 и 30 байт, а получили 0x21 и 0x31, почему? Из-за выравнивания! Помните, что размер выделения должен быть выровнен по 8-байтовой (или 16-байтовой на 64-битной) границе? Итак, для 12 байт у нас выделено 32, а для 30 байт - 32+16. '1' в добавленном размере относится к флагу PREV_INUSE, поэтому он не "учитывается" в фактическом размере. Давайте, например, рассмотрим чанк по адресу 0x5555555592a0:
Вы, вероятно, также заметили наши данные (LoremIpsum1 и LoremIpsum2) по адресам 0x555555555592a0 и 0x5555555592c0 . Наконец, у нас есть чанк размером 0x00000000000000000290 в начале кучи и еще один размером 0x0000000000020d20 (top chunk) в конце.
After Free
Чтобы получить лучшее представление о последствиях работы функции free, давайте изменим исходную программу, добавив еще несколько выделений, и рассмотрим, как ptmalloc обрабатывает их.
Скомпилируйте приведенную выше программу и загрузите ее в gdb, установив точку останова после вызовов free.
Каждое дополнительное выделение 0x10 или меньше байт будет интегрировано в Fastbins, так как оно превышает максимально допустимое (в нашем случае это 7) в tcache для этого размера:
Теперь куча выглядит так, как показано ниже:
Basic Heap Overflow
Посмотрите на приведенный ниже фрагмент кода. Обратите внимание, что программа допускает ввод данных размером, превышающим выделенное пространство (строки 9,10,15). Удобно для нас, что функция system примет в качестве параметра строку, на которую указывает переменная p1, и выполнит ее как системную команду. pwd - это жестко закодированная команда, которая будет выполнена, подразумевая, что функция system просто выведет текущий каталог:
При этом, если мы скомпилируем и запустим программу, то получим следующий результат:
Давайте загрузим программу в gdb и установим точку останова после функции scanf. Введя правильный входной сигнал, получим следующий результат:
Продолжаем программу, все идет как положено:
Как показано ниже, нам нужно 32 байта, чтобы добраться до строки pwd и перезаписать ее:
Итак, давайте воспользуемся следующей строкой и посмотрим, что произойдет:
Посмотрите на этот беспорядок!!! Сначала обратите внимание на код программы, что буфер выделяется после буфера, который может быть переполнен. Между прочим, размер и флаги были изменены, но самое главное команда pwd теперь заменена на/bin/sh :
Запустив программу и введя вышеупомянутые данные, мы получим Shell:
From Integer Overflow to Code Execution
Теперь рассмотрим более интересный случай целочисленного переполнения:
Начиная со строки 5, мы определяем fp как указатель на функцию, которая принимает const char * в качестве параметра и возвращает целое число. Функция toInt в строках 7-13 выделяет место для переменной ti (строка 10) и присваивает ей адрес функции int atoi(const char *str) Си. Позже в main мы определяем переменную nt в строке 24, которая может быть использована для вызова atoi с помощью объявления(*nt)(const char *str).
Кроме того, функция main создает массив беззнаковых целых чисел длины argv[1] и заполняет этот массив в строках 27-33. Цикл while прерывается, когда пользователь вводит значение 1 или когда i == length. Наконец, в строке 35 вызывается atoi с параметром, принятым за аргумент argv[2].
Целочисленное переполнение происходит из-за того, что размер массива был определен как беззнаковое целое число, но так как в строке 21 нет относительной проверки при его присвоении, пользователь может вставить произвольное большое арифметическое значение и вызвать переполнение. В приведенном ниже демонстрационном примере я буду использовать немного другую версию вышеприведенной программы, чтобы вывести некоторую дополнительную информацию для наглядности.
Давайте загрузим программу в gdb, установив две точки останова в начале и конце главной функции. Теперь запустим программу и зададим несколько допустимых параметров (например, gef> r 5 3 ):
После нажатия второй точки останова куча будет выглядеть так, как показано ниже:
Обратите внимание на указатель массива по адресу 0x555555555592a0, а также на указатель функции по адресу 0x5555555592c0 . Рассмотрим содержимое кучи по этим адресам:
Для длины, равной 5, запрашиваемый размер составляет 5*4 байта, поэтому malloc выделил (минимально) 32 байта:
Аналогично, второй вызов malloc (см. функцию toInt()) привел к следующему распределению:
Размер unsigned int для данной системы составляет 4 байта, поэтому максимально допустимое значение будет 0xffffffffff == 4294967295 . Поскольку ввод пользователя умножается на размер беззнакового целого, см. строку 21, мы предполагаем, что размер, превышающий 4294967295/4, вызовет переполнение:
Действительно, обратите внимание, что во втором случае выделенный размер равен 0, но цикл wile в строке 27 позволит нам выйти за пределы выделенного пространства. Давайте проверим это с помощью gdb:
Обратите внимание, что второй вызов malloc буквально исчез из кучи, поскольку выделенное пространство было перезаписано значениями, которые мы вставили (2,3,4,5,...). В том числе и адрес функции atoi, который был заменен значением 0x00000001000000. Продолжая выполнение, мы получим ошибку SIGSEGV, так как наша программа будет использовать указатель функции, который хранится по адресу 0x00005555555592c0 // Строка 35: (*nt)(argv[2]) . Поскольку этот указатель был заменен на недопустимый адрес, результатом будет крах программы:
Exploitation
Можете ли вы придумать функцию, подобную atoi, которая принимает строку в качестве параметра и выполняет команду?
Предполагая, что ASLR отключен, мы должны заменить адрес atoi (0x7ffff7e115e0 в моем случае) на 0x7ffff7e1f2c0, который является адресом системной функции. Поскольку всего у нас 6 байт, а на одном входе мы можем перезаписать только 4 (из-за массива, определяемого unsigned int), мы сначала перезапишем часть f7e1f2c0 (десятичная запись: 4158780096), а затем часть 7ffff (десятичная запись: 32767). Запустим программу с gef➤ r 1073741824 /bin/sh и вставим следующие значения:
Адрес atoi был перезаписан адресом системной функции, а в качестве параметра был указан /bin/sh:
Таким образом, нажатие клавиши c в gef приведет к появлению оболочки:
Это все для этой части. Оставайтесь с нами для следующей части!
valsamaras.medium.com
Введение в бинарную эксплуатацию x64 Linux (часть 2)
Давайте применим на практике то, что мы видели до сих пор, на простом примере. В приведенной ниже программе у нас есть два выделения размером 12 и 30 байт соответственно. Затем мы копируем значения LoremIpsum1 и LoremIpsum2 в переменные val1 и val2 и, наконец, освобождаем выделенную память с помощью функции free:
еперь скомпилируем и загрузим программу в gdb, установив одну точку останова после вызова malloc и еще одну после вызова функции free:
b *main+89, b *main+109
Before Free
Нажмите run в gdb и введите heap arenas в gef, чтобы отследить структуру malloc_state. Вспомните изображение, которое мы использовали во вступительном сообщении, помня, что у нас один поток:
Мы запросили 12 и 30 байт, а получили 0x21 и 0x31, почему? Из-за выравнивания! Помните, что размер выделения должен быть выровнен по 8-байтовой (или 16-байтовой на 64-битной) границе? Итак, для 12 байт у нас выделено 32, а для 30 байт - 32+16. '1' в добавленном размере относится к флагу PREV_INUSE, поэтому он не "учитывается" в фактическом размере. Давайте, например, рассмотрим чанк по адресу 0x5555555592a0:
Вы, вероятно, также заметили наши данные (LoremIpsum1 и LoremIpsum2) по адресам 0x555555555592a0 и 0x5555555592c0 . Наконец, у нас есть чанк размером 0x00000000000000000290 в начале кучи и еще один размером 0x0000000000020d20 (top chunk) в конце.
After Free
Чтобы получить лучшее представление о последствиях работы функции free, давайте изменим исходную программу, добавив еще несколько выделений, и рассмотрим, как ptmalloc обрабатывает их.
Скомпилируйте приведенную выше программу и загрузите ее в gdb, установив точку останова после вызовов free.
Каждое дополнительное выделение 0x10 или меньше байт будет интегрировано в Fastbins, так как оно превышает максимально допустимое (в нашем случае это 7) в tcache для этого размера:
Теперь куча выглядит так, как показано ниже:
Basic Heap Overflow
Посмотрите на приведенный ниже фрагмент кода. Обратите внимание, что программа допускает ввод данных размером, превышающим выделенное пространство (строки 9,10,15). Удобно для нас, что функция system примет в качестве параметра строку, на которую указывает переменная p1, и выполнит ее как системную команду. pwd - это жестко закодированная команда, которая будет выполнена, подразумевая, что функция system просто выведет текущий каталог:
Код:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main()
{
char *p0,*p1;
p0 = malloc(0x10);
p1 = malloc(0x10);
strcpy(p1,"pwd");
printf("Enter your name:");
scanf("%100s",p0);
printf("Hello %s \n",p0);
system(p1);
free(p0);
free(p1);
}
При этом, если мы скомпилируем и запустим программу, то получим следующий результат:
Давайте загрузим программу в gdb и установим точку останова после функции scanf. Введя правильный входной сигнал, получим следующий результат:
Продолжаем программу, все идет как положено:
Как показано ниже, нам нужно 32 байта, чтобы добраться до строки pwd и перезаписать ее:
Итак, давайте воспользуемся следующей строкой и посмотрим, что произойдет:
Посмотрите на этот беспорядок!!! Сначала обратите внимание на код программы, что буфер выделяется после буфера, который может быть переполнен. Между прочим, размер и флаги были изменены, но самое главное команда pwd теперь заменена на/bin/sh :
Запустив программу и введя вышеупомянутые данные, мы получим Shell:
From Integer Overflow to Code Execution
Теперь рассмотрим более интересный случай целочисленного переполнения:
C:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef int(*fp) (const char *);
fp *toInt()
{
fp *ti;
ti = malloc(sizeof(*ti));
*ti = atoi;
return ti;
}
int main(int argc, char **argv)
{
unsigned int *array, length, size, i;
length = atoi(argv[1]);
size = sizeof(unsigned int) * length;
array = malloc(size);
fp *nt = toInt();
i = 0;
while(i < length)
{
scanf("%ld",&array[i]);
if(array[i] == 1)
break;
i++;
}
(*nt)(argv[2]);
return 0;
}
Начиная со строки 5, мы определяем fp как указатель на функцию, которая принимает const char * в качестве параметра и возвращает целое число. Функция toInt в строках 7-13 выделяет место для переменной ti (строка 10) и присваивает ей адрес функции int atoi(const char *str) Си. Позже в main мы определяем переменную nt в строке 24, которая может быть использована для вызова atoi с помощью объявления(*nt)(const char *str).
Кроме того, функция main создает массив беззнаковых целых чисел длины argv[1] и заполняет этот массив в строках 27-33. Цикл while прерывается, когда пользователь вводит значение 1 или когда i == length. Наконец, в строке 35 вызывается atoi с параметром, принятым за аргумент argv[2].
Целочисленное переполнение происходит из-за того, что размер массива был определен как беззнаковое целое число, но так как в строке 21 нет относительной проверки при его присвоении, пользователь может вставить произвольное большое арифметическое значение и вызвать переполнение. В приведенном ниже демонстрационном примере я буду использовать немного другую версию вышеприведенной программы, чтобы вывести некоторую дополнительную информацию для наглядности.
Давайте загрузим программу в gdb, установив две точки останова в начале и конце главной функции. Теперь запустим программу и зададим несколько допустимых параметров (например, gef> r 5 3 ):
После нажатия второй точки останова куча будет выглядеть так, как показано ниже:
Обратите внимание на указатель массива по адресу 0x555555555592a0, а также на указатель функции по адресу 0x5555555592c0 . Рассмотрим содержимое кучи по этим адресам:
Для длины, равной 5, запрашиваемый размер составляет 5*4 байта, поэтому malloc выделил (минимально) 32 байта:
Аналогично, второй вызов malloc (см. функцию toInt()) привел к следующему распределению:
Размер unsigned int для данной системы составляет 4 байта, поэтому максимально допустимое значение будет 0xffffffffff == 4294967295 . Поскольку ввод пользователя умножается на размер беззнакового целого, см. строку 21, мы предполагаем, что размер, превышающий 4294967295/4, вызовет переполнение:
Действительно, обратите внимание, что во втором случае выделенный размер равен 0, но цикл wile в строке 27 позволит нам выйти за пределы выделенного пространства. Давайте проверим это с помощью gdb:
Обратите внимание, что второй вызов malloc буквально исчез из кучи, поскольку выделенное пространство было перезаписано значениями, которые мы вставили (2,3,4,5,...). В том числе и адрес функции atoi, который был заменен значением 0x00000001000000. Продолжая выполнение, мы получим ошибку SIGSEGV, так как наша программа будет использовать указатель функции, который хранится по адресу 0x00005555555592c0 // Строка 35: (*nt)(argv[2]) . Поскольку этот указатель был заменен на недопустимый адрес, результатом будет крах программы:
Exploitation
Можете ли вы придумать функцию, подобную atoi, которая принимает строку в качестве параметра и выполняет команду?
Предполагая, что ASLR отключен, мы должны заменить адрес atoi (0x7ffff7e115e0 в моем случае) на 0x7ffff7e1f2c0, который является адресом системной функции. Поскольку всего у нас 6 байт, а на одном входе мы можем перезаписать только 4 (из-за массива, определяемого unsigned int), мы сначала перезапишем часть f7e1f2c0 (десятичная запись: 4158780096), а затем часть 7ffff (десятичная запись: 32767). Запустим программу с gef➤ r 1073741824 /bin/sh и вставим следующие значения:
Адрес atoi был перезаписан адресом системной функции, а в качестве параметра был указан /bin/sh:
Таким образом, нажатие клавиши c в gef приведет к появлению оболочки:
Это все для этой части. Оставайтесь с нами для следующей части!
The toddlerâs introduction to Heap exploitationâââOverflows(Part 3)
In the previous parts (1, 2) we discussed about the heap structure and we tried to simplify these concepts using a real life example. Youâ¦
valsamaras.medium.com