Изучение PWN #8
МихаилПолезная информация по ИБ и разборы задачек CTF.
Предыдущая задача - Изучение PWN #7.
Мы подошли к изучению темы уязвимости форматных строк. Первое вводное задание - Format Zero.
Исходники:
int main(int argc, char **argv) {
struct {
char dest[32];
volatile int changeme;
} locals;
char buffer[16];
printf("%s\n", BANNER);
if (fgets(buffer, sizeof(buffer) - 1, stdin) == NULL) {
errx(1, "Unable to get buffer");
}
buffer[15] = 0;
locals.changeme = 0;
sprintf(locals.dest, buffer);
if (locals.changeme != 0) {
puts("Well done, the 'changeme' variable has been changed!");
} else {
puts(
"Uh oh, 'changeme' has not yet been changed. Would you like to try "
"again?");
}
exit(0);
}
После беглого изучения исходного кода понимаем, что нам нужно изменить значение переменной changeme, которая определяется в структуре locals после массива dest. Посмотрим, на что мы можем повлиять своим вводом. Видим, что пользовательский ввод записывается в массив buffer типа char размером 16, при этом последний символ заменяется на 0. После чего используется функция sprintf.
Попробуем для начала проверить, что произойдет в памяти программы, если мы попробуем переполнить буфер, как делали в заданиях прошлого раздела.
Для наглядности, я буду использовать IDA на Windows и сервер IDA linux_server64 на Kali.
Открываем файл format-zero (предварительно скопировав его с виртуалки qemu, чтобы у нас был скомпилированный с нужными флагами, отключающими защиту, файл) в IDA, переходим к декомпилированному коду (Tab) и ставим точку останова на функции sprintf:

Выбираем Remote Linux Debugger :

На Kali запускаем ./linux_server64 (файл находится в dbgsrv в папке Иды).
Указываем IP-адрес Kali:

Запускаем, после чего нужно в Kali выполнить ввод, попробуем ввести 32*"A":

В IDA наводим мышкой на s и видим, что в ней хранится только 14 букв "A". Сделаем шаг на одну строку в декомпилированном коде и увидим, что содержимое s скопировалось в v4. Переменная changeme, которая в декомпилированном коде называется v5 не перезаписалась.
Посмотрим адреса переменной v4 и v5 (двойной клик ЛКМ по переменной в декомпилированном коде):
v4 - 0x00007FFFFFFFDED0 v5 - 0x00007FFFFFFFDEF0
Посчитаем смещение:
0x00007FFFFFFFDEF0 - 0x00007FFFFFFFDED0 = 0x20 = 32
Таким образом, простое переполнение буфера вводом большого количества мусорных символом в данной задаче не сработает, так как наш ввод ограничен 14-ю символами. Посмотрим, что мы сможем сделать с функцией sprintf.
Функция sprintf в языке программирования C является одной из функций семейства стандартной библиотеки для форматированного вывода. Она используется для формирования строк с использованием форматных спецификаторов. Одним из основных рисков использования sprintf является потенциал переполнения буфера, так как функция не выполняет проверку размера выходного буфера. Это может привести к перезаписи соседних данных в памяти, что угрожает безопасности приложения.
Основные спецификаторы формата:

Если мы введем вместо обычной строки спецификатор формата, функция sprintf при копировании нашего ввода из buffer запишет в locals.dest данные из стека, из которого она была вызвана, отформатировав их согласно спецификатору. Таким образом, при вводе спецификатора %x мы передаем только два символа, но функция sprintf воспримет это как формат и выведет данные из стека в hex-формате, то есть запишет 8 символов в locals.dest. Давайте проверим это, запускаем процесс отладки, в Kali вводим %x.
В IDA можем посмотреть, что хранится в buffer:

Сделаем шаг, чтобы выполнилась функция sprintf и посмотрим, что записалось в locals.dest ( v4 в декомпилированном коде):

Видим, что записалось 8 байт, которые представляют собой адрес начала buffer, а также 9-ым байтом дописался байт 0x0A, обозначающий символ возврата каретки. Получается, что для того, чтобы перезаписать переменную changeme нам необходимо ввести 4 раза %x - %x%x%x%x.
Запускаем отладку заново, вводим в Kali - %x%x%x%x, делаем шаг в отладчике и смотрим память:

Видим, что все произошло так, как мы и предполагали и по адресу 0x00007FFFFFFFDEF0, по которому расположено значение переменной changeme записался символ возврата каретки 0x0A.

P.s. это не единственный способ решения, можно также передать, например, `%32d`, что запишет в `locals.dest` 32 десятичные цифры, и т.д.