Bu rehber, UEFI Runtime sürücüleri kavramını, işletim sistemi ile nasıl etkileşime girdiklerini ve bunları ayıklamak için deneysel bir girişimi açıklamaktadır.
Makaleden hızlı bir çıkarım yapmak istersek;
Örneğin, UEFI ResetSystem()'i bu tür arayüzlerden biri olarak tanımlar ve OEM'in bunu uygulayan Runtime sürücüsünü içeren ürün yazılımını uygulamasını gerektirir. Örnek olarak ASUS dizüstü bilgisayarımı ele alırsak, ResetSystem() SBRun adlı bir Runtime sürücüsünde uygulanmaktadır.
Bu arayüzler "Runtime hizmetleri" olarak adlandırılır ve 14 hizmet için tanımlanmıştır. Tanımlarının C gösterimini EDK2'de bulabilirsiniz.
Neden önemsiyoruz?
Runtime sürücülerinin bazı ilginç özellikleri vardır:
Diğer bir neden ise Runtime sürücülerinin tersine mühendislik topluluğunda popülerlik kazanması ve standart işletim sistemi/sistem bütünlüğünü bozacak şekilde giderek daha yaygın olarak kullanılmasıdır.
Örneğin, sistemin sahibi, tersine mühendislik için bir "Backdoor'a" sahip olmak amacıyla OEM ürün yazılımı tarafından sağlanan Runtime hizmetlerini geçersiz kılan bir 3. taraf Runtime sürücüsü yükleyebilir. EfiGuard ve efi-memory bunlara örnektir. Sistem sahibinin niyeti bu olsa da, bazı yazılımlar bunu tespit etmek ve sistem bütünlüğünün bozulabileceğinin farkında olmak isteyebilir.
Windows'ta Runtime sürücülerini nasıl tespit edebiliriz?
Ne yazık ki Windows çekirdeğinde herhangi bir Runtime sürücüsünü bulmak için belgelenmiş bir arayüz yoktur. Ancak, bunun için kötüye kullanılabilecek birkaç uygulama detayı vardır, örneğin,
Bazı Runtime hizmetlerinin adresleri HalEfiRuntimeServicesBlock içinde saklanır.
Yukarıda görüldüğü gibi Runtime hizmetlerine işaretçiler içeren, ayırt edici bir RUNTSERV imzasına sahip ve bellekte yerleşik olan bir EFI_RUNTIME_SERVICES global değişkeni vardır.
Runtime sürücüleri de bellekte yerleşiktir, belirli bir bitişik fiziksel ve sanal bellek aralığında eşlenir ve 4KB hizalı adreslerde DOS başlığına sahiptir.
Runtime sürücülerini destekleyen fiziksel bellek adresleri Windows yönetim aralıklarının dışındadır.
Bu gerçeklere dayanarak, fiziksel veya sanal bellekteki DOS başlığı taranabilir ve tüm Runtime sürücüleri bulunmaya çalışılabilir.
Diğer sınırlı (ayrıntılar daha sonra) ancak tartışmasız daha kolay ve güvenli yol ise HalEfiRuntimeServicesBlock'a bakmaktır. HalEfiRuntimeServicesBlock, aşağıda gösterildiği gibi, bir avuç Runtime hizmet adresinin kopyalarından oluşan Windows tanımlı bir yapıdır. (Referans: ReconBru 2017 - Getting Physical with USB Type-C, Windows 10 RAM Forensics and UEFI Attacks)
Doğrudan EFI_RUNTIME_SERVICES yapısını kullanmak yerine, Windows bu yapıyı başlangıçta başlatır ve Runtime hizmetlerini çağırmak için kullanır. HalEfiRuntimeServicesBlock yapısı bulunarak, Runtime hizmetlerinin adresleri ve bunları uygulayan Runtime sürücüleri bulunabilir.
Karşılaşılan zorluklar:
HalEfiRuntimeServicesBlock, sadece Windows 10 19H2'ye kadar HalQuerySystemInformation() ile bulunabilir. Daha yeni sürümler için yaratıcı olmak gerekir.
Runtime sürücülerini destekleyen fiziksel bellek adres aralığı Windows tarafından bilinmez ve MmGetPhysicalMemoryRanges() tarafından bildirilmez. Bu, olası bir Runtime sürücü adresini test etmek için kullanılabilir.
MmCopyMemory(), sanal veya fiziksel bellek belirtilmiş olmasına bakılmaksızın, Runtime sürücülerini destekleyen belleği okumayı asla başaramaz. Bu, güvenli bir arama işleminin uygulanmasını zorlaştırır.
Sınırlamalar
HalEfiRuntimeServicesBlock yaklaşımı oldukça iyi çalışsa da, bazı temel sınırlamaları vardır.
İlk olarak, Runtime hizmetlerini uygulamayan Runtime sürücüleri bulunmaz. Bu tür Runtime sürücülerinin Windows'u ve sistem bütünlüğünü doğrudan etkilemek için resmi olarak tanımlanmış bir yolu yoktur ancak bunun yerine backdoor uygulamak için diğer kodları patch edebilir. umap ve voyager bu tür korsan sürücülere örnektir. Bellek tarama tabanlı bir yaklaşım bu sorunu çözecektir.
İkinci olarak, Runtime sürücüleri EFI_RUNTIME_SERVICES içindeki pointeri değiştirmek yerine orijinal Runtime hizmetinin başına trambolin kodu yazarak veya PE başlığını geçersiz kılarak bu yaklaşımdan kolayca gizlenebilir. Bunlar daha akıllı analiz ve döküm ile hafifletilebilir, ancak taramaya karşı kolay bir karşı önlemdir.
Üçüncü olarak, bellek analizi ile ilgili genel bir zorluk olarak, bellek dökümü dosyalarının sınıflandırılması kolay değildir. Bellek içeriği, yer değiştirme (kod yamaları) nedeniyle botlar arasında biraz değişebileceğinden, hash değerleri her seferinde değişir. Döküm dosyalarını sınıflandırmak ve kullanışlı bir veritabanı oluşturmak için ssdeep gibi hashleme gereklidir. Ayrıca, bir döküm dosyası gerçek ürün yazılımında bulunan sürücü adını ve GUID'yi içermez.
Son olarak, bu Windows'a özgüdür ve kullanışsızdır. Büyük ölçüde yakında bozulabilecek Windows uygulama ayrıntılarına bağlıdır.
Makaleden hızlı bir çıkarım yapmak istersek;
- UEFI Runtime sürücüleri, işletim sistemi başlamadan önce Ring-0 ayrıcalığıyla çalışan ürün yazılımının bir parçasıdır.
- İşletim sistemine Runtime hizmetleri adı verilen bazı bellenime bağlı özellikler için arayüzler sağlarlar.
- Windows bu Runtime hizmetlerinin adreslerini HalEfiRuntimeServicesBlock içine kaydeder.
- Runtime sürücülerinin temel adresleri, HalEfiRuntimeServicesBlock içeriğinden bulunabilir ancak HalEfiRuntimeServicesBlock ve temel adresleri güvenli bir şekilde bulmak zordur.
- Runtime sürücülerinin dökümü, bunlarla ilgili sorunları teşhis etmek için yararlıdır, ancak HalEfiRuntimeServicesBlock tabanlı yaklaşım temelde Runtime hizmetlerini uygulayan sürücülerle sınırlıdır.
Örneğin, UEFI ResetSystem()'i bu tür arayüzlerden biri olarak tanımlar ve OEM'in bunu uygulayan Runtime sürücüsünü içeren ürün yazılımını uygulamasını gerektirir. Örnek olarak ASUS dizüstü bilgisayarımı ele alırsak, ResetSystem() SBRun adlı bir Runtime sürücüsünde uygulanmaktadır.
Bu arayüzler "Runtime hizmetleri" olarak adlandırılır ve 14 hizmet için tanımlanmıştır. Tanımlarının C gösterimini EDK2'de bulabilirsiniz.
C:
///
/// EFI Runtime Services Table.
///
typedef struct {
///
/// The table header for the EFI Runtime Services Table.
///
EFI_TABLE_HEADER Hdr;
//
// Time Services
//
EFI_GET_TIME GetTime;
EFI_SET_TIME SetTime;
EFI_GET_WAKEUP_TIME GetWakeupTime;
EFI_SET_WAKEUP_TIME SetWakeupTime;
//
// Virtual Memory Services
//
EFI_SET_VIRTUAL_ADDRESS_MAP SetVirtualAddressMap;
EFI_CONVERT_POINTER ConvertPointer;
//
// Variable Services
//
EFI_GET_VARIABLE GetVariable;
EFI_GET_NEXT_VARIABLE_NAME GetNextVariableName;
EFI_SET_VARIABLE SetVariable;
//
// Miscellaneous Services
//
EFI_GET_NEXT_HIGH_MONO_COUNT GetNextHighMonotonicCount;
EFI_RESET_SYSTEM ResetSystem;
//
// UEFI 2.0 Capsule Services
//
EFI_UPDATE_CAPSULE UpdateCapsule;
EFI_QUERY_CAPSULE_CAPABILITIES QueryCapsuleCapabilities;
//
// Miscellaneous UEFI 2.0 Service
//
EFI_QUERY_VARIABLE_INFO QueryVariableInfo;
} EFI_RUNTIME_SERVICES;
Neden önemsiyoruz?
Runtime sürücülerinin bazı ilginç özellikleri vardır:
- İşletim sistemi yüklenmeden önce başlarlar ve önyükleme sürecini etkileyebilirler.
- Ring-0 ayrıcalığıyla çalışırlar.
- Normal işletim sistemi yürütmesi sırasında Runtime hizmetleri aracılığıyla çağrılırlar.
- Windows'ta yaygın olarak bilinen hiçbir izleme aracı veya hata ayıklayıcı (aygıt sürücülerinin aksine) tarafından listelenmezler.
- Secure Boot devre dışı bırakıldığı sürece herkes tarafından geliştirilebilir ve yüklenebilirler.
- İşletim sisteminden erişilebilen depolama alanında dosya olarak bulunmayabilirler.
- Bunlarla ilgili görünürlük ve dosyalara erişim eksikliği nedeniyle, bunlarla ilgili sorunları teşhis etmek, işletim sistemi tabanlı çekirdek modülleriyle ilgili sorunlardan daha zor olabilir. Örneğin, Runtime sürücü kodunda tersine mühendislik yapmak için öncelikle diskteki bir dosyayı yakalamak yerine Runtime sürücülerini bellekte bulmanız ve bir dosyaya çıkarmanız gerekir.
Diğer bir neden ise Runtime sürücülerinin tersine mühendislik topluluğunda popülerlik kazanması ve standart işletim sistemi/sistem bütünlüğünü bozacak şekilde giderek daha yaygın olarak kullanılmasıdır.
Örneğin, sistemin sahibi, tersine mühendislik için bir "Backdoor'a" sahip olmak amacıyla OEM ürün yazılımı tarafından sağlanan Runtime hizmetlerini geçersiz kılan bir 3. taraf Runtime sürücüsü yükleyebilir. EfiGuard ve efi-memory bunlara örnektir. Sistem sahibinin niyeti bu olsa da, bazı yazılımlar bunu tespit etmek ve sistem bütünlüğünün bozulabileceğinin farkında olmak isteyebilir.
Windows'ta Runtime sürücülerini nasıl tespit edebiliriz?
Ne yazık ki Windows çekirdeğinde herhangi bir Runtime sürücüsünü bulmak için belgelenmiş bir arayüz yoktur. Ancak, bunun için kötüye kullanılabilecek birkaç uygulama detayı vardır, örneğin,
Bazı Runtime hizmetlerinin adresleri HalEfiRuntimeServicesBlock içinde saklanır.
Yukarıda görüldüğü gibi Runtime hizmetlerine işaretçiler içeren, ayırt edici bir RUNTSERV imzasına sahip ve bellekte yerleşik olan bir EFI_RUNTIME_SERVICES global değişkeni vardır.
Runtime sürücüleri de bellekte yerleşiktir, belirli bir bitişik fiziksel ve sanal bellek aralığında eşlenir ve 4KB hizalı adreslerde DOS başlığına sahiptir.
Runtime sürücülerini destekleyen fiziksel bellek adresleri Windows yönetim aralıklarının dışındadır.
Bu gerçeklere dayanarak, fiziksel veya sanal bellekteki DOS başlığı taranabilir ve tüm Runtime sürücüleri bulunmaya çalışılabilir.
Diğer sınırlı (ayrıntılar daha sonra) ancak tartışmasız daha kolay ve güvenli yol ise HalEfiRuntimeServicesBlock'a bakmaktır. HalEfiRuntimeServicesBlock, aşağıda gösterildiği gibi, bir avuç Runtime hizmet adresinin kopyalarından oluşan Windows tanımlı bir yapıdır. (Referans: ReconBru 2017 - Getting Physical with USB Type-C, Windows 10 RAM Forensics and UEFI Attacks)
C:
typedef struct _HAL_RUNTIME_SERVICES_BLOCK
{
void* GetTime;
void* SetTime;
void* ResetSystem;
void* GetVariable;
void* GetNextVariableName;
void* SetVariable;
void* UpdateCapsule;
void* QueryCapsuleCapabilities;
void* QueryVariableInfo;
} HAL_RUNTIME_SERVICES_BLOCK;
Doğrudan EFI_RUNTIME_SERVICES yapısını kullanmak yerine, Windows bu yapıyı başlangıçta başlatır ve Runtime hizmetlerini çağırmak için kullanır. HalEfiRuntimeServicesBlock yapısı bulunarak, Runtime hizmetlerinin adresleri ve bunları uygulayan Runtime sürücüleri bulunabilir.
Karşılaşılan zorluklar:
HalEfiRuntimeServicesBlock, sadece Windows 10 19H2'ye kadar HalQuerySystemInformation() ile bulunabilir. Daha yeni sürümler için yaratıcı olmak gerekir.
Runtime sürücülerini destekleyen fiziksel bellek adres aralığı Windows tarafından bilinmez ve MmGetPhysicalMemoryRanges() tarafından bildirilmez. Bu, olası bir Runtime sürücü adresini test etmek için kullanılabilir.
MmCopyMemory(), sanal veya fiziksel bellek belirtilmiş olmasına bakılmaksızın, Runtime sürücülerini destekleyen belleği okumayı asla başaramaz. Bu, güvenli bir arama işleminin uygulanmasını zorlaştırır.
Sınırlamalar
HalEfiRuntimeServicesBlock yaklaşımı oldukça iyi çalışsa da, bazı temel sınırlamaları vardır.
İlk olarak, Runtime hizmetlerini uygulamayan Runtime sürücüleri bulunmaz. Bu tür Runtime sürücülerinin Windows'u ve sistem bütünlüğünü doğrudan etkilemek için resmi olarak tanımlanmış bir yolu yoktur ancak bunun yerine backdoor uygulamak için diğer kodları patch edebilir. umap ve voyager bu tür korsan sürücülere örnektir. Bellek tarama tabanlı bir yaklaşım bu sorunu çözecektir.
İkinci olarak, Runtime sürücüleri EFI_RUNTIME_SERVICES içindeki pointeri değiştirmek yerine orijinal Runtime hizmetinin başına trambolin kodu yazarak veya PE başlığını geçersiz kılarak bu yaklaşımdan kolayca gizlenebilir. Bunlar daha akıllı analiz ve döküm ile hafifletilebilir, ancak taramaya karşı kolay bir karşı önlemdir.
Üçüncü olarak, bellek analizi ile ilgili genel bir zorluk olarak, bellek dökümü dosyalarının sınıflandırılması kolay değildir. Bellek içeriği, yer değiştirme (kod yamaları) nedeniyle botlar arasında biraz değişebileceğinden, hash değerleri her seferinde değişir. Döküm dosyalarını sınıflandırmak ve kullanışlı bir veritabanı oluşturmak için ssdeep gibi hashleme gereklidir. Ayrıca, bir döküm dosyası gerçek ürün yazılımında bulunan sürücü adını ve GUID'yi içermez.
Son olarak, bu Windows'a özgüdür ve kullanışsızdır. Büyük ölçüde yakında bozulabilecek Windows uygulama ayrıntılarına bağlıdır.