четверг, 13 октября 2016 г.

Win10 теперь защищена от обнуления SecurityDescriptor

В Win10 закрыли еще один вектор атаки, основанный на обнулении SecurityDescriptor'а у процесса с высокими привилегиями. Сама атака описана довольно давно в https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf.

В Win10 добавили несколько проверок, которые задействуются при доступе к securable object'ам и теперь, при обнуленном SecurityDescriptor'е, ОС будет падать в BSOD с кодом BAD_OBJECT_HEADER.

Нужно заметить, что атака описанная на BH была довольно эффективной, т.к. никакие защитные механизмы ОС типа SMEP эту самую ОС от данной атаки никак не защищали, так как никакой payload не выполнялся. Впрочем, данный тип атаки основывался на возможности записи NULL в любую область памяти ядра. Соответственно, подобную защиту можно будет обойти при возможности писать любое значение в память ядра простым обнулением DACL в SecurityDescriptor'е.

среда, 9 марта 2016 г.

Еще один способ использования BURNMEMORY

Лет 5 назад был пост про BURNMEMORY опцию (http://kitrap08.blogspot.ru/2011/12/burnmemory.html), задаваясь вопросом - а зачем оно вообще нужно. В комментах были даны ответы, но сейчас мне встретился еще один вариант использования BURNMEMORY/removememory, и этот вариант уже имеет непосредственное отношение к практике.

Встретился мне этот вариант в доке: Intel Debug Extensions for WinDbg* for Intel Processor Trace.

Intel Processor Trace это новая фича процессоров Intel 6й серии(Skylake), которая суть есть трассировка выполнения с низким оверхедом. Данные трассировки будут скидываться в память, конфигурировать которую нужно через BIOS / UEFI firmware, но только в том случае, если это самое firmware поддерживает данную фичу. А вот если не поддерживает, то на помочь придет как раз  BURNMEMORY/removememory.

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

Ну и третий вариант - опция badmemory.

пятница, 4 марта 2016 г.

Verifier

Потребовалось мне, для своих нужд, проинжектировать dll и перехватить в процессе некоторые функции. Да так, чтобы кода было минимум, без всяких там сторонних движков и прочих detours'ов.

Всем этим требованиям удовлетворяет механизм verifier'a. Вот только штука эта отладочная и даже системные приложения не идеальны и полны разного рода косяков и багов. Соответственно verifier начинает проверять, ворчать и сыпать брекпойнтами на любой чих. В отладчике соответственно видно что-то типа такого:

VERIFIER STOP 00000210: pid 0x464: Critical section not initialized.

    759C08C0 : Critical section address.
    008A0BF0 : Critical section debug info address.
    00000000 : Not used.
    00000000 : Not used.
   
Или такого:

VERIFIER STOP 00000006: pid 0x464: corrupted heap pointer or using wrong heap

    00EF1000 : Heap used in the call
    05A507F0 : Heap block
    00000214 : Block size
    80DF1000 : Heap owning the block

При отсутствующем отладчике, соответственно, получим необрабатываемое исключение в том процессе, куда мы заинжектили dll и все благополучно упадет.

В большинстве случаев, брякается все это дело в VerifierStopMessageEx в verifier.dll. А в самом начале функции стоит следующая проверка:

if ( AVrfpProcessBeingTerminated || !AVrfpStopInitialized )
    return 0;
   
То есть бряки перестанут сыпаться, если установить AVrfpProcessBeingTerminated в TRUE. Осталась задача - легко найти эту переменную. VerifierStopMessageEx не экспортируется, а вот VerifierStopMessage экспортируется и имеет в начале нужную переменную:

.text:1124B860 _VerifierStopMessage@40 proc near
.text:1124B860                 mov     edi, edi
.text:1124B862                 push    ebp
.text:1124B863                 mov     ebp, esp
.text:1124B865                 sub     esp, 1Ch
.text:1124B868                 mov     [ebp+var_1C], 0
.text:1124B86F                 mov     [ebp+var_18], 0
.text:1124B876                 mov     [ebp+var_14], 0
.text:1124B87D                 mov     [ebp+var_8], 0
.text:1124B884                 cmp     _AVrfpProcessBeingTerminated, 0
.text:1124B88B                 jnz     short loc_1124B896

Соответственно, найти ее становится элементарным делом. И после нахождения данной переменной и установки ее значения в TRUE, из своей инжектированной dll, все проблемные int 3 более не вызывались.

P.S. К слову, verifier оказался хотя и удобным решением, но тем не менее, не без недостатков. Оказалось, что он перехватывает не все. К примеру, ZwContinue он перехватывать отказался, хотя LdrLoadDll из той же ntdll.dll перехватил.

пятница, 22 января 2016 г.

Peb decommit bug

Старый добрый DOS с декоммитом Peb валит win7 в BSOD:

 void FreePeb()
{
    PVOID pebAddress = NULL;
    
    __asm
    {
        mov eax, DWORD ptr fs:[0x30]
        mov DWORD ptr [pebAddress], eax
    }

    VirtualFreeEx( GetCurrentProcess(), pebAddress, 0, MEM_DECOMMIT );
}

int wmain( int argc, wchar_t *argv[] )
{   
    FreePeb();

    for ( unsigned int i = 0x1000; i < 0x2000; i++ )
    {
        __asm
        {
            push 0
            push 0
            push 0
            push 0
            push 0
            push retSysenter
            mov edx, esp
            mov eax, dword ptr [i]
            sysenter
retSysenter:
            add esp, 5*4
        }
    }

    cin.get();
    return 0;
}

А на win10 данный DOS уже не работает, PEB декоммитнуть больше не получится из-за новых битов защиты:

VirtualFreeEx => NtFreeVirtualMemory => MiCheckSecuredVad => возвращает STATUS_INVALID_PAGE_PROTECTION(0xC0000045)

У сожалению нет возможности проверить точную версию ОС, где был пофикшен данный баг, возможно это случилось в dev builds win10 или даже еще раньше.

суббота, 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 за интересный кейс!

четверг, 2 января 2014 г.

Hacking games with PIN

Сегодня речь пойдет о играх, а точнее, об их взломе.

Может возникнуть вопрос, причем тут игры, ведь блог в основном относится к теме системного программирования / реверсинга / декомпиляции?
Да вот как-то после прочтения заметки в блоге HEX'а про создание игр (http://rebl0g.wordpress.com/2013/12/15/%D0%BC%D0%B5%D1%87%D1%82%D1%8B-%D0%B4%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B0/#more-217) навеяло. Но в данной заметке будет рассмотрено прямо противоположное созданию действие - взлом.

Что в основном ломают в играх? Определенно, игровые ресурсы.
Как ломают тоже в принципе давно известно: ищут / меняют значения памяти в программах типа gamehack.
Но, конечно, gamehack я описывать в своем блоге не собираюсь, вместо этого я опишу альтернативный вариант поиска нужных значений в памяти.

Тут как раз и пересекается тематика блога и игр, ибо речь пойдет про PIN.

Итак,  нам нужно найти и изменить определенные игровые ресурсы, скажем, деньги. Для этого нужно как-то найти, где в памяти, а еще лучше, прямо в бинаре лежат нужные нам данные. Зачем нам бинарь, ведь достаточно изменить значения в памяти? К примеру, мы хотим еще и пореверсить код игры, или пропатчить бинарь игры, для этого желательно иметь точку, где происходит работа с игровыми ресурсами.

Как будем искать нужные данные? Подход в чем-то схож с подходом gamehack.

Игровые деньги могут уменьшаться, скажем при покупке чего-либо, или увеличиваться, с течением времени. Логично предположить, что в конечном итоге это приведет нас к двум инструкциям sub / add.

Далее, все что нужно, это написать PIN модуль, который смотрит текущие инструкции на предмет sub /add и выводит все инструкции в лог.
После чего, запускаем игру под PIN до изменения наблюдаемых величин( игровой валюты к примеру ) и после изменения.
Наблюдаемую величину можно захардкодить в PIN модуле, чтобы отсеять лишнюю информацию из лога.

В итоге, получаем нечто вроде:

VOID SubReg( ADDRINT ea, string *disasm, ADDRINT reg1Value, ADDRINT reg2Value )
{
...
    if ( reg1Value == VALUE_TO_WATCH )
        cout << hex << ea << " " << disasm << dec << " reg1Value = " << reg1Value << " reg2Value = " << reg2Value << endl;
...
}

VOID Instruction( INS ins, VOID *v )
{
    ADDRINT ea = INS_Address( ins );

    if ( ea < gMainExe.GetModuleStartAddress() || ea > gMainExe.GetModuleEndAddress() )
        return;

    if (INS_Opcode(ins) == XED_ICLASS_SUB && INS_OperandReg(ins, 0) != REG_ESP && !INS_OperandIsImmediate(ins, 1) && INS_OperandIsReg(ins, 0))
    //if (INS_Opcode(ins) == XED_ICLASS_ADD && INS_OperandReg(ins, 0) != REG_ESP && !INS_OperandIsImmediate(ins, 1) && INS_OperandIsReg( ins, 0))
    {
        if ( INS_OperandIsReg( ins, 1 ) )
        {
            INS_InsertCall( ins, IPOINT_BEFORE, (AFUNPTR)SubReg,
                                IARG_ADDRINT, ea,
                                IARG_PTR, new string( INS_Disassemble(ins) ),
                                IARG_REG_VALUE, INS_OperandReg( ins, 0 ),
                                IARG_REG_VALUE, INS_OperandReg( ins, 1 ),
                                IARG_END );
        }   
    }
}

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

Пример лога(игра WarGames):

487696 sub edx, ecx reg1Value = 3f7a reg2Value = 9c4
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a

В выделенной строке лог после покупки юнита за 2500(9c4h) игровых денег.
То есть из общего баланса (3f7a) вычитается стоимость юнита. Таким образом, мы вышли на код работы с игровой валютой, дальше можно патчить в памяти через тот же PIN, или на диске или анализировать/реверсить в IDA.

Код в IDA:

.text:00487687                 xor     ecx, ecx
.text:00487689                 mov     cx, word_7470B2[edx]
.text:00487690                 mov     edx, dword_73B65C[eax]
.text:00487696                 sub     edx, ecx
.text:00487698                 mov     eax, [ebp+var_8]
.text:0048769B                 xor     ecx, ecx
.text:0048769D                 mov     cl, [eax+5]

Такой подход оправдал себя и на двух других играх, для Orion2 искалась не sub инструкция, а add, так как доход игровой валюты там меняется с течением времени.

После всех этих манипуляций мое любопытство по поводу жизнеспособности метода с PIN было удовлетворено, и игры благополучно были отправлены обратно в архив.