आखिरी 0day jailbreak: Tachy0n
(blog.siguza.net)- tachy0n iOS 13.0~13.5 पर काम करने वाला एक kernel privilege escalation exploit था, जिसे 23 मई 2020 को unc0ver v5.0.0 में 0day के रूप में शामिल किया गया और इसने उस समय के नवीनतम iOS को सीधे निशाना बनाया
- इसका मूल
lio_listioकी Lightspeed race condition थी, जोkalloc.16object को दो बार free करके अलग-अलग objects को एक ही memory की ओर point करवाने में सक्षम बनाती थी - इसी परिवार की bug पहले iOS 11 के Spice jailbreak/untether में भी इस्तेमाल हुई थी, लेकिन app sandbox और
racoonenvironment में privilege, sandbox और spray techniques के लिहाज़ से बड़े फर्क थे - unc0ver के exploit ने बार-बार
OSDataoverlap हासिल किया, औरIOBufferMemoryDescriptorवIOAcceleratorFamily2का उपयोग करके fake task और fake mach port बनाए - iOS 14 के बाद Apple allocator separation, sequestering, PAC और object-specific hardening की ओर बढ़ा, यानी exploit strategy को ही ब्लॉक करने वाले मॉडल की तरफ, और सार्वजनिक iOS kernel exploit ज्ञान अब iOS 13 के दौर की तुलना में काफी पीछे है
tachy0n का सार्वजनिक होना असामान्य क्यों था
- tachy0n एक पुराना exploit था जो iOS 13.0 से 13.5 तक प्रभावित करता था, और 23 मई 2020 को unc0ver v5.0.0 में शामिल होकर सार्वजनिक हुआ
- उस समय के हिसाब से यह एक सामान्य kernel local privilege escalation (kernel LPE) था, लेकिन नवीनतम iOS version को प्रभावित करने वाले 0day jailbreak के रूप में इसका सार्वजनिक होना दुर्लभ था
- Apple ने exploit के सार्वजनिक होने के लगभग 1 हफ्ते बाद इस bug के लिए समर्पित patch जारी किया
- यह bug iOS 13.5 में 0day था, लेकिन पहले 1day के रूप में इसका उपयोग हो चुका था
- Pwn20wnd ने app sandbox से पहुँचा जा सकने वाला 0day खोजा, और शुरुआती बिंदु ज्ञात 1day bugs पर regression testing था
- iOS 12 का SockPuppet iOS 12.3 में patch हुआ था, लेकिन iOS 12.4 में फिर दिखा
- इस मामले ने इस तरह की bug class पर Apple की regression testing की कमी दिखा दी, और Pwn20wnd ने कुछ ज्ञात 1day पर regression tests लागू करके hit पाया
Lightspeed: lio_listio race condition
- tachy0n की मुख्य bug Synacktiv की Lightspeed bug थी, जो CVE-2020-9859 और CVE-2018-4344 की संभावना से जुड़ी थी
- vulnerability
lio_listiosyscall में थी, जो asynchronous या batch file I/O चलाती है - kernel submitted I/O jobs को track करने के लिए
aio_lio_contextstructure allocate करता है- इस structure में
io_waiter,io_issued,io_completedfields होते हैं
- इस structure में
- वास्तविक काम अलग thread में होता है, और जब सभी I/O पूरे हो जाते हैं तो
do_aio_completionउस context को free कर देता है - अगर कोई job schedule ही नहीं हुई हो, तो
lio_listioका current thread context को free करता है - समस्या यह थी कि इस check में race condition थी
- कोई job दूसरे thread को submit हो सकती थी, लेकिन check होने से पहले ही पूरी होकर context free हो जाता था
- तब
lio_listiodangling pointer बन चुकेlio_contextको फिर से check करता था
double free exploit तक कैसे पहुँचता है
- exploit के लिए ज़रूरी sequence यह था
lio_listiolio_contextallocate करता है- job पूरी होती है और
do_aio_completionlio_contextfree कर देता है - attacker-controlled object उसी freed memory में reallocate किया जाता है और उसे
lio_context->io_issued == 0जैसा दिखाया जाता है lio_listioइसे देखकर attacker object को दोबारा free कर देता है- फिर उसी memory को किसी दूसरे object के लिए reallocate किया जाता है, जिससे दो अलग allocations एक ही memory की ओर point करने लगते हैं
- 64-bit devices पर
lio_contextसबसे छोटे zonekalloc.16में जाता था - iOS 14 से पहले object type की परवाह किए बिना same-sized allocations एक ही allocation site share करते थे
- C++ objects, pointer arrays और user-provided data buffers एक ही size bucket में एक-दूसरे की memory reuse कर सकते थे
- यह double free उस सामान्य double free से अलग तरह से काम करता था जो बीच में reallocation न होने पर तुरंत fatal corruption दे देता है
- allocation के दौरान
lio_context->io_issuedकभी 0 नहीं होता - free होने के बाद allocator पहले 8 bytes को canary value और freelist pointer या object address XOR value से overwrite करता है
- इसलिए दूसरा free तभी होता है जब बीच में reallocation हो और bytes 4~7 शून्य हों
- allocation के दौरान
- असली exploit इस race को कई बार retry कर सकता था, और system के किसी दूसरे object द्वारा संयोग से ज़रूरी bytes को 0 बनाकर double free trigger करना व्यवहार में दुर्लभ था
Spice में पहले इसका उपयोग
- यही bug iOS 11.x को target करने वाले Spice jailbreak/untether में भी इस्तेमाल हुई थी
- Spice पर Jake Blair team ने Sparkey और littlelailo के साथ काम किया था, और उस समय नवीनतम version iOS 13.x था
- लक्ष्य app environment और
racoonenvironment, दोनों में mach port forgery बनाना था- iOS 14 से पहले के kernel exploits में अगर user-controlled value को mach port pointer की तरह interpret करवाया जा सके, तो बाद के चरण बहुत आसान हो जाते थे
- Lightspeed से mach port forgery बनाने का मूल flow यह था
lio_contextका पहला free trigger करना- size 1 या 2 वाले OOL mach ports descriptor के साथ mach message spray करना
- पहली entry को
MACH_PORT_NULLरखकर उसेkalloc.16में डालना औरio_issuedको 0 जैसा दिखाना - दूसरे free से OOL mach ports array को free करना
kalloc.16में controlled data spray करके mach ports array को fake pointer से replace करना
app sandbox और racoon की constraints में फर्क
- A7~A9(X) पर PAN नहीं था, इसलिए सिर्फ
mmapऔरmlockसे userland address को kernel pointer की तरह dereference कराया जा सकता था - A10 और A11 को भी support करने की कोशिश हुई, लेकिन app sandbox में उपयुक्त kernel address leak और controlled data placement target नहीं मिल पाने से यह पूरा नहीं हो सका
- जिन 1day bugs का उपयोग होना था, उनमें Ian Beer का kernel stack information leak और backboardd sandbox escape शामिल था
- योजना shared memory pointer leak करने या kernel
__DATAsegment में data रखने की थी - उपयुक्त target न मिलने के कारण app path में A10/A11 support नहीं बन पाया
- योजना shared memory pointer leak करने या kernel
racoonpath में स्थितियाँ अलग थीं- यह root के रूप में चलता था, लेकिन सामान्य app की तुलना में और कड़े sandbox के साथ
- IOSurface access न होने से सामान्य
IOSUnserializeXMLviaIOSurface::setValuespray का उपयोग नहीं किया जा सकता था - इसकी जगह
RootDomainUserClient::secureSleepSystemOptionsके भीतर केOSUnserializeXMLcall से कुछ objects को leak-style spray किया जा सकता था
racoonके sandbox profile में सभी sysctl reads और writes की अनुमति थी, और इसे root privilege भी मिला हुआ था- kernel slide पता होने पर kernel
__DATAमें मौजूद sysctl global को ज्ञात address वाले data store की तरह इस्तेमाल किया जा सकता था - Spice में इसके लिए
vm.swapfileprefixचुना गया
- kernel slide पता होने पर kernel
- kernel slide प्राप्त करने के लिए panicall का CVE-2018-4413 इस्तेमाल हुआ
sysctl_procargsxinformation leak के ज़रिएkernel_mapकी लगभग एक page जितनी uninitialized kernel memory leak की जा सकती थी- इससे kernel code और heap pointer मिले, और A7~A11 को संभाला जा सका
- app sandbox में
sysctl_procargsxblocked था, इसलिए वही तरीका वहाँ संभव नहीं था
unc0ver के लिए tachy0n exploit की संरचना
- unc0ver का target A8~A13 था, इसलिए A10+ को नज़रअंदाज़ करना या userland dereference पर निर्भर रहना संभव नहीं था
- exploit को fail हो सकने वाले memory corruption stage को ध्यान में रखकर दो-स्तरीय संरचना में डिज़ाइन किया गया
- निचली layer
lio_listioचलाने वाले freerer threads और IOSurface के ज़रिएOSDataunserialize करने वाले racer threads चलाती थी - default configuration freerer 4 और racer 16 थी, और इसे adjust किया जा सकता था
- निचली layer
- IOSurface के माध्यम से unserialize होने वाला data कई
OSDataentries वालाOSDictionaryथाio_issuedके अनुरूप position का 0 होना ज़रूरी था0x41414141,0x69696969जैसी magic values और key valuekoverlap detection के लिए इस्तेमाल हुईं
- race के बाद सभी
OSDatavalues की जाँच की जाती थी- जिन objects की magic value बदल गई हो, उन्हें system के दूसरे object द्वारा ले लिया गया माना जाता था और बाद में cleanup target के रूप में mark किया जाता था
- अगर key और buffer के भीतर का
kvalue अलग हो, तो इसे इस बात का संकेत माना जाता था कि अलगOSDataobjects एक ही backing buffer की ओर point कर रहे हैं
- code की
maybe_reyoinkऔरoverlapfunctions यह overlap information बनाकर ऊपरी layer को देती थीं - ऊपरी layer इन overlapped
OSDataobjects का उपयोग करके fake mach port बनाती थी- एक
OSDataको free करना - OOL port descriptor वाले mach message spray करना
- दूसरे
OSDataको free करना - fake task port pointer वाला नया
OSDatareallocate करना
- एक
ज्ञात kernel address पर controlled data रखना
- exploit OOL ports descriptor array में reallocated content को
OSDataके रूप में पढ़कर mach port का raw kernel pointer leak कर सकता था - आगे यह task port और
IOSurfaceRootservice port address leak करने में काम आता था, लेकिन मुख्य समस्या यह थी कि controlled buffer का स्थिर kernel address विश्वसनीय रूप से कैसे मिले - XNU source में मिला candidate था
IOMemoryDescriptor- इसका
_rangesfieldIOVirtualRangearray होता है, और एक singleIOVirtualRangeठीकkalloc.16में फिट होता है - लेकिन सामान्य
IOMemoryDescriptorमें अगर सिर्फ एक range हो तो heap allocation की जगह_singleRangeइस्तेमाल होता है
- इसका
IOBufferMemoryDescriptorएक अपवाद था, जो एक ही range के लिएIONew(IOAddressRange, 1)कॉल करके heap allocation करता था- इसे मनचाहे ढंग से allocate करके user address space में map करने के लिए सुविधाजनक जगह
IOAcceleratorFamily2का AGX interface थाIOGraphicsAccelerator2में type 0 userclient खोलने परIOAccelContext2मिलता था::clientMemoryForType()से तीन memory descriptors map किए जा सकते थे- type 0 का size 0x8000 bytes था, इसलिए victim descriptor की पहचान में इसका उपयोग हुआ
- exploit ने यह loop इस्तेमाल किया
IOAccelContext2खोलना और overlappedOSDataके दो objects लेना- एक
OSDataको free करना - पहले से खुले
IOAccelSharedUserClient2कोIOConnectAddClient()से connect करना - बचे हुए
OSDataको पढ़कर जाँचना कि पहले 8 bytes page-aligned kernel pointer हैं और अगले 8 bytes0x8000हैं - अगर condition पूरी न हो तो
IOAccelContext2बंद करके दोहराना
pageable memory, fake port, zone_require
- memory descriptor को process में map कर लेने और उसका kernel address जान लेने के बाद भी एक समस्या बची रही: memory
kIOMemoryPageableके रूप में बनती थी - fake mach port और fake task object तक preemption बंद होने की स्थिति में पहुँचा जा सकता था, इसलिए kernel side पर उस page को fault-in करना पड़ता था
- यह
IOAccelContext2::processSidebandBufferको अप्रत्यक्ष रूप से कॉल करने वालेIOAccelContext2::submit_data_buffersexternal method 2 को दो बार बुलाकर किया गया- shared memory की शुरुआत से 0x10 bytes आगे के structure को पढ़ा जाता था
- पहले structure में
tok == 0x100होता था और उसे पूरी page cover करने के लिए बनाया जाता था ताकि processing दूसरी page तक जाए - दूसरी page में बाद में fake object data रखा जा सकता था
- इसके बाद के चरण fake task, fake port, OOL descriptor switcheroo और arbitrary read primitive बनाने तक जाते थे
zone_requirebypass भी ज़रूरी था- उस समय
zone_requirezone_mapके बाहर की pages को अनुमति देता था और page के पहले0x20bytes को metadata की तरह interpret करता था - सही zone index भर देने पर यह इच्छित zone के लिए pass की तरह काम कर सकता था
- इसी कारण task के लिए एक page और mach port के लिए एक page, कुल दो pages चाहिए थे
- उस समय
- यह exploit अब GitHub पर सार्वजनिक है
सार्वजनिक होने के बाद analysis और patch
- नवीनतम signed version के लिए पूर्ण 0day exploit का सार्वजनिक होना iOS jailbreak scene में खास ध्यान का विषय बना
- उस समय Project Zero में काम कर रहे Brandon Azad ने exploit के सार्वजनिक होने के 4 घंटे के भीतर vulnerability समझ ली और Apple को सूचना दी
- यह analysis How to unc0ver a 0-day in 4 hours or less में संकलित है
- exploit के सार्वजनिक होने के 6 दिन बाद Synacktiv ने नई पोस्ट में यह संभावना रखी कि iOS 12 का मूल fix memory leak पैदा कर रहा था, और उस memory leak को ठीक करने की कोशिश ने शायद मूल bug को फिर से जीवित कर दिया
- exploit के सार्वजनिक होने के 9 दिन बाद Apple ने patch जारी किया
- बाद में XNU में इस bug के लिए regression test जोड़ा गया
- सार्वजनिक होने के 54 दिन बाद reverse-engineered version “tardy0n” Odyssey jailbreak में शामिल किया गया, और इसका target भी iOS 13.0~13.5 ही था
iOS 14 के बाद exploit environment कैसे बदला
- iOS 14 ने Apple की kernel security strategy में बदलाव दिखाया
- iOS 14 से पहले शुरुआती primitive चाहे heap overflow हो, C++ object over-release हो, या type confusion, अगला target आम तौर पर mach port ही होता था
- iOS 14 में सबसे बड़े बदलावों में से एक allocator
kallocऔरzallocथे- zone map को कई “kheap” ranges में बाँटा गया
- user-controlled data और kernel objects को अलग-अलग heaps में अलग किया गया
- kernel objects पर sequestering लागू की गई ताकि किसी specific zone को allocated virtual address pages reboot से पहले किसी दूसरे zone के लिए reuse न हों
- physical memory free हो सकती थी, लेकिन virtual memory range दूसरे object के लिए reuse नहीं होती थी, जिससे kernel object type confusion व्यावहारिक रूप से रुक गई
- guard pages, हर boot पर बदलने वाले zone allocation start positions, और बाद की refinements ने cross-zone attacks की reliability को बहुत कम कर दिया
- Apple सिर्फ individual bugs को रोकने के बजाय exploit strategy को रोकने की दिशा में गया
- अगर exploit kmsg struct को corruption target बनाता, तो उस structure को sign कर दिया जाता
- अगर pipe buffer को stable kernel read/write interface की तरह इस्तेमाल किया जाता, तो संबंधित pointers को PAC से सुरक्षित किया जाता
- अगर unrelated objects को victim की तरह इस्तेमाल करने का तरीका सामने आता, तो उस object type को harden कर दिया जाता
- नतीजतन exploit development में शुरुआती memory corruption 0day से ज़्यादा मूल्य exploit strategy का हो गया
सार्वजनिक ज्ञान में टूटन
- iOS 14 से पहले यह आकलन था कि सार्वजनिक iOS security research का ज्ञान निजी ज्ञान के लगभग बराबर था
- iOS 14 के बाद, कुछ अपवादों को छोड़कर, जानकारी साझा करना लगभग रुक गया
- iOS 19 beta आने में कुछ ही हफ्ते बचे होने के बावजूद, iOS 18 या iOS 17 के लिए कोई सार्वजनिक kernel exploit उपलब्ध नहीं बताया गया
- Apple security notes में कभी-कभी जंगली इस्तेमाल में आए vulnerabilities दिखते हैं, लेकिन सार्वजनिक जानकारी निजी research की बराबरी नहीं कर पा रही
- यह तथ्य कि tachy0n के सार्वजनिक होने को अभी सिर्फ 5 साल हुए हैं, दिखाता है कि iOS kernel exploit का क्षेत्र कितनी तेज़ी से बदल चुका है
2 टिप्पणियां
Apple का hardware बेहतरीन है, लेकिन उसका software उपयोगकर्ताओं को बाँधकर रखने की मंशा से भरा हुआ है.
आप अपनी बनाई और build की हुई app को सिर्फ अपने ही device पर चलाना चाहें, तब भी 100 डॉलर का subscription चाहिए.
अगर आप छोटे या मध्यम स्तर के open source apps इस्तेमाल करते हैं और उन्हें खुद build करके चलाने वाले developer हैं,
तो Apple के device पर vulnerability का फायदा उठाकर jailbreak करके sideload करने से बेहतर है कि बस Android इस्तेमाल करें.
Hacker News की रायें
यह बात प्रभावशाली है कि 1 ट्रिलियन डॉलर की कंपनी को हराने का तरीका वही सरल और उबाऊ काम था जिसमें Apple खास तौर पर कमजोर है: regression testing
iOS 12 में jailbreak के लिए इस्तेमाल हुई बड़ी कमजोरियों में से एक SockPuppet थी, जिसे Project Zero के Ned Williamson ने खोजकर Apple को रिपोर्ट किया था, iOS 12.3 में पैच किया गया था, और बाद में Project Zero bug tracker पर सार्वजनिक किया गया था
लेकिन iOS 12.4 में यह ऐसे वापस आ गई जैसे कभी पैच हुई ही न हो, और शायद Apple ने उस version के लिए XNU को अलग branch में fork करते समय patch लागू नहीं किया
यह इस बात का मजबूत संकेत था कि इस तरह की कमजोरियों के लिए regression tests नहीं थे, और कुछ ज्ञात 1-day को automate कर देने भर से Pwn सीधे hit कर सकता था
जिज्ञासा है कि Linux, FreeBSD, OpenWRT, OpenSSH जैसे projects के नए versions पर पुरानी कमजोरियां लगातार चलाने वाले CI farms कितने लोग चला रहे होंगे
20 साल पहले university के दिनों में मैंने Mozilla में volunteer QA किया था, और rendering/layout या JavaScript engine bugs पर केंद्रित regression tests का एक लगातार बढ़ता collection था
reproduce करने और fix verify करने के लिए minimal test cases बनाते-बनाते, उन्हें build pipeline में डालना भी आसान था
Bugs से बचना असंभव है, लेकिन जिस bug को ठीक करने में समय और पैसा लगा हो उसका फिर से जिंदा हो जाना सबसे खराब scenario है
quality को महत्व देने वाले organizations निश्चित तौर पर regression testing में invest करते हैं, लेकिन कई organizations QA की कद्र नहीं करते और या तो उसे करते ही नहीं या सबसे सस्ते outsource vendor को दे देते हैं
Apple के पास jailbreak जैसे ऐतिहासिक रूप से सबसे high-profile bug class के लिए regression tests न होना सचमुच अजीब है
आजकल Mozilla पर कई आलोचनाएं हो सकती हैं, लेकिन 2000s की शुरुआत में भी वह Tinderbox और Bugzilla जैसे tools के साथ काफी मजबूत QA और CI/CD चला रहा था
जब DevOps popular हुआ और इन तरीकों को mainstream बनाया, तो मुझे लगा था कि यह तो सब पहले से कर ही रहे होंगे, लेकिन वह मेरा भ्रम था
यह security और feature development के separation से बनी Conway's law जैसी स्थिति है
भले ही build/release process और mature regression test suite मौजूद हों, internal org structure की वजह से ऐसे security issues शायद उसके अंदर शामिल ही न हों
आसानी से हजारों थीं, और शायद वह SQLite था
यह अपनाने लायक तरीका है
अगर fixes को backport नहीं किया जाता, तो tests को भी backport न किए जाने की संभावना ज्यादा लगती है
kheap separation, task port mitigations, SSV, SPTM जैसी बातें देखकर ऐसा लगा जैसे किसी विदेशी भाषा में दोस्त से अच्छी बातचीत चल रही हो और अचानक brain surgery या nuclear physics की व्याख्या शुरू हो जाए, और समझ एकदम खाई में गिर जाए
blast furnace refurbishment पर हुई बातचीत को interpret करने की कोशिश करते समय भी कुछ ऐसा ही लगा था
अफसोस है कि jailbreak अब पहले जितना active नहीं रहा
jailbreak किए हुए iPad से मैंने व्यावहारिक तौर पर ज्यादा कुछ नहीं किया, लेकिन मजा आया था, और आज होता तो tethering app, UTM और JIT solution install करना चाहता
SideStore भी promising लगा था, लेकिन मेरा account कभी paid Apple Developer account था, इसलिए मेरे पास 10 app IDs बची हैं जो expire नहीं होतीं, और इस वजह से नया account बनाए या फिर से पैसे दिए बिना UTM जैसे apps install नहीं कर सकता
उसे खोने के बाद मैं Android पर वापस चला गया, और तब तक Android basic features के मामले में काफी पकड़ बना चुका था
सुना है कि Apple आजकल jailbreak के लिए 1 million dollars देता है, और वह free-market price की lower bound होगी
क्या किसी broker के जरिए जाना पड़ता है, या कोई public email है जहां first-line customer support में जाकर मामला दब न जाए
अगर यह सच है तो Apple ने कमाल की strategy अपनाई है
devices पर root पाने के सारे रास्ते बंद कर दो, फिर jailbreak developers जो vulnerabilities मुफ्त में खोजते हैं, उन्हें Apple patch कर सकता है
लेख के मुताबिक private communities के पास अब भी exploits हैं और Apple उन्हें patch करता है
public community या इस developer की तरफ ही किसी वजह से अब ऐसा नहीं दिखता
पूरे लेख में मेरी पसंदीदा line थी: “iOS 13.0 में उस bug को unpatch करने वाले किसी व्यक्ति का भी धन्यवाद करना चाहूंगा। वह भी काफी cool move था।”
“5 साल बाद हम कहां होंगे, इसकी कल्पना भी नहीं कर सकता” — लेकिन मैं कर सकता हूं
iMessage अब भी devices, accounts और data takeover को संभव बनाता है
लेख में यह नहीं बताया गया कि यह tethered था या untethered
जिस jailbreak unc0ver में इसे शामिल करके distribute किया गया था, वह शायद “semi-untethered” था, जहां तक मुझे याद है