Основная идея эксплойтов ret2ret состоит в том, чтобы найти значение в стеке, которое можно слегка изменить, чтобы оно указывало на буфер, который можно переполнить.Выполнив столько ret
инструкций, сколько необходимо для получения этого значения, вы сможете в конечном итоге выполнить код в буфере (эксплойт).
Стек из примера непосредственно перед выполнением call 0x80482b0 <_init+56>
выглядитвот так:
--------------------
| 0xbffff6f0 | ebp+28
--------------------
| 0xb7fdb000 | ebp+24
--------------------
| 0xb800167c | ebp+20
--------------------
| 0xbffff770 | ebp+16 (previous stack frame)
--------------------
| 0xbffff764 | ebp+12 (points to the argv array)
--------------------
| 0x2 | ebp+8 (holds the value of argc)
--------------------
| main ret addr | ebp+4
--------------------
|previous ebp value| ebp
--------------------
| appears to be |
| unused | ebp-8
--------------------
| |
| 256-byte |
| buf |
| |
| |
| | ebp-264
--------------------
| | ebp-268 (holds the second argument to strcpy)
--------------------
| | ebp-272 = esp (holds the first argument to strcpy)
--------------------
Итак, как выполнить успешную атаку ret2ret на этот стек?Мы знаем, что последняя инструкция в main
это ret
.Нам нужно найти способ выполнить эту инструкцию несколько раз, чтобы в конечном итоге выполнялся любой код, который мы решили вставить в buf
.Помните, что каждый раз, когда ret
выполняется в 32-битном режиме x86, он извлекает 4-байтовое значение из стека.Итак, первое, что нам нужно выяснить - это сколько 4-байтовых значений нужно извлечь из стека, чтобы получить значение, которое представляет указатель на buf
.Автор статьи показывает, что первое такое значение находится на ebp+28
.Диапазон 0xbffff6d8 - 0xbffff5d0 содержит 256-байтовый буфер.Однако значение в ebp+28
равно 0xbffff6f0, что на самом деле находится за пределами буфера.Но поскольку strcpy
добавит в конец байт NULL, мы можем сделать так, чтобы он перезаписывал первый байт ebp+28
на NULL, чтобы он содержал 0xbffff600, который указывает внутри буфера.Начиная с ebp+4
, существует 7 4-байтовых значений.Таким образом, нам нужно выполнить ret
7 раз, а затем выполнение будет продолжаться где-то в buf
.
. Для достижения этого значение в ebp+4
должно быть установлено на адрес ret
в main
, что составляет 0x080483b7.Точно так же все 4-байтовые расположения вплоть до ebp+24
должны быть установлены в 0x080483b7.Таким образом, первые 6 выполнений ret
просто перейдут на ret
, чтобы выполняться снова и снова.Но в последний раз при выполнении ret
управление будет передано в 0xbffff6f0, который находится в буфере, который будет перезаписан.
Теперь рассмотрим, что происходит, когда evilbuf
из кода эксплойта передается как argv[1]
.Буфер содержит 261 однобайтовых инструкций NOP.Следующие 7 байтов являются инструкциями, которые выполняют системный вызов exit (хотя реальный эксплойт мог бы сделать что-то более интересное).Эти 268 байтов перезапишут все байты от ebp-264
до ebp+3
.Тогда есть 6 копий адреса ret
.В конце, strcpy
щедро добавит для нас NULL, перезаписав таким образом байт в ebp+28
.
После того, как strcpy
вернется и ret
в main
будет выполнен впервые,оно будет выполнено еще 6 раз, а затем будет выполнена инструкция по адресу 0xbffff600.Это один из наших НОПов.Тогда остальные сани NOP будут выполняться, пока не достигнет заданной последовательности инструкций (полезной нагрузки), которая в этом случае просто выполняет системный вызов выхода.
Ret2ret не требует предварительногознание адреса буфера.Следовательно, он может работать, даже если ОС рандомизирует базовый адрес стека.Но для этого нужно знать адрес инструкции ret
.