Бинарные уязвимости/Off-by-one: различия между версиями
Dzeni (обсуждение | вклад) м (→Off-by-one) |
Dzeni (обсуждение | вклад) м (→GOT/PLT) |
||
Строка 53: | Строка 53: | ||
* '''.got''' - (Global Offset Table) таблица смещений для всех внешних символов | * '''.got''' - (Global Offset Table) таблица смещений для всех внешних символов | ||
− | * '''.plt''' - (Procedure Linkage Table) таблица привязки процедур. заглушки, которые ищут адрес в .got.plt и либо переходят по адресу( | + | * '''.plt''' - (Procedure Linkage Table) таблица привязки процедур. заглушки, которые ищут адрес в .got.plt и либо переходят по адресу(если он найден), либо запускают код поиска адреса. |
* '''.got.plt''' - got для plt. содержит либо целевые адреса (если они были записаны), либо адрес обратно в .plt для запуска поиска. | * '''.got.plt''' - got для plt. содержит либо целевые адреса (если они были записаны), либо адрес обратно в .plt для запуска поиска. | ||
Версия 11:11, 26 ноября 2021
Содержание
Off-by-one
Копирование исходной строки в целевой буфер может привести к off-by-one на стеке, если длина исходной строки равна длине целевого буфера. Когда длины совпадают, единственный NULL байт копируется выше целевого буфера. Поскольку целевой буфер расположен в стеке, то единственный NILL байт может перезаписать младший бит EBP, хранящийся в стеке, что может привести к выполнению произвольного кода.
Рассмотрим пример уязвимого кода
void foo(char* arg);
void bar(char* arg);
void foo(char* arg) {
bar(arg); /* [1] */
}
void bar(char* arg) {
char buf[256];
strcpy(buf, arg); /* [2] */
}
int main(int argc, char *argv[]) {
if(strlen(argv[1])>256) { /* [3] */
printf("Attempted Buffer Overflow\n");
fflush(stdout);
return -1;
}
foo(argv[1]); /* [4] */
return 0;
}
В данном коде вызывается функция main, которая проверяет длину входа и вызывает функцию foo. Функция foo вызывает функцию bar. В функции bar определяется локальный массив buf, размещающийся на стеке и содержащий 256 элементов, и функция копирует аргумент arg с помощью функции strcpy. Данная функция копирует arg, переданный во втором аргументе, в buf до тех пор пока не встретит в ней нулевой байт. При этом сам нулевой байт также будет скопирован, и может затереть младший бит EBP. Рассмотрим стековые кадры этих функций.
Эксплуатация
Способ эксплуатации данной уязвимости заключается в том, передать строку длиной 256, которая содержит новый адрес возврата на shellcode и сам shellcode. Таким образом, при копировании в buf, будет изменен EBP (младший бит затерт нулем), регистр станет указывать выше по стеку (в buf) на адрес возврата, указывающий на shell code.
GOT/PLT
в любой системе есть два типа двоичных файлов: статически связанные и динамически связанные. Статически связанные двоичные файлы являются самодостаточными, содержат весь код, необходимый для их работы в одном файле, и не зависят от каких-либо внешних библиотек. Динамически подключаемые двоичные файлы не включают в себя множество функций, но полагаются на системные библиотеки для обеспечения части функций. Например, когда ваш двоичный файл использует puts для печати некоторых данных, фактическая реализация puts является частью системной библиотеки C.
Чтобы найти эти функции, ваша программа должна знать адрес puts для ее вызова. Хотя это можно записать в исходный двоичный файл во время компиляции, у этой стратегии есть некоторые проблемы:
- каждый раз, когда библиотека изменяется, адреса функций внутри библиотеки меняются, при обновлении libc необходимо будет пересобрать каждый двоичный файл в вашей системе.
- современные системы, использующие ASLR, загружают библиотеки в разные места при каждом запуске программы. Адреса с жестким кодированием сделали бы это невозможным.
Была разработана стратегия, называемая relocation, позволяющая найти все эти адреса при запуске программы и предоставить механизм для вызова этих функций из библиотек.
Для реализации relocation в ELF файлах есть несколько специальных разделов
- .got - (Global Offset Table) таблица смещений для всех внешних символов
- .plt - (Procedure Linkage Table) таблица привязки процедур. заглушки, которые ищут адрес в .got.plt и либо переходят по адресу(если он найден), либо запускают код поиска адреса.
- .got.plt - got для plt. содержит либо целевые адреса (если они были записаны), либо адрес обратно в .plt для запуска поиска.
Рассмотрим пример кода.
int main(int argc, char **argv) {
puts("Hello world!");
puts("Hi world!");
exit(0);
}
При вызове
puts("Hello world!");
будет последовательно происходить следующее:
- при вызове межмодульной (библиотечной) функции произойдет обращение к соответствующей записи в .plt
- обращение к соответствующей записи в .got.plt в поисках адреса; адрес будет не найден (т.к. это первый вызов данной функции)
- будет запущен код поиска адреса, найденный адрес записан в .dot.plt, вызвана функция puts
При вызове
puts("Hi world!");
будет последовательно происходить следующее:
- при вызове межмодульной (библиотечной) функции произойдет обращение к соответствующей записи в .plt
- обращение к соответствующей записи в .got.plt в поисках адреса; адрес будет найден
- передача адреса, возврат в .plt
- вызов resolver для вызова функции puts
Уязвимость
Уязвимость в данном случае может появиться, если у атакующего есть возможность перезаписать адрес функции в .got.plt. Заменив адрес возврата, на адрес контролируемой памяти, атакующий может добиться выполнения произвольного кода, при повторном вызове межмодульной функции.
Relocation read only(RELRO)
Для защиты от данной уязвимости был введен механизм RELRO, ограничивающий права на запись. Он имеет два уровня: частичный (ограничивает запись в .got) и полный (ограничивает также запись в .got.plt)