Xz/liblzma: Bash चरण के obfuscation की व्याख्या
(gynvael.coldwind.pl)- xz/liblzma backdoor सिर्फ अंतिम binary payload की समस्या नहीं है; build के दौरान चलने वाला Bash चरण extraction और decryption की कई परतों के पीछे छिपा है, जिससे analysis काफी कठिन हो जाता है
- प्रभावित versions xz/liblzma 5.6.0 और 5.6.1 हैं, और obfuscated script तथा binary payload दो ऐसी files में मौजूद हैं जो test files जैसी दिखती हैं
- Stage 0
m4/build-to-host.m4से शुरू होता है, corrupted दिखने वाली xz stream को restore करता है, औरxz -dसे Stage 1 script निकालकर चलाता है - Stage 1
good-large_compressed.lzmaसे data काटता और replace करता है, फिर Stage 2 Bash script चलाता है; 5.6.1 में Linux पर चलने की 5 बार जांचने वाला code जोड़ा गया है - Stage 2
.ofile को build और link process में घुसाने के लिए file carving, substitution cipher, और AWK-आधारित RC4-variant decryption का उपयोग करता है; 5.6.1 में आगे अतिरिक्त scripts चलाने के लिए एक extensible structure भी शामिल है
xz/liblzma backdoor के Bash चरण का दायरा
- Andres Freund ने oss-security mailing list पर xz/liblzma backdoor की खोज की सूचना दी, और यह backdoor OpenSSH server को प्रभावित करता है
- analysis का विषय binary backdoor खुद नहीं, बल्कि उससे पहले चलने वाला Bash-आधारित शुरुआती चरण और उसका obfuscation तरीका है
- Bash flow Stage 0 से Stage 2 तक जाता है, और एक संभावित Stage 3 के कुछ संकेत भी दिखते हैं जो पूरी तरह implemented नहीं लगता
- obfuscated और encrypted चरणों के साथ बाद का binary backdoor दो test files के भीतर छिपे हैं
tests/files/bad-3-corrupt_lzma2.xztests/files/good-large_compressed.lzma
- प्रभावित versions 5.6.0 और 5.6.1 हैं, और दोनों versions के बीच कुछ छोटे अंतर हैं
Stage 0: corrupted test file जैसी दिखने वाली xz stream को restore करना
- शुरुआती बिंदु
m4/build-to-host.m4का code है, जो build process के किसी चरण में execute होता दिखता है - मुख्य pipeline
sed,tr, औरxz -dको जोड़कर Stage 1 script निकालती है tests/files/bad-3-corrupt_lzma2.xzके bytes पढ़ने के बादtr "\t \-_" " \t_\-"से कुछ bytes replace किए जाते हैं0x09tab को0x20space में बदला जाता है0x20space को0x09tab में बदला जाता है0x2dhyphen को0x5funderscore में बदला जाता है0x5funderscore को0x2dhyphen में बदला जाता है
- यह replacement
bad-3-corrupt_lzma2.xzको फिर से एक सही xz stream में बदल देता है - अंत में restored stream को
xzसे decompress किया जाता है, errors को ignore करते हुए Stage 1 script हासिल की जाती है और तुरंत execute कर दी जाती है- stream कटी हुई लगती है, लेकिन meaningful output पहले ही लिखी जा चुकी होती है, इसलिए समस्या नहीं होती
Stage 1: good-large_compressed.lzma से Stage 2 निकालना
- Stage 1 Andres Freund के mail में
####Hello####से शुरू होने वाली एक छोटी Bash file है - दोनों versions का पहला अंतर दूसरी लाइन के comment के अंदर मौजूद random bytes हैं
- 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 में
[ ! $(uname) = "Linux" ] && exit 0के रूप में Linux check जोड़ा गया है, और यही check 5 बार दोहराया गया है - Stage 2 extraction एक लंबी
export i=...लाइन से शुरू होती है, और यह value बाद मेंeval $iके जरिए किसी function जैसी इस्तेमाल होती है - extraction flow एक ऐसी pipeline structure है जो standard output और standard input को लगातार जोड़ती रहती है
- पहले
xz -dcसेgood-large_compressed.lzmaको decompress किया जाता है eval $iबार-बारheadcall करके 1024 bytes छोड़ता है और 2048 bytes output करता है- file के अंत में 5.6.0 में 724 bytes, और 5.6.1 में 939 bytes output होते हैं
- पहले
- यह
headchain खाली low-entropy क्षेत्रों को हटाकर सिर्फ वास्तविक data वाले हिस्सों को जोड़ती है - इसके बाद
tail -c +31233से शुरुआती हिस्सा हटा दिया जाता है- इसी शुरुआती हिस्से में आगे निकलने वाला binary backdoor छिपा है
- 5.6.0 में प्रभावी रूप से शुरुआती 31264 bytes, और 5.6.1 में 31232 bytes हटते हैं
- इसके बाद
trको एक साधारण substitution cipher की तरह इस्तेमाल किया जाता है, और 5.6.0 तथा 5.6.1 की mapping keys अलग हैं- 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:
- replaced data को फिर
xz -F raw --lzma1 -dcसे decompress किया जाता है, और परिणामस्वरूप मिला Stage 2 तुरंत execute हो जाता है
Stage 2: build process में बदलाव और extension mechanism
- Stage 2 वही
infected.txtfile है जिसे Andres Freund ने मूल mail में संलग्न किया था, और यही वह Bash script है जहां वास्तविक compilation process modification होता है - obfuscation के नज़रिए से Stage 2 में तीन दिलचस्प fragments हैं, जिनमें से दो सिर्फ 5.6.1 में दिखाई देते हैं
- 5.6.1 के ये दो fragments लगभग एक जैसे हैं और
tests/files/में खास signatures वाली files खोजते हैं- Fragment 1
~!:_ Wऔर|_!{ -खोजता है - Fragment 3
jV!.^%और%.R.1Zखोजता है
- Fragment 1
- signature मिलने पर filename और offset निकाले जाते हैं, और पहले signature के बाद से दूसरे signature से पहले तक की byte range काट ली जाती है
- काटे गए data को 5.6.0 में उपयोग की गई
trkey से substitution decryption किया जाता है, फिरxz -F raw --lzma2 -dcसे decompress करevalके जरिए execute किया जाता है - जांचे गए 5.6.0 और 5.6.1 TAR archives में उन signatures वाली कोई file नहीं मिली
- यह structure एक extension और patch system जैसा दिखता है, जो मौजूदा payload-carrying test files को दोबारा बदले बिना, सिर्फ नई test files जोड़कर भविष्य में scripts चलाने की सुविधा देता है
Stage 2: binary backdoor .o file निकालना
- Stage 2 का एक और मुख्य हिस्सा
.ofile निकालकर उसे compile और link process में शामिल करने वाला code है - extraction का target output
liblzma_la-crc64-fast.oहै, और यही अंतिम binary backdoor है - extraction process पहले के चरणों की तरह कई standard tools को pipeline में जोड़ती है
good-large_compressed.lzmaकोxz -dcसे decompress किया जाता है- Stage 1 में इस्तेमाल हुई
eval $iकीheadchain फिर से उपयोग की जाती है sed "s/\(.\)/\1\n/g"से हर byte के बाद newline जोड़कर उसे प्रति byte एक line के रूप में बदल दिया जाता है
- अगली AWK script input stream पर RC4-जैसी decryption करती है
- key array
((i * 7) + 5) % 256से बनाई जाती है - 5.6.0 output PRNG stream के शुरुआती 4096 bytes छोड़ता है
- 5.6.1 शुरुआती 8192 bytes छोड़ता है
- मूल RC4 XOR का उपयोग करता है, लेकिन यह code
(v + k) % 256रूप की addition का उपयोग करता है
- key array
- decrypted data को
xz -dc --single-streamसे decompress किया जाता है - इसके बाद
headसे आवश्यक byte range काटकरliblzma_la-crc64-fast.oके रूप में save किया जाता है- compressed लेकिन आंशिक रूप से modified payload का आकार 5.6.0 में 88792 bytes और 5.6.1 में 88664 bytes है
- अंतिम extracted range लगभग 86KB की है
केवल standard tools से बने obfuscation की प्रकृति
- पूरा Bash चरण केवल standard command-line tools से बना है
sedtrxzheadtailawkgrepcutexpr
- payload उन binary files में छिपा है जो सामान्य test files जैसी दिखती हैं, और आवश्यक हिस्से ही file carving के जरिए निकाले जाते हैं
- obfuscation में simple substitution cipher और AWK-आधारित RC4-variant decryption दोनों का इस्तेमाल होता है
- execution कुल 3 चरणों में आगे बढ़ती है, और 5.6.1 में बाद में अलग test files जोड़कर scripts चलाने की संरचना शामिल है
- पूरी संरचना को harmless दिखाने और अच्छी तरह छिपाने में काफी मेहनत की गई है, और अगर यह मामला संयोग से पकड़ा गया, तो यह सवाल छोड़ता है कि अब तक ऐसे कितने मिलते-जुलते मामले अनदेखे रह गए होंगे
1 टिप्पणियां
Hacker News की राय
सरल किए गए explanation और noise image comparison की वजह से समझ आता है कि लोग जिस sophistication की बात कर रहे हैं, उसका मतलब क्या है
reddit पर यह भी देखा कि “sandboxing” वाला तरीका एक dot से टूट गया था, और
#includeके ठीक अगली line में सबसे बाईं तरफ dot दिखता हैhttps://git.tukaani.org/?p=xz.git;a=commitdiff;h=328c52da8a2...
https://old.reddit.com/r/linux/comments/1brhlur/xz_utils_bac...
+,+.,+जैसा दिखता है, और वह एक dot नजर से छूट जाता हैसिर्फ साधारण गलती से test हमेशा fail या हमेशा pass हो जाए, वही काफी सिरदर्द है; ऐसे में समझ आता है कि यह malicious behavior के लिए अच्छा target क्यों बनता है
a) इस package को cmake से build करने वाला असल में लगभग कोई नहीं है
b) cmake और
-DENABLE_SANDBOX=landlockसे build करके देखें तो build बस fail हो जाता है: https://i.imgur.com/7xbeWFx.pngवह dot sandboxing को disable नहीं करता, सिर्फ cmake से build करना असंभव बना देता है। अगर किसी ने सच में cmake से build किया होता, तो error देखकर समझ जाता कि कुछ गलत है; इसलिए इसे security कम करने की malicious कोशिश मानना तार्किक नहीं लगता
“अगर यह संयोग से मिला, तो अभी कितनी चीजें अनदेखी बची होंगी” सबसे अहम सवाल है
Andres Freund ने backdoor लगे लोकप्रिय open source projects में से सिर्फ एक को ही संयोग से खोज लिया होगा, ऐसा संभव नहीं लगता। अगर ऐसी एक दर्जन चीजें पहले से बाहर मौजूद हों तो भी हैरानी नहीं होगी
अगला attacker run time बढ़ने जैसे निशान छोड़ने में कहीं ज्यादा सावधान होगा
यहां असहज करने वाली बात यह है कि unit tests ने attack path खोल दिया। tests न होते तो इसे इस तरह छिपाना काफी मुश्किल होता
bad-3-corrupt_lzma2.xzमें तीन streams हैं, और पहली व तीसरी valid xz streams हैं। लिखा है कि बीच वाली stream में stream header, block header, index, stream footer सही हैं और सिर्फ LZMA2 data corrupt है, और--single-streamइस्तेमाल करने पर decompress होना चाहिए####Hello####और####World####strings README के निर्देश सच में follow करने पर plausible normal result दिखाने में मदद करती हैं$ cat tests/files/bad-3-corrupt_lzma2.xz | xz -d --single-stream####Hello####ये strings shell comments हैं, इसलिए payload execution में बाधा नहीं डालतीं
आखिर में, बाद में ये एक marker की तरह काम करती हैं, जिससे regex filename को सीधे reference किए बिना या असली Hello/World strings का इस्तेमाल किए बिना file ढूंढ सकता है
$ gl_am_configmake=\grep -aErls "#{4}[[:alnum:]]{5}#{4}$" $srcdir/ 2>/dev/null``$ echo $gl_am_configmake./tests/files/bad-3-corrupt_lzma2.xzयह check transitively apply होना चाहिए, ताकि Linux distributions जब नए liblzma version में update करने की कोशिश करें, तो नए binary dependency की वजह से build fail हो या warning आए
Linux distribution builds में यह practice कितनी common है, मुझे नहीं पता। अगर common है तो cleanup में जबरदस्त काम लगेगा, और शुरू से ही यह संभव है या नहीं, यह भी unclear है। bazel से संभव लगता है, लेकिन दूसरे build systems के बारे में पक्का नहीं कह सकता
जानना चाहूंगा कि क्या किसी ने GitHub पर ऐसे ही head | tail trick पहले से खोजे हैं। यह मानना मुश्किल है कि इसे खास इसी के लिए नया invent किया गया होगा
एक बहुत बड़ी
.shfile license दिखाती है और consent लेने के बाद, खुद कोcatकरकेhead/tailpipe से गुजारती है औरcpioके जरिए असली assets extract करती हैबेहतर जवाब तो नहीं है, लेकिन यह उलझा हुआ bash का ढेर अपने-आप में संदिग्ध नहीं लगता?
जैसे डेवलपमेंट दुनिया के दूसरे हिस्सों में होता है, क्या इसे कम अपारदर्शी तरीके से नहीं लिखा जा सकता था ताकि साफ दिखे कि क्या हो रहा है?
मैं समझता हूँ कि maintainer किसी बाहरी contributor जितनी कड़ी review के बिना malicious code डाल सकता है, लेकिन “संक्षिप्त” code के ढेर, जो असल में अनजाने obfuscation जैसा दिखता है, उससे बेहतर रास्ता होना चाहिए
मूल समस्या यह है कि 80–90 के दशक में कई Unix-जैसे systems थे, जिनमें हर एक में अपनी खामियाँ और गायब features थे, और software authors build dependencies को न्यूनतम रखना चाहते थे
इसलिए कई communities ने हर जगह चलने वाली shell scripts से builds automate करने को standard बना लिया. लेकिन shell scripts लिखना दर्दनाक था, और लोग M4 macro preprocessor जैसे tools से shell scripts generate करने लगे
नतीजा यह हुआ कि अगर कोई AIX या किसी टूटे-फूटे प्राचीन Unix पर code चलाना चाहे, तो उसके लिए कई projects के पास विशाल और अपारदर्शी shell scripts के ढेर हो गए
इस भेदना-कठिन shell झाड़ी को हटाने के लिए supported platforms की संख्या बहुत घटानी होगी, ज्यादा साफ build tools पर standardize करना होगा, और portable builds के लिए shell की जरूरत न रखने वाली languages में core infrastructure का अधिक हिस्सा बनाना होगा
लेकिन यह बहुत बड़ा काम है, और core C libraries का बड़ा हिस्सा एक-दो unpaid volunteers maintain करते हैं. “Obscurnix-1997” support हटाना भी अक्सर काफी विवादास्पद फैसला बन जाता है
इसलिए core infrastructure का बड़ा हिस्सा अब भी अज्ञात-सी machine-generated shell script दलदल से घिरा हुआ है
काफी संख्या में tools इसी तरीके से build होते हैं
जो संदिग्ध लगता है वह बार-बार होने वाली tr calls हैं. ऐसी चीज दिखे तो लगता है कोई बहुत चालाक बनने की कोशिश कर रहा है, और यहाँ “चालाकी” नकारात्मक अर्थ में है. अगर मैं maintainer होता तो ऐसा code आने पर पूछता कि यह क्या करता है. क्योंकि ऐसी chaining से बचने के लिए लगभग हमेशा बेहतर समाधान होता है
असली समस्या यह थी कि यह code आने पर देखने के लिए कोई दूसरा maintainer नहीं था. stack का एक महत्वपूर्ण component एक व्यक्ति पर निर्भर था, और इस मामले में वह व्यक्ति malicious था
execकरने से रोकते हुए चलाने का कोई तरीका है? सोचता हूँ कि bash में “secure mode” जैसा कुछ हो तो कैसा होहालांकि xv के configure script में ऐसे काल्पनिक “secure mode” के साथ bash कैसे चलाया जा सकता है, इसकी कल्पना नहीं कर पा रहा, इसलिए बात वापस लेता हूँ
https://github.com/tukaani-project/.github/issues/2
build tools और पुराने Unix utilities सहित पूरा C ecosystem एक security mess है जो exploit होने का इंतजार कर रहा है, और अंततः exploit होगा
बस यह देख लीजिए कि एक dot से सब कुछ बिगाड़ना कितना आसान है. अब लोगों को समझना चाहिए कि दुनिया की security C पर दाँव पर नहीं लगाई जा सकती
आधुनिक toolchain वाले Ada या Rust का इस्तेमाल करें
समझ नहीं आता कि यह code review पार करके merge कैसे हो गया. अगर मैं कुछ miss नहीं कर रहा हूँ, तो यह हास्यास्पद रूप से लापरवाही भरा लगता है
ऊपर से यह test file के रूप में marked binary file, यानी “good”/“bad” xz compressed test files के अंदर बुरी तरह obfuscate किया गया था. जब तक पता न हो कि क्या ढूँढना है, इसे पहचानने का कोई तरीका नहीं था
LTS distros इस्तेमाल करने से कुछ हद तक protection मिल सकती है. Slackware packages के लिए lzma, यानी
tar.xz, इस्तेमाल करता लगता है, लेकिन-currentको छोड़कर आखिरी stable release में यह समस्या नहीं थीअगर free software की दिशा में एक कदम और जाना चाहें तो Hyperbola GNU में भी यह समस्या नहीं थी
इसके अलावा Slackware
-currentभीsshdको xz से link नहीं करता, और systemd भी इस्तेमाल नहीं करता