
Android कर्नेल भेद्यता CVE-2019-2215 को ट्रिगर करना और विश्लेषण करना
नवंबर 2017 में, syzkaller प्रणाली द्वारा लिनक्स कर्नेल में एक use-after-free बग का पता लगाया गया था। फरवरी 2018 में इसे कुछ लिनक्स कर्नेल और एंड्रॉइड संस्करणों में पैच किया गया था।
यह सुधार एंड्रॉइड मासिक सुरक्षा बुलेटिन में कभी शामिल नहीं किया गया था, इसलिए इसे Pixel और Pixel2 जैसे कई नए जारी उपकरणों में पैच नहीं किया गया।
सितंबर 2019 में, Project Zero द्वारा एंड्रॉइड को इस बग के सुरक्षा निहितार्थों के बारे में सूचित किया गया। फिर एंड्रॉइड ने इस भेद्यता को अधिक औपचारिक और ज्ञात बनाने के लिए इसे CVE-2019-2215 प्रदान किया।
CVE-2019-2215 binder.c में एक use-after-free है जो किसी एंड्रॉइड एप्लिकेशन से विशेषाधिकार की प्राप्ति (रूट एक्सेस प्राप्त करना) की अनुमति देता है। इस भेद्यता का शोषण करने के लिए उपयोगकर्ता की सहभागिता की कोई आवश्यकता नहीं है। इसके लिए केवल एक दुर्भावनापूर्ण स्थानीय एप्लिकेशन की स्थापना की आवश्यकता होती है।
यहाँ हम इस एंड्रॉइड कर्नेल भेद्यता को अधिक विस्तार से पेश करने जा रहे हैं और इस भेद्यता का उपयोग पूरे एंड्रॉइड डिवाइस का रूट एक्सेस (विशेषाधिकार वृद्धि) प्राप्त करने के लिए करेंगे।
हम इस प्रूफ ऑफ कॉन्सेप्ट (PoC) का उपयोग करेंगे:
https://github.com/cloudfuzz/android-kernel-exploitation
हम पहले आपको एंड्रॉइड एमुलेटर पर इस भेद्यता को ट्रिगर करने और कर्नेल क्रैश कराने का एक तरीका दिखाएंगे। फिर यह देखने के लिए कि यह कितना खतरनाक हो सकता है, हम अनुकरणित एंड्रॉइड डिवाइस में रूट एक्सेस प्राप्त करने के लिए PoC का उपयोग जारी रखेंगे। फिर हम यह देखने के लिए कर्नेल कोड का विश्लेषण करेंगे कि इसका कारण क्या है (स्थैतिक और गतिशील विश्लेषण)।
विश्लेषण के बाद, हम देखेंगे कि हमें रूट एक्सेस कैसे मिला। अंत में हम देखते हैं कि पैच का उपयोग करके इस भेद्यता को कैसे कम किया जाता है।
इस भेद्यता को ट्रिगर करके कर्नेल क्रैश कराने के लिए हम इन चरणों का उपयोग करते हैं:
निम्नलिखित वीडियो देखें:
[वीडियो]
इस (और अगले) अनुभाग में हम स्थैतिक और गतिशील विश्लेषण का उपयोग करके यह समझने जा रहे हैं कि क्रैश क्यों होता है।
यहाँ हम समस्या को समझने के लिए कर्नेल कोड का विश्लेषण (स्थैतिक विश्लेषण) करेंगे। crash_report.txt में KASan से रिपोर्ट है जो कहती है कि यह एक use-after-free बग है। इसका अर्थ है कि एक ऑब्जेक्ट हीप में आवंटित किया जाता है (और हमारे पास उसका एक संदर्भ होता है), फिर हम ऑब्जेक्ट को हीप से मुक्त कर देते हैं और फिर हम गलती से उसे संदर्भ द्वारा कॉल करते हैं। इस रिपोर्ट में इन तीन चरणों का स्टैक ट्रेस मुद्रित होता है।
यदि आपको ऊपर से याद है, हमने PoC में 'trigger.cpp' का उपयोग किया था।
यहाँ trigger.cpp का मुख्य कोड है:``` int main() { //1 int fd, epfd; //2 struct epoll_event event = {.events = EPOLLIN}; //3 fd = open("/dev/binder", O_RDONLY); //4 epfd = epoll_create(1000); //5 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); //6 ioctl(fd, BINDER_THREAD_EXIT, NULL); }
आइए देखें कि 'trigger.cpp' क्या कर रहा है।
एंड्रॉइड में (अन्य Unix-आधारित ऑपरेटिंग सिस्टम की तरह) कुछ प्रोसेस होते हैं। हम जो भी प्रोग्राम चलाते हैं, वह एक (या अधिक) प्रोसेस बनाता है और इन प्रोसेस को OS द्वारा हैंडल किया जाता है। OS उनके बीच स्विच कर सकता है (मल्टी-टास्किंग) या किसी प्रोसेस को समाप्त कर सकता है, आदि। सुरक्षा कारणों से, प्रोसेस डिफ़ॉल्ट रूप से एक दूसरे से अलग (isolated) होते हैं।
कुछ मामलों में, एक प्रोसेस को दूसरे प्रोसेस के साथ डेटा का आदान-प्रदान करने की आवश्यकता हो सकती है। इसे इंटरप्रोसेस कम्युनिकेशन (IPC) कहा जाता है। लिनक्स में प्रोसेस के बीच संवाद करने के कई तरीके हैं। एंड्रॉइड ने **'Binder'** नामक एक विशिष्ट IPC तंत्र पेश किया है। Binder इंटर-प्रोसेस संचार को सुगम बनाने के लिए एक कर्नेल ड्राइवर है।
एंड्रॉइड में, IPC कुछ कर्नेल मेथड्स को सीधे कॉल करके (उनमें से अधिकांश drivers/binder.c में हैं) या उच्च-स्तरीय इम्प्लीमेंटेशन का उपयोग करके (उदाहरण के लिए java में) किया जा सकता है।

**binder** का उपयोग करने के लिए, हमें कर्नेल binder मॉड्यूल को खोलना चाहिए। यह trigger.cpp की लाइन 3 का उपयोग करके किया जाता है। फिर हमारे पास एक फ़ाइल डिस्क्रिप्टर पॉइंटर होता है, इस fd का उपयोग करके IPC के कर्नेल इनिशिएटर और प्राप्तकर्ताओं की पहचान की जा सकती है।
ड्राइवर के साथ सभी इंटरैक्शन **'ioctl**' कमांड्स के एक छोटे से सेट के माध्यम से होंगे (BINDER_THREAD_EXIT, BINDER_WRITE_READ, ...)।
binder के बारे में अधिक: [link1](https://www.nds.ruhr-uni-bochum.de/media/attachments/files/2012/03/binder.pdf), [link2](http://rts.lab.asu.edu/web_438/project_final/CSE_598_Android_Architecture_Binder.pdf)
लिनक्स में, हमारे पास '**event polling**' नामक एक अवधारणा है। '**epoll**' API का उपयोग तब किया जाता है जब हम कई फ़ाइल डिस्क्रिप्टरों को मॉनिटर करना चाहते हैं (फ़ाइल डिस्क्रिप्टर वे होते हैं जो हमें ड्राइवर खोलने या IO के साथ काम करने आदि पर मिलते हैं)।
**epoll** एक कर्नेल स्ट्रक्चर है जिसमें दो महत्वपूर्ण फ़ील्ड होते हैं।
* interest list = उन फ़ाइल डिस्क्रिप्टरों की सूची जिन्हें हम मॉनिटर करना चाहते हैं।
* ready list = उन फ़ाइल डिस्क्रिप्टरों की सूची जो I/O के लिए तैयार हैं।
इवेंट पोलिंग का उपयोग करने के लिए, हम पहले एक epoll बनाते हैं (लाइन 4), फिर एक फ़ाइल डिस्क्रिप्टर (**fd**) से जुड़े इवेंट (&event) को कर्नेल की **epoll_ctl** विधि को कॉल करके अपने बनाए गए epoll (**epfd**) में जोड़ते या हटाते हैं (EPOLL_CTL_ADD)।
=> `epoll_event event` एक इवेंट है, जो तब ट्रिगर होता है जब संबंधित फ़ाइल (fd) रीड ऑपरेशन के लिए उपलब्ध होती है।
अब हम समझ गए हैं कि **trigger.cpp** क्या कर रहा है (गहराई में जाने की आवश्यकता नहीं!)। यह **binder** मॉड्यूल को खोलता है, उसे सुनने के लिए एक **epoll** बनाता है जब वह तैयार हो। फिर लाइन 6 पर, हम उस binder से बाहर निकलते हैं जिसे हमने लाइन 3 में शुरू किया था।
#### आवंटन:
open() को कॉल करके, हम वास्तव में **open_binder()** को कॉल करते हैं (binder.c में open() का इम्प्लीमेंटेशन), open_binder() में, एक नया **'binder_proc'** स्ट्रक्चर बनाया जाएगा और:
` fd->pricate_data = binder_proc`
**epoll_create()** को कॉल करने पर, एक नया epoll स्ट्रक्चर बनाया जाएगा और एक कतार संरचना में जोड़ा जाएगा।

**epoll_ctrl(epdf, ADD, fd, event)** को कॉल करने पर, एक नया **ep_item** बनाया जाता है, **fd** (सुनने के लिए फ़ाइल डिस्क्रिप्टर) को इस **ep_item** से संबद्ध किया जाता है, और इसे event_poll के **रेड-ब्लैक ट्री** (ep में ep_items को सहेजने के लिए एक डेटा संरचना) में डाला जाता है। यह **ep_item_poll()** को भी कॉल करता है, यह विधि ep_item के साथ कॉलबैक फ़ंक्शन संबद्धता को संभालती है।
यह नया **binder_thread** स्ट्रक्चर बनाता है (**आवंटन यहाँ होता है**), इसे **binder_proc** (ऊपर बनाया गया) से जोड़ता है, फिर एक **epoll_entry** स्ट्रक्चर बनाया जाता है, इसकी दो सूचियाँ होती हैं, **epoll_entry->wait** और **epoll_entry->whead**, इन दोनों सूचियों में पहले बनाए गए **binder_thread** का पॉइंटर होता है।
फिर **epoll_entry** को ep_item से जोड़ा जाता है (**ep_item->pwqlist** एक सूची है जिसमें यह epoll_entry होता है)।

#### मुक्त करना:
**ioctl(fd, ...)** को कॉल करने पर, **binder_proc** को fd->private_data के माध्यम से एक्सेस किया जाता है, फिर **binder_thread** स्ट्रक्चर को मेमोरी से **मुक्त (freed)** कर दिया जाता है।
#### उपयोग:
जब हमारी वर्तमान प्रोसेस समाप्त होती है, तो **epoll_ctl(epfd, DEL, fd, event)** को कॉल किया जाएगा।
यह **ep_remove(event_poll, ep_item)** को कॉल करता है। यह विधि **ep_item->pwqlist** से **epoll_entry** प्राप्त करती है, फिर **ep_item (ep_tem->wait)** की वेट सूची प्राप्त करती है, यह एक लिंक्ड सूची है, यह इस वेट सूची के एक आइटम को हटाना चाहती है।
यह निम्नलिखित कोड का उपयोग करता है (सूडो कोड प्रयोग किया गया है):```
entry = wait->entry;
entry.next.prev = entry.prev;
entry.prev.next = entry.next;
यहाँ wait->entry उस binder_thread के लिए एक पॉइंटर है जिसे मेमोरी से हटा दिया गया था! तो यह use after free है और एक बग का कारण बनता है!!
हमने एक event_poll बनाया जिसमें एक red_black_tree है, प्रत्येक नोड एक ep_item है जिसमें एक फ़ील्ड होती है जो epoll_entry की सूची है, प्रत्येक epoll_entry में binder_thread struct (wait, whead) के दो पॉइंटर होते हैं।
ioctl() कॉल करके हमने binder_thread को मेमोरी से मुक्त कर दिया, फिर बाहर निकलते समय इस struct को एक पॉइंटर के माध्यम से एक्सेस किया जाता है जो अभी भी उपलब्ध था!
डायनेमिक विश्लेषण एक प्रोग्राम का वास्तविक समय में डेटा निष्पादित करके परीक्षण और मूल्यांकन है; एक प्रोग्राम में त्रुटियों को खोजने के लिए जब वह चल रहा हो।
चरण:
Android कर्नेल को KASan के बिना बनाएं
हम इसे KASAN के बिना बनाते हैं ताकि लेखन (write) और अनलिंक (unlink) संचालन की निगरानी कर सकें और देख सकें कि अनलिंक ऑपरेशन के बाद वास्तव में क्या हो रहा है।
नए बनाए गए कर्नेल के साथ एमुलेटर बूट करें
एमुलेटर लॉन्च करें
भेद्यता ट्रिगर बनाएं और इसे वर्चुअल डिवाइस पर पुश करें
GDB में ब्रेक करें
कस्टम पायथन स्क्रिप्ट (dynamic-analysis.py रिपॉजिटरी में) लोड करें: फ़ंक्शन कॉल्स को ट्रेस करने और binder_thread संरचना के चंक को उसके मुक्त होने से पहले और बाद में डंप करने के लिए। साथ ही अनलिंक ऑपरेशन किए जाने से पहले और बाद में उसी binder_thread संरचना को डंप करें।
इस फ़ाइल में हम पहले सभी ब्रेकपॉइंट हटाते हैं और फिर 2 ब्रेकपॉइंट (BP) लगाते हैं; पहला सिंबल “binder_free_thread” है (binder_free_thread फ़ंक्शन को ट्रेस करेगा) binder_thread मुक्त होने से पहले, stop फ़ंक्शन कॉल किया जाएगा; इसलिए पैरामीटर और सिंबल (gb.write(....) ) के साथ दिखाए जाएंगे और फिर कॉलबैक विधि (जिसे हमने set_dump_binder_thread पर सेट किया है) कॉल की जाएगी; इस फ़ंक्शन में binder_thread_address हमारे ग्लोबल वेरिएबल में सेट होगा और gdb.execute कमांड द्वारा उत्पन्न किसी भी आउटपुट को GDB के मानक आउटपुट में भेजता है।
दूसरा सिंबल “remove_wait_queue” है (remove_wait_queue फ़ंक्शन को ट्रेस करेगा) जिन पैरामीटरों को हम देखना चाहते हैं वे हैं "wq_head", "wq_entry" और बाहर निकलने के लिए wait.c:52 पर ब्रेकपॉइंट सेट किया जाएगा। उनका कॉलबैक फ़ंक्शन dump_binder_thread है। ये ब्रेकपॉइंट दिखाएंगे कि अनलिंक ऑपरेशन से पहले और बाद में क्या होता है।
परिणाम:
binder_free_thread(thread=0xffff88800c18f200)(enter) 0xffff88800c18f200: 0xffff88806793c000 0x0000000000000001 0xffff88800c18f210: 0x0000000000000000 0x0000000000000000 0xffff88800c18f220: 0xffff88800c18f220 0xffff88800c18f220 0xffff88800c18f230: 0x0000002000001b35 0x0000000000000001 0xffff88800c18f240: 0x0000000000000000 0xffff88800c18f248 0xffff88800c18f250: 0xffff88800c18f248 0x0000000000000000 0xffff88800c18f260: 0x0000000000000000 0x0000000000000000 0xffff88800c18f270: 0x0000000000000003 0x0000000000007201 0xffff88800c18f280: 0x0000000000000000 0x0000000000000000 0xffff88800c18f290: 0x0000000000000003 0x0000000000007201 0xffff88800c18f2a0: 0x0000000000000000 0xffff88805c05cae0 0xffff88800c18f2b0: 0xffff88805c05cae0 0x0000000000000000 0xffff88800c18f2c0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2d0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2e0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2f0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f300: 0x0000000000000000 0x0000000000000000 0xffff88800c18f310: 0x0000000000000000 0x0000000000000000 0xffff88800c18f320: 0x0000000000000000 0x0000000000000000 0xffff88800c18f330: 0x0000000000000000 0x0000000000000000 0xffff88800c18f340: 0x0000000000000000 0x0000000000000000 0xffff88800c18f350: 0x0000000000000000 0x0000000000000000 0xffff88800c18f360: 0x0000000000000000 0x0000000000000000 0xffff88800c18f370: 0x0000000000000000 0x0000000000000000 0xffff88800c18f380: 0x0000000000000000 0x0000000000000001 0xffff88800c18f390: 0xffff88806d4bb200
हमारे पायथन कोड में हमारे पास नीचे दिए गए कोड थे:``` gdb.write ( "{function}({param})(enter)\n".format( function=self.function_name, param=params ) )
और यह binder_free_thread फ़ंक्शन है, इसलिए इस फ़ंक्शन का पैरामीटर binder_thread का एक पॉइंटर है:```
static void binder_free_thread(struct binder_thread *thread)
{
[...]
kfree(thread);
}
परिणाम में हमारे पास था(binder_free_thread(thread=0xffff88800c18f200)(enter)).
बाद की पंक्तियाँ निष्पादन का परिणाम दिखाती हैं, नीचे दिए गए कमांड से हमें binder_thread.wait का ऑफसेट मिलता है:``` p offsetof(struct binder_thread, wait)
परिणाम 0xa0 है और यदि हम कमांड में wait के बजाय wait.head डालते हैं, तो परिणाम 0xa8 होगा और इसमें `0xffff88805c05cae0` शामिल है।```
0xffff88805c05cae0 is pointer to eppoll_entry->wait.entry which is of type struct list_head
remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter) 0xffff88800c18f200: 0xffff88800c18f600 0x0000000000000001 0xffff88800c18f210: 0x0000000000000000 0x0000000000000000 0xffff88800c18f220: 0xffff88800c18f220 0xffff88800c18f220 0xffff88800c18f230: 0x0000002000001b35 0x0000000000000001 0xffff88800c18f240: 0x0000000000000000 0xffff88800c18f248 0xffff88800c18f250: 0xffff88800c18f248 0x0000000000000000 0xffff88800c18f260: 0x0000000000000000 0x0000000000000000 0xffff88800c18f270: 0x0000000000000003 0x0000000000007201 0xffff88800c18f280: 0x0000000000000000 0x0000000000000000 0xffff88800c18f290: 0x0000000000000003 0x0000000000007201 0xffff88800c18f2a0: 0x0000000000000000 0xffff88805c05cae0 0xffff88800c18f2b0: 0xffff88805c05cae0 0x0000000000000000 0xffff88800c18f2c0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2d0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2e0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2f0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f300: 0x0000000000000000 0x0000000000000000 0xffff88800c18f310: 0x0000000000000000 0x0000000000000000 0xffff88800c18f320: 0x0000000000000000 0x0000000000000000 0xffff88800c18f330: 0x0000000000000000 0x0000000000000000 0xffff88800c18f340: 0x0000000000000000 0x0000000000000000 0xffff88800c18f350: 0x0000000000000000 0x0000000000000000 0xffff88800c18f360: 0x0000000000000000 0x0000000000000000 0xffff88800c18f370: 0x0000000000000000 0x0000000000000000 0xffff88800c18f380: 0x0000000000000000 0x0000000000000001 0xffff88800c18f390: 0xffff88806d4bb200
remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter) में, wq_head, binder_thread.wait का पता है और wq_entry, wait.head का डेटा है।
इसके बाद unlink ऑपरेशन होगा।
Breakpoint 3 at 0xffffffff802aa5be: file /home/ashfaq/workshop/android-4.14-dev/goldfish/kernel/sched/wait.c, line 53. remove_wait_queue_wait.c:52(exit) 0xffff88800c18f200: 0xffff88800c18f600 0x0000000000000001 0xffff88800c18f210: 0x0000000000000000 0x0000000000000000 0xffff88800c18f220: 0xffff88800c18f220 0xffff88800c18f220 0xffff88800c18f230: 0x0000002000001b35 0x0000000000000001 0xffff88800c18f240: 0x0000000000000000 0xffff88800c18f248 0xffff88800c18f250: 0xffff88800c18f248 0x0000000000000000 0xffff88800c18f260: 0x0000000000000000 0x0000000000000000 0xffff88800c18f270: 0x0000000000000003 0x0000000000007201 0xffff88800c18f280: 0x0000000000000000 0x0000000000000000 0xffff88800c18f290: 0x0000000000000003 0x0000000000007201 0xffff88800c18f2a0: 0x0000000000000000 0xffff88800c18f2a8 0xffff88800c18f2b0: 0xffff88800c18f2a8 0x0000000000000000 0xffff88800c18f2c0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2d0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2e0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2f0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f300: 0x0000000000000000 0x0000000000000000 0xffff88800c18f310: 0x0000000000000000 0x0000000000000000 0xffff88800c18f320: 0x0000000000000000 0x0000000000000000 0xffff88800c18f330: 0x0000000000000000 0x0000000000000000 0xffff88800c18f340: 0x0000000000000000 0x0000000000000000 0xffff88800c18f350: 0x0000000000000000 0x0000000000000000 0xffff88800c18f360: 0x0000000000000000 0x0000000000000000 0xffff88800c18f370: 0x0000000000000000 0x0000000000000000 0xffff88800c18f380: 0x0000000000000000 0x0000000000000001 0xffff88800c18f390: 0xffff88806d4bb200
हाइलाइट किया गया भाग इस के कारण है।परिणाम unlink ऑपरेशन के बाद का है और हम देख सकते हैं कि unlink करने के लिए पता (0xffff88800c18f2a0 + 0x8) next और previous में लिखा जाता है।
इसका अर्थ है:
(binder_thread->wait.head) का पॉइंटर = binder_thread->wait.head.next = `binder_thread->wait.head.prev
इस भाग में हम दिखाएंगे कि हम इस बग का उपयोग करके root एक्सेस कैसे प्राप्त कर सकते हैं।
ऊपर से याद कीजिए, हमारे पास एक binder_thread struct था, जिसे free किया गया था और एक पॉइंटर का उपयोग करके पुनः उपयोग किया गया था। यह binder_thread struct का कोड है:``` struct binder_thread { struct binder_proc proc; struct rb_node rb_node; struct list_head waiting_thread_node; int pid; int looper; / only modified by this thread*/ bool looper_need_return; /* can be written by other thread*/ struct binder_transaction *transaction_stack; struct list_head todo; bool process_todo; struct binder_error return_error; struct binder_error reply_error; wait_queue_head_t wait; struct binder_stats stats; atomic_t tmp_ref; bool is_dead; struct task_struct *task; };
फ़ील्ड्स में से एक पॉइंटर **task_struct** है, इस स्ट्रक्चर में **addr_limit** नामक एक फ़ील्ड होती है।
जब हम किसी प्रोसेस में किसी एड्रेस को एक्सेस करना चाहते हैं, तो यह जाँचा जाता है कि एड्रेस **user-space** में है या नहीं, यदि वह **kernel-space** में होता तो यह एक्सेस ब्लॉक कर दी जानी चाहिए। यह जाँच हमारे एड्रेस की तुलना **addr_limit** से करके की जाती है, यदि हमारा एड्रेस **addr_limit** से कम है तो हमारे पास वैध एक्सेस होती है।
**addr_limit** वास्तव में user-space को kernel-space से अलग करता है, इसलिए **task_struct** में इस फ़ील्ड को बदलकर हमें kernel-space तक पूर्ण एक्सेस मिल जाती है और हम कुछ भी कर सकते हैं!
यहाँ हमारे पास एक्सप्लॉइट करने के लिए दो चरण हैं:
1. kernel-space में task_struct का एड्रेस ढूँढना
2. task_struct में addr_limit को बदलना
#### task_struct का एड्रेस ढूँढना
कर्नेल में, हम vectored I/O कर सकते हैं, इसका मतलब है कि हम एक file descriptor (फ़ाइल, सॉकेट, आदि) से या में डेटा के एक से अधिक हिस्से (chunk) लिख या पढ़ सकते हैं।
Vectored I/O **writev**, **readv**, **recvmsg** विधियों और **iovec** स्ट्रक्चर का उपयोग करके किया जाता है।```
struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ \
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */ \
};
वेक्टर्ड I/O का उपयोग करके, हम बफ़र्स की एक सरणी पर I/O ऑपरेशन कर रहे हैं (iovec). प्रत्येक iovec में बफ़र के लिए एक पॉइंटर (iov_base) और बफ़र का आकार (iov_len) होता है।
उदाहरण के लिए, यदि हम बफ़र्स की एक सरणी को फ़ाइल (fd) में लिखना चाहते हैं, तो हम कॉल करते हैं
writev(fd, iovecStack, count)
यह विधि (readv और recvmsg की तरह) सबसे पहले iovec सरणी (iovecStack) को kernel-space में कॉपी करती है, फिर इन बफ़र्स से पढ़ती है और fd में लिखती है।
हम pipe का उपयोग करके बफ़र्स को लिख और पढ़ सकते हैं। pipe एक struct है जो हमें दो फ़ाइल डिस्क्रिप्टर देता है, एक पढ़ने के लिए और एक लिखने के लिए। pipe की लंबाई बाइट्स में होती है, जब कोई प्रक्रिया pipe में उसकी लंबाई से अधिक लिखती है, तो pipe उस प्रक्रिया में ब्लॉक हो जाता है और किसी अन्य प्रक्रिया द्वारा उस pipe से पढ़े जाने की प्रतीक्षा करता है (उस pipe के रीड फ़ाइल डिस्क्रिप्टर का उपयोग करके)।
कर्नेल मेमोरी (एक struct के लिए) को उसके आकार के अनुसार आवंटित करने का प्रयास करता है। उदाहरण के लिए जब हमने binder_thread struct को मेमोरी से मुक्त किया (स्टैटिक विश्लेषण देखें), और उसके बाद यदि हमारे पास binder_thread के समान आकार वाला एक struct है, तो उसके मुक्त किए गए binder_thread के same स्थान पर आवंटित होने की अच्छी संभावना है।
पहले हम एक iovecStack (iovec structs की सरणी) बनाते हैं जिसका आकार binder_thread struct के समान होता है।
फिर हम binder_thread को मेमोरी से मुक्त करते हैं (स्टैटिक विश्लेषण का 'free' भाग देखें)
फिर हम इस iovecStack पर writev() कॉल करते हैं। इस विधि का पहला भाग iovecStack को kernel space में कॉपी करता है,
यह अधिक संभावना है कि हमारा iovecStack मुक्त किए गए binder_thread के same स्थान पर आवंटित हो।
यदि हमारे पास iovecStack में पर्याप्त iovec हैं, तो binder_thread के स्थान पर मेमोरी इस तरह दिखती है:

आप देखते हैं कि iovecStack[10].iov_base, iovecStack[10].iov_len और iovecStack[11].iov_base binder_thread के wait.lock, wait.head.next और wait.head.prev फ़ील्ड्स. के same स्थान पर होंगे।
स्टैटिक विश्लेषण के 'use' भाग में हमने देखा कि unlinking प्रक्रिया (लिंक्ड सूची से एक आइटम हटाना) के दौरान binder_thread के wait भाग तक पहुँच के कारण क्रैश हुआ।
Unlinking प्रक्रिया के दौरान, wait.head अनलिंक हो जाता है और इसके next और prev फ़ील्ड binder_thread के wait फ़ील्ड की ओर इंगित करेंगे (unlinking)।

writev का दूसरा भाग होने से पहले (iovecs से फ़ाइल में लिखना), यदि हम unlink प्रक्रिया चलाते हैं, तो iovecStack[10].iov_len और iovecStack[11].iov_base एक kernel address के साथ अधिलेखित हो जाएंगे। फिर शेष writev चलाने के बाद, जब यह iovecStack[11] को प्रोसेस करना चाहता है, तो यह iovecStack[11].iov_base (=binder_thread में wait का पता) से iovecStack[11].iov_len लंबाई का डेटा पढ़ता है।
यदि iovecStack[11].iov_len पर्याप्त है, तो हम binder_thread के wait फ़ील्ड से task_struct फ़ील्ड तक पढ़ते हैं ताकि हमें task_struct पॉइंटर मिल जाए।
इसलिए task_struct पॉइंटर प्राप्त करने के लिए हम निम्नलिखित करते हैं:
रिपॉजिटरी में exploit.cpp देखें।
अब हमारे पास kernel-space में task_struct का पता है (task_ptr)।
यहाँ हम pipe के बजाय socket_pair का उपयोग करते हैं। और हम socket से पढ़ने और iovecs में लिखने के लिए recvmsg() का उपयोग करते हैं।
चरण:
लिखने के बाद, **recvmsg**() सॉकेट से पढ़ना शुरू करता है और **iovecStack** में लिखता है। कचरे के कारण, इसने **iovecStack**[10] तक लिखा है।
तो यह **iovecStack**[12] में **finalSocketData** लिखना शुरू करता है, पता **iovecStack[12].iov_base** से प्राप्त करता है, जो कि **binder_thread में wait** का पता है, unlink ऑपरेशन के कारण, **iovecStack[12].iov_len** 4 बाइट्स पर सेट है, इसलिए लिखता है:
* 0x1 को iovecStack[10].iov_len में
* 0x41414141 को iovecStack[11].iov_base में
* 0x8 + 0x8 + 0x8 + 0x8 को iovecStack[11].iov_len में
* **pointer_to_addr_limit** को iovecStack[12].iov_base में
अब **iovecStack[11]** में 4 बाइट्स लिखे जा चुके हैं, इसलिए **recvmsg**() शेष **finalSocketData** लिखने के लिए **iovecStack[12]** पर जाता है:
लिखता है **0xFFFFFFFFFFFFFFFE **को **iovecStack[12].iov_base** में स्थित पते पर, जो **pointer_to_addr_limit** पर सेट है, इसका मतलब है कि recvmsg() addr_limit को 0xFFFFFFFFFFFFFFFE में बदल देता है (0xFFFFFFFFFFFFFFFF नहीं, क्योंकि arm64 के साथ कुछ समस्याएँ हैं)।
अब यूज़र-स्पेस लगभग सभी कर्नेल-स्पेस तक विस्तारित हो जाता है! (और कुछ भी कर सकता है!!)
# पैच
binder_poll() thread->wait waitqueue पास करता है, जिस पर कार्य के लिए सोया जा सकता है। जब epoll का उपयोग करने वाला थ्रेड BINDER_THREAD_EXIT का उपयोग करके स्पष्ट रूप से बाहर निकलता है, तो waitqueue मुक्त हो जाती है, लेकिन इसे <span style="text-decoration:underline;">संबंधित epoll डेटा संरचना से कभी नहीं हटाया जाता</span>। जब प्रक्रिया बाद में बाहर निकलती है, तो epoll क्लीनअप कोड waitlist तक पहुँचने का प्रयास करता है, जिसके परिणामस्वरूप use-after-free होता है।
इसे रोकने के लिए, थ्रेड के बाहर निकलने पर POLLFREE का उपयोग करें।
हमारे पास यह कोड था:```
static int binder_thread_release(struct binder_proc *proc, struct binder_thread *thread)
{
.
.
.
int active_transactions = 0;
.
.
.
binder_thread_dec_tmpref(thread);
return active_transactions;
}
ये पंक्तियाँ स्रोत कोड में जोड़ी गई हैं:
static int binder_thread_release(struct binder_proc *proc,
binder_thread *thread)
{
.
.
.
/*
* If this thread used poll, make sure we remove the waitqueue
* from any epoll data structures holding it with POLLFREE.
* waitqueue_active() is safe to use here because we're holding
* the inner lock.
*/
if ((thread->looper & BINDER_LOOPER_STATE_POLL) && waitqueue_active(&thread->wait)) {
wake_up_poll(&thread->wait, EPOLLHUP | POLLFREE);
}
binder_inner_proc_unlock(thread->proc);
/*
* This is needed to avoid races between wake_up_poll() above and
* and ep_remove_waitqueue() called for other reasons (eg the epoll file
* descriptor being closed); ep_remove_waitqueue() holds an RCU read
* lock, so we can be sure it's done after calling synchronize_rcu().
*/
if (thread->looper & BINDER_LOOPER_STATE_POLL)
synchronize_rcu();
.
.
.
}
पूरा कोड यहाँ देखें
ऑपरेटिंग सिस्टम सॉफ्टवेयर की एक परत है जो सभी हार्डवेयर को अधिक कुशलता से काम करने और एक बुनियादी ढाँचा बनाने के लिए ज़िम्मेदार है, जिसके शीर्ष पर आपके द्वारा उपयोग किए जाने वाले एप्लिकेशन काम कर सकते हैं; इसका मूल कर्नेल है।
शोषण के पीछे का विचार सरल है: सॉफ्टवेयर में बग होते हैं, और बग सॉफ्टवेयर को गलत व्यवहार करने या किसी कार्य को गलत तरीके से करने का कारण बनते हैं, जिसे उसे सही ढंग से करने के लिए डिज़ाइन किया गया था; बग का शोषण करने का अर्थ है इस गलत व्यवहार को हमलावरों के लिए लाभ में बदलना।
जो बग शोषण योग्य होते हैं उन्हें भेद्यताएँ (vulnerabilities) कहा जाता है।
एक विशेषाधिकार प्राप्त उपयोगकर्ता या प्रोसेस वह होता है जिसके पास डिवाइस का पूर्ण अभिगम होता है।
अधिकांश इंस्ट्रक्शन सेट आर्किटेक्चर निष्पादन के लिए कम से कम दो मोड प्रदान करते हैं:
विशेषाधिकार प्राप्त : सभी मशीन-स्तरीय इंस्ट्रक्शन सुलभ होते हैं।
अविशेषाधिकार प्राप्त : केवल इंस्ट्रक्शनों का एक उपसमुच्चय सुलभ होता है।
Use-After-Free भेद्यताएँ एक प्रकार की मेमोरी भ्रष्टाचार त्रुटि हैं, जिनका उपयोग हैकर्स द्वारा मनमाना कोड निष्पादित करने के लिए किया जा सकता है।
Use-After-Free विशेष रूप से मेमोरी को मुक्त करने के बाद उस तक पहुँचने के प्रयास को संदर्भित करता है, जो किसी प्रोग्राम को क्रैश कर सकता है या, Use-After-Free त्रुटि के मामले में, संभावित रूप से मनमाना कोड निष्पादन या यहाँ तक कि पूर्ण रिमोट कोड निष्पादन क्षमताओं को सक्षम कर सकता है।
यह Android कर्नेल में Binder के भीतर एक use-after-free है। यह बग एक स्थानीय विशेषाधिकार वृद्धि (local privilege escalation) भेद्यता है जो किसी भेद्य डिवाइस से पूर्ण समझौता करने की अनुमति देती है। यदि इसे ब्राउज़र रेंडरर शोषण के साथ जोड़ा जाए, तो यह बग किसी दुर्भावनापूर्ण वेबसाइट के माध्यम से डिवाइस से पूरी तरह समझौता कर सकता है। यह Chrome सैंडबॉक्स के अंदर से पहुँच योग्य है।
नोट : यह Pixel 1 और 2 पर काम करता है, लेकिन Pixel 3 और 3a पर नहीं। यह हमला देखें।
Android सुरक्षा बुलेटिन Google द्वारा (मासिक) प्रकाशित एक सूची है। इस सूची में वे सुरक्षा भेद्यताएँ शामिल होती हैं जिन्हें ठीक कर दिया गया है और जो Android फ्रेमवर्क, Linux कर्नेल आदि को प्रभावित करती हैं।
Syzkaller एक कर्नेल फज़र है। फज़िंग एक परीक्षण तकनीक है जिसमें एक स्वचालित प्रोग्राम लक्षित प्रोग्राम के लिए अर्ध-यादृच्छिक इनपुट उत्पन्न करता है ताकि यह जाँचा जा सके कि कोई बग ट्रिगर होता है या नहीं। फज़िंग C या C++ प्रोग्रामों में मेमोरी भ्रष्टाचार बग खोजने में विशेष रूप से उपयोगी है।
Project Zero Google द्वारा नियोजित सुरक्षा विश्लेषकों की एक टीम है जिसका कार्य zero-day भेद्यताएँ खोजना है। Zero-day भेद्यता वह भेद्यता है जो उन लोगों के लिए अज्ञात होती है जिन्हें इसे ठीक करना चाहिए।
GDB का पूर्ण रूप GNU Project Debugger है, जो C और C++ प्रोग्रामों को डिबग करने के लिए UNIX सिस्टम का सबसे लोकप्रिय डिबगर है। GDB आपको प्रोग्राम को एक निश्चित बिंदु तक चलाने, फिर रोकने और उस बिंदु पर विशेष चरों के मान प्रिंट करने, या प्रोग्राम में एक-एक पंक्ति करके आगे बढ़ने और प्रत्येक पंक्ति को निष्पादित करने के बाद प्रत्येक चर के मान प्रिंट करने की सुविधा देता है।
आप डिबगिंग के लिए अपने Android इम्यूलेटर को gdbserver से कनेक्ट कर सकते हैं।
Kernel Address SANitizer (KASAN) एक गतिशील मेमोरी त्रुटि डिटेक्टर है जिसे out-of-bound और use-after-free बग खोजने के लिए डिज़ाइन किया गया है। KASAN प्रत्येक मेमोरी एक्सेस से पहले वैधता जाँच डालने के लिए कंपाइल-टाइम इंस्ट्रूमेंटेशन का उपयोग करता है, और इसलिए इसके लिए एक कंपाइलर संस्करण की आवश्यकता होती है जो उसका समर्थन करता हो। कर्नेल मेमोरी एक्सेस को shadow map के विरुद्ध जाँचा जा सकता है ताकि यह देखा जा सके कि वे वैध हैं या नहीं।
QEMU (Quick Emulator) एक मुफ्त और ओपन-सोर्स इम्यूलेटर है जो हार्डवेयर वर्चुअलाइजेशन करता है। Android Emulator, QEMU इम्यूलेटर से व्युत्पन्न है; यह Android डिवाइसों को बूट करने के लिए समर्थन जोड़ता है, विशिष्ट Android हार्डवेयर (OpenGL, GPS, GSM, Sensors) और एक GUI इंटरफ़ेस का अनुकरण करता है। Android इम्यूलेटर QEMU को विभिन्न तरीकों से विस्तारित करता है।
स्थैतिक विश्लेषण में, हम किसी प्रोग्राम में बग या कोई समस्या खोजने के लिए उसके स्रोत कोड का उपयोग करते हैं। हम प्रोग्राम को चलाते नहीं हैं।
गतिशील विश्लेषण में, हम प्रोग्राम के चलने के दौरान उसके व्यवहार का विश्लेषण करते हैं। उदाहरण के लिए, विशेष इनपुट प्रदान करके।
Android NDK एक टूल-सेट है जो आपको Android डिवाइस में C और C++ जैसे नेटिव कोड चलाने की सुविधा देता है।
Android Goldfish कर्नेल का उपयोग Android इम्यूलेटर में कर्नेल कोड चलाने के लिए किया जाता है। इसे क्लोन और बदला जा सकता है और फिर इम्यूलेटर में उपयोग करने के लिए बनाया जा सकता है।
ADB एक कमांड-लाइन टूल है। यह चल रहे Android डिवाइस के साथ संवाद करने और उससे शेल प्राप्त करने में सहायता करता है। इसका उपयोग डिबगिंग उद्देश्यों के लिए किया जा सकता है।
https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html
https://nvd.nist.gov/vuln/detail/CVE-2019-2215
https://www.tutorialspoint.com/gnu_debugger
https://www.kernel.org/doc/html/latest/dev-tools/kasan.html
https://android.googlesource.com/kernel/goldfish/
adb शेल लॉन्च करें और ट्रिगर PoC चलाएं