HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   ANTICHAT > БЕЗОПАСНОСТЬ И УЯЗВИМОСТИ > Электроника и Фрикинг
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 13.12.2019, 17:28
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
С нами: 3526561

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

Как и всё, что работает под управлением центрального процессора, операционная система – это тоже программа, которая на этапе тестирования требовала отладки. Поскольку системное пространство памяти в Win логически разделено на два региона, инженерам требовались технические люки из юзера в кернел. После того-как всё было (типа)настроено, окончательно избавляться от этих туннелей было уже поздно – пришлось-бы опять переписывать львиную долю кода, что совсем не вдохновляло разработчиков. Поэтому лазейки просто прикрыли фиговым листом в надежде "авось прокатит"..

Это касается таких механизмов как TRAP-бит в регистре флагов Eflags, привелегия Debug в пользовательском режиме, регистры отладки DR0-DR7 и несколько модельно-специфичных регистров MSR. В результате не только админ, но и обычный юзер получил в награду аппаратные рычаги дебага, не воспользоваться которыми было-бы грех – нужно просто отключив шаблонное мышление обратить взор чуть дальше собственного носа.

В статье рассматриваются несколько простых фишек, которые помогут нам применить в своих программах довольно могучий защитный приём, с убойным радиусом поражения – это код, который будет трассировать сам-себя, но не будет дебажиться в отладчике.
----------------------------------------

Debug в реальном режиме

Начнём с того, что отладка – неотъемлемая часть программирования, и фактически поддерживается на аппаратном уровне через флаг трассировки процессора TRAP-Flag. Манипулируя битами регистра EFLAGS, программист может оперировать флагом TF как в реальном, так и в защищённом режиме работы процессора, и в обоих случаях на единичное его состояние CPU реагирует исключением #DB – Debug Breakpoint. Более того, в независимости от текущего режима, за обработку этого исключения отвечает прерывание INT-1.

Обработчик INT-1 в реальном режиме представляет из себя шунт-заглушкуIRET (Interrupt Return), по типу зашёл/вышел без каких-либо действий. По этой причине, без отладчика от взведённого флага TF программам реального режима "не холодно, не жарко". В качестве демонстрации я загрузился под чистым DOS, и в отладчике авера KAV AVPUtil дизассемблировал INT-1 (см.в меню Alt+U):



Пусть вас не смущает здесь устаревший термин BIOS.. для обратной совместимости он эмулируется всеми современными EFI. Кстати, если запустить виндовый debug, то перед нами предстанет совсем иная картина. Дело в том, что Win32 эмулирует DOS в режиме процессора V86(virtual), и соответственно обработчики у него свои, а не чисто-досовские. Например на 32-битных системах можно запустить debug.com прямо из виндовой консоли и командой
Код:
–d 0:0
просмотреть таблицу векторов прерываний IVT (Interrupt Vector Table) – дворд по смещению (4) и будет вектором INT-1 (после нулевого). Теперь командой
Код:
–u 0070:018b
дизассемблируем этот адрес и получим такой код:

Код:


Код:
C:\> debug  ;// векторы:   INT-1                     INT-3
-d 0:0      ;//         -----------               -----------
0000:0000   68 10 A7 00 8B 01 70 00 - 16 00 98 03 8B 01 70 00   h.....p.......p.
0000:0010   8B 01 70 00 B9 06 0E 02 - 40 07 0E 02 FF 03 0E 02   ..p.....@.......
0000:0020   46 07 0E 02 0A 04 0E 02 - 3A 00 98 03 54 00 98 03   F.......:...T...
0000:0030   6E 00 98 03 88 00 98 03 - A2 00 98 03 FF 03 0E 02   n...............
-
-u 0070:018b
0070:018B   1E            PUSH    DS
0070:018C   50            PUSH    AX
0070:018D   B84000        MOV     AX,0040
0070:0190   8ED8          MOV     DS,AX
0070:0192   F70614030024  TEST    WORD PTR [0314],2400   ;// тест поля 0040:0314 на режим V86
0070:0198   754F          JNZ     01E9                   ;// выйти если нет!
0070:019A   55            PUSH    BP
0070:019B   8BEC          MOV     BP,SP
0070:019D   8B460A        MOV     AX,[BP+0A]      ;// иначе: AX = значение регистра FLAGS
0070:01A0   5D            POP     BP
0070:01A1   A90001        TEST    AX,0100         ;// проверить в нём бит(8) Trap-flag
0070:01A4   7543          JNZ     01E9            ;// выйти, если TF=1 (не нуль)
;//~~~~~~~~~~~~~~
0070:01E9   58            POP     AX
0070:01EA   1F            POP     DS
0070:01EB   CF            IRET                    ;// Interrupt Return
Таким образом, если мы хотим написать свой отладчик реального режима, то должны перехватить прерывание INT-1, и в своём обработчике организовать вывод на экран состояния регистров процессора и прочую служебную информацию. Процессор будет ждать от нашего обработчика инструкцию IRET, после чего наткнётся на следующую инструкцию в коде и опять сгенерит исключение #DB – круг замкнётся.. Так будет продолжаться до тех пор, пока мы принудительно не сбросим флаг TF в нулевое состояние.

Флаг трассировки в защищённом режиме процессора

После того как мастдай получает управление от загрузчика (NTLDR в win-xp, или BOOTMGR в win7+) и отправляет на покой реальный режим, картина координально меняется. Теперь уже нету таблицы векторов-прерываний IVT в том смысле, который вкладывает в неё DOS – ей на замену Win выстраивает свою таблицу IDT – Interrupt Dispatch Table (таблица диспетчеризации прерываний) со-своими обработчиками. В защищённом режиме, термин "обработчик" переименовали в ISR – Interrupt Service Routine (подпрограммы сервисов) и делятся они на два типа – "ловушка" (trap)для отлова исключений, и "шлюзы" процедур обработки прерываний.

Размер и адрес таблицы IDT хранится в регистреIDTR текущего процессора – с пользовательского уровня этот регистр доступен только для чтения инструкцией SIDT (store IDT). В отличии от остальных регистров, размер его 6-байт, где первые 2-байта определяют размер(лимит) таблицы, а следующие 4-байта – базу таблицы в ядерной памяти. Сама таблица IDT занимает один сегмент виртуальной памяти и состоит из 256 8-байтных дескрипторов – в каждом дескрипторе хранятся флаги доступа и указатель на ISR конкретного прерывания. Код ниже демонстрирует вариант чтения и вывода на консоль значения регистра IDTR:

C-подобный:


Код:
format   pe console
include
'win32ax.inc'
entry    start
;
--
--
-
.
data
capt     db
13
,
10
,
' IDT info v0.1'
db
13
,
10
,
' *********************'
db
13
,
10
,
' Base...: 0x%08X'
db
13
,
10
,
' Limit..: 0x%04X'
,
0
value    dd
0
,
0
frmt     db
'%s'
,
0
;
--
--
-
.
code
start
:
mov     ebx
,
value
;
// EBX = адрес приёмника
sidt
[
ebx
]
;
// Store_IDTR по адресу
movzx   eax
,
word
[
value
]
;
// EAX = 2-байтный лимит таблицы
mov     ebx
,
dword
[
value
+
2
]
;
// EBX = сл.4-байта = адрес таблицы
cinvoke  printf
,
capt
,
ebx
,
eax
;
// выводим на консоль!
@exit
:
cinvoke  scanf
,
frmt
,
frmt
+
2
cinvoke  exit
,
0
;
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
section
'.idata'
import data readable
library  msvcrt
,
'msvcrt.dll'
import   msvcrt
,
printf
,
'printf'
,
scanf
,
'scanf'
,
exit
,
'exit'


Отладчик ядерного уровня WinDbg, на команду
Код:
!idt –a
отзывается более информативным сообщением. Отметим, что системы класса Win обрабатывают не все 256 прерываний
Код:
INT 0x00..0xFF
– большая часть из них вообще необрабатываемые "Unexpected_Interrupt" и попадают они прямиком в цепкие лапы диспетчера исключений, который тупо проглатывает их, даже не оповещая об этом юзера:



Во-всей этой кухне, важным для нас моментом является то, что отладчики пользовательского уровня вообще не используют аппаратный флаг TF для своей работы, соответственно и прерывание INT-1 остаётся не при делах. По текущему указателю регистра EIP они вставляют только программные бряки INT-3 с однобайтным опкодом
Код:
0xCC
. То-есть такие отладчики как "OllyDbg", на каждом шаге запоминают байт по указателю EIP, и на его место записывают INT-3. Так-что обычной проверкой своей контрольной суммы можно обнаружить факт отладки приложения.

Самотрассировка – идея фикс..

В общем случае мы пришли к тому, что поведение программы со-взведённым флагом TF напрямую зависит от окружающей обстановки. Отладчики любого цвета и ориентации маскируют TF, перехватывая обращения к нему с любого уровня. Зато в реальной ситуации и без отладчика, при взведённом TF процессор моментально генерит исключение #DB и если оно не обрабатывается пользовательским SEH-фреймом, то диспетчер-исключений тупо прибивает процесс, посчитав его глючным.

В 32-битном регистре флагов EFLAGS, бит TF занимает позицию (8), однако мы не можем модифицировать его напрямую и приходиться осуществлять это действие через стек в три этапа – запомнить, чекнуть, восстановить. Вот несколько нехитрых способов установки флага TF в единицу:

C-подобный:


Код:
;
// (1) Модификация через EAX и BTS (bit select)
;
//---------------------------------------------
pushfd
;
// запомнить регистр EFLAGS в стеке
pop     eax
;
// снять его в регистр EAX
bts     eax
,
8
;
// взвести в EAX бит 8 = TF
push    eax
;
// затолкать его опять в стек
popfd
;
// перезаписать EFLAGS
;
// (2) Взводим TF через OR AH
;
//-----------------------------------------
pushfd
;
// EFLAGS в стек
pop     eax
;
// EAX = EFLAGS
or      ah
,
1
;
// взвести в AH бит 1 (AH биты 15-8, AL биты 7-0)
push    eax
;
// затолкать его опять в стек
popfd
;
// перезаписать EFLAGS значением EAX
;
// (3) Прямой мод в стеке
;
//-------------------------
pushfd
  or      dword
[
esp
]
,
0x100
;
// взвести бит 8 по адресу ESP
popfd
Если в этот момент наш процесс не отлаживается, то после выполнения следующей за
Код:
POPFD
инструкции, системный инквизитор тут-же выстрелит в нас окном где сказано, что это необрабатываемое "Unknown" исключение, и что наше приложение больше не жилец. На самом деле система знает, что это Single-Step, просто обрабатывать его не видит причин – кому надо, тот пусть и занимается этим:



Если у кого-то это окно и вызывает отвращение, то нам оно только радует глаз. Самотрассировка - self-Debug - подразумевает установку SEH-обработчика на это исключение, с последующей модификацией флага TF. Внутри обработчика мы можем вытворять со-своим кодом всё-что пожелает наша душа, и это будет абсолютно прозрачно для отладчика! Поскольку он постоянно фиксит флаг TF и обнаружив сразу-же сбрасывает его в нуль, то соответственно и исключения Single-Step под отладчиком не возникает. В результате, пользовательский SEH не получит уже управления, и Оля пойдёт топтать совсем не ту тропинку,.

Нужно сказать, что Win32-отладчики поглащают только отладочное исключение #DB, а остальные исправно отлавливают, передавая управление SEH-фрейму юзера. Так-что представленная на суд идея имеет право на жизнь только при взведённом флаге TF с прерыванием INT-1:



Оригинальное мнение на счёт взведённого флага TF имеет первая версия отладчика OllyDbg, которая напрочь отказывается трассировать весь последующий код, и позорно капитулирует сообщением типа: "Я не в курсе, как реагировать на команду по этому адресу. Попробуйте установить точку-останова на следующей команде". Если внять совету отладчика и установить по F2 бряк (в данном случае) на адрес
Код:
0х0040204В
, то ситуацию это всё-равно не спасет и исключения #DB не произойдёт в любом случае, а значит и наш SEH не получит управления. Вот и попробуй разберись с этой мутью..



Детали реализации кода

Чтобы воплотить идею в жизнь, рассмотрим некоторые её нюансы..
Значит установка пользовательского SEH-обработчика и флага TF в единицу, позволяют перехватить исключение #DB в неотлаживаемом процессе и получить контекст всех регистров CPU. (теме SEH была посвященаотдельная статья). Чтобы лишним стековым SEH-фреймом не привлекать внимание взломщика, хорошей идеей будет не расширять цепочку обработчиков, а модифицировать уже имеющийся системный фрейм с маркером -1, просто подсунув ему указатель на свой обработчик (мы ведь не планируем финализировать свою прожку). В этом случае, в стеке будут маячить уже не два фрейма, а дефолтный один. Пример такой махинации представлен ниже:

C-подобный:


Код:
;
// Вставляем в системный SEH-фрейм свой обработчик
;
//------------------------------------------------
sub     ebx
,
ebx
;
// EBX = 0
push    dword
[
fs
:
ebx
]
;
// указатель на SEH в стек (fs:0)
pop     ebp
;
// снимаем его в EBP
add     ebp
,
4
;
// смещаемся к обработчику в SEH-фрейме
mov     dword
[
ebp
]
,
new_Seh
;
// записать туда свой адрес!


Опытный глаз конечно-же сразу обнаружит подвох с SEH-фреймом, ведь финальный обработчик системы с терминальным
Код:
0хFFFFFFFF
находится в kernel32.dll и не может указывать на наш код. Но задурманить таким приёмом мозги пионерам–крэкерам вполне возможно. Причём в данном случае я поместил в стек указатель на фрейм в начале кода, а осуществляю подмену чуть позже (желательно делать это вообще в другой ветке).

На рисунке так-же видно, что вторая версия отладчика OllyDbg категорически не хочет выставлять флаг TF в единицу, и комбинация перезаписи регистра EFLAGS инструкциями
Код:
push eax/popfd
здесь не срабатывает – флаги как были
Код:
0х206
, так и остались.

Ещё следует обратить внимание на то, что в отличии от реального режима, в защищённом система сбрасывает флаг TF после каждого стэпа, поэтому внутри обработчика его нужно взводить по-новой, чтобы после исполнения очередной инструкции SEH-обработчик опять получил управление. Отметим, что исключение #DB процессор генерит не НА текущей инструкции, а уже ПОСЛЕ её исполнения. Такой алгоритм процессора освобождает нас от постоянной коррекции регистра EIP внутри обработчика – достаточно только взводить TF на каждом шаге, а EIP сам будет скакать по нужным инструкциям.

Чтобы убедиться в этом, можно написать тестовый код, который взведёт флаг TF и из регистрового контекста SEH, выведет на экран состояние регистров
Код:
EIP
и
Код:
EFLAGS
. На рисунке ниже, окно переднего плана было запущено под реальным процессором без отладчика, а в отладчике – я тупо просмотрел, куда указывает регистр EIP. Здесь видно, что если SEH-обработчик не спит и из его трубы идёт дым, то он получает управление уже после выполнения инструкции
Код:
JMP_SHORT
, и флаг TF в структуре CONTEXT оказался опять сброшен:



Практическая часть

Приведённый ниже пример демонстрирует эту технику. Я старался сделать его максимально понятным, и убрал из него всё лишнее. Здесь обычная проверка пароля, код которой изначально находится в секции-данных программы, от куда потом копируется в выделенную память SEH-обработчиком. По окончанию, на этот код SEH сразу-же передаёт управление. Алгоритм будет работать только в неотлаживаемом процессе, при взведённом флаге TF. Пароль на валидность проверяется по его хэш-сумме, инклуд "except.inc" предоставляет доступ к структуре CONTEXT процессора, и прикреплён скрепкой в подвале темы(нужно положить его в дир "fasm\include\.."):

C-подобный:


Код:
format   pe console
include
'win32ax.inc'
include
'except.inc'
;
//<--- смотри скрепку
entry    start
;
//-----
.
data
capt     db
13
,
10
,
' TRAP_Crackme v0.1'
db
13
,
10
,
' *************************'
db
13
,
10
,
' Type pass: '
,
0
okey     db
13
,
10
,
' Pass OK!'
,
0
wrong    db
13
,
10
,
' Pass WRONG!'
,
0
pass     dw
'T'
+
'R'
+
'A'
+
'P'
+
'f'
+
'l'
+
'a'
+
'g'
;
// хэш валидного пароля (0x02d1)
frmt     db
'%s'
,
0
len
=
endCheckPass
-
CheckPass
;
// длина продуры проверки
;
//=== Процедура проверки пароля, по его хэшу =============
;
// позже скопируем её в выделенную область памяти. =======
CheckPass
:
;
//<--- маркер начала.
mov     esi
,
buff
;
// ESI = адрес введённого юзером пароля
and     ebx
,
0
;
// EBX =0
mov     eax
,
ebx
;
// EAX =0
@
01
:
lodsb
;
//  AL = очередной байт из ESI
add     bx
,
ax
;
// считать сумму в регистр ВХ
or      al
,
al
;
// проверка AL на нуль
jnz     @
01
;
// повторить, если не нуль..
mov     eax
,
okey
;
// иначе: EAX = адрес мессаги "OK!"
cmp     bx
,
[
pass
]
;
// сравнить хэш юзера с валидным хэшем.
je      @prn
;
// если равно..
mov     eax
,
wrong
;
// иначе: заменить EAX на "Wrong!"
@prn
:
cinvoke  printf
,
eax
;
// мессагу на консоль
push    @exit
;
// дальний (far) адрес перехода
ret
;
// перейти на него!!!
endCheckPass
:
;
//<--- маркер конца блока.
;
//========================================================
buff     db
?
;
// буфер для юзерского пароля,
;
// простилается до конца секции-данных,
;
// так-что переполнение scanf() нам не грозит.
;
//-----
.
code
start
:
pushfd
;
// флаги в стек
sub     ebx
,
ebx
;
// EBX =0
push    dword
[
fs
:
ebx
]
;
// запомнить указатель на системный SEH
cinvoke  printf
,
capt
;
// запрос на ввод пароля
cinvoke  scanf
,
frmt
,
buff
;
//  .........^^^
;
// Вставляем в системный SEH-фрейм свой обработчик
pop     ebp
;
// EBP = линк на него
add     ebp
,
4
;
// смещаемся к адресу обработчика
mov     dword
[
ebp
]
,
new_Seh
;
// мод системного SEH-фрейма!
;
// Взводим флаг TF, и если процесс не отлаживается,
;
// то управление примет наш обработчик исключений SEH.
or      dword
[
esp
]
,
0x100
;
// взвести бит (8) в стеке
popfd
;
// перезапись регистра EFLAGS
nop
;
//
xchg    eax
,
eax
;
// NOP, чтоб сгенерить исключение #DB
cinvoke  printf
,
wrong
;
// если под отладчиком, то "Wrong_Pass"
@exit
:
cinvoke  scanf
,
frmt
,
frmt
+
2
;
// конец программы!
cinvoke  exit
,
0
;
//
;
//~~~~~~ Пользовательский SEH-обработчик ~~~~~~~~~
;
// Выделяет память с атрибутами RWEx,
;
// копирует туда процедуру проверки пароля из секции-данных,
;
// и передаёт на неё управление. ~~~~~~~~~~~~~~~~~
align
256
;
// отделить SEH от полезного кода
new_Seh
:
invoke  VirtualAlloc
,
0
,
0x1000
,
0x3000
,
0x40
;
// выделить Execute-память
push    eax
;
// запомнить указатель на неё
mov     edi
,
eax
;
// EDI = приёмник (выделенная память)
mov     esi
,
CheckPass
;
// ESI = источник
mov     ecx
,
len
;
// ECX = длина копируемого блока
rep     movsb
;
// скопировать из ESI в EDI !!!
pop     eax
;
// EAX = указатель на начало кода
mov     esi
,
[
esp
+
12
]
;
// ESI = линк на контекст регистров
mov
[
esi
+
CONTEXT
.
regEip
]
,
eax
;
// ставим EIP на начало кода-проверки
xor     eax
,
eax
;
// команда "ребут контекста" диспетчеру
ret
;
// выйти из SEH-обработчика!!!
;
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
section
'.idata'
import data readable
library  msvcrt
,
'msvcrt.dll'
,
kernel
,
'kernel32.dll'
import   msvcrt
,
printf
,
'printf'
,
scanf
,
'scanf'
,
exit
,
'exit'
import   kernel
,
VirtualAlloc
,
'VirtualAlloc'
Для наглядности, здесь я упростил задачу до максимума, и внутри SEH сбрасываю в выделенную память весь код по одному/единственному исключению #DB. Но гораздо интересней залезть под капот алгоритма и исполнять код-проверки буквально на каждом шаге процессора, увеличив тем-самым скважность исключений #DB. Во-первых это на порядок усложнит взлом, а во-вторых – добавит нам скилла.

Организовать это дело совсем не сложно и если кого заинтересует этот приём, мы можем осуществить его вместе. Нужно всего-то добавить в процедуру проверки-пароля "флаг окончания", и внутри SEH-обработчика проверять его на единицу. Если индикатор = нуль, значит взводим TF в регистровом контексте и продолжаем трэйс (менять больше ничего не надо). Соответственно, если "флаг окончания теста" =1, значит оставляем TF сброшенным и заканчиваем трэйс.

Заключение (в хорошем смысле слова)

Здесь мы рассмотрели только основную идею самотрассировки в надежде, что это послужит генератором идей. В этом направлении, наши возможности ограничивает только фантазия – например, можно озадачить SEH распаковкой или декриптором зашифрованного кода, жонглировать контекстом любых регистров, вплоть до отладочных DR0-DR7 (которые для юзера считаются привилегированными), и многое другое. А всё потому, что в брачном периоде, парнями из Microsoft была заброшена капсула, которая со-временем сыграла злую шутку с отладчиками прикладного уровня – они доверяли флагу TF на все 100%, а он оказался хитрым на все 200. До скорого..
 
Ответить с цитированием

  #2  
Старый 13.12.2019, 19:41
Tony
Новичок
Регистрация: 11.10.2006
Сообщений: 10
С нами: 10306800

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

Наконец-то) Я уж думал творческий запой у Marylin)
 
Ответить с цитированием

  #3  
Старый 13.12.2019, 22:04
Hardreversengineer
Новичок
Регистрация: 20.08.2019
Сообщений: 0
С нами: 3544291

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

Я аж всплакнул, думал не дождусь.
Как же мне нравятся вставки кода в статье)
 
Ответить с цитированием

  #4  
Старый 13.12.2019, 22:07
Aleks Binakril
Новичок
Регистрация: 04.12.2019
Сообщений: 0
С нами: 3391664

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

Увлекательная статья, автор подскажите а у вас есть такие же идеи по написанию статей по анпакингу
 
Ответить с цитированием

  #5  
Старый 14.12.2019, 10:45
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
С нами: 3526561

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

@Aleks Binakrilпросьба в комментах оставлять только то, что касается данной темы - остальные вопросы в личку.
 
Ответить с цитированием

  #6  
Старый 15.12.2019, 10:47
Mikl___
Новичок
Регистрация: 14.12.2019
Сообщений: 0
С нами: 3377258

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

Marylin,
большое спасибо за статью!
 
Ответить с цитированием
Ответ



Предыдущая тема Следующая тема

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


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




ANTICHAT ™ © 2001- Antichat Kft.