
JavaScriptCore में एक पैच की गई कमजोरी का शोषण
यह WebKit में एक कमजोरी का शोषण है, जिसे मूल रूप से Fluoroacetate द्वारा वैंकूवर में pwn2own प्रतियोगिता के दौरान खोजा गया था। जबकि मैंने इस बग की खोज नहीं की, मैंने अपने शोषण विकास कौशल का अभ्यास करने के लिए यह शोषण लिखा। इस शोषण के लिए मूल लेख यहाँ Zero Day Initiative से है। जबकि यह लेख बहुत अच्छा है और कमजोरी को समझने में मेरी मदद करने में सहायक था, यह किसी ऐसे व्यक्ति के दृष्टिकोण से है जो कमजोरी को सत्यापित कर रहा है। मैंने पाया कि जब इस शोषण को खरोंच से इंजीनियर करने का प्रयास किया जाता है तो कुछ महत्वपूर्ण विवरण गायब होते हैं और मुझे आशा है कि मैं ZDI लेख में छूटे कुछ अंतरालों को भरूंगा और एक जटिल शोषण को खरोंच से इंजीनियर करने के व्यावहारिक कौशल प्राप्त करूंगा।
ये चरण JavaScriptCore (JSC), WebKit के JavaScript इंजन, में मनमाना कोड निष्पादन प्राप्त करने की रूपरेखा के रूप में कार्य करते हैं
जिस कमजोरी का शोषण किया जाएगा वह एक पूर्णांक अतिप्रवाह है जो WebKit के DFG जस्ट इन टाइम (JIT) कंपाइलर द्वारा उत्पादित कोड में होता है। यह विशेष रूप से compileNewArrayWithSpread फंक्शन में होता है। जब DFG द्वारा जावास्क्रिप्ट स्प्रेड सिंटैक्स का उपयोग करके एक नई सरणी बनाने वाले कोड को JIT किया जाता है, तो यह फंक्शन कॉल किया जाएगा।

JIT कोड के अंदर, पहले यह सरणी के आकार की गणना करेगा। यह सरणी कंस्ट्रक्टर को पास किए गए प्रत्येक तर्क की लंबाई जोड़कर करता है। जैसे-जैसे यह प्रत्येक जोड़ के लिए आकार की गणना करता है, यह आकार के अतिप्रवाह की जाँच करता है। इसके बाद यह compileAllocateNewArray फंक्शन को कॉल करेगा, जो इस फंक्शन में गणना की गई लंबाई को पास करता है।

compileAllocateNewArray फिर पहले गणना की गई लंबाई को emitAllocateButterfly को पास करेगा।

emitAllocateButterfly फिर आकार को 3 बिट बाईं ओर शिफ्ट करेगा जो इसे 8 से गुणा करने के बराबर है। हालाँकि, अतिप्रवाह के लिए कोई जाँच नहीं है और इस प्रकार 0x20000001 जैसी संख्या 0x8 में अतिप्रवाह कर सकती है।
यह C प्रोग्राम इस कमजोरी को दर्शाता है:


हम इस कमजोरी का उपयोग करके जावास्क्रिप्ट इंजन को यह सोचने में धोखा दे सकते हैं कि हमने आकार 0x20000001 की एक सरणी आवंटित की है, लेकिन वास्तव में केवल 1 JSValue (8 बाइट्स) के लिए पर्याप्त स्थान आवंटित किया है। इसके परिणामस्वरूप एक आउट-ऑफ-बाउंड्स (OOB) रीड और राइट (R/W) प्रिमिटिव प्राप्त होगा, जिसका उपयोग मनमाना R/W और अंततः रिमोट कोड निष्पादन (RCE) प्राप्त करने के लिए किया जा सकता है।
यह पुष्टि करने के लिए कि हमारे पास OOB रीड है, हम JSC के एड्रेस सैनिटाइज़र (ASAN) बिल्ड पर इस कमजोरी को ट्रिगर करने का प्रयास करेंगे।
ऐसा करने के लिए WebKit निर्देशिका से हम कमांड चला सकते हैं:```bash Tools/Scripts/set-webkit-configuration --asan Tools/Scripts/build-jsc --jsc--only --debug
यह JSC का ASAN सक्षम के साथ एक डीबग बिल्ड बनाएगा जो हमें यह सत्यापित करने की अनुमति देगा कि क्या हमने सफलतापूर्वक कमजोरी को ट्रिगर किया है या नहीं।
यहाँ exploit.js का पहला पुनरावृत्ति है।```javascript
function jitMe(array){
return [...array]
}
let dummy = [1.1]
for(let i = 0; i < 200; i++){
jitMe(dummy);
}
let a = []
let len = 0x20000001
for(let i = 0; i < len; i++){
a[i] = 1.1
}
jitMe(a)
यह चलाने पर मुझे निम्नलिखित त्रुटि मिलती है:
Program terminated with signal SIGKILL, Killed. The program no longer exists.
मेरा अनुमान था कि इतनी बड़ी ऐरे आवंटित करने का प्रयास करते समय बहुत अधिक मेमोरी खपत हो रही थी। इसकी पुष्टि करने के लिए, मैंने m_jit.breakpoint() को compileNewArrayWithSpread के अंदर कॉल जोड़कर JITed कोड में एक ब्रेकपॉइंट जोड़ा, जो JITed कोड में एक int3 निर्देश जोड़ता है।
ब्रेकपॉइंट जोड़ने के बाद मुझे पता चला कि वह हिट नहीं हुआ और फिर मैंने 0x20001 की लंबाई का परीक्षण करने का निर्णय लिया। तब मुझे एहसास हुआ कि कोड संकलित भी नहीं किया जा रहा था, इसलिए मैंने DFG कंपाइलर को सक्रिय करने के लिए और अधिक पुनरावृत्तियाँ जोड़ दीं।```javascript function jitMe(array){ for(let i = 0; i < 0x4000; i++){ let x = 1 + 1 } return [...array] }
let dummy = [1.1] for(let i = 0; i < 60; i++){ print(i) jitMe(dummy); }
let a = []
let len = 0x20000001
for(let i = 0; i < len; i++){ a[i] = 1.1 }
jitMe(a)
प्रोग्राम को जैसा है वैसा परीक्षण करने पर अभी भी SIGKILL होता है, हालांकि, छोटी लंबाई के साथ परीक्षण करने पर, ब्रेकपॉइंट हिट हो जाता है। इस बिंदु पर मुझे अभी भी ऐसा लगता है कि JSC उस बड़ी सरणी को संसाधित करने का प्रयास करते समय मेमोरी से बाहर हो रहा है।
इससे निपटने के लिए, मैंने एक छोटी `a` सरणी आवंटित करने का निर्णय लिया और फिर निम्नलिखित `exploit.js` के परिणामस्वरूप दूषित सरणी बनाते समय इसे कई बार उपयोग करने के लिए स्प्रेड सिंटैक्स का उपयोग किया।```
function jitMe(array){
for(let i = 0; i < 0x4000; i++){
let x = 1 + 1
}
return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array]
}
let dummy = [1.1]
for(let i = 0; i < 100; i++){
print(i)
jitMe(dummy);
}
let a = []
let len = 0x20000010 / 0x10
for(let i = 0; i < len; i++){
a[i] = 1.1
}
jitMe(a)
इस कोड का उपयोग करके हम SIGKILL के बिना ब्रेकपॉइंट हिट करने में सक्षम थे! जैसा कि आमतौर पर होता है, एक समस्या को ठीक करने पर दूसरी समस्या सामने आती है और हमें इसके बजाय SIGABORT मिला... gdb bt कमांड का उपयोग करके हम देख सकते हैं कि operationNewArrayWithSize को कॉल किया गया, जिसने create को कॉल किया।
यह अजीब लगता है कि हमारा JITed कोड operationNewArrayWithSize को कॉल कर रहा होगा और यह निश्चित रूप से होना चाहिए कि JITed कोड को किसी कारण से जावास्क्रिप्ट इंजन के लिए धीमा पथ लेना पड़ा।

हम compileAllocateNewArrayWithSize में देख सकते हैं कि वास्तव में operationNewArrayWithSize के लिए एक बेलआउट है। फिर हमें यह पता लगाने की आवश्यकता है कि वास्तव में हम धीमे मामले में क्यों बेल आउट कर रहे हैं।
हम देख सकते हैं कि compileNewArrayWithSpread में shouldConvertLargeSizeToArrayStorage को false पर सेट किया गया है और वह धीमा पथ संकलित कोड में नहीं होगा।
इसलिए यह समझ में आता है कि धीमा पथ emitAllocateJSObject के भीतर कहीं हिट हो रहा है।

emitAllocateJSObject emitAllocateJSCell को कॉल करता है, जो बदले में emitAllocate को कॉल करता है।