Xz/liblzma: Penjelasan Obfuskasi Tahap Bash
(gynvael.coldwind.pl)- Backdoor xz/liblzma bukan hanya soal payload biner akhir; tahap Bash yang dijalankan saat build disembunyikan lewat berlapis-lapis proses ekstraksi dan dekripsi, sehingga sulit dianalisis
- Versi yang terdampak adalah xz/liblzma 5.6.0 dan 5.6.1, dan skrip yang diobfuskasi serta payload biner berada di dua berkas yang tampak seperti berkas pengujian
- Stage 0 dimulai dari
m4/build-to-host.m4, memulihkan stream xz yang tampak rusak, lalu mengekstrak dan menjalankan skrip Stage 1 denganxz -d - Stage 1 memotong dan mengganti data dari
good-large_compressed.lzma, lalu menjalankan **skrip Bash Stage 2`; pada 5.6.1 ditambahkan kode yang memeriksa apakah berjalan di Linux sebanyak 5 kali - Stage 2 menggunakan file carving, cipher substitusi, dan dekripsi varian RC4 berbasis AWK untuk menyisipkan berkas
.oke proses build dan linking; 5.6.1 juga memuat struktur ekstensi yang dapat menjalankan skrip tambahan di masa mendatang
Cakupan tahap Bash pada backdoor xz/liblzma
- Andres Freund mengumumkan penemuan backdoor xz/liblzma di milis oss-security, dan backdoor ini berdampak pada OpenSSH server
- Fokus analisis bukan backdoor biner itu sendiri, melainkan tahap awal berbasis Bash yang berjalan sebelumnya serta cara obfuscasinya
- Alur Bash berlanjut dari Stage 0 hingga Stage 2, dan sebagian juga memperlihatkan potensi Stage 3 yang tampaknya belum diimplementasikan sepenuhnya
- Tahap yang diobfuskasi/dienkripsi dan backdoor biner berikutnya disembunyikan di dalam dua berkas pengujian
tests/files/bad-3-corrupt_lzma2.xztests/files/good-large_compressed.lzma
- Versi yang terdampak adalah 5.6.0 dan 5.6.1, dengan sedikit perbedaan di antara keduanya
Stage 0: memulihkan stream xz yang tampak seperti berkas pengujian rusak
- Titik awalnya adalah kode di
m4/build-to-host.m4, dan tampaknya dijalankan di suatu titik dalam proses build - Pipeline intinya menghubungkan
sed,tr, danxz -duntuk mengekstrak skrip Stage 1 - Setelah membaca byte dari
tests/files/bad-3-corrupt_lzma2.xz, sebagian byte diganti dengantr "\t \-_" " \t_\-"- Tab
0x09diubah menjadi spasi0x20 - Spasi
0x20diubah menjadi tab0x09 - Tanda hubung
0x2ddiubah menjadi underscore0x5f - Underscore
0x5fdiubah menjadi tanda hubung0x2d
- Tab
- Substitusi ini berfungsi mengubah
bad-3-corrupt_lzma2.xzkembali menjadi stream xz yang valid - Pada akhirnya, stream yang telah dipulihkan didekompresi dengan
xz, dan skrip Stage 1 didapatkan serta langsung dijalankan sambil mengabaikan error- Stream tampak terpotong, tetapi itu tidak menjadi masalah karena output yang bermakna sudah ditulis
Stage 1: mengeluarkan Stage 2 dari good-large_compressed.lzma
- Stage 1 adalah berkas Bash pendek yang dimulai dengan
####Hello####dalam email Andres Freund - Perbedaan pertama antara kedua versi adalah byte acak pada komentar baris kedua
- 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:
- Pada 5.6.1 ditambahkan pemeriksaan Linux berbentuk
[ ! $(uname) = "Linux" ] && exit 0, dan pemeriksaan yang sama diulang 5 kali - Ekstraksi Stage 2 dimulai dari baris panjang
export i=..., dan nilai ini kemudian digunakan seperti semacam fungsi yang dipanggil denganeval $i - Alur ekstraksinya berupa struktur pipeline yang terus menyambungkan standard output dan standard input
- Pertama,
good-large_compressed.lzmadidekompresi denganxz -dc eval $iberulang kali memanggilheaddengan pola membuang 1024 byte dan mengeluarkan 2048 byte- Di akhir berkas, 5.6.0 mengeluarkan 724 byte, sedangkan 5.6.1 mengeluarkan 939 byte
- Pertama,
- Rangkaian
headini menghapus area kosong berentropi rendah dan hanya menyambungkan area yang berisi data nyata - Setelah itu, bagian awal dibuang dengan
tail -c +31233- Di bagian awal ini tersembunyi backdoor biner yang diekstrak pada tahap berikutnya
- Efeknya, pada 5.6.0 sebanyak 31264 byte pertama dibuang, sedangkan pada 5.6.1 sebanyak 31232 byte pertama dibuang
- Berikutnya
trdigunakan seperti cipher substitusi sederhana, dengan kunci pemetaan yang berbeda antara 5.6.0 dan 5.6.1- 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:
- Data yang telah disubstitusi didekompresi lagi dengan
xz -F raw --lzma1 -dc, lalu Stage 2 hasilnya langsung dijalankan
Stage 2: modifikasi proses build dan mekanisme ekstensi
- Stage 2 adalah berkas
infected.txtyang dilampirkan Andres Freund pada email aslinya, berupa skrip Bash tempat modifikasi proses kompilasi sesungguhnya terjadi - Dari sudut pandang obfuskasi, Stage 2 memiliki tiga potongan menarik, dua di antaranya hanya muncul pada 5.6.1
- Dua potongan pada 5.6.1 hampir identik satu sama lain dan mencari berkas dengan signature tertentu di
tests/files/- Fragment 1 mencari
~!:_ Wdan|_!{ - - Fragment 3 mencari
jV!.^%dan%.R.1Z
- Fragment 1 mencari
- Jika signature ditemukan, nama berkas dan offset diekstrak, lalu rentang byte dari setelah signature pertama hingga sebelum signature kedua dipotong
- Data yang dipotong didekripsi dengan substitusi menggunakan kunci
tryang dipakai pada 5.6.0, kemudian didekompresi denganxz -F raw --lzma2 -dcdan dijalankan denganeval - Dalam arsip TAR 5.6.0 dan 5.6.1 yang diperiksa, tidak ada berkas dengan signature tersebut
- Struktur ini tampak seperti sistem ekstensi/patch yang memungkinkan skrip dijalankan di masa mendatang hanya dengan menambahkan berkas pengujian baru, tanpa perlu kembali mengubah berkas pengujian pembawa payload yang sudah ada
Stage 2: ekstraksi berkas .o backdoor biner
- Inti lain dari Stage 2 adalah kode yang mengekstrak berkas
.odan menyisipkannya ke proses kompilasi/linking - Artefak hasil ekstraksinya adalah
liblzma_la-crc64-fast.o, dan inilah backdoor biner final - Proses ekstraksi, seperti tahap sebelumnya, menghubungkan berbagai tool standar dalam pipeline
good-large_compressed.lzmadidekompresi denganxz -dc- Rangkaian
headdarieval $iyang digunakan pada Stage 1 dipakai lagi sed "s/\(.\)/\1\n/g"menambahkan newline setelah setiap byte, sehingga menjadi format satu baris per byte
- Skrip AWK berikutnya melakukan dekripsi mirip RC4 terhadap stream input
- Array kunci dibuat dengan
((i * 7) + 5) % 256 - 5.6.0 melewati 4096 byte pertama dari stream PRNG output
- 5.6.1 melewati 8192 byte pertama
- RC4 asli menggunakan XOR, tetapi kode ini memakai penjumlahan berbentuk
(v + k) % 256
- Array kunci dibuat dengan
- Data yang telah didekripsi didekompresi dengan
xz -dc --single-stream - Setelah itu, rentang byte yang diperlukan dipotong dengan
headdan disimpan sebagailiblzma_la-crc64-fast.o- Ukuran payload yang terkompresi tetapi sebagian dimodifikasi adalah 88792 byte pada 5.6.0 dan 88664 byte pada 5.6.1
- Rentang ekstraksi final berukuran sekitar 86KB
Karakter obfuskasi yang dibuat hanya dengan tool standar
- Seluruh tahap Bash tersusun hanya dari tool command-line standar
sedtrxzheadtailawkgrepcutexpr
- Payload disembunyikan dalam berkas biner yang tampak seperti berkas pengujian normal, lalu hanya area yang diperlukan dikeluarkan lewat file carving
- Obfuskasinya memakai gabungan cipher substitusi sederhana dan dekripsi varian RC4 berbasis AWK
- Eksekusi berlangsung dalam total 3 tahap, dan pada 5.6.1 terdapat struktur yang memungkinkan skrip dijalankan nanti dengan menambahkan berkas pengujian terpisah
- Struktur keseluruhan ini dibuat dengan banyak upaya agar tampak tidak berbahaya dan tersembunyi dengan baik; jika ini adalah kasus yang ditemukan secara kebetulan, ia meninggalkan pertanyaan berapa banyak kasus serupa yang belum ditemukan
1 komentar
Komentar Hacker News
Berkat penjelasan yang disederhanakan dan perbandingan gambar noise, jadi lebih kebayang apa yang orang maksud dengan kecanggihan
Saya juga melihat di reddit bahwa metode “sandboxing” dirusak hanya oleh satu titik, dan ada titik di paling kiri baris tepat setelah
#includehttps://git.tukaani.org/?p=xz.git;a=commitdiff;h=328c52da8a2...
https://old.reddit.com/r/linux/comments/1brhlur/xz_utils_bac...
+,+.,+, dan satu titik itu nyaris tak terlihatBahkan jika hanya karena kesalahan sederhana sampai pengujian selalu gagal atau selalu berhasil pun sudah merepotkan, jadi mudah terlihat kenapa ini jadi sasaran yang bagus untuk perilaku jahat
a) praktis tidak ada yang membangun paket ini dengan cmake
b) jika dibangun dengan cmake dan
-DENABLE_SANDBOX=landlock, build-nya langsung gagal: https://i.imgur.com/7xbeWFx.pngTitik itu bukan menonaktifkan sandboxing, melainkan hanya membuatnya tidak bisa dibangun dengan cmake. Jika benar ada orang yang mencoba membangunnya dengan cmake, mereka akan melihat error dan sadar ada yang salah, jadi tidak masuk akal jika ini dianggap upaya jahat untuk menurunkan keamanan
Jika ini ditemukan secara kebetulan, maka pertanyaan terpenting adalah “berapa banyak yang masih belum ditemukan”
Sulit dipercaya bahwa Andres Freund secara kebetulan menemukan satu-satunya proyek open source populer yang telah disusupi backdoor. Tidak akan mengejutkan kalau sudah ada selusin hal seperti ini di alam liar
Penyerang berikutnya kemungkinan akan jauh lebih hati-hati agar tidak meninggalkan jejak seperti peningkatan waktu eksekusi
Pikiran yang mengganggu di sini adalah bahwa unit test justru membuka jalur serangan. Tanpa test, akan jauh lebih sulit menyembunyikan ini
bad-3-corrupt_lzma2.xzmemiliki tiga stream, dan stream pertama serta ketiga adalah stream xz yang valid. Stream di tengah memiliki stream header, block header, index, dan stream footer yang benar, hanya data LZMA2-nya yang rusak, dan tertulis bahwa jika memakai--single-streammaka itu seharusnya bisa didekompresiString
####Hello####dan####World####membuat hasil normal yang masuk akal muncul ketika instruksi README benar-benar diikuti$ cat tests/files/bad-3-corrupt_lzma2.xz | xz -d --single-stream####Hello####String-string ini adalah komentar shell sehingga tidak mengganggu eksekusi payload
Terakhir, di kemudian hari ini berfungsi sebagai penanda agar regex bisa menemukan file tersebut tanpa langsung merujuk ke nama file atau memakai string Hello/World yang sebenarnya
$ gl_am_configmake=\grep -aErls "#{4}[[:alnum:]]{5}#{4}$" $srcdir/ 2>/dev/null``$ echo $gl_am_configmake./tests/files/bad-3-corrupt_lzma2.xzPemeriksaan ini harus berlaku secara transitif, sehingga ketika distribusi Linux mencoba memperbarui ke versi liblzma baru, build juga harus gagal atau memunculkan peringatan karena adanya dependensi biner baru
Saya tidak tahu seberapa umum praktik seperti ini dalam build distribusi Linux. Kalau ternyata umum, membersihkannya akan jadi pekerjaan besar, dan belum jelas apakah itu benar-benar memungkinkan. Dengan bazel tampaknya bisa, tapi saya kurang tahu soal sistem build lainnya
Saya penasaran apakah ada yang sudah mencari trik head | tail serupa di GitHub. Rasanya sulit percaya ini diciptakan khusus untuk kasus ini
File
.shbesar menampilkan lisensi dan meminta persetujuan, lalu melakukancatpada dirinya sendiri, melewatkannya melalui pipehead/tail, dan mengekstrak aset sebenarnya dengancpioTidak ada jawaban yang lebih baik, tetapi rasanya gumpalan bash yang sulit dipahami ini sendiri sudah merupakan tanda yang mencurigakan
Sama seperti di area lain dalam dunia pengembangan, tidakkah ini bisa ditulis dengan cara yang kurang buram agar lebih jelas terlihat apa yang sebenarnya terjadi?
Saya paham bahwa maintainer bisa menyisipkan kode berbahaya tanpa tinjauan seketat kontributor eksternal, tetapi seharusnya ada cara yang lebih baik daripada gumpalan kode yang “ringkas”, yang pada praktiknya terlihat seperti obfuscation yang tidak disengaja
Masalah intinya adalah pada era 80–90-an ada banyak sekali sistem turunan Unix, masing-masing dengan bug dan fitur yang hilang, dan para penulis perangkat lunak ingin meminimalkan dependensi build
Karena itu, banyak komunitas menstandarkan otomatisasi build dengan skrip shell yang bisa berjalan di mana saja. Tetapi skrip shell menyakitkan untuk ditulis, sehingga orang akhirnya menghasilkan skrip shell memakai alat seperti preprosesor makro M4
Hasilnya, banyak proyek memiliki gumpalan besar skrip shell yang buram, untuk berjaga-jaga jika suatu hari ada orang yang ingin menjalankan kodenya di AIX atau Unix kuno yang rusak
Untuk menyingkirkan rimba shell yang sulit ditembus ini, kita harus sangat mengurangi jumlah platform yang didukung, menstandarkan alat build yang lebih bersih, dan membangun lebih banyak infrastruktur inti dalam bahasa yang tidak memerlukan shell untuk build portabel
Tetapi ini pekerjaan besar, dan cukup banyak library C inti dikelola oleh satu atau dua sukarelawan tanpa bayaran. Menghentikan dukungan untuk “Obscurnix-1997” juga biasanya menjadi keputusan yang cukup kontroversial
Jadi cukup banyak infrastruktur inti masih dikelilingi rawa skrip shell hasil mesin yang tidak jelas asal-usulnya
Tidak sedikit alat yang dibangun dengan cara seperti ini
Yang terlihat mencurigakan adalah pemanggilan
tryang berulang. Kalau melihat itu, saya menganggap ada orang yang sedang berusaha terlalu “cerdik”, dan “cerdik” di sini bermakna negatif. Jika saya maintainer, saya akan meminta penjelasan tentang apa yang dilakukan kode seperti itu saat diajukan. Karena hampir selalu ada solusi yang lebih baik daripada chaining semacam ituMasalah sebenarnya adalah saat kode ini masuk, tidak ada maintainer lain yang meninjaunya. Komponen penting dalam stack bergantung pada satu orang, dan dalam kasus ini orang itu memang berniat jahat
execsesuatu? Rasanya akan menarik kalau bash punya semacam “mode aman”Tapi untuk skrip configure xv, saya sama sekali tidak bisa membayangkan bagaimana bash bisa dijalankan dalam “mode aman” hipotetis seperti itu, jadi lupakan saja
https://github.com/tukaani-project/.github/issues/2
Seluruh ekosistem C, termasuk alat build dan utilitas Unix lama, adalah kekacauan keamanan yang tinggal menunggu untuk dieksploitasi, dan pada akhirnya memang akan dieksploitasi
Lihat saja betapa mudahnya satu titik merusak segalanya. Orang-orang sekarang perlu sadar bahwa keamanan dunia tidak bisa lagi ditumpukan pada C
Saya berharap orang memakai Ada atau Rust dengan toolchain modern
Saya benar-benar tidak paham bagaimana ini bisa lolos code review dan di-merge. Kalau saya tidak melewatkan sesuatu, ini tampak sangat ceroboh sampai tidak masuk akal
Selain itu, ini sangat di-obfuscate di dalam file biner yang ditandai sebagai file uji, yaitu file uji kompresi xz “good”/“bad”. Kalau tidak tahu apa yang harus dicari, tidak ada cara untuk menyadarinya
Memakai distro LTS bisa memberi perlindungan sampai tingkat tertentu. Slackware tampaknya memakai lzma, yaitu
tar.xz, untuk paket, tetapi rilis stabil terakhir selain-currenttidak terdampak masalah iniJika ingin melangkah sedikit lebih jauh ke sisi perangkat lunak bebas, Hyperbola GNU juga tidak terdampak masalah ini
Selain itu, Slackware
-currentjuga tidak menautkansshdke xz, dan juga tidak memakai systemd