Данный туториал для начинающих, но подразумевается, что читатель уже знаком с основами работы функции malloc библиотеки glibc. Подробно рассмотрим как эксплуатировать переполнение кучи в Linux на примере 32-разрядного Raspberry PI/ARM1176.  Так же разберем некоторые нюансы эксплуатации и в x86-x64 системах. Для этого будем использовать инструменты  GDB + GEF. Переходим сразу к уязвимому коду, который я позаимствовал из лабораторных заданий Protostar, а именно данное задание. Вкратце о коде.  Создаются структуры i1, i2, i3.  При запуске программы передаются два аргумента, которые копируются по адресам указателей i1->name и i2->name соответственно.   И в конце выводится сообщение "and that's a wrap folks!". Вызвать функцию winner.  Для начала компилируем код: Для вызова функции winner, нужно взять ее адрес и записать в указатель, в котором находится адрес функции printf .  Для этого надо переполнить указатель i1->name и перезаписать адрес  i2->name адресом функции winner.  Что бы его переполнить, необходимо вычислить длину смещения от  i1->name до i2->name.   Выглядит запутанным, но обо всем по порядку.  Вычисляем длину. Эту длину можно подобрать экспериментальным путем. Загружаем нашу программу в GDB  Смотрим код И ставим брейк поинт в конце кода И запускаем с параметрами Смотрим адрес начала кучи: И по этому адресу смотрим содержимое памяти Желтым выделены адреса чанков (chunk). Зеленым выделено количество байтов от одного чанка до другого. Давайте взглянем на выделенную память в куче.  Здесь мы можем наблюдать чанк по адресу 0x22160, рассмотрим его поближе 16 байт служебные 12 байт пользовательские данные 4 байта адрес этого чанка Итого получается 32 байта весь кусок кучи. Из него вычитаем 12 байт данных и получаем 20 байт, это и будет наша длинна до следующего чанка. (Для 64 битной системы эти расчеты следовательно будут с удвоенными слагаемыми 32 + 24 + 8 = 64 байта, длинна будет 40 байт) Теперь запустим с входным параметром 24 байта (20 байт до адреса следующего чанка и 4 байта для замещения этого адреса): Получаем Segmentation fault. Теперь через отладчик посмотрим изнутри, что происходит.  Поставим точку останова на второй вызов функции strcopy, который находится по адресу 0x000105e8 И запустим программу Переходим к точке останова и наблюдаем, как перезаписывается адрес указателя нашими символами: Для вызова функции winner, нужно взять ее адрес и записать в указатель, в котором находится адрес функции printf (по факту там находится функция puts).  Теперь нужно узнать по какому адресу функция puts располагается в GOT. Что бы это узнать воспользуемся GEF командой got. Эта команда выводит текущее состояние таблицы GOT запущенного процесса.  И так, мы видим, что наш указатель находится по адресу 0x21018. Будем писать в него адрес нашей функции winner.  Теперь посмотрим по какому адресу располагается функция winner. Функция расположена по адресу 0x10504.  Пришло время составить наш эксплоит и запустить его из под отладчика. Посмотрим изнутри как перезаписывается адрес в указателе i2->name и заносится в него адрес нашей функции: Здесь мы видим, что по адресу 0x21018 будет записан адрес функции 0x10504 <winner>, что и следовало ожидать. Продолжим выполнение командой nexti и проверим, что находится по адресу  0x21018:  А в нем находится адрес нашей функции 0x10504 <winner>. Теперь выходим из отладчика и запускаем программу обычным способом с нашим эксплоитом: Видим, что bash вывел предупреждение, о том, что нуль-байты в наших адресах были проигнорированы (ignored null byte in input), но приложение выдает нам заветное сообщение, тем самым подтверждая, что мы вызвали функцию winner. Эксплуатация произошла и задача решена.   Теперь перейдем в Linux для архитектуры x86-x64.  В нем установлена последняя версия GDB 10.1.90 на момент написания этого поста и отлична от ARM1176, для Raspberry версия GDB 8.2.1. Например, что бы узнать адрес функции puts, сначала так же смотрим адрес указателя .plt а затем можно сразу увидеть адрес функции указателя в закомментированном виде такой командой: это избавляет от дополнительных телодвижений.  Мелочь, а приятно. Далее смотрим адрес функции winner Смотрим адрес функции puts 0x555555558020 и обнаруживаем, что в нем содержится число x20, которое соответствует  символу пробела, а это значит, что при передаче этого адреса программе через командную оболочку, например bash воспримет это как разделитель аргументов. Поэтому эксплуатация через шелл не получится. Как вариант, можно заключить аргументы в кавычки "$(...)", например так: В общем случае это сработало бы. Но в нашем случае это не помогло, так как оказалось, что наши полученные адреса нужно выравнивать до 8 байт нуль-байтами, т.е.  \x20\x80\x55\x55\x55\x55\x00\x00 \x75\x51\x55\x55\x55\x55\x00\x00   Но и тут возникает проблема, потому что командная оболочка воспринимает нуль-байты как конец строки и эксплойт не сработает, т.к. адреса получаются неверными. Для решения этой проблемы, можно воспользоваться функцией execve языка C.  Пишем эксплоит: В коде думаю все ясно.  Компилируем и запускаем Теперь эксплоит отрабатывает как следует: На этом все. Спасибо за внимание. hacking & radio electronics