Вы хотите перезаписать адрес возврата из функции echo байтами, прочитанными из стандартного ввода, чтобы теперь он указывал на точку входа not_called
.
Возьмем, к примеру, Mac OS/X 10.10, также известную как Yosemite. Я упростил код и добавил дополнительный printf, чтобы получить фактический адрес функции not_called
:
#include <stdlib.h>
#include <stdio.h>
void echo(void) {
char buf[4]; /* Way too small */
gets(buf);
puts(buf);
}
void not_called(void) {
printf("This routine is never called\n");
printf("If you see this message, something bad has happened\n");
exit(0);
}
int main(void) {
printf("not_called is at address %p\n", not_called);
echo();
}
Давайте скомпилируем и выполним этот код с помощью clang:
chqrlie> clang t20.c && ./a.out
Вывод вполне ясен:
not_called is at address 0x106dade50
warning: this program uses gets(), which is unsafe.
Используя шестнадцатеричный редактор, давайте подделаем входные данные и вставим их в консоль: короткий буфер buf
выровнен по 64 битам, на 8 байт ниже сохраненной копии указателя кадра стека rbp
, за которым следует адрес возврата, который мы хотим перезаписать. Например, ввод в шестнадцатеричном формате:
0000 3031 3233 3435 3637-3839 3031 3233 3435 0123456789012345
0010 50de da06 0100 0000- P��.....
Давайте вставим эти 24 байта в консоль и нажмем Enter:
0123456789012345P��^F^A^@^@^@
0123456789012345P��^F^A
This routine is never called
If you see this message, something bad has happened
Segmentation fault: 11
Функция echo
использует для чтения стандартный ввод, 24 байта сохраняются за концом buf
, перезаписывая указатель кадра rbp
, адрес возврата и дополнительный 0 байт. Затем echo
вызывает puts
для вывода строки в buf
. Вывод останавливается на первом "'\0'", как и ожидалось. Затем rbp
восстанавливается из стека и получает поврежденное значение, управление передается на адрес возврата. Адрес возврата был перезаписан адресом функции not_called
, так что это то, что выполняется дальше. Действительно, мы видим сообщение от функции not_called
, и по какой-то причине exit
падает вместо корректного выхода из процесса.
Я специально использовал gets
, чтобы читатели поняли, как легко вызвать переполнение буфера с помощью этой функции. Независимо от того, насколько велик буфер, ввод может привести к сбою программы или заставить ее делать интересные вещи.
Еще одна интересная находка заключается в том, как Mac OS/X пытается помешать злоумышленникам слишком легко использовать этот трюк: адрес, выдаваемый программой, меняется от одного запуска к другому:
chqrlie > ./a.out < /dev/null
not_called is at address 0x101db8e50
warning: this program uses gets(), which is unsafe.
chqrlie > ./a.out < /dev/null
not_called is at address 0x10af4ae50
warning: this program uses gets(), which is unsafe.
chqrlie > ./a.out < /dev/null
not_called is at address 0x102a46e50
warning: this program uses gets(), which is unsafe.
Код каждый раз загружается по другому адресу, выбранному случайным образом. Ввод, необходимый для возврата функции echo
к not_called
, каждый раз разный. Попробуйте свою собственную ОС и проверьте, использует ли она этот трюк. Попробуйте ввести соответствующий ввод, чтобы выполнить работу (это зависит от вашего компилятора и вашей системы). Веселиться!
person
chqrlie
schedule
27.02.2015