В этой части мы рассмотрим уязвимость, обнаруженную во второй части этой серии, и то, как она обеспечивала контроль над регистром EIP процессора.
Ключевые мометы
1. Чтобы получить контроль над регистром EIP процессора, отправьте пакетное радио, содержащее 1000 букв A. WinAPRS завершится с нарушением доступа.
2. Типичный способ использования этой уязвимости - найти адрес памяти, содержащий последовательность инструкций: POP r32, POP r32, RET(PPR).
3. Если адрес обработчика находится вне диапазона адресов загруженного модуля, то обработчик не проверяется и SafeSEH эффективно обходится.
4. Mona вовсе не искала POP, POP, RET, а искала другие гаджеты.
5. Адрес обработчика исключений был перезаписан на 0x42424242 (BBBB)
Во второй части этой серии статей мы рассмотрели наше программное и аппаратное обеспечение WinAPRS. Затем мы начали обратный инжиниринг WinAPRS и его фаззинг на наличие уязвимостей с использованием модифицированного программного обеспечения с открытым исходным кодом. Наконец, мы обнаружили потенциально опасную уязвимость. В этой статье мы рассмотрим эту уязвимость, чтобы узнать, как она предоставила нам контроль над регистром EIP процессора. Затем мы обнаружим некоторые ограничения и пойдем на некоторые компромиссы, чтобы продолжить движение к нашей цели — получить обратную оболочку для радиолюбителей.
WinDbg обнаружил инструкцию ЦП, вызвавшую нарушение.
Я использовал IDA Pro для просмотра этого блока кода и нашел следующую функцию:
Я проверил стек вызовов после сбоя и обнаружил, что он был поврежден данными из моей полезной нагрузки (0x41 символ).
Я также проверил цепочку структурированного обработчика исключений (SEH) и обнаружил, что она также была повреждена.
Моему 1000-байтовому пакету каким-то образом удалось перезаписать цепочку SEH. Затем сработало исключение, и процессор перешел на адрес обработчика SEH, который был перезаписан на 0x41414141. Поскольку я контролировал полезную нагрузку 0x41414141, это указывало на то, что я должен иметь возможность указывать ЦП на любой адрес памяти, который я хочу, и получать выполнение кода. Дела пошли вверх.
Чтобы понять, что происходит, я установил точку останова непосредственно перед циклом lodsb по адресу 0x0055990C. Затем я отправил пакет размером 1000 байт. Просматривая код, я обнаружил, что ESI и EDI в конечном итоге были установлены как таковые:
Цикл копирует один байт из ESI (источник) в адрес, на который указывает EDI (назначение). Он будет зацикливаться, пока не найдет байт NULL. Это имеет смысл, поскольку строки C обычно заканчиваются NULL. Я проверил данные в ESI и обнаружил, что они содержат содержимое моего пакета.
Затем я проверил EDI и обнаружил, что он также содержит часть пакета.
Именно тогда я заметил, что адрес памяти EDI был близок к ESI, но выше. Я быстро подсчитал и обнаружил, что он на 880 байт выше.
Я вернулся и проверил исходное нарушение прав доступа и обнаружил, что оно было вызвано, когда операция stosb пыталась записать в нераспределенный адрес памяти.
Казалось, что мой пакет оказался больше, чем ожидало приложение. Он скопировал 1000 байт в исходный буфер, который, по-видимому, мог содержать только 880 байт. В результате он переполнился буфером назначения. Это означало, что исходный буфер не содержал байта NULL для завершения строки, потому что байт NULL был помещен в буфер назначения. Затем цикл скопировал 880 байт моей полезной нагрузки в место назначения, что перезаписало последние 120 байт исходной полезной нагрузки, включая байт NULL. Поскольку эта функция не нашла ограничитель NULL, она просто продолжила копирование в цикле до бесконечности. Исходный буфер назначения теперь стал исходным буфером, и байты затем были скопированы в память сразу после исходного буфера назначения.
По сути, он просто бесконечно перезаписывал память первыми 880 байтами моей полезной нагрузки, пока не перезаписал обработчик SEH и, наконец, не попытался записать в нераспределенное пространство памяти и не вызвал исключение. Это была хорошая новость для меня. Теперь у меня был способ вызвать исключение, и в то же время я мог перезаписать обработчик SEH, что позволило бы мне указать центральному процессору какое-то другое место в памяти и потенциально получить выполнение кода. Следующим моим шагом было выяснить точное смещение в моей полезной нагрузке, которое перезапишет адрес обработчика SEH.
Я использовал msf-pattern_create для создания строки длиной 1000 символов без повторяющихся символов.
Затем я сгенерировал пакет с этими данными.
Я передал пакет, что вызвало сбой в WinAPRS. Затем я проверил значение EIP.
Затем я использовал msf-pattern_offset, чтобы найти точное смещение для перезаписи EIP.
Я создал новый пакет, чтобы посмотреть, смогу ли я перезаписать EIP с помощью BBBB (0x42424242).
Я передал пакет и обнаружил, что EIP был перезаписан на 0x42424242 (BBBB), как и ожидалось.
Это означало, что я мог управлять EIP и отправлять выполнение на любой адрес, который я хотел. Типичный способ использования уязвимости такого типа — найти адрес памяти, содержащий последовательность инструкций: POP r32, POP r32, RET (PPR). Это выталкивает два DWORD из стека, оставляя адрес следующего обработчика SEH сверху. Затем инструкция RET указывает ЦП вернуться к этому адресу и начать выполнение кода там. Поскольку у меня есть контроль над этим адресом, я могу включить несколько инструкций JMP в мой шелл-код в стеке и в идеале сделать что-нибудь забавное, например получить выполнение команды оболочки.
Оказалось, я не мог отправить исполнение на любой адрес. Было два ограничения. Адрес не может содержать байт NULL (0x00). Если бы я включил какие-либо байты NULL в свою полезную нагрузку, цикл копирования увидел бы это как конец строки исходного буфера и остановил копирование. Это предотвратит бесконечный цикл, перезапись SEH и нарушение прав доступа. Игра закончена. Чтобы получить контроль над EIP, мне понадобился цикл для бесконечного продолжения копирования. Следовательно, любой адрес, который я использовал, не должен содержать байт NULL. Кроме того, моя полезная нагрузка не могла содержать байты 0xC0 и 0xDB. Это управляющие символы протокола KISS, которые имеют особое значение в пакете KISS. Если моя полезная нагрузка включала какой-либо символ, WinAPRS интерпретировала бы это как конец моего пакета и обрезала полезную нагрузку, вызывая те же проблемы, которые были отмечены выше.
Оглядываясь назад на вывод narly, все пространство памяти WinAPRS содержит байт NULL.
Пространство стека также содержало байт NULL, поэтому действительно некуда было передать выполнение. Все остальные загруженные модули были модулями операционной системы и включали рандомизацию адресного пространства (ASLR). ASLR означает, что адреса для каждого модуля будут меняться каждый раз при перезагрузке Windows. Поэтому невозможно жестко запрограммировать адрес для перехода в любом из этих модулей. Мне нужен был бы способ передать адрес памяти мне, как злоумышленнику, каким-то образом, по любительскому радио, чтобы вычислить адрес для инструкции PPR.
Microsoft впервые внедрила ASLR в Windows Vista, хотя с тех пор они улучшили его. Ради интереса я решил вернуться к старой доброй Windows XP SP3. Ориентация на Windows XP позволила бы мне найти надежный адрес памяти для инструкции POP, POP, RET, что позволило бы выполнить код. Получив это, я мог работать над собственной полезной нагрузкой шеллкода, чтобы обеспечить обратную оболочку через любительское радио, чего я действительно хотел добиться в этом проекте. Оттуда я мог изучить другие варианты с Windows 10, чтобы увидеть, смогу ли я добиться чего-либо (спойлер: я сделал! Мы вернемся к Windows 10 позже в этой серии).
Я настроил виртуальную машину Windows XP SP3 и скопировал WinAPRS и установщики для всех своих инструментов. Используя WinDbg и narly, я проверил защиты на всех загруженных модулях.
Превосходно. Ни у одного из них не был включен ASLR, хотя у большинства из них был включен SafeSEH. SafeSEH — это другой механизм защиты, который пытается предотвратить именно такой эксплойт переполнения буфера на основе SEH. Модули, скомпилированные с включенным SafeSEH, имеют таблицу со списком всех допустимых обработчиков исключений для модуля. Когда возникает исключение, Windows проверяет, попадает ли адрес обработчика в диапазон памяти, относящийся к загруженному модулю. Если это так, он проверит, действителен ли обработчик для этого модуля, проверив таблицу. Если он недействителен, он не будет выполнять обработчик. Если адрес обработчика находится за пределами диапазона адресов загруженного модуля, то обработчик не проверяется и SafeSEH эффективно обходит.
Плагин Corelan mona.py для отладчика Immunity имеет встроенную функцию под названием «jseh», которая будет искать гаджеты SEH в памяти, которая существует вне каких-либо загруженных модулей. Я запустил отладчик Immunity и использовал Mona для поиска такого гаджета.
Было найдено 42 гаджета, но, к сожалению, все они требовали JMP для EAX или EBX. В данном случае в этих регистрах не было адреса моего шеллкода, так что ничего из этого не сработало. Я просмотрел исходный код Mona, чтобы выяснить, что именно он ищет, думая, что, возможно, есть какие-то другие гаджеты, которые не были включены в поиск. Я обнаружил - Мона вообще не искала POP, POP, RET. Вместо этого он искал другие гаджеты. Я добавил в список 16 вариаций POP, POP, RET.
Затем я снова запустил сценарий.
Бинго. У меня было несколько кандидатов в конце списка. Я выбрал последний и изменил свою полезную нагрузку, чтобы использовать его в качестве адреса обработчика SEH.
Я выполнил полезную нагрузку и обнаружил, что обработчик SEH теперь указывает на инструкцию POP, POP, RET (красные контуры ниже). Я установил точку останова на адрес обработчика Next SEH, и когда я продолжил выполнение кода, точка останова сработала (зеленые контуры ниже). Это указывало на то, что я успешно указал EIP на свою полезную нагрузку.
В этот момент EIP указывал на адрес обработчика NSEH. Это дало мне для работы только четыре байта инструкций ЦП, потому что сразу после NSEH идет адрес SEH, который указывает на инструкции POP, POP, RET. Мне пришлось заполнить NSEH инструкциями, которые бы указывали EIP на начало моей полезной нагрузки. Я проверил память вокруг EIP и обнаружил, что начало моей полезной нагрузки было всего на 101 байт впереди NSEH. Моя полезная нагрузка включала 783 A, что означало, что я мог бы получить 783 байта шелл-кода, если бы нашел способ указать EIP на это место.
Примерно в это же время во время проекта я получил в свои руки Raspberry Pi со шляпой TNC-Pi ( https://www.tnc-x.com/TNCPi.htm ). Я сделал специальный кабель, чтобы подключить его к моему портативному трансиверу Kenwood, и он быстро заработал.
Теперь, вместо использования инструмента генерации звука Direwolf, я мог отправлять необработанные пакетные данные на последовательный порт Pi. Это упростит разработку работающего эксплойта. Я нашел скрипт на Python, который позволил мне создавать собственные пакеты AX.25: https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html
Я сделал простой тестовый пакет, чтобы убедиться, что он работает.
Адрес обработчика исключений был перезаписан на 0x42424242 (BBBB), что и ожидалось. Это сработало.Этот скрипт станет основой моего кода эксплойта. Я знал, что это будет не так просто, как добавление полезной нагрузки шелл-кода, сгенерированного msfvenom, в эксплойт, потому что Metasploit не поставляется с полезной нагрузкой обратной оболочки KISS TNC. С чего бы это? Я собирался написать пользовательскую полезную нагрузку. Это оказалось намного сложнее, чем я ожидал.
Следующие шаги
В следующем выпуске этой серии будет рассмотрена полезная нагрузка трехэтапного шеллкода, позволяющая решить проблемы, с которыми я столкнулся при попытке вызова многих полезных API-интерфейсов Win32. Мы шаг за шагом пройдемся по шелл-коду сборки, объясняя назначение каждого фрагмента кода. Конечной целью шелл-кода будет открытие обратной оболочки интерактивной командной строки по любительскому радио.
Перевод вот ЭТОЙ статьи.
Ключевые мометы
1. Чтобы получить контроль над регистром EIP процессора, отправьте пакетное радио, содержащее 1000 букв A. WinAPRS завершится с нарушением доступа.
2. Типичный способ использования этой уязвимости - найти адрес памяти, содержащий последовательность инструкций: POP r32, POP r32, RET(PPR).
3. Если адрес обработчика находится вне диапазона адресов загруженного модуля, то обработчик не проверяется и SafeSEH эффективно обходится.
4. Mona вовсе не искала POP, POP, RET, а искала другие гаджеты.
5. Адрес обработчика исключений был перезаписан на 0x42424242 (BBBB)
Во второй части этой серии статей мы рассмотрели наше программное и аппаратное обеспечение WinAPRS. Затем мы начали обратный инжиниринг WinAPRS и его фаззинг на наличие уязвимостей с использованием модифицированного программного обеспечения с открытым исходным кодом. Наконец, мы обнаружили потенциально опасную уязвимость. В этой статье мы рассмотрим эту уязвимость, чтобы узнать, как она предоставила нам контроль над регистром EIP процессора. Затем мы обнаружим некоторые ограничения и пойдем на некоторые компромиссы, чтобы продолжить движение к нашей цели — получить обратную оболочку для радиолюбителей.
Отслеживание ошибки
После отправки полезной нагрузки пакетной радиосвязи, содержащей 1000 A, WinAPRS аварийно завершает работу с нарушением прав доступа. После попытки продолжить выполнение я каким-то образом получил контроль над регистром EIP процессора.
WinDbg обнаружил инструкцию ЦП, вызвавшую нарушение.
Я использовал IDA Pro для просмотра этого блока кода и нашел следующую функцию:
Я проверил стек вызовов после сбоя и обнаружил, что он был поврежден данными из моей полезной нагрузки (0x41 символ).
Я также проверил цепочку структурированного обработчика исключений (SEH) и обнаружил, что она также была повреждена.
Моему 1000-байтовому пакету каким-то образом удалось перезаписать цепочку SEH. Затем сработало исключение, и процессор перешел на адрес обработчика SEH, который был перезаписан на 0x41414141. Поскольку я контролировал полезную нагрузку 0x41414141, это указывало на то, что я должен иметь возможность указывать ЦП на любой адрес памяти, который я хочу, и получать выполнение кода. Дела пошли вверх.
Чтобы понять, что происходит, я установил точку останова непосредственно перед циклом lodsb по адресу 0x0055990C. Затем я отправил пакет размером 1000 байт. Просматривая код, я обнаружил, что ESI и EDI в конечном итоге были установлены как таковые:
Цикл копирует один байт из ESI (источник) в адрес, на который указывает EDI (назначение). Он будет зацикливаться, пока не найдет байт NULL. Это имеет смысл, поскольку строки C обычно заканчиваются NULL. Я проверил данные в ESI и обнаружил, что они содержат содержимое моего пакета.
Затем я проверил EDI и обнаружил, что он также содержит часть пакета.
Именно тогда я заметил, что адрес памяти EDI был близок к ESI, но выше. Я быстро подсчитал и обнаружил, что он на 880 байт выше.
Я вернулся и проверил исходное нарушение прав доступа и обнаружил, что оно было вызвано, когда операция stosb пыталась записать в нераспределенный адрес памяти.
Казалось, что мой пакет оказался больше, чем ожидало приложение. Он скопировал 1000 байт в исходный буфер, который, по-видимому, мог содержать только 880 байт. В результате он переполнился буфером назначения. Это означало, что исходный буфер не содержал байта NULL для завершения строки, потому что байт NULL был помещен в буфер назначения. Затем цикл скопировал 880 байт моей полезной нагрузки в место назначения, что перезаписало последние 120 байт исходной полезной нагрузки, включая байт NULL. Поскольку эта функция не нашла ограничитель NULL, она просто продолжила копирование в цикле до бесконечности. Исходный буфер назначения теперь стал исходным буфером, и байты затем были скопированы в память сразу после исходного буфера назначения.
По сути, он просто бесконечно перезаписывал память первыми 880 байтами моей полезной нагрузки, пока не перезаписал обработчик SEH и, наконец, не попытался записать в нераспределенное пространство памяти и не вызвал исключение. Это была хорошая новость для меня. Теперь у меня был способ вызвать исключение, и в то же время я мог перезаписать обработчик SEH, что позволило бы мне указать центральному процессору какое-то другое место в памяти и потенциально получить выполнение кода. Следующим моим шагом было выяснить точное смещение в моей полезной нагрузке, которое перезапишет адрес обработчика SEH.
Я использовал msf-pattern_create для создания строки длиной 1000 символов без повторяющихся символов.
Затем я сгенерировал пакет с этими данными.
Я передал пакет, что вызвало сбой в WinAPRS. Затем я проверил значение EIP.
Затем я использовал msf-pattern_offset, чтобы найти точное смещение для перезаписи EIP.
Я создал новый пакет, чтобы посмотреть, смогу ли я перезаписать EIP с помощью BBBB (0x42424242).
Я передал пакет и обнаружил, что EIP был перезаписан на 0x42424242 (BBBB), как и ожидалось.
Это означало, что я мог управлять EIP и отправлять выполнение на любой адрес, который я хотел. Типичный способ использования уязвимости такого типа — найти адрес памяти, содержащий последовательность инструкций: POP r32, POP r32, RET (PPR). Это выталкивает два DWORD из стека, оставляя адрес следующего обработчика SEH сверху. Затем инструкция RET указывает ЦП вернуться к этому адресу и начать выполнение кода там. Поскольку у меня есть контроль над этим адресом, я могу включить несколько инструкций JMP в мой шелл-код в стеке и в идеале сделать что-нибудь забавное, например получить выполнение команды оболочки.
Оказалось, я не мог отправить исполнение на любой адрес. Было два ограничения. Адрес не может содержать байт NULL (0x00). Если бы я включил какие-либо байты NULL в свою полезную нагрузку, цикл копирования увидел бы это как конец строки исходного буфера и остановил копирование. Это предотвратит бесконечный цикл, перезапись SEH и нарушение прав доступа. Игра закончена. Чтобы получить контроль над EIP, мне понадобился цикл для бесконечного продолжения копирования. Следовательно, любой адрес, который я использовал, не должен содержать байт NULL. Кроме того, моя полезная нагрузка не могла содержать байты 0xC0 и 0xDB. Это управляющие символы протокола KISS, которые имеют особое значение в пакете KISS. Если моя полезная нагрузка включала какой-либо символ, WinAPRS интерпретировала бы это как конец моего пакета и обрезала полезную нагрузку, вызывая те же проблемы, которые были отмечены выше.
Оглядываясь назад на вывод narly, все пространство памяти WinAPRS содержит байт NULL.
Пространство стека также содержало байт NULL, поэтому действительно некуда было передать выполнение. Все остальные загруженные модули были модулями операционной системы и включали рандомизацию адресного пространства (ASLR). ASLR означает, что адреса для каждого модуля будут меняться каждый раз при перезагрузке Windows. Поэтому невозможно жестко запрограммировать адрес для перехода в любом из этих модулей. Мне нужен был бы способ передать адрес памяти мне, как злоумышленнику, каким-то образом, по любительскому радио, чтобы вычислить адрес для инструкции PPR.
Microsoft впервые внедрила ASLR в Windows Vista, хотя с тех пор они улучшили его. Ради интереса я решил вернуться к старой доброй Windows XP SP3. Ориентация на Windows XP позволила бы мне найти надежный адрес памяти для инструкции POP, POP, RET, что позволило бы выполнить код. Получив это, я мог работать над собственной полезной нагрузкой шеллкода, чтобы обеспечить обратную оболочку через любительское радио, чего я действительно хотел добиться в этом проекте. Оттуда я мог изучить другие варианты с Windows 10, чтобы увидеть, смогу ли я добиться чего-либо (спойлер: я сделал! Мы вернемся к Windows 10 позже в этой серии).
Я настроил виртуальную машину Windows XP SP3 и скопировал WinAPRS и установщики для всех своих инструментов. Используя WinDbg и narly, я проверил защиты на всех загруженных модулях.
Превосходно. Ни у одного из них не был включен ASLR, хотя у большинства из них был включен SafeSEH. SafeSEH — это другой механизм защиты, который пытается предотвратить именно такой эксплойт переполнения буфера на основе SEH. Модули, скомпилированные с включенным SafeSEH, имеют таблицу со списком всех допустимых обработчиков исключений для модуля. Когда возникает исключение, Windows проверяет, попадает ли адрес обработчика в диапазон памяти, относящийся к загруженному модулю. Если это так, он проверит, действителен ли обработчик для этого модуля, проверив таблицу. Если он недействителен, он не будет выполнять обработчик. Если адрес обработчика находится за пределами диапазона адресов загруженного модуля, то обработчик не проверяется и SafeSEH эффективно обходит.
Плагин Corelan mona.py для отладчика Immunity имеет встроенную функцию под названием «jseh», которая будет искать гаджеты SEH в памяти, которая существует вне каких-либо загруженных модулей. Я запустил отладчик Immunity и использовал Mona для поиска такого гаджета.
Было найдено 42 гаджета, но, к сожалению, все они требовали JMP для EAX или EBX. В данном случае в этих регистрах не было адреса моего шеллкода, так что ничего из этого не сработало. Я просмотрел исходный код Mona, чтобы выяснить, что именно он ищет, думая, что, возможно, есть какие-то другие гаджеты, которые не были включены в поиск. Я обнаружил - Мона вообще не искала POP, POP, RET. Вместо этого он искал другие гаджеты. Я добавил в список 16 вариаций POP, POP, RET.
Затем я снова запустил сценарий.
Бинго. У меня было несколько кандидатов в конце списка. Я выбрал последний и изменил свою полезную нагрузку, чтобы использовать его в качестве адреса обработчика SEH.
Я выполнил полезную нагрузку и обнаружил, что обработчик SEH теперь указывает на инструкцию POP, POP, RET (красные контуры ниже). Я установил точку останова на адрес обработчика Next SEH, и когда я продолжил выполнение кода, точка останова сработала (зеленые контуры ниже). Это указывало на то, что я успешно указал EIP на свою полезную нагрузку.
В этот момент EIP указывал на адрес обработчика NSEH. Это дало мне для работы только четыре байта инструкций ЦП, потому что сразу после NSEH идет адрес SEH, который указывает на инструкции POP, POP, RET. Мне пришлось заполнить NSEH инструкциями, которые бы указывали EIP на начало моей полезной нагрузки. Я проверил память вокруг EIP и обнаружил, что начало моей полезной нагрузки было всего на 101 байт впереди NSEH. Моя полезная нагрузка включала 783 A, что означало, что я мог бы получить 783 байта шелл-кода, если бы нашел способ указать EIP на это место.
Примерно в это же время во время проекта я получил в свои руки Raspberry Pi со шляпой TNC-Pi ( https://www.tnc-x.com/TNCPi.htm ). Я сделал специальный кабель, чтобы подключить его к моему портативному трансиверу Kenwood, и он быстро заработал.
Теперь, вместо использования инструмента генерации звука Direwolf, я мог отправлять необработанные пакетные данные на последовательный порт Pi. Это упростит разработку работающего эксплойта. Я нашел скрипт на Python, который позволил мне создавать собственные пакеты AX.25: https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html
Я сделал простой тестовый пакет, чтобы убедиться, что он работает.
Адрес обработчика исключений был перезаписан на 0x42424242 (BBBB), что и ожидалось. Это сработало.Этот скрипт станет основой моего кода эксплойта. Я знал, что это будет не так просто, как добавление полезной нагрузки шелл-кода, сгенерированного msfvenom, в эксплойт, потому что Metasploit не поставляется с полезной нагрузкой обратной оболочки KISS TNC. С чего бы это? Я собирался написать пользовательскую полезную нагрузку. Это оказалось намного сложнее, чем я ожидал.
Следующие шаги
В следующем выпуске этой серии будет рассмотрена полезная нагрузка трехэтапного шеллкода, позволяющая решить проблемы, с которыми я столкнулся при попытке вызова многих полезных API-интерфейсов Win32. Мы шаг за шагом пройдемся по шелл-коду сборки, объясняя назначение каждого фрагмента кода. Конечной целью шелл-кода будет открытие обратной оболочки интерактивной командной строки по любительскому радио.
Перевод вот ЭТОЙ статьи.
Like