Copy Link
Add to Bookmark
Report
5x02 Loader from ring0
Loader iz ring0
S verom u Boga, deroko/ARTeam
1. Uvod
2. Potrebno znanje
3. Realizacija
4. Zakljucak
1. Uvod /------------------------------------------------------------
-------/
Zasto uopste pisati ring0 loader? Iz zabave svakako. Prednost
koje ring0 loader ima je brzina. Takodje ring0 loader moze samo da radi
kado DebugLoader buduci da moramo nekako da signaliziramo ring0 kodu da
bas na odredjenoj adresi hocemo da nesto patchujemo. crackme koji cemo
ovde koristiti je obican dialog sa NAG screenom koga cemo da ubijemo iz
ring0. Razlog sto koristim ASPack je vise nego prost, ASPack se lako
otpakuje, a poenta ovog teksta je ring0 loader...
2. Potrebno znanje /-------------------------------------------------
------------------/
Prvo moramo znati kako uopste radi debugger ali sa stanovista
ring0 buduci da se cela logika debuggera u ring3 zasniva na EVENT-ovima
i WaitForDebugEvent i ContinueDebugEvent. Debugger koristi nekoliko
interupta da bi uopste mogao da radi. Oni su po IA32 manualu odredjeni
kao vektori 1 i 3 pri cemu int 1 oznacava Single Step, a int 3 oznacava
BreakPoint. Takodje int 1 se generise ukoliko se dogodi neki od uslova
postavljen u dr0/3 registrima na nacin opisan u dr7 registru (execution,
read, write, byte, word, dword) no to nije trenutno bitno za nas buduci
da ce nam za nas rad trebati samo int 3h.
Kad god se u Debugovanom procesu desi neki Exception ring0 kod
dobija kontrolu preko nekoliko IDTove:
:idt
Int Type Sel:Offset Attributes Symbol/Owner
IDTbase=8003F400 Limit=07FF
0000 IntG32 0008:804D8BFF DPL=0 P _KiTrap00
0001 IntG32 0008:F03FA760 DPL=0 P icextension!.text+62E0
0002 TaskG 0058:00000000 DPL=0 P _KiTrap02
0003 IntG32 0008:F03F9FB0 DPL=3 P icextension!.text+5B30
0004 IntG32 0008:804D92E0 DPL=3 P _KiTrap04
0005 IntG32 0008:804D9441 DPL=0 P _KiTrap05
0006 IntG32 0008:804D95BF DPL=0 P _KiTrap06
0007 IntG32 0008:804D9C33 DPL=0 P _KiTrap07
0008 TaskG 0050:00000000 DPL=0 P _KiTrap08
0009 IntG32 0008:804DA060 DPL=0 P _KiTrap09
000A IntG32 0008:804DA185 DPL=0 P _KiTrap0A
000B IntG32 0008:804DA2CA DPL=0 P _KiTrap0B
000C IntG32 0008:804DA530 DPL=0 P _KiTrap0C
000D IntG32 0008:804DA827 DPL=0 P _KiTrap0D
000E IntG32 0008:804DAF25 DPL=0 P _KiTrap0E
000F IntG32 0008:804DB25A DPL=0 P _KiTrap0F
0010 IntG32 0008:804DB37F DPL=0 P _KiTrap10
Normalno softice u outputu maskira koje je IDTove hookovao da se
posteni ljudi ne bi zbunili:
0000 IntG32 0008:F05B6A2E DPL=0 P NTice!.text+0008A6AE
0001 IntG32 0008:F03FA760 DPL=0 P icextension!.text+62E0
0002 IntG32 0008:F060AF97 DPL=0 P NTice!.data+9297
0003 IntG32 0008:F03F9FB0 DPL=3 P icextension.text+5B30
0004 IntG32 0008:804D92E0 DPL=3 P _KiTrap04
0005 IntG32 0008:804D9441 DPL=0 P _KiTrap05
0006 IntG32 0008:F060AFA6 DPL=0 P NTice!.data+92A6
0007 IntG32 0008:804D9C33 DPL=0 P _KiTrap07
0008 TaskG 0050:00001178 DPL=0 P
0009 IntG32 0008:804DA060 DPL=0 P _KiTrap09
000A IntG32 0008:804DA185 DPL=0 P _KiTrap0A
000B IntG32 0008:804DA2CA DPL=0 P _KiTrap0B
000C IntG32 0008:F060AFB5 DPL=0 P NTice!.data+92B5
000D IntG32 0008:F060AFC4 DPL=0 P NTice!.data+92C4
000E IntG32 0008:F060AFD3 DPL=0 P NTice!.data+92D3
000F IntG32 0008:804DB25A DPL=0 P _KiTrap0F
0010 IntG32 0008:804DB37F DPL=0 P _KiTrap10
Ako pazljivo pogledate vidite da je SoftICE hookovao poprilican
deo IDTa. Zasto?
Prosto debugger mora da uhvati exception i da ga procesira, dok
ring3 debugger to cini preko Debug Porta i biva signaliziran
preko raznih _KiTrapXX, SoftICE exceptione hvata odmah i procesira
ih preko svojeg koda kako bi utvrdio da li je exception odigran
pod uslovima kad treba SoftICE da reaguje ili pak kad se exception
prosledjuje defaul handleru u IDTu.
E bas to cemo i mi uciniti, mi cemo hvatati exceptione iz ring0
i odlucivati da li je rec o nasim uslovima i to cemo procesirati
ili pak cemo izvrsavanje predati default handleru.
Da bismo uospte hookovali IDT moramo da znamo kako da dodjemo do njega.
Adresu IDT-a dobijamo preko instrukcije sidt na sledeci nacin:
<++>
.data
idttable dq ?
.code
sidt fword ptr[idttable]
mov eax, dword ptr[idttable+2]
<++>
sidt radi prostu stvar daje nam LIMIT i VirtulenuAdresu IDT-a, u sledecem
formatu:
+---------+---------------------+
| LIMIT | Virtuelna Adresa |
+---------+---------------------+
0 15 16 47
Takodje ima jos jedan elgantniji nacin koji sam naso u themida protektoru,
kako bi se izbeglo koriscenje promenljivih:
push edi
sidt fword ptr[esp-2]
pop edi
Buduci da sidt zahteva 6 bajtova da stavi svoje podatke, to je slucaj veoma
prost, pravimo mesto na stacku i stavljamo podatke iz sidt na [esp-2]:
Stack:
[EDI] push edi
sidt fword ptr[esp-2]
Izgleda stacka:
[EDI] [Virtuelna Adresa] <---- ESP pokazuje ovde
[dummy] [LIMIT]
prostim pop edi dobijamo virtuelnu adresu IDTa.
Ok posto imamo IDT adresu red je pocnemo malo da hookujemo IDT. IDT
predstavlja tabelu gde je svaki handle predtsavljen sa po 8 bajtova, a
struktura koja opisuje svaki handlde u IDT izgleda ovako:
31 16 15 13 12 8 7 5 4 0
+----------------+---+-----+-----------+------+---------+
| Offset 31..16 | P | DPL | 0 D 1 1 1 | 0 0 0| |
+----------------+---+-----+-----------+------+---------+
31 16 15 0
+--------------------------+----------------------------+
| Segment Selector | Offset 15..0 |
+--------------------------+----------------------------+
Da bismo hookovali neki IDT trebamo prvo da znam koji hocemo da hookujemo:
U nasem slucaju to int 3h i to cinimo na sledeci nacin:
.data
idttable dq ?
.code
sidt fword ptr[idttable]
mov ebx, dowrd ptr[idttable+2]
lea eax, [ebx+3*8] ;gde je smesten int 3h?
mov cx, [eax+6] ;uzimamo High word
rol ecx, 16
mov cx, [eax] ;uzimamo Low word
Posle ove male operacije, ecx register ce imati Virtuelni Adresu handlea. To
moramo sacuvati pre hookovanja jer ukoliko nisu ispunjeni nasi uslovi, onda
moramo pozvati default handle, a ne da ubijemo kernel ili program =)
Posto sacuvamo nas handle dalje cemo ubaciti nas:
mov ecx, offset __mynewint3h
mov [eax], cx
rol ecx, 16
mov [eax+6], cx
i to je cela mudrost hookovanja idt-a.
Dalje sto moramo znati jeste kako da pisemo po memoriji gde god zelimo. Ceo
problem se ovde svodi na prosto uklanjane WriteProtectiona iz cr0 registra.
Rec je o bitu 16 doticnog registra, ukidanjem WP protekcije drajver moze bez
brige da pise gde god hoce.
mov eax, cr0
and eax, 0FFFEFFFFh
mov cr0, eax
Posle ovog mozete do mile volje da piste po ring0, ali pazite da slucajno ne
probate da piste po nepostojecoj memoriji, jer u suprotnom, BSOD.
Posto zavrsimo nase pisanje po memoriji moramo povratiti nazad cr0:
mov eax, cr0
or eax, 10000h
mov cr0, eax
Prosto zar ne?
Jos jedan uslov je ostao a to je da znamo kad je u pitanju nas proces, i kad
treba da procesiramo int 3h ubacen u ring3 kode?!?!
Postoje 2 nacina:
1. da pozovemo PsGetCurrentProcessId
2. da uzmemo cr3 register procesa i na osnovu njega da vrsimo
identifikaciju procesa
Disassembly od PsGetCurrentProcessId:
.text:0040E245 _PsGetCurrentProcessId@0 proc near
.text:0040E245 mov eax, large fs:124h
.text:0040E24B mov eax, [eax+1ECh]
.text:0040E251 retn
.text:0040E251 _PsGetCurrentProcessId@0 endp
Pre pozivanja ovog koda mi moramo da namestimo FS register na 30h,
jer FS register u ring0 pokazuje na KPCR strukturu:
kd> dt nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x01c SelfPcr : Ptr32 _KPCR
+0x020 Prcb : Ptr32 _KPRCB
+0x024 Irql : UChar
+0x028 IRR : Uint4B
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B
+0x034 KdVersionBlock : Ptr32 Void
+0x038 IDT : Ptr32 _KIDTENTRY
+0x03c GDT : Ptr32 _KGDTENTRY
+0x040 TSS : Ptr32 _KTSS
+0x044 MajorVersion : Uint2B
+0x046 MinorVersion : Uint2B
+0x048 SetMember : Uint4B
+0x04c StallScaleFactor : Uint4B
+0x050 DebugActive : UChar
+0x051 Number : UChar
+0x052 Spare0 : UChar
+0x053 SecondLevelCacheAssociativity : UCha
+0x054 VdmAlert : Uint4B
+0x058 KernelReserved : [14] Uint4B
+0x090 SecondLevelCacheSize : Uint4B
+0x094 HalReserved : [16] Uint4B
+0x0d4 InterruptMode : Uint4B
+0x0d8 Spare1 : UChar
+0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB
kd>
Ovo je jedan nacin, mada ja vise volim da koristim drugi nacin koji se
sastoji u promeni contexta procesa, tacnije sledeci set instrukcija ce
ponovo ucitati cr3 register datog procesa i time ce izmeniti PDE/PTE na
koji nacin cemo moci da postavimo prvi int 3h u kontekst naseg procesa.
Inace cr3 pokazuje na adresu PDE (Page Directory Table) i sadrzi fizicku,
a ne virtuelnu, memoriju na kojoj se nalazi PDE. Budci da svaki process
ima svoju posebnu memoriju to dalje znaci da cr3 mozemo da koristimo kao
neku vrstu Process ID-a na winNT sistemima.
Da bi smo ovo izveli moramo da koristimo 3 DDIja iz ntoskrnl.exe:
PsLookupProcessByProcessId
KeStackAttachProcess
KeStackDetachProcess
Prototipi:
PsLookupProcessByProcessId (PID, ptr EPROCESS)
KeStackAttachProcess(PEPROCESS, PTR KAPC_STATE)
KeUnstackDetachProcess(PTR KAPC_STATE)
Budici da 10tak liija koda govore vise nego ceo DDK i MSDN prelazimo na
kod odmah:
PsLookupProcessByProcessId ce nam vratiti PTR na EPROCESS strukturu procesa
ciji PID prosledimo ovom DDIju.
.data
eprocess dd ?
.code:
...
push offset eprocess
push pid
call PsLookupProcessByProcessId
Ako PsLookupProcessByProcessId ne uspe onda eax != 0, u suprotnom ako je eax = 0
sve je proslo bez problema, buduci da PsLookupProcessByProcessId uvecava reference
count u object_headeru, trebalo bi da pozovemo posle ObDereferenceObject, dajuci
mu eprocess strukturu kao argument:
push eprocess
call ObDereferenceObject
Sledece sto moramo pozvati je : KeStackAttachProcess kako bi PDE/PTE bili
reloadovani za context naseg procesa jer u suprotnom ne bi iz ring0 videli
memoriju procesa koji zelimo da crackujemo, vec bi videli memororiju naseg
loader.exe.
KeStackAttachProcess prima 2 argumenta a to su pointer na eprocess strukturu
i pointer na vec alociranu KAPC_STRUKTURU, doduse nije nam od znacaja sadrzaj
KAP_STATE strukture ali evo ga:
kd> dt nt!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY
+0x010 Process : Ptr32 _KPROCESS
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
kd>
Buduci da mi ovo strukturu necemo koristiti, mozemo slobnodno da lociramo buffer
duzine ove strukture i to da predamo KeStackAttachProcess, posto nabavimo nas
cr3 register onda cemo pozvati KeUnstackDetachProcess ovako:
<++>
.data
apcstate db 20h dup(0)
eprocess dd ?
.code
...
push offset eprocess
push pid
call PsLookupProcessByProcessId
test eax, eax
jnz __error
push eprocess
call ObDereferenceObject
push offset apcstate
push eprocess
call KeStackAttachProcess
mov eax, cr3
mov c_cr3, eax
<ovde ide ujedno i kod za patchovanje>
push offset apcstate
call KeUnstackDetachProcess
<++>
Jos jedan trik ovde koji je jako bitan, buduci da KeStackAttachProcess i promena
konteksta nece dovesti uvek do momentalnog relodovanja PDE/PTE onda moramo postaviti
SEH i procitati jedan bajta sa nase patch adrese, ako pratimo PTE u SoftICE videcemo
kako se on reloaduje:
push offset sehhandle
push dword ptr fs:[0]
mov dword ptr fs:[0], esp
mov eax, insertint3h
mov ebx, [eax]
mov byte ptr[eax], 0cch
__safe: pop dword ptr fs:[0]
add esp, 4
Doduse kako bi ucnio kod sto citljivijim koristim 2 makroa za postavljanje i
uklanjanje SEHa:
init_ring0_seh __safe
mov eax, insertint3h
mov ebx, [eax]
mov byte ptr[eax], 0cch
__safe: remove_ring0_seh
Pri cemu su init_ring0_seh i remove_ring0_seh slicni, ako ne i isti
sa makroima koje koristim u ring3 aplikacijama.
<++>
init_ring0_seh macro _xxx
local __handle
call __handle
mov ecx, [esp+0ch]
mov [ecx.context_eip], offset _xxx
push dword ptr[esp+8]
pop [ecx.context_esp]
xor eax, eax
ret
__handle: push dword ptr fs:[0]
mov dword ptr fs:[0], esp
endm
remove_ring0_seh macro
pop dword ptr fs:[0]
add esp, 4
endm
<++>
E sad kad znamo sve sto se treba znati red je da ovo i realizujemo:
3. Realizacija aka Sprovodjenje reci u delo /------------------------
--------------------------------------------/
Pogledajmo nas crackme:
001B:00406001 PUSHAD
001B:00406002 CALL 0040600A
001B:00406007 JMP 459D64F7
001B:0040600C PUSH EBP
001B:0040600D RET
001B:0040600E CALL 00406014
001B:00406013 JMP 00406072
001B:00406015 MOV EBX,FFFFFFED
Ok procedura dolazenja do OEPa u ASpack stvarno nije teska tako da necu
ni da se zadrzavam na tome:
001B:004063B0 JNZ 004063BA
001B:004063B2 MOV EAX,00000001
001B:004063B7 RET 000C
001B:004063BA PUSH 00401000
001B:004063BF RET <--- ovde cemo da postavimo nas int 3h
001B:004063C0 MOV EAX,[EBP+00000426]
001B:004063C6 LEA ECX,[EBP+0000043B]
001B:004063CC PUSH ECX
i konacno crackme:
001B:00401000 PUSH 00
001B:00401002 CALL KERNEL32!GetModuleHandleA
001B:00401007 PUSH 00
001B:00401009 PUSH 00401022
001B:0040100E PUSH 00
001B:00401010 PUSH 000003E7
001B:00401015 PUSH EAX
001B:00401016 CALL USER32!DialogBoxParamA
001B:0040101B PUSH 00
001B:0040101D CALL KERNEL32!ExitProcess
001B:00401022 ENTER 0000,00
001B:00401026 PUSHAD
001B:00401027 XOR EAX,EAX
001B:00401029 CMP DWORD PTR [EBP+0C],00000110
001B:00401030 JZ 00401049
001B:00401032 CMP DWORD PTR [EBP+0C],10
001B:00401036 JNZ 00401062
001B:00401038 PUSH 00
001B:0040103A PUSH DWORD PTR [EBP+08]
001B:0040103D CALL USER32!EndDialog
001B:00401042 MOV EAX,00000001
001B:00401047 JMP 00401062
001B:00401049 PUSH 00
001B:0040104B PUSH 00402004 ; "nag"
001B:00401050 PUSH 00402000 ; "NAG"
001B:00401055 PUSH DWORD PTR [EBP+08]
001B:00401058 CALL USER32!MessageBoxA <-- NAG
001B:0040105D MOV EAX,00000001
001B:00401062 MOV [ESP+1C],EAX
001B:00401066 POPAD
001B:00401067 LEAVE
001B:00401068 RET 0010
001B:0040106B JMP [KERNEL32!ExitProcess]
001B:00401071 JMP [KERNEL32!GetModuleHandleA]
001B:00401077 JMP [USER32!DialogBoxParamA]
001B:0040107D JMP [USER32!MessageBoxA]
001B:00401083 JMP [USER32!EndDialog]
Da se ne bih zadrzavao obajsnjavajuci kako cemo da ubijemo ovaj NAG, recicu samo
da cemo na adresu 0040104Ah da ubacimo 0FFh...
Super sad imamo 2 adrese:
1. 004063BFh gde ubacujemo nas int3h
2. 0040104Ah gde ubacujemo 0FFh
Sad je sve prosto, za detaljniju implementaciju pogledajte source kod od loader.asm
i ring0.asm buduci da oni sadrze sve sto sam gore pomenuo, a i kod uvek govori vise
nego hiljade reci.
int3h hook:
<++>
myint3h: initint ;sacuvaj registre i postavi
;fs na 30h i es i ds na 23h
mov eax, cr3
cmp eax, c_cr3
jne __passdown
mov eax, [esp.int_eip] ;uzmimo EIP sacuvan na stacku
dec eax ;umanjimo eip za 1 buduci da se
;int3h exception odigrao nakon
;izvrsavanja int3h
cmp eax, insertint3h ;da li je to adresa na koju smo
jne __passdown ;ubacili int 3h???
mov eax, patchme ;sad proveravamo da li je PTE
shr eax, 22 ;u memoriji, buduci da je ring3
test dword ptr[eax*4+0C0300000h], 1 ;sastavljen od PAGE = 4K
jz __passdown ;nema potrebe za proverom
mov eax, patchme ;za PAGE = 4MB
shr eax, 12 ;proveravamo zatim da li je
test dword ptr[eax*4+0C0000000h], 1 ;PAGE prisutan u memoriji
jz __passdown ;ako je nas PAGE prisutan u
;memoriji patchujemo nasu
mov eax, cr0 ;zeljenu adresu
and eax, 0FFFEFFFFh
mov cr0, eax
mov eax, patchme ;patchujemo nasu adresu
mov byte ptr[eax], 0ffh ;sa bajtom 0ffh iliti -1
mov eax, cr0
or eax, 10000h
mov cr0, eax
mov [esp.int_eip], 401000h ;sad samo postavljamo sacuvan
;eip na oep i odande nastavljamo
;izvrsavanje
restoreint ;povrati registre
iretd ;i idemo nazad u ring3
__passdown: restoreint ;pa vracamo registre nazad
jmp cs:[oldint3h] ;i skacemo na defaul handler
<++>
initint i removeint su samo 2 makroa koje koristim prilikom hookovanja interupta:
<++>
initint macro
pushad
push fs
push ds
push es
mov eax, 30h
mov fs, ax ;fs pokazuje na KPCR
mov eax, 23h
mov ds, ax
mov es, ax
endm
restoreint macro ;samo da vratimo registre
pop es ;natrag
pop ds
pop fs
popad
endm
<++>
Ako pazljivo pogledate source mog drajvera videcete da je u pitanju tasm32, pa eto
uspeo sam da namestim da mi se drajveri kompajliraju koristeci tasm32 i ms link.
Kao bonus dajem vam moj makefile za drajvere.
Jos jedna napomena, ako pokrenete loader videte da smo ubili nag, ali ako pokrenete
crackme.exe opet bez loadera program ce pasti:
001B:004063B0 JNZ 004063BA
001B:004063B2 MOV EAX,00000001
001B:004063B7 RET 000C
001B:004063BA PUSH 00401000
001B:004063BF INT 3
001B:004063C0 MOV EAX,[EBP+00000426]
001B:004063C6 LEA ECX,[EBP+0000043B]
001B:004063CC PUSH ECX
Ako pogledate adresu 004063BFh, vidite da je int 3h ostao tamo!? int 3h je ostao u
cache-u pa da bi ga uklonili kako kaze SoftICE command reference manual, moramo
ili da rekompajliramo fajl, ili da flusujemo cache ili da rucno editujemo instrukciju
koja je tamo ranije bila, ili prosto prekopiramo fajl...
Pa to je to...
4. Zakljucak /-------------------------------------------------------
------------/
Hmmm zakljucak? Ajd da vidim da neko napise brzi debug loader od ovog?
Salu na stranu, ovo moze biti korisno za ucenje i razumevanje win32.
U narednom broju phzine cu malko pisati o hookovanju DDIja i pokazati
kako se to moze lepo iskoristiti.
Greetzing: svim clanovima ARTeam, svim dobrim koderima
Fuckz: lamerima, medju kojima dominira nas giga...
S verom u Boga, deroko/ARTeam