Автор: @straight_blast; [email protected]

Цель этой публикации - рассказать, как можно использовать отладчик для определения соответствующего пути кода, который может вызвать сбой. Я надеюсь, что этот пост будет поучительным для людей, которые хотят узнать, как использовать отладчик для анализа уязвимостей.

В этом посте не будет подробно рассказываться об основах связи RDP и MS_T120. Заинтересованным читателям следует обратиться к следующим блогам, в которых резюмируется основа потребности в информации:





RDP означает« Really DO Patch! - это действительно исправление! - Общие сведения об уязвимости RDP, поддающейся червю, CVE-2019-0708 |…
Во время цикла майских исправлений Microsoft, выпущенного во вторник, были выпущены рекомендации по безопасности для уязвимости в удаленном рабочем столе…, которая будет защищена завтра. mcafee.com »



Кроме того, в этом посте не будет предоставлен PoC-код, так как его цель - показать анализ уязвимостей с помощью отладчика.

Целевая машина (отлаживаемая) будет Windows 7 x64, а машина отладчика будет Windows 10 x64. И отладчик, и отладчик будут работать в VirtualBox.

Настройка среды отладки ядра с помощью VirtualBox

  1. На целевой машине запустите cmd.exe с правами администратора. Используйте команду bcdedit, чтобы включить отладку ядра.
bcdedit /set {current} debug yes
bcdedit /set {current} debugtype serial
bcdedit /set {current} debugport 1
bcdedit /set {current} baudrate 115200
bcdedit /set {current} description "Windows 7 with kernel debug via COM"

Когда вы снова наберете bcdedit, должно отобразиться что-то похожее на следующий снимок экрана:

2. Выключите целевой компьютер (отлаживаемый) и щелкните правой кнопкой мыши целевой образ в VirtualBox Manager. Выберите «Настройки», а затем «Последовательные порты». Скопируйте настройки, как показано на следующем изображении, и нажмите «ОК»:

3. Щелкните правой кнопкой мыши изображение, на котором будет размещен отладчик, перейдите к настройке «Последовательные порты», скопируйте настройки, как показано, и нажмите «ОК»:

4. Не выключайте отладочную виртуальную машину и загрузите отладочную виртуальную машину. На виртуальной машине отладчика загрузите и установите WinDBG. Я буду использовать предварительную версию WinDBG.



5. После установки отладчика выберите «Присоединить к ядру», установите «Скорость передачи» на «115200» и «Порт» на «com1». Также щелкните «начальный перерыв».

Нажмите «ОК», и отладчик теперь готов к подключению к отладчику.

6. Запустите целевой компьютер «debuggee», и отобразится следующее приглашение. Выберите тот, у которого включен отладчик, и продолжайте.

На стороне отладчика WinDBG установит соединение с отлаживаемым. Чтобы отладчик полностью загрузился, потребуется несколько вручную ввести «g» в «командную строку отладчика». Кроме того, поскольку отладка выполняется через «com», первоначальный запуск займет немного времени.

7. После загрузки отлаживаемой программы запустите «cmd.exe» и введите «netstat -ano». Найдите PID, который запускает порт 3389, как показано ниже:

8. Вернитесь в отладчик и нажмите «Главная» - ›« Прервать », чтобы включить командную строку отладчика, и введите:

!process 0 0 svchost.exe

Это перечислит множество процессов, связанных с svchost.exe. Нас интересует процесс с PID 1216 (0x4C0).

9. Теперь переключимся в контекст svchost.exe, который запускает RDP. В командной строке отладчика введите:

.process /i /p fffffa80082b72a0

После переключения контекста приостановите отладчик и выполните команду «.reload», чтобы перезагрузить все символы, которые будет использовать процесс.

Определение соответствующего пути кода

Не повторяя слишком много общедоступной информации, исправленная уязвимость изменила код в IcaBindVirtualChannels. Мы знаем, что если IcaFindChannelByName находит строку «MS_T120», он вызывает IcaBindchannel, например:

_IcaBindChannel(ChannelControlStructure*, 5, index, dontcare) 

На следующих снимках экрана показан соответствующий непропатченный код в IcaBindVirtualChannels:

Мы собираемся установить две точки останова.

Один будет на _IcaBindChannel, где структура управления каналом хранится в таблице указателей каналов. Индекс того, где хранится структура управления каналом, основан на индексе того, где имя виртуального канала объявлено в clientNetworkData пакета MCS Initial Connect и GCC Create.

а другой - по «вызову _IcaBindChannel» в IcaBindVirtualChannels.

Эти точки останова предназначены для наблюдения за созданием виртуальных каналов и порядком их создания.

bp termdd!IcaBindChannel+0x55 ".printf \"rsi=%d and rbp=%d\\n\", rsi, rbp;dd rdi;.echo"
bp termdd!IcaBindVirtualChannels+0x19e ".printf \"We got a MS_T120, r8=%d\\n\",r8;dd rcx;r $t0=rcx;.echo"

Точка останова сначала достигает следующего со значением индекса «31»:

Перечисление стека вызовов с «kb» показывает следующее:

Мы видим, что IcaBindChannel вызывается из IcaCreateChannel, который можно отследить вплоть до rdpwsx! MSCreateDomain. Если мы посмотрим на эту функцию в дизассемблере, мы заметили, что она создает канал MS_T120:

Кроме того, но, глядя на исправленный termdd.sys, мы знаем, что исправленный код принудительно устанавливает индекс для виртуального канала MS_T120 равным 31, эта первая точка останова указывает, что первым создаваемым каналом является канал MS_T120.

Следующее попадание в точку останова - это вторая точка останова (в пределах IcaBindVirtualChannel), за которой снова следует 1-я точка останова (в пределах IcaBindChannel):

Это происходит, поскольку он наблюдал значение MS_T120 из clientNetworkData. Если мы сравним адрес и контент, показанные на изображении выше, с односторонним способом выше, то увидим, что они идентичны. Это означает, что оба относятся к одной и той же структуре управления каналом. Однако ссылка на эту структуру хранится в двух разных местах:

rsi = 31, rbp = 5; 
[rax + (31 + 5) * 8 + 0xe0] = MST_120_structure
rsi = 1, rbp = 5; 
[rax + (1 + 5) * 8 + 0xe0] = MS_T120_structure

Другими словами, есть две записи в таблице указателей каналов, которые имеют ссылки на структуру MS_T120.

После этого создается еще несколько каналов, которые нас не волнуют:

Следующим шагом в поиске другого подходящего кода для просмотра будет установка прерывания чтения / записи в структуре MS_T120. Несомненно, структура MS_T120 в будущем будет "касаться".

Я установил точку останова для чтения / записи данных в красном поле, как показано ниже:

По мере того как мы продолжаем выполнение, мы получаем вызовы IcaDereferenceChannel, которые нас не интересуют. Затем мы нажимаем termdd! IcaFindChannel с дополнительной информацией из стека вызовов:

Термины termdd! IcaChannelInput и termdd! IcaChannelInputInternal звучат как что-то, что может обрабатывать данные, отправленные в виртуальный канал.

Профессиональный совет - установить точку останова перед вызовом функции, чтобы увидеть, могут ли регистры или стеки (в зависимости от того, как данные передаются в функцию) содержать распознаваемые или читаемые данные.

Я установлю точку останова на вызове IcaChannelInputInternal внутри функции IcaChannelInput:

bp termdd!IcaChannelInput+0xd8

Нас интересуют вызовы точки останова IcaChannelInput после вызова IcaBindVirtualChannels. На изображении выше, прямо перед вызовом IcaChannelInputInternal, регистр rax содержит адрес, который ссылается на «A», которые я передал как данные через виртуальный канал.

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

ba r8 rax+0xa 

Причина, по которой мне пришлось добавить 0xA в регистр rax, заключается в том, что для прерывания чтения / записи требуется адрес выравнивания (заканчивается на 0x0 или 0x8 для x64 env)

Итак, «A» теперь работают в функции «memmove». Если посмотреть на стек вызовов, то «memmove» вызывается из «IcaCopyDataToUserBuffer».

Давайте выйдем из «memmove» (gu), чтобы увидеть, где находится адрес назначения, по которому перемещаются «A».

Которая вот смотрит на это с дизассемблера:

Значения «Src», «Dst» и «Size» следующие:

Таким образом, «memmove» копирует «А» из адресного пространства ядра в адресное пространство пользователя.

Теперь мы установим другие группы перерывов при чтении / записи в адресном пространстве пользователя, чтобы увидеть, как эти значения «касаются».

ba r8 00000000`030ec590
ba r8 00000000`030ec598
ba r8 00000000`030ec5a0
ba r8 00000000`030ec5a8

(примечание: если вы получили сообщение «Слишком много точек останова по данным для процессора 0…», удалите некоторые из установленных вами старых точек останова, затем снова введите «g»)

Затем мы получаем доступ к rdpwsx! IoThreadFunc:

Точка останова коснулась раздела памяти в выделенном красном поле:

Похоже, что rdpwsx! IoThreadFunc - это код, который анализирует и обрабатывает содержимое данных MS_T120.

Использование дизассемблера обеспечит лучшее представление:

Теперь мы будем использовать команду «p» для обхода каждой инструкции.

Похоже, что из-за того, что я поставил «AAAA», все пошло по другому пути.

Согласно сообщению в блоге от ZDI, нам нужно отправить обработанные данные на канал MS_T120 (по нашему выбранному индексу), чтобы он завершил канал (освободив структуру управления каналом MS_T120), так что когда RDPWD! SignalBrokenConnection попытается достичь снова в канал MS_T120 по индексу 31 из структуры указателя канала, он будет использовать структуру управления каналом Freed MS_T120, что приведет к сбою.

На основе rdpwsx! IoThreadFunc, кажется, имеет смысл создавать обработанные данные, которые попадут в функцию IcaChannelClose.

Когда созданные данные верны, они попадут в rdpwsx! IcaChannelClose

Прежде чем переходить через IcaChannelClose, давайте установим точку останова в структуре канала управления MS_T120, чтобы увидеть, как это повлияет.

На следующем рисунке показан стек вызовов при чтении точки останова. Выполняется вызов ExFreePoolWithTag, который освобождает структуру управления каналом MS_T120.

Мы можем продолжать с «g», пока не достигнем точки останова в termdd! IcaChannelInput:

Взглянув на адрес, который содержит структуру управления каналом MS_T120, содержимое выглядит совсем иначе.

Кроме того, стек вызовов показывает, что вызов IcaChannelInput исходит от RDPWD! SignalBrokenConnection. В блоге ZDI отмечалось, что эта функция вызывается при разрыве соединения.

Мы будем использовать команду «t» для входа в функцию IcaChannelInputInternal. Как только мы окажемся внутри функции, мы установим новую точку останова:

bp termdd!IcaFindChannel

Как только мы окажемся внутри функции IcaFindChannel, используйте «gu», чтобы выйти из нее и вернуться к функции IcaChannelInputInternal:

Регистры rax содержат ссылку на освобожденную структуру канала управления MS_T120.

По мере того, как мы продолжаем выполнять код, адрес MS_T120 + 0x18 используется в качестве параметра (rcx) для функции ExEnterCriticalRegionAndAcquireResourceExclusive.

Давайте посмотрим на rcx:

И вот, если мы разыменуем rcx, это ничего! Итак, давайте перейдем к ExEnterCriticalRegionAndAcquireResourceExclusive и посмотрим на результат: