Tuesday, October 29, 2019

myapp @Safe-10.10.10.147@HTB BO DEP (ROP в локальный system@main)

Продолжая серию своих черновых заметок о бинарщине, расскажу о переполнении буфера в машинке Safe все на том же HTB. Интересно здесь может быть то, что необходимый "стандартный" гаджет "pop rdi; ret" здесь не находится, поэтому использовать pwntools полностью автоматизированно, как мы это сделали в Ellinson, не получится.



Запускаем приложение, оно показывает вывод команды uptime, затем спрашивает вопрос, и выводит пользовательский ввод.


Сгенерим 500 символов и пошлем в приложение, оно упадет в Segmentation fault, из чего следует, что приложение уязвимо к переполнению.

Запустим в дибаггере и посмотрим, что творится в памяти на момент падения. Для верности пошлем 2000 байт.

Замечаем, что на момент падения буфер записался в регистры rsp, rsi, r9 и r13. Это означает, что их содержимое мы можем контролировать при переполнении.
rdi - используется для передачи значений аргументов в функцию в х64 (а у нас именно такой случай) - пустой.

Определим какие места нашего буфера попали в какие регистры.
rsp: 120. По смещению 120 в нашем буфере мы попадаем в стек.

rsi: 1024

r9: 1072

r13: 336

Технически нам надо получить шелл, т.е. как-то прыгнуть на функцию system с аргументом “/bin/bash”.

Первое, что приходит в голову - реализовать переход на libc, где есть и system и строка “/bin/sh”, как мы это делали ранее. Но, во-первых, машинка простая (20 очков, вместо 40 за Ellingson), т.е. решение должно быть проще, а во-вторых, внутри приложения уже есть вызов system, которым надо бы воспользоваться…

Полезная статья по теме: 
 
Посмотрим, какие у нас есть функции, обратим внимание на наличие странной функции test по по смещению 0x401152:

Посмотрим main (по смещению 0x40115F), убедимся в наличии там вызова system. Командочка, которая нам в этом поможет objdump -D myapp.

Важно: Скриншоты из objdump -D будут в AT&T синтаксисе, где операнды команд следуют наоборот, чем в более привычном Intel, но, надеюсь, это не будет большим неудобством. 

Посмотрим на test (0x401152):

test - очень загадочная функция.
Особенно любопытен прыжок на исполнение r13 (по адресу 0x401159). Трудно себе представить исходный код, скомпилированный в такую инструкцию… Но запомним этот момент, - из него следует, что то, что нам нужно выполнить мы можем записать в r13 и подстроить так, чтобы исполнение программы попало на смещение 0x401159.

Но вернемся к нашем регистрам. При отправке 2000 байт, по смещению 120 мы попадаем в RSP, по смещению 336 - попадаем в R13, по смещению 1024 - попадаем в RSI, по смещению 1072 - попадаем в R9.
Наиболее интересно попадание в стек (в RSP). Попадая в стек мы его контролируем, т.е. можем туда записать то, что нам нужно и выполнить это. Аргументы для исполняемой функции в х64 берутся из регистра rdi, а чтобы положить содержимое стека в rdi нам надо найти гаджет типа “pop rdi; ret”, который положит текущее содержимое стека в rdi и вернется обратно.

Гаджета “pop rdi; ret” в исполняемой (у нас DEP) части кода не нашлось :(. Но зато нашелся полностью эквивалентный, “mov rdi, rsp” (по смещению  0x401156, как отмечено выше, в AT&T синтаксисе на скриншоте он выглядит как "mov %rsp, %rdi”) и через некоторое количество инструкций - “ret”, - в той самой загадочной функции “test” (см. картинку выше).

Получается, что если прыгнуть на 0x401156, то согласно коду test, выполнится, как минимум, следующее:
  • текущее содержимое стека (из регистра rsp) положится в регистр rdi (это хорошо, так как после переполнения мы контролируем стек и можем записать туда то, что нам нужно).
  • вызовется инструкция, располагающаяся в r13 ("jmp r13” по смещению 0x401159)
"jmp r13” - крайне странная инструкция, что создает впечатление подстроенности и усиливает веру в то, что мы на правильном пути. Т.е., если в r13 нам удастся положить адрес system из main, то system выполнится, а параметр для system будет взят из rdi, куда его надо заблаговременно записать из стека (инструкция "mov rdi, rsp", или аналог), который мы контролируем.

Таким образом, кусочек нашего пейлоада переполнения в стеке может выглядеть так: p64(0x401156) + '/bin/bash\x00’, где p64(0x401156) - вызовет инструкцию копирования текущего указателя стека в rdi, а ‘/bin/bash\x00’- куда будет показывать указатель стека (т.е. что фактически будет положено в rdi), после отработки 0x401156. С параметрами для system, вроде, разобрались, теперь надо подумать как вызвать system.

Если положить адрес system в r13, то инструкция “jmp r13”, расположенная по смещению 0x401159 (в test), его выполнит. 
Положить из стека в r13 можно инструкциями “pop r13; ret”, поищем такой гаджет. 
В нашем дизассемблере, полученном с помощью “objdump -D myapp”, можно найти вот такую последовательность: 

По смещению 0x401206 идет последовательность таких инструкций:
  • положить текущий стек в r13
  • положить текущий стек в r14
  • положить текущий стек в r15
  • выйти.
Т.е. практически то, что нам нужно - “pop r13; ret”, но с добавлением лишних pop r14 и pop r15, что не является проблемой, ибо мы полностью контролируем стек и можем положить туда нужное количество мусора для успешного его размещения в регистрах r14 и r15.

Таким образом, еще один кусочек нашего пейлоуда должен выглядеть так: p64(0x401206) + p64(0x40116e), где p64(0x401206) - положит содержимое стека в r13, а p64(0x40116e) - что будет лежать в стеке. 

Из картинки с кодом main выше видно, что по адресу 0x40116e располагается инструкция вызова system из main: 

Но надо еще дойти до ret, от которого нас отделяют инструкции “pop r14; pop r15”, расположенные по смещениями 0x401208 и 0x40120a, которые положат содержимое стека в регистры r14 и к15 соответственно . Нужно обеспечить наличие чего-то в стеке для этих инструкций. Это сделать несложно, так как в стек мы класть умеем.

Итак, собираем наш пейлоад: payload = junk + p64(0x401206) + p64(0x40116E) + '\x42’*8 + ‘\x43’*8 + p64(0x401156) + '/bin/bash\x00’, где junk = ‘\x41’*120
Поясним, что здесь происходит:
  • junk - в момент переполнения доведет нас до текущего указателя стека (регистр rsp). Вспомним, что на момент переполнения мы находимся на выходе из main (0x4011ac, см выше скриншот EDB). После выхода из main инструкция для исполнения будет взята из стека (из rsp).
  • p64(0x401206) подсовывает в текущее положение стека (в rsp) инструкцию по адресу 0x401206. Смотрим выше, 0x401206 - это "pop r13” - положить текущее содержимое стека в r13
  • следующее слагаемое нашего пейлоада, передаваемое в стек - p64(0x40116e). Выше указано, что по адресу 0x40116e располагается вызов system из main - это и есть то, что будет положено в r13 описанной выше инструкцией.
  • далее следует 2 мусора по 8 байт: “BBBB BBBB” и “CCCC CCCC”. Помним, что после выполнения кода по смещению 0x401206 ("pop r13”), будет выполняться код по смещению 0x401208 (“pop r14”), код по смещению 0x40120a (“pop r15”) и затем - по смещению 0x40120c - “ret”, см картинку с загадочной функцией test выше. В результате этой последовательности инструкций - “pop r14; pop r15; ret” - записанный нами мусор в стек - “BBBB BBBB” и “CCCC CCCC” будет положен в регистры r14 и r15 соответственно, и после этого будет реализован выход на адрес из стека.
  • далее в нашем стеке следует p64(0x401156) + ‘/bin/bash\x00’. Как отмечалось выше, по смещению 0x401156 располагается инструкция “mov rdi, rsp”, размещающая в RDI текущее содержимое стека, а строка ‘/bin/bash\x00’ - то самое, нужное нам, содержимое стека, которое будет положено в RDI.
  • посмотрим выше на код чудесной функции test. За только что описанной инструкцией по смещению 0x401156 (mov rdi, rsp) следует инструкция по смещению 0x401159 (jmp r13). В результате этого jmp исполнение кода перейдет на адрес в r13, а как мы ранее отмечали, на тот момент в r13 будет лежать вызов system из main, который возьмет аргумент из rdi, где лежит строка “/bin/bash”. 

В результате, мы получаем RCE в виде запуска /bin/bash с помощью system, взятого из main, без перехода на libc. В целом, вместо "/bin/bash" может быть любая другая команда. 

Для того, чтобы стало совсем понятно что происходит, отладим наш эксплоит в gdb с замечательным палгином gef.

Код нагрузки изменим на запуск /bin/id, чтобы не связываться с интерактивностью (как было бы в случае bash).
Пересылку нагрузки организуем через файл, подготовленный следующим образом: 

Посмотрим нашу нагрузку:

Запустим наше приложение в отладчике и поставим breakpoint на выход из main (смещение: 0x4011ac, скриншот с кодом main см. выше), пейлоад передадим на stdin.
 
Окно отладчика сразу после запуска, приложение остановилось на созданной breakpoint:

Из картинки видно, что мы находимся на выходе из main: “
ret”, rip = 0x4011ac, наша breakpoint. При выходе из main следующая инструкция берется из стека. 
Следующая инструкция, которая будет выполнена - pop r13, что видно из rsp = 0x401206, в результате ее исполнения содержимое стека будет положено в r13. r13 пока пустой, там содержится 0x1
Следующие 8 байт в стеке - инструкция по смещению 0x40116e - вызов system@plt из main (
call 0x401040 ).

Наберем nexti (следующая инструкция) и посмотрим, что произойдет дальше. 

Из картинки следует, что текущая выполняемая инструкция (смотрим регистр rip) - pop r13 - положить содержимое стека в r13.
Содержимое стека (см. регистр rsp) - смещение 0x40116e - это адрес инструкции вызова system из main (
call 0x401040 )

Вводим nexti, смотрим, что будет дальше. 

На картинке отчетливо видно, что в регистр r13 было положено смещение 0x40116e, по которому располагается вызов system из main (
call 0x401040 )
Текущая исполняемая инструкция - pop r14 - положит в r14 текущее содержимое стека.
Текущее содержимое стека (смотрим регистр rsp) - “BBBB BBBB” 

Набираем nexti, смотрим что будет дальше. 

В r14  теперь “BBBB BBBB
Следующая инструкция (см. регистр rip) - pop r15 - положит содержимое стека в регистр r15.
Текущее содержимое стека (регистр rsp) - “CCCC CCCC"

Набираем nexti, смотрим что происходит дальше. 

В регистр r15 записались символы “CCCC CCCC
Программа находится на инструкции ret по смещению 0x40120c, см. регистр rip. После выхода, адрес следующей инструкции будет взят из стека.
Стек (rsp) указывает на инструкцию mov rdi, rsp - положить содержимое стека в rdi - расположенную по смещению 0x401156 в функции test ( mov rdi, rsp)

Набираем nexti, смотрим, что происходит. 

Текущая исполняемая инструкция (см. регистр rip) расположена по смещению 0x401156 - mov rdi, rsp - положит в регистр rdi содержимое стека (регистр rsp)
Стек указывает на строку “/bin/id” - это то, что будет положено в rdi

Набираем nexti 

Наблюдаем полностью подготовленную нужную нам для запуcка system(“/bin/id”) ситуацию:
  • в rdi лежит строка “/bin/id”. Помним, что при запуске функции, аргумент будет браться из rdi.
  • следующая исполняемая инструкция расположена по смещению 0x401159 - jmp r13 - переход на исполнение инструкции по адресу в r13 (см. регистр rip)
  • В r13 лежит инструкция по смещению 0x40116e - вызов system из main (
    call 0x401040 ). При отработке этой инструкции будет выполнена функция system с аргументом из регистра rdi.

Набираем nexti . 

Текущая исполняемая инструкция (регистр rip) расположена по смещению 0x40116e - вызов system из main (
call 0x401040 )
Аргумент для system будет браться из rdi, в котором сейчас содержится строка “/bin/id

Набираем nexti и видим, что system(“/bin/id”) выполнилась. 


Код эксплоита и предложение прикладываю.

Хочется выразить огромнейшую благодарность моему доброму приятелю и коллеге Артему, который познакомил меня с прекрасным плагином gef и пояснил многие моменты, в которых я бы точно сам не разобрался.

Всем добра!



PS: Один мой приятель спросил для кого я все это пишу на русском языке. Я немного смутился и ничего разумного ответить не смог, так куча вопросов на которые я не смог быстро сообразить ответ заняли все мои мысли:
- какие претензии к русскому языку? Почему вообще все статьи надо писать на английском (хотя, вполне вероятно, будущие заметки я буду писать на нем)?
- почему я не могу писать заметки для себя? Но писать их более-менее прилично, чтобы в них мог еще кто-то разобраться, может быть, это тоже будет кому-то полезно. В частности, шпаргалку по certutil я писал для себя в бытность админа УЦ, но сейчас это - самая популярная статья в этом блоге, причем я ее с момента написания не обновлял, а, вероятно, имело бы смысл добавить релевантный функционал декода бинарей в Base64 и скачивания файлов.
- в чем проблема пользоваться автоматическим переводом от того же Google?
- мне тупо проще писать по-русски, так как это мой родной язык
- ....