Эксплуатация SIGRed (CVE-2020–1350) на Windows Server 2012/2016/2019
Автор: Worawit Wangwarunyoo, Исследовательская группа DATAFARM, Datafarm Company Limited
Это статья описывается эксплуатацию (RCE) SIGRed (CVE-2020–1350) на Windows Server 2012 R2 - Windows Server 2019. Подробную информацию об уязвимости смотри в публикации исследовательской группы (https://research.checkpoint.com/202...ing-a-17-year-old-bug-in-windows-dns-servers/)
Подготовка серверов имен
Чтобы сократить количество шагов по настройке доменного имени, я настраиваю "Conditional Forwarders" на целевом DNS-сервере Windows, как показано на рисунке ниже. Пока я использую dnslib для создания своего вредоносного DNS-клиента и сервера для домена "evildns.com".
Вызов ошибки
Если мы просто проследим за публикацией checkpoint, чтобы вызвать ошибку, мы, скорее всего, закончим сбоем внутри функции memcpy, вызываемой из dns!SigWireRead.
Причина в том, что SIG Resource Record размещается в конце кучи процесса. Для запуска ошибки необходимо перезаписать прошлый буфер записи SIG размером около 64 КБ, поэтому функия memcpy попытается выполнить запись за текущее пространство кучи, которое является недопустимой областью памяти. Если мы дампим память вокруг адреса, который вызвал сбой, мы видим, что адрес недействителен.
Чтобы эксплуатировать переполнения кучи, обычно мы должны знать внутреннюю кучу и некоторую структуру объекта, которая размещена в куче.
Диспетчер кучи WinDNS
WinDNS управляет собственными пулами памяти. Есть 4 сегмента пула памяти (dns!StandardAllocLists) для разных размеров распределения (0x50, 0x68, 0x88, 0xa0). Если требуемый размер выделения больше 0xa0, WinDNS будет использовать нативную кучу Windows (с функциями HeapAlloc и HeapFree).
Ниже приведен псевдокод для функции dns!Mem_Alloc используемой для динамически выделяемой памяти в dns.
Далее идет псевдокод для функции dns!Mem_Free, используемой для освобождения памяти, выделенной функцией Mem_Alloc
После реверс-инжиниринга функций Mem_Alloc и Mem_Free мы можем увидеть некоторые проблемы, которые помогают нам эксплуатировать уязвимость.
Куча WinDNS никогда не возвращает память в нативную кучу Windows
Если размер памяти меньше или равен 0xa0, WinDNS просто помещает его в начало списка. Нативная куча Windows рассматривает ее как используемую память. Таким образом, мы можем свободно повредить нативные метаданные чанка кучи Windows, потому что метаданные кучи проверяются при выделении и освобождении.
Все значения заголовка кучи WinDNS известны
Заголовок кучи WinDNS содержит метаданные, такие как тип буфера, размер, индекс сегмента, cookie (фиксированное значение). Все значения метаданных известны без необходимости утечки информации. Поскольку мы должны начать эксплуатацию с перезаписи множества объектов в куче, это условие очень полезно для эксплуатации этой ошибки без помощи другой ошибки утечки информации.
Свободные фрагменты хранятся в виде односвязного списка
Такая структура данных может означать, что чанки выделяются и освобождаются в обратном порядке (LIFO). Известны методы злоупотребления свободными чанками в односвязном списке после повреждения памяти. Например:
- Поддельный свободный список для контроля следующего места размещения. Это может привести к перекрыванию чанков, произвольной записи (я думаю, что произвольная запись затруднена в этом случае, потому что файл cookie 8 байтов проверяется до того, как функция Mem_Alloc вернет адрес)
- Управление порядком выделения чанков путем их освобождения в обратном порядке
Мониторинг объектов в куче
Чтобы узнать, какие объекты в куче выделяются при обработке пользовательского запроса, я добавляю точку останова в функцию Mem_Alloc для печати трассировки стека для мониторинга объектов и пути кода. Затем я отправляю на сервер различные DNS-запросы. Ниже приведен его образец.
Я нашел несколько интересных объектов. Во-первых, объект DNS Resource Record (RR) создается каждый раз, когда DNS-сервер Windows кэширует ответ от полномочного сервера имен (SIG-запись, выделенная при срабатывании ошибки, также является объектом RR). Заголовок RR имеет поле размера данных, которое может быть перезаписано и использовано для утечки информации позже. Другой объект - это тайм-аут. Он содержит указатель на функцию с 1 аргументом. Мы можем перезаписать их, чтобы позже управлять регистром RIP (счетчика программ).
Переполнение буфера кучи без сбоев
Теперь этот шаг должен быть легким после изучения большого количества кучи WinDNS. Все, что я сделал, - это заставил сервер выделять много объектов RR, которые никогда не освобождаются во время эксплуатации. Затем, освобождая объект, за которым следует множество объектов RR (общий размер должен быть > 64 КБ), освобожденный чанк будет выделен объектом SIG RR при срабатывании уязвимости.
Утечка информации
При запуске ошибки мы можем перезаписать действительный объект RR, изменив только поле размера данных. Затем мы можем запросить перезаписанный RR для утечки соседнего блока. Утечка переполненных данных бесполезна, поэтому сначала нужно освободить соседний кусок. Куча WinDNS запишет указатель на следующий свободный чанк, после чего мы сможем передать указатель на адрес кучи.
Тщательно обработав переполненные данные и свободный порядок, мы можем получить утечку адреса кучи в переполненной области. Потому что мы можем полностью контролировать переполнение данных, поэтому мы можем создать там поддельный свободный список. Тогда в нашей зоне управления будут размещены новые объекты. Мы можем их читать/писать.
Затем я попытался сделать утечку в модуле dns.exe, найдя объект кучи, содержащий адрес в исполняемом модуле. Я нашел 2 объекта (один указывает на раздел BSS, другой указывает на раздел строк). Нужно выполнить запрос, чтобы сервер выделял объект, который имеет указатель на dns.exe, затем считыть его содержимое, а затем вычислить базовый адрес dns.exe.
Примечание: при утечке адреса DNS мы можем определить целевую ОС и версию по младшим 12 битам, потому что модуль должен загружаться при запуске страницы памяти.
Контроль счетчика программ (RIP)
Как упоминалось в предыдущем разделе, объект тайм-аута содержит указатель на функцию с 1 аргументом. Мы можем выделить его в области переполнения, а затем перезаписать для управления ПК. Указатель на функцию в объекте тайм-аута используется в функции dns!Timeout_CleanupDelayedFreeList. Но dns.exe, начиная с Windows Server 2012, компилируется с Control Flow Guard (CFG). При включенном CFG мы можем перейти только к функции, которая находится в разрешенном списке. Если мы попытаемся перейти к эпилогу функции (для запуска ROP), мы закончим сбоем внутри функции ntdll! LdprValidateUserCallTarget, как показано на рисунке ниже (из Windows Server 2012 R2).
Обычной процедурой выполнения кода, когда включен CFG, является изменение адреса возврата в стеке, что требует произвольной возможности записи. Но мы не можем делать произвольную запись сейчас. Мы также не знаем адреса стека потоков. Теперь мы можем контролировать только кучу в области переполнения. Очень редко можно увидеть адрес стека хранилища программ в объекте кучи. Я даже не пытаюсь найти адрес стека в куче.
Затем я попытался выполнить произвольное чтение путем поиска объекта, содержащего указатель на данные. Затем выделил его в нашей области управления и перезаписал указатель. Я ожидал, что сервер разыменует указатель и скопирует мне данные. Но то, что я обнаружил, требует так много условий, не может быть использовано (может быть объект, который можно использовать для произвольного чтения, но я не могу его найти).
Затем я проверил функции в dns.exe, где разрешен CFG, в надежде найти что-нибудь полезное. Большинство функций можно пропустить, потому что мы можем управлять только 1 аргументом. Мы можем полностью игнорировать функции с аргументом больше 1. Потратив бесчисленное количество часов на обход CFG, я обнаружил функцию dns!NsecDNSRecordConvert.
Из декомпилированного кода param_1, очевидно, является указателем на структуру.В строке 15 сервер находит длину буфера для копирования из указателя строки в param_1+0x20. В строке 18 сервер выделяет память для хранения данных. В строке 22 сервер копирует строку в новую выделенную память. С полностью управляемым аргументом функции мы можем заставить функцию копировать данные (в виде строки) с любого адреса в новую выделенную память. Кроме того, мы можем создать новую выделенную память в нашей контролируемой области. Затем прочтитать данные. Таким образом, мы можем выполнять произвольное чтение с помощью функции dns!NsecDNSRecordConvert.
Выполнение кода
Имея возможность вызывать функцию в списке CFG с 1 аргументом и произвольным чтением, я думал сделать выполнение кода с помощью функций kernel32!WinExec или msvcrt!system. Я выбрал msvcrt!system, потому что kernel32.dll, скорее всего, будет изменен ежемесячным патчем Microsoft. Смещение в msvcrt.dll должно использоваться на любом уровне исправлений.
Чтобы выполнить код с помощью системы msvcrt!system, я нахожу функцию msvcrt!Memcpy, читая из таблицы импорта dns.exe, а затем вычисляя адрес msvcrt!system. Наконец, повторяем шаги по контролю PC, и переходим к msvcrt!system. Ура, получите исполнение кода.
После успешной разработки эксплойта для Windows Server 2012R2 я только изменил смещения dns.exe и msvcrt.dll для Windows Server 2016 и Windows Server 2019. И работает отлично.
Примечание. Dll в Windows Server 2019 скомпилированы с подавлением экспорта CFG (на рисунке ниже — msvcrt.dll). Если dns.exe разрешает это, путь эксплойта должен быть намного сложнее.
Демонстрационное видео
Вот демонстрационные видео для Windows Server 2012 R2, Windows Server 2016 и Windows Server 2019.
Ссылки
Автор https://medium.com/@datafarm.cybers...on-windows-server-2012-2016-2019-80dd88594228
Автор перевода: yashechka
Переведено специально для https://xss.pro
Автор: Worawit Wangwarunyoo, Исследовательская группа DATAFARM, Datafarm Company Limited
Это статья описывается эксплуатацию (RCE) SIGRed (CVE-2020–1350) на Windows Server 2012 R2 - Windows Server 2019. Подробную информацию об уязвимости смотри в публикации исследовательской группы (https://research.checkpoint.com/202...ing-a-17-year-old-bug-in-windows-dns-servers/)
Подготовка серверов имен
Чтобы сократить количество шагов по настройке доменного имени, я настраиваю "Conditional Forwarders" на целевом DNS-сервере Windows, как показано на рисунке ниже. Пока я использую dnslib для создания своего вредоносного DNS-клиента и сервера для домена "evildns.com".
Вызов ошибки
Если мы просто проследим за публикацией checkpoint, чтобы вызвать ошибку, мы, скорее всего, закончим сбоем внутри функции memcpy, вызываемой из dns!SigWireRead.
Причина в том, что SIG Resource Record размещается в конце кучи процесса. Для запуска ошибки необходимо перезаписать прошлый буфер записи SIG размером около 64 КБ, поэтому функия memcpy попытается выполнить запись за текущее пространство кучи, которое является недопустимой областью памяти. Если мы дампим память вокруг адреса, который вызвал сбой, мы видим, что адрес недействителен.
Чтобы эксплуатировать переполнения кучи, обычно мы должны знать внутреннюю кучу и некоторую структуру объекта, которая размещена в куче.
Диспетчер кучи WinDNS
WinDNS управляет собственными пулами памяти. Есть 4 сегмента пула памяти (dns!StandardAllocLists) для разных размеров распределения (0x50, 0x68, 0x88, 0xa0). Если требуемый размер выделения больше 0xa0, WinDNS будет использовать нативную кучу Windows (с функциями HeapAlloc и HeapFree).
Ниже приведен псевдокод для функции dns!Mem_Alloc используемой для динамически выделяемой памяти в dns.
Далее идет псевдокод для функции dns!Mem_Free, используемой для освобождения памяти, выделенной функцией Mem_Alloc
После реверс-инжиниринга функций Mem_Alloc и Mem_Free мы можем увидеть некоторые проблемы, которые помогают нам эксплуатировать уязвимость.
Куча WinDNS никогда не возвращает память в нативную кучу Windows
Если размер памяти меньше или равен 0xa0, WinDNS просто помещает его в начало списка. Нативная куча Windows рассматривает ее как используемую память. Таким образом, мы можем свободно повредить нативные метаданные чанка кучи Windows, потому что метаданные кучи проверяются при выделении и освобождении.
Все значения заголовка кучи WinDNS известны
Заголовок кучи WinDNS содержит метаданные, такие как тип буфера, размер, индекс сегмента, cookie (фиксированное значение). Все значения метаданных известны без необходимости утечки информации. Поскольку мы должны начать эксплуатацию с перезаписи множества объектов в куче, это условие очень полезно для эксплуатации этой ошибки без помощи другой ошибки утечки информации.
Свободные фрагменты хранятся в виде односвязного списка
Такая структура данных может означать, что чанки выделяются и освобождаются в обратном порядке (LIFO). Известны методы злоупотребления свободными чанками в односвязном списке после повреждения памяти. Например:
- Поддельный свободный список для контроля следующего места размещения. Это может привести к перекрыванию чанков, произвольной записи (я думаю, что произвольная запись затруднена в этом случае, потому что файл cookie 8 байтов проверяется до того, как функция Mem_Alloc вернет адрес)
- Управление порядком выделения чанков путем их освобождения в обратном порядке
Мониторинг объектов в куче
Чтобы узнать, какие объекты в куче выделяются при обработке пользовательского запроса, я добавляю точку останова в функцию Mem_Alloc для печати трассировки стека для мониторинга объектов и пути кода. Затем я отправляю на сервер различные DNS-запросы. Ниже приведен его образец.
Я нашел несколько интересных объектов. Во-первых, объект DNS Resource Record (RR) создается каждый раз, когда DNS-сервер Windows кэширует ответ от полномочного сервера имен (SIG-запись, выделенная при срабатывании ошибки, также является объектом RR). Заголовок RR имеет поле размера данных, которое может быть перезаписано и использовано для утечки информации позже. Другой объект - это тайм-аут. Он содержит указатель на функцию с 1 аргументом. Мы можем перезаписать их, чтобы позже управлять регистром RIP (счетчика программ).
Переполнение буфера кучи без сбоев
Теперь этот шаг должен быть легким после изучения большого количества кучи WinDNS. Все, что я сделал, - это заставил сервер выделять много объектов RR, которые никогда не освобождаются во время эксплуатации. Затем, освобождая объект, за которым следует множество объектов RR (общий размер должен быть > 64 КБ), освобожденный чанк будет выделен объектом SIG RR при срабатывании уязвимости.
Утечка информации
При запуске ошибки мы можем перезаписать действительный объект RR, изменив только поле размера данных. Затем мы можем запросить перезаписанный RR для утечки соседнего блока. Утечка переполненных данных бесполезна, поэтому сначала нужно освободить соседний кусок. Куча WinDNS запишет указатель на следующий свободный чанк, после чего мы сможем передать указатель на адрес кучи.
Тщательно обработав переполненные данные и свободный порядок, мы можем получить утечку адреса кучи в переполненной области. Потому что мы можем полностью контролировать переполнение данных, поэтому мы можем создать там поддельный свободный список. Тогда в нашей зоне управления будут размещены новые объекты. Мы можем их читать/писать.
Затем я попытался сделать утечку в модуле dns.exe, найдя объект кучи, содержащий адрес в исполняемом модуле. Я нашел 2 объекта (один указывает на раздел BSS, другой указывает на раздел строк). Нужно выполнить запрос, чтобы сервер выделял объект, который имеет указатель на dns.exe, затем считыть его содержимое, а затем вычислить базовый адрес dns.exe.
Примечание: при утечке адреса DNS мы можем определить целевую ОС и версию по младшим 12 битам, потому что модуль должен загружаться при запуске страницы памяти.
Контроль счетчика программ (RIP)
Как упоминалось в предыдущем разделе, объект тайм-аута содержит указатель на функцию с 1 аргументом. Мы можем выделить его в области переполнения, а затем перезаписать для управления ПК. Указатель на функцию в объекте тайм-аута используется в функции dns!Timeout_CleanupDelayedFreeList. Но dns.exe, начиная с Windows Server 2012, компилируется с Control Flow Guard (CFG). При включенном CFG мы можем перейти только к функции, которая находится в разрешенном списке. Если мы попытаемся перейти к эпилогу функции (для запуска ROP), мы закончим сбоем внутри функции ntdll! LdprValidateUserCallTarget, как показано на рисунке ниже (из Windows Server 2012 R2).
Обычной процедурой выполнения кода, когда включен CFG, является изменение адреса возврата в стеке, что требует произвольной возможности записи. Но мы не можем делать произвольную запись сейчас. Мы также не знаем адреса стека потоков. Теперь мы можем контролировать только кучу в области переполнения. Очень редко можно увидеть адрес стека хранилища программ в объекте кучи. Я даже не пытаюсь найти адрес стека в куче.
Затем я попытался выполнить произвольное чтение путем поиска объекта, содержащего указатель на данные. Затем выделил его в нашей области управления и перезаписал указатель. Я ожидал, что сервер разыменует указатель и скопирует мне данные. Но то, что я обнаружил, требует так много условий, не может быть использовано (может быть объект, который можно использовать для произвольного чтения, но я не могу его найти).
Затем я проверил функции в dns.exe, где разрешен CFG, в надежде найти что-нибудь полезное. Большинство функций можно пропустить, потому что мы можем управлять только 1 аргументом. Мы можем полностью игнорировать функции с аргументом больше 1. Потратив бесчисленное количество часов на обход CFG, я обнаружил функцию dns!NsecDNSRecordConvert.
Из декомпилированного кода param_1, очевидно, является указателем на структуру.В строке 15 сервер находит длину буфера для копирования из указателя строки в param_1+0x20. В строке 18 сервер выделяет память для хранения данных. В строке 22 сервер копирует строку в новую выделенную память. С полностью управляемым аргументом функции мы можем заставить функцию копировать данные (в виде строки) с любого адреса в новую выделенную память. Кроме того, мы можем создать новую выделенную память в нашей контролируемой области. Затем прочтитать данные. Таким образом, мы можем выполнять произвольное чтение с помощью функции dns!NsecDNSRecordConvert.
Выполнение кода
Имея возможность вызывать функцию в списке CFG с 1 аргументом и произвольным чтением, я думал сделать выполнение кода с помощью функций kernel32!WinExec или msvcrt!system. Я выбрал msvcrt!system, потому что kernel32.dll, скорее всего, будет изменен ежемесячным патчем Microsoft. Смещение в msvcrt.dll должно использоваться на любом уровне исправлений.
Чтобы выполнить код с помощью системы msvcrt!system, я нахожу функцию msvcrt!Memcpy, читая из таблицы импорта dns.exe, а затем вычисляя адрес msvcrt!system. Наконец, повторяем шаги по контролю PC, и переходим к msvcrt!system. Ура, получите исполнение кода.
После успешной разработки эксплойта для Windows Server 2012R2 я только изменил смещения dns.exe и msvcrt.dll для Windows Server 2016 и Windows Server 2019. И работает отлично.
Примечание. Dll в Windows Server 2019 скомпилированы с подавлением экспорта CFG (на рисунке ниже — msvcrt.dll). Если dns.exe разрешает это, путь эксплойта должен быть намного сложнее.
Демонстрационное видео
Вот демонстрационные видео для Windows Server 2012 R2, Windows Server 2016 и Windows Server 2019.
Ссылки
- https://research.checkpoint.com/202...ing-a-17-year-old-bug-in-windows-dns-servers/
- https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1350
- https://github.com/paulc/dnslib
Автор https://medium.com/@datafarm.cybers...on-windows-server-2012-2016-2019-80dd88594228
Автор перевода: yashechka
Переведено специально для https://xss.pro