Я читаю превосходную книгу Джона Эриксона "Взлом: искусство эксплуатации" и пытаюсь понять его изложение переполнения буфера. Книга кажется немного устаревшей; в его примерах он работает под управлением x86 linux, и у меня возникают проблемы с репликацией результатов на x64 (я знаю, что в последние годы была добавлена большая защита стека). В частности, я изо всех сил пытаюсь воспроизвести его exploit_notesearch.c
программу.
В начале своей книги он продемонстрировал программу notesearch.c
, которая запускает suid root и имеет следующие начальные строки после включения в библиотеку и объявления функций:
int main(int argc, char *argv[]) {
int userid, printing=1, fd;
char searchstring[100];
if(argc>1)
strcpy(searchstring, argv[1]);
else
searchstring[0]=0;
...
Теперь Эриксон позже демонстрирует эксплойт для этой программы под названием exploit_notesearch.c
, скелет которого:
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a"
"\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80";
int main(int argc, char *argv[]) {
unsigned int i, *ptr, ret, offset=170;
char *command, *buffer;
...
if(argc>1)
offset=atoi(argv[1]);
ret=(unsigned int) &i-offset;
...
system(command);
free(command);
}
Области, которые я выбрал, просто копируют правильные данные в command
, начиная с записи "./notesearch '"
, затем вставляя 60 NOP
байтов, затем вставляя данные, хранящиеся в shellcode
, а затем заполняя оставшуюся часть выделенной памяти адресом ret
и заканчивая строка с '
.
Насколько я понимаю, идея эксплойта должна заключаться в следующем. После выполнения строки system(command)
система поместит в стек новый sh новый кадр стека для функции main
с notesearch
. Внизу этого стекового фрейма находится адрес, к которому EIP
должен возвращаться после завершения основной функции, а где-то посередине - место, выделенное для буфера searchstring
. ret
предназначен для аппроксимации начала пространства, выделенного для searchstring
, который мы перезаписываем с помощью инструкций NOP
(в качестве коэффициента выдумки), шелл-кода (который при выполнении открывает оболочку root), а затем десятков копий адреса ret
, чтобы мы перезаписали адрес возврата для EIP
. Система выполняет main
как обычно, но затем вместо возврата к адресу в коде exploit_notesearch
возвращается к адресу ret
и продолжает выполнение шелл-кода по желанию. Идея определения ret
заключается в том, что i
живет где-то в кадре стека непосредственно над кадром стека для функции main
, равной notesearch.c
, поэтому searchstring
не должен находиться слишком далеко от i
, и следовательно, экспериментируя с различными смещениями, мы сможем найти тот, который работает. (Сани NOP
означают, что мы не должны быть абсолютно точными.)
Я думаю, что я в основном понял это правильно, но есть несколько проблем. Принципиальным является то, что мое понимание того, как работает system()
, основано на догадках, поскольку Эриксон не подробно описывает, как это работает. Чтобы понять, что происходит, я попытался переписать эту программу для совместимости с x64 linux, внеся следующие изменения:
- , заменив код, указанный Эриксоном, на код, написанный для x64
- изменив
i
, ret
и offset
на длинные беззнаковые целые числа и изменив приращение i
в for
l oop на 8
, чтобы учесть большие адреса памяти - компиляция
notesearch.c
и exploit_notesearch.c
с флагом -fno-stack-protector
Однако это не сработало вообще, поэтому для отладки я добавил строку к notesearch.c
, которая печатает адрес searchstring
и строка exploit_notesearch.c
, которая печатает адрес ret
. После запуска ./exploit_notesearch
несколько раз я получал странные результаты:
trial 1:
ret: 0x7ffdc21f25ce
searchstring: 0x7ffee3c209a0
trial 2:
ret: 0x7fff6115703e
searchstring: 0x7ffd1233afb0
trial 3:
ret: 0x7ffeab00781e
searchstring: 0x7fff3c8a8760
Итак, что здесь происходит? Кажется, что вызов system()
изменяет стек действительно непредсказуемым образом, иногда помещая новый кадр стека ниже старого, а иногда выше. Отладка с помощью gdb
не помогла, так как кажется, что весь вызов system()
объединен в одну строку call 0x555555554710 <system@plt>
, которая не дала никакой информации.
Итак, мои основные вопросы - это :
- как команды оболочки, вызываемые с
system()
, взаимодействуют со стеком? - это сделано в x64 linux иначе, чем в x86, или я действительно неправильно понял код, написанный Эриксоном?
- есть ли способ отключить эти меры безопасности на x64 linux при компиляции, чтобы я мог следить за кодом Эриксона, пока я учусь?
Извинения за многословный вопрос и спасибо заранее.
Редактировать: По предложению Шута ниже я отключил ASLR и теперь программа работает правильно. В качестве последующего вопроса, есть ли у кого-нибудь рекомендации для понимания ASLR? Ура!