Xz/liblzma: Bash aşaması gizlemesinin açıklaması
(gynvael.coldwind.pl)- 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.m4içinden başlıyor; bozuk gibi görünen bir xz stream’ini onarıyor vexz -dile Stage 1 script’ini çıkarıp çalıştırıyor - Stage 1,
good-large_compressed.lzmaiç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,
.odosyası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.xztests/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.m4içindeki kod; derleme sürecinin bir yerinde çalıştırılıyor gibi görünüyor - Temel pipeline,
sed,trvexz -dkomutlarını bağlayarak Stage 1 script’ini çıkarıyor tests/files/bad-3-corrupt_lzma2.xzdosyasının byte’ları okunduktan sonratr "\t \-_" " \t_\-"ile bazı byte’lar değiştiriliyor0x09tab karakteri0x20boşluğa dönüşüyor0x20boşluk0x09tab karakterine dönüşüyor0x2dtire0x5falt çizgiye dönüşüyor0x5falt çizgi0x2dtireye dönüşüyor
- Bu yerine koyma işlemi,
bad-3-corrupt_lzma2.xzdosyasını yeniden geçerli bir xz stream’i haline getiriyor - Sonunda onarılan stream
xzile 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.0:
- 5.6.1’e
[ ! $(uname) = "Linux" ] && exit 0biç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 sonraeval $iile ç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 -dcile 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
- Önce
- Bu
headzinciri, boş düşük entropili alanları kaldırıp yalnızca gerçek verinin bulunduğu alanları ardışık hale getiriyor - Ardından
tail -c +31233ile 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"
- 5.6.0:
- Yerine koyma uygulanmış veri
xz -F raw --lzma1 -dcile 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.txtdosyası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,
~!:_ Wve|_!{ -arıyor - Fragment 3,
jV!.^%ve%.R.1Zarıyor
- Fragment 1,
- İ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
tranahtarıyla yerine koyma yöntemiyle çözülüyor; ardındanxz -F raw --lzma2 -dcile açılıpevalile ç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ı,
.odosyası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 -dcile açılıyor- Stage 1’de kullanılan
eval $iiçindekiheadzinciri 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) % 256ile 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) % 256biçiminde toplama kullanıyor
- Anahtar dizisi
- Şifresi çözülen veri
xz -dc --single-streamile açılıyor - Ardından gereken byte aralığı
headile kesilipliblzma_la-crc64-fast.oolarak 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
sedtrxzheadtailawkgrepcutexpr
- 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
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;
#includesatırının hemen altındaki satırın en solunda bir nokta görünüyorhttps://git.tukaani.org/?p=xz.git;a=commitdiff;h=328c52da8a2...
https://old.reddit.com/r/linux/comments/1brhlur/xz_utils_bac...
+,+.,+gibi görünüyor ve o tek nokta fark edilmiyorSadece 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
a) Bu paketi cmake ile derleyen neredeyse hiç kimse yok
b) cmake ve
-DENABLE_SANDBOX=landlockile derlemeyi denerseniz build doğrudan başarısız oluyor: https://i.imgur.com/7xbeWFx.pngO 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
Bir sonraki saldırgan, çalışma süresindeki artış gibi izler bırakma konusunda çok daha az dikkatsiz olacaktır
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
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-streamkullanı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.xzBu 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
Devasa bir
.shdosyası lisansı gösterip onay aldıktan sonra kendisinicatediyor, ardındanhead/tailboru hattından geçiripcpioile asıl varlıkları çıkarıyorDaha 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ı
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
Azımsanmayacak sayıda araç bu şekilde derleniyor
Şü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
execedemesin diye çalıştırmanın bir yolu var mı? Bash'te bir tür “güvenli mod” olsa nasıl olurdu diye düşündümAma 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
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
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
Ü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
LTS dağıtımları kullanmak bir ölçüde koruma sağlayabilir. Slackware pakette lzma, yani
tar.xzkullanıyor gibi görünüyor ama-currentdışı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
-currentdasshdyi xz'ye linklemiyor ve systemd de kullanmıyor