NSO Zero-click iMessage İstismarına Derinlemesine Bir Bakış: Remote Code Execution

Bu blog gönderisinde tartışılan güvenlik zafiyeti,13 Eylül 2021 tarihinde iOS’un 14.8 sürümünde CVE-2021-30860 olarak düzeltildi.

ZAFİYETİN/İSTİSMARIN TEKNİK DETAYLARI (From One to Zero)

Daha önceki Million Dollar Dissident from 2016’ya benzer vakalarda, SMS mesajlarında hedeflere bağlantılar gönderiliyordu:

Hedef sadece linke tıkladığında, “one-click exploit” olarak bilinen bir teknikle hacklenmiş oldu. Ancak son zamanlarda NSO’nun, hedeflendiklerinden tamamen habersiz şekilde bir oltalama linkine tıklamayan teknik olarak bilgi sahibi hedefler olsalar dahi, kendi istemcilerine (client) “zero-click” sömürüsü teknolojisini sunduğu belgelenmiştir.

Zero-click senaryosunda kullanıcı etkileşimi gerekmiyor. Yani, saldırganın oltalama mesajları göndermesine gerek yoktur; sadece exploit arka planda sessizce çalışır. Bir cihazı kullanmamak dışında, zero-click sömürüsünü işletmeyi önlemenin bir yolu yoktur. Savunması olmayan bir silahtır (şu an için).

Garip Bir Püf Noktası

Pegasus için ilk giriş noktası iPhone’daki iMessage uygulamasıdır. Bunun anlamı; bir kurbanın yalnızca telefon numarası veya AppleID kullanıcı adı kullanarak hedef alınabileceğidir. iMessage, GIF resimleri(“Meme” kültüründe popüler olan, tipik olarak küçük ve düşük kaliteli animasyonlu resimler) için yerel desteğe sahiptir. iMessage üzerinden yaptığınız sohbetlerde GIF gönderip alabilirsiniz ve bunlar sohbet penceresinde oynatılır. Apple, bu GIF’leri yalnızca bir kez oynatmak yerine sonsuz döngüye sokmak istedi. Bu nedenle, iMessage dosya ayrıştırma ve işletme işlemi (bir mesaj alınıp, gösterilmeden önce) başlarında, “IMTranscoderAgent” işlemi sırasında (“BlastDoor” sandbox dışında) .gif eklentisi üzerinden herhangi görüntü dosyasının aktarılabildiği aşağıdaki yöntemi çağırır:

[IMGIFUtils copyGifFromPath:toDestinationPath:error]

“Selector” adına bakıldığında, burada amaçlanan muhtemelen döngü miktarı alanını düzenlemeden önce GIF dosyasını kopyalamaktı, ancak bu yöntem için semantik farklıdır. Kaputun altında, kaynak görüntü dosyasını hedef dizinde yeni bir GIF dosyasına dönüştürmek için “CoreGraphics” API’lerini kullanır. Ve sırf kaynak dosya adının .gif ile bitmesi, bu onun gerçekten bir GIF dosyası olduğu anlamına gelmez.

Önceki bir Project Zero blog gönderisinde ayrıntılı olarak açıklanan ImageIO kütüphanesi, kaynak dosyanın tipini doğru biçimde tahmin etmek ve dosya uzantısını yok sayarak ayrıştırmak için kullanılır. Bu “sahte gif” hilesini kullanarak, 20’den fazla görüntü codec bileşeni aniden iMessage Zero-click saldırı yüzeyinin bir parçası olur, bazı çok belirsiz ve karmaşık formatlar dahil olmak üzere, muhtemelen yüz binlerce kod satırını uzaktan ifşa edilmiş olundu.

Not: Apple, iOS 14.8.1’den (26 Ekim 2021) başlayarak "IMTranscoderAgent"tan erişilebilen mevcut ImageIO biçimlerini kısıtladıklarını bize bildirdi ve “GIF decoding” işleminin tamamen “BlastDoor” içerisinde yer almasıyla birlikte; iOS 15.0’dan (20 Eylül 2021) başlayarak GIF kod dizinini "IMTranscoderAgent"tan tamamen kaldırdı.

GIF’inizde Bir PDF

NSO, “CoreGraphics PDF” ayrıştırıcısındaki bir güvenlik açığını hedeflemek için “sahte gif” hilesini kullanır.

PDF, yaygın kullanımı ve karmaşıklığı nedeniyle yaklaşık on yıl önce sömürü için popüler bir hedefti. İlave olarak, PDF içerisinde Javascript’in kullanılabilmesi güvenilir exploitlerin geliştirilmesini kolay hale getirmiştir. “CoreGraphics PDF” ayrıştırıcısı Javascript’i yorumlamıyor gibi görünüyor, ancak NSO, “CoreGraphics PDF” ayrıştırıcısında eşit derecede güçlü bir şey bulmayı başardı…

Olağanüstü Sıkıştırma

1990’ların sonlarında, bant genişliği ve depolama şimdi olduğundan çok daha kıt durumdaydı. JBIG2 standardı işte bu ortamda ortaya çıktı. JBIG2, piksellerin yalnızca siyah veya beyaz olan görüntüleri sıkıştırmak için tasarlanmış, bir alana özgü bir görüntü codec bileşenidir.

Metin belgelerinin taranması için son derece yüksek sıkıştırma oranları elde etmek üzere geliştirildi ve aşağıda gösterilen XEROX WorkCenter cihazı gibi üst düzey ofis tarayıcı/yazıcı cihazlarında uygulandı ve kullanıldı. On yıl önce bunun gibi bir cihazın “Scan to PDF” işlevini kullandıysanız, PDF’nizde muhtemelen bir JBIG2 akışı bulunurdu.


JBIG2 kullanan bir Xerox WorkCentre 7500 serisi çok işlevli yazıcı

Bu tarayıcılar tarafından üretilen PDF dosyaları son derece küçüktü, belki yalnızca birkaç kilobyte’tan ibaretti. JBIG2’nin bu exploitle ilgili olağanüstü sıkıştırma oranlarını elde etmek için kullandığı iki yeni teknik vardır:

Teknik 1: Segmentasyon ve Yer Değiştirme

Etkili bir şekilde her metin belgesi, özellikle İngilizce veya Almanca gibi küçük alfabeli dillerde yazılmış olanlar, her sayfada birçok tekrarlanan harften (glif olarak da bilinir) oluşur. JBIG2, her sayfayı gliflere ayırmaya çalışır, ardından aynı görünen glifleri eşleştirmek için basit pattern eşleştirme kullanır:
image4
Basit pattern eşleştirme, bir sayfada birbirine benzeyen tüm şekilleri bulabilir, buradaki koşulda tüm 'e’ler tespit edilmektedir.

JBIG2 aslında glifler hakkında hiçbir şey bilmiyor ve OCR (optik karakter tanıma) yapmıyor. Bir JBIG kodlayıcı, yalnızca bağlantılı piksel bölgelerini arar ve benzer görünen bölgeleri birlikte gruplandırır. Sıkıştırma algoritması, çokça benzer görünen tüm bölgeleri, bunlardan yalnızca birinin bir kopyasıyla değiştirmektir:
image1
Benzer gliflerin tüm oluşumlarını yalnızca birinin bir kopyasıyla değiştirmek, genellikle oldukça okunaklı ve çok yüksek sıkıştırma oranları sağlayan bir belge çıktısı verir.

Bu durumda çıktı mükemmel bir şekilde okunabilir ancak depolanacak bilgi miktarı önemli ölçüde azalır. Tüm sayfa için tüm orijinal piksel bilgilerini depolamak yerine, "referans glif"in sıkıştırılmış bir versiyonundaki her karaktere ve kopyaların yerleştirilmesi gereken tüm yerlerin göreceli koordinatlarına ihtiyacınız olacak. “Decompression” algoritması daha sonra çıktı sayfasına bir tuval gibi davranır ve depolanan tüm konumlarda tam olarak aynı glifi işler.

Böyle bir şemada önemli bir mesele vardır: Zayıf bir kodlayıcının yanlışlıkla benzer görünen karakterleri değiştirmesi çok kolaydır ve bu ilginç sonuçlara yol açabilir. D. Kriesel’in blogunda, taranan faturaların PDF’lerinin farklı şekillere sahip olduğu veya taranan inşaat çizimlerinin PDF’lerinin yanlış ölçümlerle sonuçlandığı bazı motive edici örneklere rastlayabiliriz. İlgilendiğimiz sorunlar bunlar değil, ancak JBIG2’nin artık yaygın bir sıkıştırma formatı olmamasının önemli nedenlerinden biridir.

Teknik 2: İyileştirme Kodlaması

Yukarıda bahsedildiği gibi, yer değişimi tabanlı sıkıştırma çıktısı kayıplı sonuçlar verir. Sıkıştırma ve açma turundan sonra, oluşturulan çıktı tam olarak girdiye benzemez. Ancak JBIG2, kayıpsız sıkıştırmanın yanı sıra ara “daha az kayıplı” sıkıştırma modunu da destekler.

Bunu, ikame glif ile her orijinal glif arasındaki farkı depolayarak(ve sıkıştırarak) yapar. Soldaki değiştirilmiş karakter ile ortadaki orijinal kayıpsız karakter arasındaki fark maskesini gösteren bir örnek bulunuyor:
image3
Fark görüntüsünü hesaplamak için bit eşlemlerde XOR operatörünü kullanmak

Bu basit örnekte, kodlayıcı sağda gösterilen fark maskesini saklayabilir, ardından decompression sırasında orijinal karakteri oluşturan tam pikselleri kurtarmak için fark maskesi ikame karakterle XORlanabilir.

Daha fazla sıkıştırma için bu blog gönderisinin kapsamı dışında ana sıkıştırma maksadıyla kullanılan dönüştürülmüş “bağlam” karakterlerinin ara formlarını oluşturan fark maskeleri için bazı püf noktaları vardır.

Tüm farklılıkları tek seferde tamamen encode etmek yerine, her iterasyonda bitleri ayarlamak, temizlemek veya dönüştürmek için mantıksal bir operatör (AND, OR, XOR veya XNOR’dan biri) kullanılarak adım adım sağlanabilir. Birbirini takip eden her iyileştirme adımı, işlenmiş çıktıyı orijinale yaklaştırır ve bu yaklaşım sıkıştırmada doğal sonuç olarak ortaya çıkan “kayıplar” üzerinde bir düzeyde kontrol sağlar. Bu tipte kodlama üzerinde iyileştirme adımlarının uygulanması fazlasıyla esneklik barındırır. Ayrıca çıktı canvas’ında (tuvalinde) hali hazırdaki değerler okunaklı haldedir.

JBIG2 Stream

CoreGraphics PDF kod çözücünün çoğu Apple’a özel kod gibi görünüyor, ancak JBIG2 uygulaması, kaynak kodu ücretsiz olarak kullanılabilen Xpdf’den gelmektedir.

JBIG2 formatı dizi dizi segmentler halinde, tek geçişte sırayla yürütülen bir dizi çizim komutu olarak düşünülebilir. “CoreGraphics JBIG2” ayrıştırıcısı, yeni bir sayfa tanımlama, bir huffman tablosunun kodunu çözme veya sayfada verilen koordinatlara bir bitmap oluşturma gibi işlemleri içeren 19 farklı segment türünü destekler.

“JBIG2Bitmap”, dikdörtgen bir piksel dizisini temsil eder. Veri alanı, işleme tuvalini içeren bir destek arabelleğine işaret eder.

Bir “JBIG2SymbolDict”, "JBIG2Bitmap"leri birlikte gruplandırır. Hedef sayfa, ayrı glifler gibi bir “JBIG2Bitmap” olarak temsil edilir.

"JBIG2Segments"e bir segment numarası ile atfedilebilir ve GList vektör tipi, tüm "JBIG2Segments"lere pointerlar depolar. Segment numarasına göre bir segment aramak için “GList” sıralı olarak taranır.

Güvenlik Zafiyeti

Buradaki zafiyet, referans segmentlerin harmanlandığı sırada oluşan klasik bir integer taşmasıdır:

Guint numSyms; // (1)


  numSyms = 0;

  for (i = 0; i < nRefSegs; ++i) {

    if ((seg = findSegment(refSegs[i]))) {

      if (seg->getType() == jbig2SegSymbolDict) {

        numSyms += ((JBIG2SymbolDict *)seg)->getSize();  // (2)

      } else if (seg->getType() == jbig2SegCodeTable) {

        codeTables->append(seg);

      }

    } else {

      error(errSyntaxError, getPos(),

            "Invalid segment reference in JBIG2 text region");

      delete codeTables;

      return;

    }

  }

...

  // get the symbol bitmaps

  syms = (JBIG2Bitmap **)gmallocn(numSyms, sizeof(JBIG2Bitmap *)); // (3)


  kk = 0;

  for (i = 0; i < nRefSegs; ++i) {

    if ((seg = findSegment(refSegs[i]))) {

      if (seg->getType() == jbig2SegSymbolDict) {

        symbolDict = (JBIG2SymbolDict *)seg;

        for (k = 0; k < symbolDict->getSize(); ++k) {

          syms[kk++] = symbolDict->getBitmap(k); // (4)

        }

      }

    }

  }

“numSyms”, çıktı resminde (1)'de bildirilen 32 bitlik bir "integer"dır. Özenle hazırlanmış referans segmentlerinin sağlanmasıyla (2)'de tekrarlanan “numSyms” toplamasının kontrollü, küçük bir değerle "overflow"a neden olması mümkündür.

Bu daha küçük değer, (3)'teki yığın tahsisi boyutu için kullanılır. Bu da syms’in küçük boyutlu bir buffer’a işaret ettiği anlamına gelir.

(4)'teki en içteki döngünün içinde “JBIG2Bitmap” işaretçi değerleri, küçük boyutlu syms bufferına yazılır.

Başka bir hile olmaksızın bu döngü, 32 GB’den fazla veriyi, küçük boyutlu syms arabelleğine yazar ve kesinlikle bir çökmeye neden olur. Bu çökmeyi önlemek için heap, syms bufferının sonundaki ilk birkaç yazma işlemi GList destek bufferını bozacak şekilde düzenlenir. Bu GList, bilinen tüm segmentleri saklar ve findSegments yordamı tarafından refSeg’lerde geçirilen segment numaralarından JBIG2Segment işaretçilerine haritalamak için kullanılır. Buradaki overflow, GList’teki JBIG2Segment işaretçilerinin üzerine (4)'teki JBIG2Bitmap işaretçilerinin yazılmasına neden olur.

JBIG2Bitmap, JBIG2Segment’ten miras aldığı için, seg->getType() sanal çağrısı, Pointer Authentication’ın etkinleştirildiği (sanal çağrılarda zayıf tip kontrolü yapmak için kullanılan) cihazlarda bile başarılı olur, ancak döndürülen tip artık jbig2SegSymbolDict’e eşit olmayacaktır. Böylece (4)'te daha fazla yazma işlemine erişilmemesine neden olur ve memory bozulmasının kapsamını sınırlar.

image6
GList destek arabelleği ve JBIG2Bitmap’in heap taşması meydana geldiğinde altındaki küçük boyutlu arabelleğin bellek düzenini gösteren basitleştirilmiş bir görünümü

Sınırları Olmayan Sınırsızlaştırma

Bozulmuş GList segmentlerinden hemen sonra, saldırgan geçerli sayfayı (geçerli çizim komutlarının işlendiği yer) temsil eden JBIG2Bitmap nesnesin çeki düzen verir. JBIG2Bitmap’ler, arabelleğin genişliğini ve yüksekliğini (bit cinsinden) ve ayrıca her satır için kaç bayt depolanacağını tanımlayan bir satır değerini saklayan, bir destek bufferının etrafındaki basit toparlayıcılardır.


Taşma sırasında bozulan segnum, w, h ve line alanlarını gösteren JBIG2Bitmap nesnesinin bellek düzeni

RefSeg’leri dikkatli bir şekilde yapılandırarak, segmentler GList arabelleğinin bitiminden sonra tam olarak üç tane daha JBIG2Bitmap işaretçisi yazdıktan sonra taşmayı durdurabilirler. Bu, geçerli sayfayı temsil eden JBIG2Bitmap’in vtable işaretçisinin ve ilk dört alanının üzerine yazar. iOS adres alanı düzeninin doğası gereği, bu işaretçilerin, adresleri “0x100000000” ile “0x1ffffffff” arasında olan ikinci 4 GB sanal bellekte olması çok muhtemeldir. Tüm iOS donanımı “little endian” olduğundan (yani, w ve satır alanlarının üzerine, JBIG2Bitmap işaretçisinin en belirgin yarısı olan 0x1 ile yazılması muhtemeldir) ve segNum ve h alanlarının üzerine, böyle “0x100000” ve “0xffffffff” arasında bir yerde yığın düzenine ve ASLR’ye bağlı olarak oldukça rastgele bir değer olan işaretçinin en az anlamlı olan yarısı ile yazılması muhtemeldir.

Bu, geçerli hedef sayfa JBIG2Bitmap’e h için bilinmeyen, ancak çok büyük bir değer verir. Bu h değeri sınır denetimi için kullanıldığından ve sayfa yedekleme arabelleğinin tahsis edilen boyutunu yansıtması gerektiğinden, bunun çizim tuvalinin “sınırını kaldırma” etkisi vardır. Bunun anlamı, daha sonraki JBIG2 segment komutlarının, sayfa destek arabelleğinin orijinal sınırlarının dışındaki belleği okuyabileceği ve yazabileceği anlamına gelir.

Ayrıca Heap Groom, geçerli sayfanın destek arabelleğini, küçük boyutlu “syms” arabelleğinin hemen altına yerleştirir, böylece JBIG2Bitmap sayfası sınırsız olduğunda, kendi alanlarını okuyabilir ve yazabilir:


Sınırsız bitmap destek bufferının JBIG2Bitmap nesnesine nasıl referans edildiği ve bellekteki destek arabelleğinden sonra bulunduğu için içindeki alanları nasıl değiştirebildiğini gösteren bellek düzeni

4 baytlık bitmapleri doğru tuval koordinatlarında renderlayerek JBIG2Bitmap sayfasının tüm alanlarına yazabilirler ve w, h ve line için yeni değerleri dikkatlice seçerek, sayfa destek bufferında rastgele ofsetlere yazabilirler.

Bu noktada, sayfa destek bufferından uzaklıklarını biliyorsanız, rastgele (kesin) bellek adreslerine yazmak da mümkün olacaktır. Fakat bu ofsetler nasıl hesaplanır? Şimdiye kadar, bu istismar, Javascript’te sınırsız bir ArrayBuffer objesi üzerinden belleğe erişim ile sonuçlanabilecek “meşru” betik dili istismarına çok benzer bir şekilde ilerlemiştir. Ancak saldırgan bu tip durumlarda, açık bir şekilde ofsetleri hesaplama ve farklı hesaplamaları yapmak için kullanılabilecek keyfi Javascript çalıştırma yeteneğine sahiptir. Bunu tek geçişli bir görüntü ayrıştırıcısında nasıl yaparsınız?

Diğer Sıkıştırma Formatım "turing-complete"

Daha önce belirtildiği gibi, JBIG2 iyileştirmesini uygulayan adımların sırası çok esnektir. İyileştirme adımları, hem çıktı bitmap’ine hem de önceden oluşturulmuş herhangi bir segmente referans verebilir ve ayrıca mevcut sayfaya veya farklı bir segmente çıktı verebilir. Decompression iyileştirmesinin parçası olan context bağımlı kısmı dikkatle işleyerek, yalnızca herhangi bir etkisi olan iyileştirme kombinasyonu operatörlerinin segment dizilerini oluşturmak mümkündür.

Pratikte bunun anlamı; bellek bölgeleri arasındaki AND, OR, XOR ve XNOR mantıksal operatörlerini, geçerli sayfanın JBIG2Bitmap destek arabelleğinden isteğe bağlı offsetlerde uygulamak mümkündür. Ve bu sınırsız olduğundan beridir… bu mantıksal işlemleri, bellekte sınırların dışındaki ofsetlerde isteğe bağlı gerçekleştirmek mümkündür:

Bunu en sıra dışı şekline getirdiğiniz zaman işler gerçekten ilginçleşmeye başlıyor. Glif boyutlu alt-dikdörtgenler üzerinde çalışmak yerine ya tek bitler üzerinde çalışsaydınız?

Şimdi sayfaya bir dizi girdi olarak uygulanacak mantıksal bit işlemi gerçekleştiren bir JBIG2 segment komutları dizisini temin edebilirsiniz. Ve sayfa arabelleği sınırsız olduğundan, bu bit işlemleri isteğe bağlı bellek gözlerinde koşturulabilir.

Bir karalama parçası içerisindeki bir adet bit ile, uygun fonksiyon içerisinde işlem yapmak için barınan bir AND, OR, XOR and XNOR mantıksal operatörünü kullanarak değişiklik sağlayabileceğinize inanabilirsiniz. En basit kanıtı; 1 ile XORing yaparak mantıksal bir NOT operatörü oluşturabileceğiniz ve ardından bir NAND geçidi oluşturmak için önüne bir AND geçidi koyabileceğinizdir.

image8
Bir XOR kapısının bir girişine bağlı bir AND kapısı. Diğer XOR geçidi sabit “1” ile bağlanarak, diğer girişine de AND kapısı çıkışı entegre edilerek doğal bir NAND geçidi elde edilebilir.

NAND geçidi burada genel bir örnek olması açısından kullanıldı. Diğer kapılar da kullanarak işlem yapan fonksiyonlar elde edilebilir.

Pratik Devreler

JBIG2’nin scripting yetenekleri yoktur, ancak bir güvenlik açığı ile birleştirildiğinde, isteğe bağlı bellek gözleri üzerinde çalışan isteğe bağlı logic kapılarının devrelerini taklit etme yeteneğine sahiptir. Öyleyse neden bunu sadece kendi bilgisayar mimarinizi oluşturmak için kullanmıyorsunuz ve bunu bir script haline getirmiyorsunuz!? Bu exploitin tam olarak yaptığı budur.
Mantıksal bit operasyonlarını tanımlayan 70.000’den fazla segment komutunu kullanarak, full 64 bitlik toplayıcı ve karşılaştırıcı gibi özelliklere sahip memory aramak ve aritmetik işlemler yapmak için kullanılan küçük bir bilgisayar mimarisi tanımlanabilir. Javascript kadar hızlı değil, ancak temelde hesaplama açısından eşdeğerdir.

Sandbox Escape Exploit için önyükleme işlemleri bu mantık devresinde çalışacak şekilde yazılmıştır. Buradaki her şey, bu garip ve emule edilmiş JBIG2 akışından geçen tek bir decompression’dan oluşturulmuştur. İnanılmaz bir durumdur ve aynı zamanda oldukça ürkütücüdür.

Kaynak: Project Zero: A deep dive into an NSO zero-click iMessage exploit: Remote Code Execution

3 Beğeni