Переполнение буфера в стеке - вызов в C с использованием scanf с ограниченным вводом
В рамках курса по безопасности CS мой класс получил задание использовать уязвимость, чтобы пройти проверку пароля, используя переполнение стека / буфера. Код с уязвимостью выглядит следующим образом:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/md5.h>
int main(int argc, char **argv) {
char correct_hash[16] = {
0xd0, 0xf9, 0x19, 0x94, 0x4a, 0xf3, 0x10, 0x92,
0x32, 0x98, 0x11, 0x8c, 0x33, 0x27, 0x91, 0xeb
};
char password[16];
printf("Insert your password: ");
scanf("%29s", password);
MD5(password, strlen(password), password);
if (memcmp(password, correct_hash, 16) == 0) {
printf("Correct Password!\n");
} else {
printf("Wrong Password, sorry!\n");
}
return 0;
}
Я понимаю классический принцип "разбивания стека" (я думаю), и здесь есть явная уязвимость переполнения, где первые 14 байтов correct_hash
массив можно перезаписать, введя пароль длиной более 15 символов при запросе. Тем не менее, я не понимаю, как использовать это, чтобы позволить memcmp
проверьте, чтобы пройти, завершив вызов. Некоторые из вещей, которые я обнаружил / попытался:
настройка
password
быть эквивалентомcorrect_hash
не работает, какpassword
хешируется с помощьюMD5()
(установить два равных невозможно в любом случае, так какscanf
вставит точно один уникальный ASCIINUL
символ в 30 доступных ему пробелах, что означает, что эти два массива никогда не могут быть эквивалентными.NUL
символы дополнительно (насколько мне известно) не могут быть вставлены в серединеscanf
строка).Перезаписать максимальное количество байтов с
scanf
(который всегда будет добавлятьNUL
символ) означает последние 3 байтаcorrect_hash
всегда будет0x00 0x91 0xeb
, Попытка случайным образом сгенерировать 16-символьный пароль, который затем хэшируется к чему-либо с этими последними 3 байтами / символами (достаточно простыми в вычислительном отношении, учитывая использование MD5), однако, не работает из-за использованияstrlen(password)
(что даст значение 29 вместо чего-то удобного, например, 16, благодаря тому, что счетчик длины только заканчиваетсяNUL
характер) в призыве кMD5()
, Это означает, что вместо хеширования 16-символьного пароля для получения ожидаемого вывода, вызовMD5()
будет хэш 16 символов изpassword
а затем 13 символов изcorrect_hash
, производя другое окончательное хэшированное значение.- Чтобы обойти эту проблему, я считаю, что нужно было бы найти строку из 29 символов (назовем ее S), где первые 16 символов S-хэша строки R, состоящей из последних 13 символов S, сопровождаются
0x00 0x91 0xeb
, Я не уверен, насколько жизнеспособным будет найти это с помощью случайных хеш-вычислений MD5, но мне не нужны мои шансы.
- Чтобы обойти эту проблему, я считаю, что нужно было бы найти строку из 29 символов (назовем ее S), где первые 16 символов S-хэша строки R, состоящей из последних 13 символов S, сопровождаются
Некоторые примечания (упомянутые в пояснениях выше):
scanf
ограничено 29-символьной строкой, но добавит ASCIINUL
всего 30 символов (16 изpassword
массив, 14 изcorrect_hash
массив) перезаписать.ASCII
NUL
символы не могут быть введены черезscanf
Итакstrlen(password)
в призыве кMD5()
(если используется максимальная длина пароля) будет 29.
Итак, вопрос в том, как еще я могу это сделать? Я что-то упускаю чрезвычайно очевидное? Является ли случайное поколение жизнеспособным решением или даже единственным решением?
Решение должно использовать переполнение буфера (в противном случае я думаю, что я мог бы сделать что-то вроде предварительной загрузки memcmp
всегда возвращает 0) и должен быть исполняемым как сценарий оболочки (если это имеет какое-либо значение).
1 ответ
Просто чтобы объединить комментарии в ответ здесь:
Суть в том, что scanf
с радостью примет нулевой байт как часть строки и не будет рассматривать его как пробел (таким образом, не прекратит чтение дополнительных байтов в строку).
Перенаправить файл или использовать echo -ne "\x00" | program
или т.п. Перенаправление входного файла, вероятно, является предпочтительным способом здесь.
Последняя команда, которую я использовал, была: echo -ne "\x49\x5a\x4e\x52\x48\x49\x41\x56\x5a\x43\x54\x52\x51\x4c\x43\x00\x81\xae\xf3\xdf\xa2\x45\xb1\x57\x19\xb3\xa9\xb8\x7d\x00\x91\xeb" | ./vulnerable
где первый набор из 16 байтов (до первого включительно \x00
) были случайно сгенерированной строкой, которая при хешировании вызвала второй набор из 16 байтов с требуемым \x00\x91\xeb
окончание. Последние 3 байта все равно не были скопированы, но я оставил их, чтобы показать строку и хэш.