суббота, 5 декабря 2015 г.

Fun with crackme

Захотелось поломать какой-нибудь сложный крякми, выбор пал на https://exelab.ru/f/index.php?action=vthread&forum=2&topic=22109&page=0
Смысл в том, чтобы извлечь картинку из памяти.

== Цель ==

Максимально быстро решить crackme, сведя к минимуму анализ и отладку.

== Первичный анализ ==

Статический анализ: код написан на asm, в импорте только ZwRaiseHardError, среди строк интересна лишь строка Db.dll.
Статикой информацию получить можно, но это долго, а значит не соответствует цели.
Динамический анализ: отладчик пока отложим в сторону, соберем полную картину действий crackme:

Собираем лог сисколов либо тулзой PIN, либо иным инструментом ( я использовал свой ).
В логе видны манипуляции с mapping'ом и unmapping'ом секций, а также пара интересных сисколов: NtResetWriteWatch и NtGetWriteWatch.
Кроме этого есть 3 вызова NtAllocateVirtualMemory с MEM_WRITE_WATCH, и один с MEM_COMMIT.
Далее, в логе видим 1868207 вызовов NtProtectVirtualMemory, скорее всего цикл связан с расшифровкой изображения.

Где может скрываться изображение? В выделенной приватной памяти, в промаппированной памяти или в стеке(экзотика, но почему нет?).

Проверим вариант с секцией, тем более, что в crackme есть строка "Db.dll" намекающая на маппинг dll с картинкой в ресурсах.
В логе есть 8 вызовов NtMapViewOfSection, собираем адреса, куда замаплена секция. Причем два из них находятся среди 1.8 миллионов вызовов NtProtectVirtualMemory:

Первый вызов завершается unmapping'ом:

...
NtProtectVirtualMemory
NtCreateSection
NtMapViewOfSection
NtQuerySystemInformation
NtUnmapViewOfSection
NtProtectVirtualMemory
...

А второй нет:

...
NtProtectVirtualMemory
NtMapViewOfSection
NtProtectVirtualMemory
...

Логично предположить, что картинка скрывается в этой замапленной секции, запоминаем адрес, по которому она была замаплена - 0x10000000.

Далее, перематываем 1.8 миллиона NtProtectVirtualMemory в логе, из стектрейса берем адрес, из которого был вызван сискол: 0x412B6E.

Дальше нам понадобится отладчик, но не OllyDbg, т.к. бряк на адресе 0x412B6E не работает из-за защиты от модификации секции.
Тем не менее, все работает в windbg(хотя я использовал оба отладчика). Брякаемся, ищем в отладчике конец цикла с NtProtectVirtualMemory.

Конец цикла тут:

001b:00412ee6 6681390f31      cmp     word ptr [ecx],310Fh
001b:00412eeb 7517            jne     00412f04                           [br=1]
001b:00412eed 8b4a18          mov     ecx,dword ptr [edx+18h]

Ставим бряк на 0x00412eed. Ждем пока сработает бряк, смотрим ecx:

kd> r ecx
ecx=1000101a

И видим адрес нашей промапленной секции.
Если цикл с NtProtectVirtualMemory был завязан на расшифровку картинки в ресурсах промапленной dll, то сигнатуру картинки можно будет визуально найти в памяти.
Смотрим память по адресу 0x10000000, видим сигнатуру PE файла, листаем, ничего похожего на сигнатуру картинки не видно.
Либо она где-нибудь в другом месте, либо предположение с расшифровкой неверно и она лежит тут, но зашифрована.

Отпускаем выполнение, отладчик опять брякается на 0x00412eed, и содержимое секции изменяется, после нескольких бряков по адресу 0x10002000 видна сигнатура PNG формата.

DR регистры используются при расшифровке, поэтому бряк на запись последнего байта зашифрованных данных не сработает.
Тем не менее, при срабатывании бряка в edi находится адрес, по которому будет записан dword с расшифрованными данными. Поэтому просто пишем условный бряк:

bp 412eed "j @edi = 0x1001539c '';'gc'"

Ждем срабатывание брекпойнта и дампим картинку:

.writemem f:\result.png 0x10002000 L014000

Результат:



Задача решена, причем с минимумом временных затрат, за счет использования инструментов, против которых защита crackme была бесполезна.
Что же касается самой защиты кода от модификации в данном crackme - возможно, если будет время и желание я поресерчу это в следующей статье.



вторник, 17 февраля 2015 г.

Анализ бага win32k (DOS)

На форуме rsdn.ru проскользнула любопытная тема http://rsdn.ru/forum/winapi/5956603,  в которой kero открыл баг в ядре графической подсистемы windows - win32k.sys.

Мне стало любопытно, а в чем собственно состоит баг? Данная заметка - мой небольшой анализ.

Прежде всего, как вызвать BSOD? kero, помимо exe, приложил еще и исходники, из них следует, что если создать определенное количество окон и установить каждому предыдущему окну владельцом текущее окно через SetWindowLong( GWL_HWNDPARENT ) и затем удалить самое последнее окно, то это вызовет удаление всех окон в цепочке и BSOD в итоге.

Из-за чего происходит BSOD? Из-за переполнения стека, которую вызвала рекурсия.

Рекурсия вызывается из-за:

xxxDestroyWindow()
{
   если у окна нет свойства WS_CHILD, вызывается xxxDW_DestroyOwnedWindows
}

xxxDW_DestroyOwnedWindows
{
   для всех окон, имеющих владельца родителя вызывается xxxDestroyWindow
}

Собственно, данная рекурсия не отличается от любой другой - если нет защиты от рекурсии, она рано или поздно исчерпает весь стек. Также происходит и тут, локальные переменные данных двух функций выжирают весь ядерный стек, при очередном push'е стека не хватает, происходит исключение, которое опять таки не может быть обработано, ибо также использует стековые переменные, система переключается на panic stack и выбрасывает исключение DOUBLE_FAULT.

Количество окон, которые система может создать без BSOD'a зависит от путей исполнения программы, а пути зависят от входных данных, к примеру добавив новый стиль в CreateWindow, или убрав любой из примера - получим другое число созданных окон без падения.

Почему нет функции, которая бы проверяла и предотвращала подобные вещи?

На самом деле она есть:

SetWindowLong( GWL_HWNDPARENT ) => ValidateOwnerDepth

BOOL ValidateOwnerDepth()
{
    UINT cDepth = 1;
    while (pwndOwner != NULL )
   {
       if ( pwndOwner == pwnd )
           return FALSE;
 
      pwndOwner = pwndOwner->spwndOwner; // всегда 0
      cDepth++;
    }
    return (cDepth <= NESTED_WINDOW_LIMIT);
}

spwndOwner всегда будет == 0, т.к. при создании окна, если отсутствует  свойство WS_CHILD и hWndParent == NULL, spwndOwner устанавливается в NULL.

Поэтому SetWindowLong будет успешно устанавливать владельца, т.к. функция валидатор всегда будет давать добро. А при удалении цепочки окон всегда будет BSOD если создано слишком много окон.

Баг воспроизводится на всех последних ОС любых разрядностей, не проверял лишь win10.
Спасибо kero за интересный кейс!