HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2

ANTICHAT — форум по информационной безопасности, OSINT и технологиям

ANTICHAT — русскоязычное сообщество по безопасности, OSINT и программированию. Форум ранее работал на доменах antichat.ru, antichat.com и antichat.club, и теперь снова доступен на новом адресе — forum.antichat.xyz.
Форум восстановлен и продолжает развитие: доступны архивные темы, добавляются новые обсуждения и материалы.
⚠️ Старые аккаунты восстановить невозможно — необходимо зарегистрироваться заново.
Вернуться   Форум АНТИЧАТ > БЕЗОПАСНОСТЬ И УЯЗВИМОСТИ > Безопасность и Анонимность > Защита ОС: вирусы, антивирусы, файрволы.
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 08.02.2026, 19:09
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
Провел на форуме:
145166

Репутация: 0
По умолчанию

Windows позволяет нам устанавливать свои юзер-обработчики исключений, которые реализованы чз механизм SEH на системах х32 (Structured Exception Handler, структурный), и VEH на х64 (векторный). Тема давно заезжена вдоль и поперёк, а потому не будем в очередной раз мусолить её от и до - здесь хотелось-бы обсудить другой вопрос. Как в дизасм-листинге спрятать обращение к сегментному регистру
Код:
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
+
90
h
]
;
// читаем 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   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 – фильтр необработанных исключений - Форум информационной безопасности - античат
Самотрассировка, или марш-бросок по периметру отладчика
ASM – Динамическое шифрование кода - Форум информационной безопасности - античат
 
Ответить с цитированием

  #2  
Старый 08.02.2026, 23:30
Ахимов
Новичок
Регистрация: 21.12.2025
Сообщений: 0
Провел на форуме:
0

Репутация: 0
По умолчанию

push KGDT_R3_(CM)TEB/pop ds/fetch ds:[TEB.disp] -> fs to ds ?
 
Ответить с цитированием

  #3  
Старый 09.02.2026, 18:42
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
Провел на форуме:
145166

Репутация: 0
По умолчанию

Цитата:

Ахимов сказал(а):

push KGDT_R3_(CM)TEB

что-то я плохо понял эту схему - какая связь между KGDT и ТЕВ?
кстати для чтения LDT есть
Код:
GetThreadSelectorEntry()
, и если передать ей
Код:
FS=53h
, можно получить весь дескриптор. Это так.. к слову, поскольку записать этот дескриптор в кэш сегментных всё-равно не получится.
 
Ответить с цитированием

  #4  
Старый 09.02.2026, 19:42
Ахимов
Новичок
Регистрация: 21.12.2025
Сообщений: 0
Провел на форуме:
0

Репутация: 0
По умолчанию

Marylin

Схема такая - меняем fs на ds например, загружаем в него селектор teb(= kgdt_*, мы используем константы из сурков nt). Можно и так push fs/pop ds.

kitrap08.blogspot.com/2010/12/blog-post.html?m=1
 
Ответить с цитированием
Ответ





Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.