Windows позволяет нам устанавливать свои юзер-обработчики исключений, которые реализованы чз механизм SEH на системах х32 (Structured Exception Handler, структурный), и VEH на х64 (векторный). Тема давно заезжена вдоль и поперёк, а потому не будем в очередной раз мусолить её от и до - здесь хотелось-бы обсудить другой вопрос. Как в дизасм-листинге спрятать обращение к сегментному регистру
1. Вводная часть
2. Чтение FS функцией GetThreadContext()
3. Модифицировать указатель в системном SEH
4. Перехватить системный обработчик исключений
5. Заключение
1. Общие сведения
По умолчанию система предлагает нам свой обработчик исключений, который описывает фрейм в стеке из двух двордов - первый дворд это линк на сл.обработчик, а второй хранит адрес процедуры текущего обработчика. Таким образом мы можем связывать несколько обработчиков в длинную цепочку "Chain". Указатель на голову этой цепи прописывается в первом-же поле "ExceptionList" структуры TEB потока, а маркером конца является значение
Здесь видно, что на данный момент в стеке имеем всего один SEH-фрейм, который предоставила нам сама система. Его обработчик исключений зарыт где-то в нёдрах Ntdll.dll по адресу
Конструкция подобного рода знакома всем, кто хоть краем уха слышал про реверс, а виной тому манипуляции с регистром
2. Чтение FS функцией GetThreadContext()
Первый вариант заключается в вызове функции
Чтобы окончательно запутать начинающего хацкера, желательно вызывать
3. Мод указателя в системном SEH
Выше упоминалось, что предлагаемый системой обработчик исключений отнюдь не наделён интеллектом, а потому можно отправить его на скамейку запасных, а самим встать на его место просто перезаписав указатель. В этом случае регистр
В качестве сигнатуры можно использовать маркер окончания цепочки SEH со-значением
Если не знать всей предыстории, то теперь сложно будет догадаться, что именно делает данный участок кода, ведь манипуляции с регистром
4. Перехват системного обработчика исключений.
Если реверсер продвинутый, он сразу обнаружит невалидный указатель в системном SEH-фрейме, который должен смотреть в либу Ntdll.dll, в то время как у нас он сейчас нацелен на обработчик в области памяти нашего-же процесса. Кстати дефолтная функция системы в Ntdll.dll называется
Ладно, оставим этот нюанс за бортом, а сами посмотрим на содержимое системного обработчика. Как видим это типичная API с прологом и эпилогом, а поскольку мы подрядись её перехватить, то указатель в SEH-фрейме останется уже прежний, что снимет с нас все подозрения. Правда для этого нужно будет сначала добавить атрибут записи в страницу Ntdll.dll (т.к. в дефолте стоит page_execute_read), прописать в пролог указатель на свой обработчик исключений, после чего восстановить прежние атрибуты на место.
Библиотека Ntdll.dll у каждого процесса своя, т.к. система проецирует/мапит её в каждый процесс отдельно. Поэтому фактически мы будем править код Ntdll только своей либы. Пока юзер не осуществляет запись в пространство системных dll, всё идёт в штатном режиме. Но при попытки записи тут-же включается механизм CoW (CopyOnWrite), и ядро оси создаёт копию своей библиотеки, чтобы эта запись не затронула оригинал. Вот пример реализации такого варианта установки пользовательских SEH:
Теперь в момент любого эксепшена в нашем приложении, управление получит кастомный обработчик, внутри которого мы должны сохранить контекст всех регистров уже знакомой функцией
5. Заключение
Здесь мы рассмотрели несколько вариантов скрытия регистра
Ссылки по теме:
SEH – фильтр необработанных исключений - Форум информационной безопасности - Codeby.net
Самотрассировка, или марш-бросок по периметру отладчика
ASM – Динамическое шифрование кода - Форум информационной безопасности - Codeby.net
FS, ведь инструкция mov eax,[fs:0] сдаёт наш план с потрохами, что не есть хорошо. Обнаружив её взломщик сразу поймёт, что это попытка установить SEH, а значит софт будет стрелять исключениями, и сам-же перехватывать их. В общем как ни крути, регистр FS в данной ситуации нужно срочно отправлять в топку, тем более, что у нас имеется неплохой выбор альтернативных вариантов, а который из них использовать - решать уже вам.1. Вводная часть
2. Чтение FS функцией GetThreadContext()
3. Модифицировать указатель в системном SEH
4. Перехватить системный обработчик исключений
5. Заключение
1. Общие сведения
По умолчанию система предлагает нам свой обработчик исключений, который описывает фрейм в стеке из двух двордов - первый дворд это линк на сл.обработчик, а второй хранит адрес процедуры текущего обработчика. Таким образом мы можем связывать несколько обработчиков в длинную цепочку "Chain". Указатель на голову этой цепи прописывается в первом-же поле "ExceptionList" структуры TEB потока, а маркером конца является значение
0xFFFFFFFF. При этом на саму ТЕВ указывает как-раз регистр FS на системах х32, и GS на х64. При наличии файла скриптов, заполненную ТЕВ (да и любую структуру) можно посмотреть в отладчике x64Dbg:Здесь видно, что на данный момент в стеке имеем всего один SEH-фрейм, который предоставила нам сама система. Его обработчик исключений зарыт где-то в нёдрах Ntdll.dll по адресу
0x77694DCD. Всё, на что он способен - это вывод окна с характером критической ошибки, да кнопкой "Закрыть", т.е. бесполезен от слова вообще. Поэтому ОС даёт нам возможность самим заниматься обработкой своих исключений, для чего всего-то нужно вписать ещё один фрейм, до системного. Программно это реализуется всего тремя строчками кода так:
C-подобный:
.code
start: push mySeh ;// указатель на наш обработчик
push [fs:0] ;// указатель на предыдущий фрейм
mov [fs:0],esp ;// регистрируем на SEH-фрейм,
;// ..в поле ТЕВ.ExceptionList
Конструкция подобного рода знакома всем, кто хоть краем уха слышал про реверс, а виной тому манипуляции с регистром
FS. Посмотрим, как и на что его можно заменить, чтобы не мозолил глаза в дизассемблерах и отладчиках пользовательского режима.2. Чтение FS функцией GetThreadContext()
Первый вариант заключается в вызове функции
GetThreadContext(), которая дампит состояние всех регистров текущего потока, в одноимённую структуру CONTEXT. Среди прочего, по смещению 0х90 в этой структуре будет лежать и FS, а значит мы можем взять его в любой другой сегментный регистр, например безобидный ES или DS, что замаскирует факт установки собственного фрейма исключений.
C-подобный:
;//...
mov [context],CONTEXT_SEGMENTS ;// флаг = 0x10004,
invoke GetThreadContext,-2,context ;// ..только сегм.регистры
;//...
push es ;//
mov es,word[context+90h] ;// читаем FS в ES
mov eax,[es:0] ;// EAX = TEB.ExceptionList
pop es ;//
;//...
push mySeh ;// регистрируем свой SEH
push eax
mov [eax],esp
;//...
Чтобы окончательно запутать начинающего хацкера, желательно вызывать
GetThreadContext() где-нибудь в начале, а обращаться к структуре CONTEXT намного позже, предварив непосредственную регистрацию SEH-фрейма обфускацией кода. Вариант конечно не фонтан, однако имеет право на жизнь.3. Мод указателя в системном SEH
Выше упоминалось, что предлагаемый системой обработчик исключений отнюдь не наделён интеллектом, а потому можно отправить его на скамейку запасных, а самим встать на его место просто перезаписав указатель. В этом случае регистр
FS вообще не будет фигурировать нигде. Единственная проблема - это найти системный SEH-фрейм в стеке текущего потока.В качестве сигнатуры можно использовать маркер окончания цепочки SEH со-значением
0xFFFFFFFF, только искать её придётся в девственном стеке, пока ещё никто не исказил стек левыми push -1. Как только найдём, то перезаписываем следующий после маркера дворд, чтобы он указывал на нашу пользовательскую процедуру обработки эксепшенов. Так это можно реализовать на практике:
C-подобный:
mov esi,esp ;// ESI = стек
@@: cmp dword[esi],-1 ;// это 0xFFFFFFFF ?
je @ok ;// да - на выход!
add esi,4 ;// нет - сл.дворд в стеке
jmp @b ;// на повтор..
@ok: add esi,4 ;// okey - прыгаем к указателю
mov [esi],mySeh ;// подменить его на свой обработчик!
Если не знать всей предыстории, то теперь сложно будет догадаться, что именно делает данный участок кода, ведь манипуляции с регистром
FS отсутствуют как таковые. В общем способ не плохой, но можно по аналогичной схеме копнуть ещё глубже.4. Перехват системного обработчика исключений.
Если реверсер продвинутый, он сразу обнаружит невалидный указатель в системном SEH-фрейме, который должен смотреть в либу Ntdll.dll, в то время как у нас он сейчас нацелен на обработчик в области памяти нашего-же процесса. Кстати дефолтная функция системы в Ntdll.dll называется
ExceptHandler(), и как видно по значению в стеке, она расположена по адресу 0x777c4dcd. Что примечательно, отладчик x64Dbg ничего не знает про неё (в окне маячит какое-то левое имя), а вот ядерный WinDbg уже в курсе всех событий. Обратите внимание, что в обоих SEH-фреймах одинаковые значения, а имена функций разные:Ладно, оставим этот нюанс за бортом, а сами посмотрим на содержимое системного обработчика. Как видим это типичная API с прологом и эпилогом, а поскольку мы подрядись её перехватить, то указатель в SEH-фрейме останется уже прежний, что снимет с нас все подозрения. Правда для этого нужно будет сначала добавить атрибут записи в страницу Ntdll.dll (т.к. в дефолте стоит page_execute_read), прописать в пролог указатель на свой обработчик исключений, после чего восстановить прежние атрибуты на место.
Код:
0:000:x86> !address 777с4dcd
Usage: Image
Allocation Base: 77720000
Base Address: 77730000
End Address: 77807000
Region Size: 000d7000
Type: 01000000 MEM_IMAGE
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ <---------//
;//-----------------------------------
0:000:x86> uf 777c4dcd
ntdll32!_except_handler4:
777c4dcd 8bff mov edi,edi
777c4dcf 55 push ebp
777c4dd0 8bec mov ebp,esp
777c4dd2 83ec14 sub esp,14h
777c4dd5 53 push ebx
777c4dd6 8b5d0c mov ebx,dword ptr [ebp+0Ch]
777c4dd9 56 push esi
777c4dda 8b7308 mov esi,dword ptr [ebx+8]
777c4ddd 333588207277 xor esi,dword ptr [ntdll32!__security_cookie]
777c4de3 57 push edi
777c4de4 8b06 mov eax,dword ptr [esi]
777c4de6 c645ff00 mov byte ptr [ebp-1],0
777c4dea c745f801000000 mov dword ptr [ebp-8],1
777c4df1 8d7b10 lea edi,[ebx+10h]
777c4df4 83f8fe cmp eax,0FFFFFFFEh
777c4df7 0f854fe50100 jne ntdll32!_except_handler4+0x2c
.....
Библиотека Ntdll.dll у каждого процесса своя, т.к. система проецирует/мапит её в каждый процесс отдельно. Поэтому фактически мы будем править код Ntdll только своей либы. Пока юзер не осуществляет запись в пространство системных dll, всё идёт в штатном режиме. Но при попытки записи тут-же включается механизм CoW (CopyOnWrite), и ядро оси создаёт копию своей библиотеки, чтобы эта запись не затронула оригинал. Вот пример реализации такого варианта установки пользовательских SEH:
C-подобный:
;//.....
mov esi,esp ;// ESI = стек
@@: cmp dword[esi],-1 ;// это 0xFFFFFFFF ?
je @ok ;// да - на выход!
add esi,4 ;// нет - сл.дворд в стеке
jmp @b ;// на повтор..
@ok: add esi,4 ;// okey - прыгаем к указателю
mov eax,[esi] ;// EAX = линк на системный обработчик
push eax eax
;// Добавить атрибут WRITE к странице Ntdll.dll
invoke VirtualProtect,eax,4096,\
PAGE_EXECUTE_READWRITE,\
oldFlags
call @f
push mySeh ;// кодируем в опкодах переход
ret ;// всего = 6 байт
@@: pop esi edi ;// перехват функции!
mov ecx,6
rep movsb
;// Восстановить атрибут страницы Ntdll.dll
pop eax
invoke VirtualProtect,eax,4096,[oldFlags],oldFlags
;//.....
Теперь в момент любого эксепшена в нашем приложении, управление получит кастомный обработчик, внутри которого мы должны сохранить контекст всех регистров уже знакомой функцией
GetThreadContext(), и обработав должным образом ошибку, прописать в структуре контекста новое значение регистра EIP, чтобы код продолжил исполнение в обычном режиме. Для восстановления контекста регистров предусмотрена функция SetThreadContext().5. Заключение
Здесь мы рассмотрели несколько вариантов скрытия регистра
FS с радаров дизассемблера и отладчика при установки пользовательских SEH. Надеюсь в природе существуют ещё аналогичные финты, и если вы можете предложить что-то своё, хотелось-бы взять их на вооружение. Удачи всем в исследованиях, пока!Ссылки по теме:
SEH – фильтр необработанных исключений - Форум информационной безопасности - Codeby.net
Самотрассировка, или марш-бросок по периметру отладчика
ASM – Динамическое шифрование кода - Форум информационной безопасности - Codeby.net