1 puan yazan GN⁺ 2024-03-31 | 1 yorum | WhatsApp'ta paylaş
  • xz/liblzma arka kapısı yalnızca nihai ikili payload’dan ibaret değil; derleme sırasında çalıştırılan Bash aşaması, birçok katmanlı çıkarma ve şifre çözme adımıyla gizlendiği için analizi zorlaştırıyor
  • Etkilenen sürümler xz/liblzma 5.6.0 ve 5.6.1; gizlenmiş script ve ikili payload, test dosyası gibi görünen iki dosyanın içinde yer alıyor
  • Stage 0, m4/build-to-host.m4 içinden başlıyor; bozuk gibi görünen bir xz stream’ini onarıyor ve xz -d ile Stage 1 script’ini çıkarıp çalıştırıyor
  • Stage 1, good-large_compressed.lzma içinden veriyi kesip çıkarıyor ve dönüştürdükten sonra Stage 2 Bash script’ini çalıştırıyor; 5.6.1’de Linux’ta çalışıp çalışmadığını 5 kez kontrol eden kod eklenmiş
  • Stage 2, .o dosyasını derleme ve linkleme sürecine araya sokmak için file carving, yerine koyma şifresi ve AWK tabanlı RC4 türevi şifre çözme kullanıyor; 5.6.1’de gelecekte ek script’lerin çalıştırılmasını sağlayabilecek bir genişletme yapısı da bulunuyor

xz/liblzma arka kapısının Bash aşamalarının kapsamı

  • Andres Freund, oss-security e-posta listesinde xz/liblzma arka kapısının keşfedildiğini duyurdu; bu arka kapı OpenSSH server’ı etkiliyor
  • Analiz konusu ikili arka kapının kendisi değil, ondan önce çalışan Bash tabanlı ilk aşamalar ve gizleme yöntemi
  • Bash akışı Stage 0’dan Stage 2’ye kadar ilerliyor; tamamen uygulanmamış gibi görünen olası bir Stage 3’ün bazı izleri de ortaya çıkıyor
  • Gizlenmiş ve şifrelenmiş aşamalar ile sonraki ikili arka kapı iki test dosyasının içinde saklanıyor
    • tests/files/bad-3-corrupt_lzma2.xz
    • tests/files/good-large_compressed.lzma
  • Etkilenen sürümler 5.6.0 ve 5.6.1; iki sürüm arasında küçük farklar var

Stage 0: Bozuk test dosyası gibi görünen xz stream’ini onarma

  • Başlangıç noktası m4/build-to-host.m4 içindeki kod; derleme sürecinin bir yerinde çalıştırılıyor gibi görünüyor
  • Temel pipeline, sed, tr ve xz -d komutlarını bağlayarak Stage 1 script’ini çıkarıyor
  • tests/files/bad-3-corrupt_lzma2.xz dosyasının byte’ları okunduktan sonra tr "\t \-_" " \t_\-" ile bazı byte’lar değiştiriliyor
    • 0x09 tab karakteri 0x20 boşluğa dönüşüyor
    • 0x20 boşluk 0x09 tab karakterine dönüşüyor
    • 0x2d tire 0x5f alt çizgiye dönüşüyor
    • 0x5f alt çizgi 0x2d tireye dönüşüyor
  • Bu yerine koyma işlemi, bad-3-corrupt_lzma2.xz dosyasını yeniden geçerli bir xz stream’i haline getiriyor
  • Sonunda onarılan stream xz ile açılıyor; hatalar yok sayılarak Stage 1 script’i elde ediliyor ve hemen çalıştırılıyor
    • Stream kesilmiş gibi görünse de anlamlı çıktı zaten yazılmış olduğu için sorun oluşturmuyor

Stage 1: good-large_compressed.lzma içinden Stage 2’yi çıkarma

  • Stage 1, Andres Freund’un e-postasında ####Hello#### ile başlayan kısa Bash dosyasıdır
  • İki sürüm arasındaki ilk fark, ikinci satırdaki yorum içinde bulunan rastgele byte’lardır
    • 5.6.0: 86 F9 5A F7 2E 68 6A BC
    • 5.6.1: E5 55 89 B7 24 04 D8 17
  • 5.6.1’e [ ! $(uname) = "Linux" ] && exit 0 biçiminde bir Linux kontrolü eklenmiş; aynı kontrol 5 kez tekrarlanıyor
  • Stage 2 çıkarma işlemi uzun export i=... satırıyla başlıyor; bu değer daha sonra eval $i ile çağrılan bir tür fonksiyon gibi kullanılıyor
  • Çıkarma akışı, standart çıktıyı ve standart girdiyi sürekli birbirine bağlayan bir pipeline yapısı
    • Önce good-large_compressed.lzma, xz -dc ile açılıyor
    • eval $i, head çağrılarını tekrarlayarak 1024 byte’ı atıp 2048 byte’ı çıktılama desenini sürdürüyor
    • Dosya sonunda 5.6.0 724 byte, 5.6.1 ise 939 byte çıktı veriyor
  • Bu head zinciri, boş düşük entropili alanları kaldırıp yalnızca gerçek verinin bulunduğu alanları ardışık hale getiriyor
  • Ardından tail -c +31233 ile baştaki kısım atılıyor
    • Bu baş kısımda bir sonraki aşamada çıkarılan ikili arka kapı saklanıyor
    • 5.6.0’da ilk 31264 byte’ın, 5.6.1’de ise 31232 byte’ın atılması etkisi oluşuyor
  • Sonra tr, basit bir yerine koyma şifresi gibi kullanılıyor; 5.6.0 ve 5.6.1’in eşleme anahtarları farklı
    • 5.6.0: tr "\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131" "\0-\377"
    • 5.6.1: tr "\114-\321\322-\377\35-\47\14-\34\0-\13\50-\113" "\0-\377"
  • Yerine koyma uygulanmış veri xz -F raw --lzma1 -dc ile tekrar açılıyor ve ortaya çıkan Stage 2 hemen çalıştırılıyor

Stage 2: Derleme sürecini değiştirme ve genişletme mekanizması

  • Stage 2, Andres Freund’un ilk e-postasına eklediği infected.txt dosyasıdır; gerçek derleme süreci değişikliği bu Bash script’iyle yapılıyor
  • Gizleme açısından Stage 2’de üç ilginç parça var; bunlardan ikisi yalnızca 5.6.1’de görülüyor
  • 5.6.1’deki iki parça birbirine neredeyse aynı; tests/files/ altında belirli imzalara sahip bir dosya arıyor
    • Fragment 1, ~!:_ W ve |_!{ - arıyor
    • Fragment 3, jV!.^% ve %.R.1Z arıyor
  • İmzalar bulunduğunda dosya adı ve offset çıkarılıyor; ilk imzadan sonrası ile ikinci imzadan öncesi arasındaki byte aralığı kesilip alınıyor
  • Kesilip alınan veri, 5.6.0’da kullanılan tr anahtarıyla yerine koyma yöntemiyle çözülüyor; ardından xz -F raw --lzma2 -dc ile açılıp eval ile çalıştırılıyor
  • İncelenen 5.6.0 ve 5.6.1 TAR arşivlerinde bu imzalara sahip bir dosya yoktu
  • Bu yapı, mevcut payload taşıyan test dosyasını yeniden değiştirmeden yalnızca yeni bir test dosyası ekleyerek gelecekte script çalıştırmayı mümkün kılan bir genişletme/yama sistemi gibi görünüyor

Stage 2: İkili arka kapı .o dosyasını çıkarma

  • Stage 2’nin bir diğer kilit kısmı, .o dosyasını çıkarıp derleme ve linkleme sürecine araya sokan koddur
  • Çıkarılan hedef çıktı liblzma_la-crc64-fast.o; nihai ikili arka kapı budur
  • Çıkarma süreci, önceki aşamalarda olduğu gibi birçok standart aracı bir pipeline içinde birbirine bağlıyor
    • good-large_compressed.lzma, xz -dc ile açılıyor
    • Stage 1’de kullanılan eval $i içindeki head zinciri yeniden kullanılıyor
    • sed "s/\(.\)/\1\n/g" ile her byte’ın arkasına satır sonu eklenerek byte başına bir satır biçimine getiriliyor
  • Sonraki AWK script’i, girdi stream’i üzerinde RC4 benzeri şifre çözme uyguluyor
    • Anahtar dizisi ((i * 7) + 5) % 256 ile oluşturuluyor
    • 5.6.0, çıktı PRNG stream’inin ilk 4096 byte’ını atlıyor
    • 5.6.1, 8192 byte’ı atlıyor
    • Orijinal RC4 XOR kullanır, ancak bu kod (v + k) % 256 biçiminde toplama kullanıyor
  • Şifresi çözülen veri xz -dc --single-stream ile açılıyor
  • Ardından gereken byte aralığı head ile kesilip liblzma_la-crc64-fast.o olarak kaydediliyor
    • Sıkıştırılmış ama kısmen değiştirilmiş payload boyutu 5.6.0’da 88792 byte, 5.6.1’de 88664 byte
    • Nihai çıkarma aralığı yaklaşık 86 KB boyutunda

Yalnızca standart araçlarla oluşturulmuş gizlemenin niteliği

  • Tüm Bash aşamaları yalnızca standart komut satırı araçlarından oluşuyor
    • sed
    • tr
    • xz
    • head
    • tail
    • awk
    • grep
    • cut
    • expr
  • Payload, normal test dosyası gibi görünen ikili dosyalarda saklanıyor ve yalnızca gereken bölümler file carving ile çıkarılıyor
  • Gizlemede basit yerine koyma şifresi ve AWK tabanlı RC4 türevi şifre çözme birlikte kullanılıyor
  • Çalıştırma toplam 3 aşama halinde ilerliyor; 5.6.1’de daha sonra ayrı bir test dosyası ekleyerek script çalıştırılmasını sağlayabilecek bir yapı bulunuyor
  • Genel yapı zararsız görünmesi ve iyi saklanması için büyük emek harcanmış bir biçimde tasarlanmış; eğer tesadüfen keşfedilmiş bir örnekse, henüz bulunmamış benzer örneklerden kaç tane kaldığı sorusunu gündeme getiriyor

1 yorum

 
GN⁺ 2024-03-31
Hacker News yorumları
  • Basitleştirilmiş açıklama ve gürültü görseli karşılaştırması sayesinde, insanların bahsettiği inceliğin ne anlama geldiği daha iyi anlaşılıyor
    reddit'te “sandboxing” yaklaşımının tek bir nokta yüzünden bozulduğunu anlatan bir şey de gördüm; #include satırının hemen altındaki satırın en solunda bir nokta görünüyor
    https://git.tukaani.org/?p=xz.git;a=commitdiff;h=328c52da8a2...
    https://old.reddit.com/r/linux/comments/1brhlur/xz_utils_bac...

    • Gerçekten şeytanca. diff'te sadece +, +., + gibi görünüyor ve o tek nokta fark edilmiyor
    • Bu tür derleme/inşa zamanı koşullu ifadelerinin kullanılmasından gerçekten nefret ediyorum. Ne zaman açılması gerekiyorsa açıldığını ve kapanması gerekiyorsa kapandığını test etmek zor, özellikle de birim test çerçevesi olmayan build sistemlerinin içindeyse daha da zor
      Sadece basit bir hatayla testlerin her zaman başarısız ya da her zaman başarılı olması bile baş ağrıtıcı; bunun neden kötü niyetli davranışlar için uygun bir hedef olduğunu görmek kolay
    • Bunun kasıtlı değil, basit bir hata olma ihtimali çok daha yüksek
      a) Bu paketi cmake ile derleyen neredeyse hiç kimse yok
      b) cmake ve -DENABLE_SANDBOX=landlock ile derlemeyi denerseniz build doğrudan başarısız oluyor: https://i.imgur.com/7xbeWFx.png
      O nokta sandboxing'i devre dışı bırakmıyor; sadece cmake ile derlemeyi imkansız hale getiriyor. Biri gerçekten cmake ile derlemeyi deneseydi hatayı görüp bir şeylerin yanlış olduğunu anlardı; bu yüzden bunu güvenliği düşürmeye yönelik kötü niyetli bir girişim olarak görmek pek mantıklı değil
  • “Bu tesadüfen bulunduysa, daha bulunmamış ne kadar şey var?” asıl önemli soru bu
    Andres Freund'ın arka kapı yerleştirilmiş popüler açık kaynak projeler arasında tek örneği tesadüfen bulmuş olması mümkün görünmüyor. Doğada bundan bir düzine daha olsa şaşırmam

    • Buna tesadüfen bulmak demek yerine, bir şeylerin ters gittiğini sezdi demek daha doğru. İkisi farklı
      Bir sonraki saldırgan, çalışma süresindeki artış gibi izler bırakma konusunda çok daha az dikkatsiz olacaktır
    • Olabilir, ama pratikte çok fazla kritik örnek olmayabilir. Daha fazla olsaydı sanırım bunun gibi durumlarla daha sık karşılaşırdık
  • Buradaki rahatsız edici düşünce, saldırı yolunu açanın birim testler olması. Testler olmasaydı bunu böyle gizlemek çok daha zor olurdu

    • Saldırgan, ilk yükün izlerini README içindeki zararsız bir paragrafla bile örtmüş. Adeta “Bakılacak bir şey yok!” der gibi
      bad-3-corrupt_lzma2.xz üç akış içeriyor ve birinci ile üçüncü geçerli xz akışları. Ortadaki akışta akış başlığı, blok başlığı, indeks ve akış sonlandırıcısı doğru; yalnızca LZMA2 verisi bozuk ve --single-stream kullanılırsa açılması gerektiği yazıyor
      ####Hello#### ve ####World#### dizgeleri, README talimatları gerçekten izlendiğinde makul görünen normal bir sonuç üretmesini sağlıyor
      $ cat tests/files/bad-3-corrupt_lzma2.xz | xz -d --single-stream
      ####Hello####
      Bu dizgeler shell yorumu olduğu için yükün çalışmasını engellemiyor
      Son olarak, daha sonra düzenli ifadenin dosya adına doğrudan bakmadan ya da gerçek Hello/World dizgelerini kullanmadan dosyayı bulabilmesi için bir işaretleyici görevi görüyor
      $ gl_am_configmake=\grep -aErls "#{4}[[:alnum:]]{5}#{4}$" $srcdir/ 2>/dev/null``
      $ echo $gl_am_configmake
      ./tests/files/bad-3-corrupt_lzma2.xz
    • Güvenlik açısından kritik projelerde, ikili dosyalar build sürecine dahil edildiğinde build altyapısının hata vermesi ya da en azından uyarı üretmesi mantıklı görünüyor
      Bu denetim geçişli olarak uygulanmalı; yani Linux dağıtımı yeni bir liblzma sürümüne güncellemeye çalıştığında da yeni ikili bağımlılık nedeniyle build başarısız olmalı ya da en azından uyarı vermeli
      Bunun Linux dağıtımı build'lerinde ne kadar yaygın bir uygulama olduğunu bilmiyorum. Yaygınsa temizlemek için muazzam bir iş gerekir ve bunun baştan mümkün olup olmadığı da belirsiz. bazel ile mümkün görünüyor ama diğer build sistemlerini bilmiyorum
  • GitHub'da buna benzer head | tail hilesini daha önce arayan biri oldu mu merak ediyorum. Bunun sırf bu olay için icat edildiğini sanmıyorum

    • Ticari yazılım şirketlerinin Unix kurulum programlarında buna epey rastladım
      Devasa bir .sh dosyası lisansı gösterip onay aldıktan sonra kendisini cat ediyor, ardından head/tail boru hattından geçirip cpio ile asıl varlıkları çıkarıyor
    • Zekice ama tamamen yeni değil; hatta bu araçların amaçlanan kullanım senaryolarına oldukça yakın
    • Makale yazmak için bir fırsat
  • Daha iyi bir cevap yok ama bu anlaşılması güç bash yığınının kendisi zaten bir kötü koku gibi durmuyor mu diye düşünüyorum
    Geliştirme dünyasının diğer alanlarında da olduğu gibi, olup bitenin daha net görülmesi için bunu daha az opak yazmanın bir yolu yok muydu?
    Yöneticinin dış katkıcılar kadar sıkı bir inceleme olmadan kötü amaçlı kod ekleyebileceğini biliyorum ama “özlü” bir kod yığını yerine, fiilen istemsiz obfuscation gibi görünen bir yaklaşımdan daha iyi bir yol olmalı

    • Bu çok eski bir kötü koku
      Asıl sorun, 80'ler ve 90'larda her birinin kendi kusurları ve eksik özellikleri olan çok sayıda Unix türevi sistem bulunması ve yazılım yazarlarının derleme bağımlılıklarını en aza indirmek istemesiydi
      Bu yüzden birçok topluluk, her yerde çalışan shell script'lerle derlemeyi otomatikleştirme konusunda standartlaştı. Ama shell script yazmak acı vericiydi ve insanlar M4 makro önişlemcisi gibi araçlarla shell script üretmeye başladı
      Sonuç olarak, birinin AIX ya da bozuk antik bir Unix üzerinde kod çalıştırmak istemesi ihtimaline karşı, birçok proje devasa ve opak shell script yığınlarına sahip oldu
      Bu delinmesi zor shell çalılığını ortadan kaldırmak için desteklenen platform sayısını ciddi biçimde azaltmak, daha temiz derleme araçlarında standartlaşmak ve taşınabilir derlemeler için shell gerektirmeyen dillerde daha fazla çekirdek altyapı oluşturmak gerekir
      Ama bu devasa bir iş ve çekirdek C kütüphanelerinin önemli bir kısmı bir ya da iki maaşsız gönüllü tarafından bakılıyor. “Obscurnix-1997” desteğini bırakmak da çoğu zaman epey tartışmalı bir karar oluyor
      Bu yüzden çekirdek altyapının önemli bir bölümü hâlâ kaynağı belirsiz makine üretimi shell script bataklığıyla çevrili
    • O shell insan eliyle yazılmış değil, üretilmiş kod. autoconf çok yaygın kullanıldığı için üretilmiş shell yapılandırma kodu dağlar gibi birikti ve autoconf, M4 makro önişlemci script'leriyle binlerce satırlık okunması zor taşınabilir shell script üretiyor
      Azımsanmayacak sayıda araç bu şekilde derleniyor
    • İlk bakışta bash'ın anlaşılması zor görünmesi tek başına bir alarm işareti değil bence. Sıkıştırılmış yazılmış bash script'leri bazen öyle görünür. Böyle sıkıştırılmış yazılması gerekip gerekmediği ise ayrı bir tartışma
      Şüpheli görünen şey, tekrar eden tr çağrıları. Böyle bir şey gördüğümde birinin kurnazlık yapmaya çalıştığını düşünürüm ve burada “kurnazlık” olumsuz anlamda. Ben yönetici olsam böyle bir kod geldiğinde ne yaptığını açıklamasını isterdim. Çünkü bu tür zincirlemelerden kaçınan daha iyi çözümler neredeyse her zaman vardır
      Asıl sorun, bu kod geldiğinde bakacak başka bir yöneticinin olmamasıydı. Yığının önemli bir bileşeni tek bir kişiye bağlıydı ve bu olayda o kişi kötü niyetliydi
    • “İstemeden” değil, asıl mesele bunun kasıtlı olarak obfuscation yapılmış olması
    • XV arka kapısının riskini yanlış anlamış olabilirim ama bash'ı bir şey exec edemesin diye çalıştırmanın bir yolu var mı? Bash'te bir tür “güvenli mod” olsa nasıl olurdu diye düşündüm
      Ama xv'nin configure script'inde bash'ı böyle varsayımsal bir “güvenli mod”da nasıl çalıştırabileceğimi hayal bile edemediğim için bunu geri alıyorum
  • https://github.com/tukaani-project/.github/issues/2

    • Oldukça komik. Bu sadece korkunç, kasıtlı bir arka kapı değil; aynı zamanda arka kapı türetilmiş bir eser olduğu, kaynak kodu dahil edilmediği ve “değişiklik yapmak için tercih edilen biçimde” dağıtılmadığı için aynı zamanda bir GPL ihlali
  • Derleme araçları ve eski Unix yardımcı programları dahil tüm C ekosistemi, istismar edilmeyi bekleyen bir güvenlik keşmekeşi ve sonunda istismar edilecek
    Sadece tek bir noktayla her şeyi mahvetmenin ne kadar kolay olduğuna bakmak yeterli. İnsanların artık dünyanın güvenliğini C'ye emanet edemeyeceğimizi anlaması gerekiyor
    Modern araç zincirlerine sahip Ada ya da Rust kullanılmasını isterim

    • Rust'a bir nokta ekleyince bozulmuyor mu?
  • Bunun kod incelemeden geçip nasıl birleştirildiğini gerçekten anlamıyorum. Bir şeyi kaçırmıyorsam akıl almaz derecede dikkatsizce görünüyor

    • Kötü niyetli aktör deponun ortak yöneticilerinden biriydi, bir süredir asıl yöneticiden daha aktifti ve tam commit yetkisine sahipti. PR ya da inceleme olmadan doğrudan master'a commit atıldı
      Üstelik bu, test dosyası olarak işaretlenmiş ikili dosyaların, yani “good”/“bad” xz sıkıştırma test dosyalarının içine yoğun biçimde obfuscation uygulanarak gizlenmişti. Ne arayacağını bilmiyorsan fark etmenin yolu yok
    • Test dosyasının commit mesajı bunun bir rastgele sayı üreteciyle yapıldığını iddia ediyor. Sürüm tarball'unu oluşturan kişi son satırı uygun yere koymuş ama bunu depoya check-in etmemiş
    • Etkin yöneticisi tek kişi olan paketlerde kod inceleme yoktur
  • LTS dağıtımları kullanmak bir ölçüde koruma sağlayabilir. Slackware pakette lzma, yani tar.xz kullanıyor gibi görünüyor ama -current dışındaki son kararlı sürüm bu sorundan etkilenmedi
    Özgür yazılım tarafına bir adım daha gitmek isterseniz Hyperbola GNU da bu sorundan etkilenmedi
    Ayrıca Slackware -current da sshdyi xz'ye linklemiyor ve systemd de kullanmıyor