ช่องโหว่นี้เป็นช่องโหว่ที่ล้นออกมาอย่างแน่นอน
การเขียน 0XFFFFFFFE ไบต์ (4 GB !!!!) จะไม่ทำให้โปรแกรมผิดพลาดได้อย่างไร
อาจเป็นไปได้ แต่ในบางครั้งคุณมีเวลาใช้ประโยชน์ก่อนที่ความผิดพลาดจะเกิดขึ้น (บางครั้งคุณสามารถทำให้โปรแกรมกลับสู่การทำงานตามปกติและหลีกเลี่ยงความผิดพลาดได้)
เมื่อ memcpy () เริ่มทำงานสำเนาจะเขียนทับบล็อคฮีปอื่น ๆ หรือบางส่วนของโครงสร้างการจัดการฮีป (เช่นรายการว่างรายการไม่ว่าง ฯลฯ )
ในบางจุดสำเนาจะพบหน้าที่ไม่ได้รับการจัดสรรและทริกเกอร์ AV (Access Violation) เมื่อเขียน GDI + จะพยายามจัดสรรบล็อกใหม่ในฮีป (ดูntdll! rtlAllocateHeap ) ... แต่ตอนนี้โครงสร้างฮีปสับสน
เมื่อถึงจุดนั้นการสร้างภาพ JPEG ของคุณอย่างระมัดระวังคุณสามารถเขียนทับโครงสร้างการจัดการฮีปด้วยข้อมูลที่ควบคุมได้ เมื่อระบบพยายามจัดสรรบล็อกใหม่ระบบอาจยกเลิกการลิงก์บล็อก (ฟรี) จากรายการฟรี
บล็อกได้รับการจัดการโดย (โดยเฉพาะ) การกระพริบ (ลิงก์ไปข้างหน้าบล็อกถัดไปในรายการ) และกะพริบ (ลิงก์ย้อนกลับบล็อกก่อนหน้าในรายการ) ตัวชี้ หากคุณควบคุมทั้งการกระพริบและการกะพริบคุณอาจมี WRITE4 ที่เป็นไปได้ (เขียนเงื่อนไขอะไร / ที่ไหน) ซึ่งคุณควบคุมสิ่งที่คุณสามารถเขียนและตำแหน่งที่คุณสามารถเขียนได้
เมื่อถึงจุดนั้นคุณสามารถเขียนทับตัวชี้ฟังก์ชัน (ตัวชี้SEH [Structured Exception Handlers]เป็นเป้าหมายของตัวเลือกในเวลานั้นย้อนกลับไปในปี 2004) และได้รับการเรียกใช้โค้ด
ดูโพสต์บล็อกกองปราบปรามการทุจริต: กรณีศึกษา
หมายเหตุ: แม้ว่าฉันจะเขียนเกี่ยวกับการหาประโยชน์โดยใช้ freelist แต่ผู้โจมตีอาจเลือกเส้นทางอื่นโดยใช้ข้อมูลเมตาของฮีปอื่น ("ข้อมูลเมตาของฮีป" เป็นโครงสร้างที่ระบบใช้เพื่อจัดการฮีปการกระพริบตาและการกะพริบเป็นส่วนหนึ่งของข้อมูลเมตาของฮีป) แต่ การหาประโยชน์จากการยกเลิกการลิงก์น่าจะเป็นวิธีที่ "ง่ายที่สุด" การค้นหาโดย Google สำหรับ "การหาประโยชน์จากฮีป" จะแสดงผลการศึกษามากมายเกี่ยวกับเรื่องนี้
สิ่งนี้เขียนเกินพื้นที่ฮีปและเข้าไปในพื้นที่ของโปรแกรมอื่น ๆ และระบบปฏิบัติการหรือไม่
ไม่เลย Modern OS ขึ้นอยู่กับแนวคิดของพื้นที่แอดเดรสเสมือนดังนั้นแต่ละกระบวนการจึงมีพื้นที่แอดเดรสเสมือนของตัวเองซึ่งช่วยให้สามารถระบุหน่วยความจำได้ถึง 4 กิกะไบต์บนระบบ 32 บิต (ในทางปฏิบัติคุณมีเพียงครึ่งหนึ่งของพื้นที่ที่อยู่ผู้ใช้ ส่วนที่เหลือเป็นของเคอร์เนล)
กล่าวโดยย่อกระบวนการไม่สามารถเข้าถึงหน่วยความจำของกระบวนการอื่นได้ (ยกเว้นว่าจะขอให้เคอร์เนลผ่านบริการ / API บางอย่าง แต่เคอร์เนลจะตรวจสอบว่าผู้เรียกมีสิทธิ์ที่จะทำเช่นนั้นหรือไม่)
ฉันตัดสินใจที่จะทดสอบช่องโหว่นี้ในช่วงปลายสัปดาห์นี้เพื่อให้เราได้รับความคิดที่ดีเกี่ยวกับสิ่งที่เกิดขึ้นแทนที่จะเป็นการคาดเดาเพียงอย่างเดียว ตอนนี้ช่องโหว่ดังกล่าวมีอายุ 10 ปีแล้วดังนั้นฉันจึงคิดว่ามันโอเคที่จะเขียนเกี่ยวกับเรื่องนี้แม้ว่าฉันจะไม่ได้อธิบายถึงส่วนการหาประโยชน์ในคำตอบนี้
การวางแผน
งานที่ยากที่สุดคือการค้นหา Windows XP ที่มีเฉพาะ SP1 เหมือนในปี 2004 :)
จากนั้นฉันดาวน์โหลดภาพ JPEG ที่ประกอบด้วยพิกเซลเดียวเท่านั้นดังที่แสดงด้านล่าง (ตัดเพื่อความกะทัดรัด):
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
[...]
ภาพ JPEG ประกอบด้วยเครื่องหมายไบนารี (ซึ่งเป็นส่วนที่อยู่ภายใน) ในภาพด้านบนFF D8
คือเครื่องหมาย SOI (Start Of Image) ในขณะที่FF E0
ตัวอย่างเช่นเป็นเครื่องหมายแอปพลิเคชัน
พารามิเตอร์แรกในส่วนของเครื่องหมาย (ยกเว้นเครื่องหมายบางตัวเช่น SOI) เป็นพารามิเตอร์ความยาวสองไบต์ซึ่งเข้ารหัสจำนวนไบต์ในส่วนของเครื่องหมายรวมทั้งพารามิเตอร์ความยาวและไม่รวมเครื่องหมายสองไบต์
ฉันเพิ่งเพิ่มเครื่องหมาย COM (0x FFFE
) หลัง SOI เนื่องจากเครื่องหมายไม่มีคำสั่งที่เข้มงวด
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
[...]
ความยาวของส่วน COM ถูกตั้งค่า00 00
เพื่อทริกเกอร์ช่องโหว่ ฉันยังฉีด 0xFFFC ไบต์หลังเครื่องหมาย COM ด้วยรูปแบบที่เกิดซ้ำซึ่งเป็นตัวเลข 4 ไบต์ในฐานสิบหกซึ่งจะเป็นประโยชน์เมื่อ "ใช้ประโยชน์" ช่องโหว่
การแก้จุดบกพร่อง
ดับเบิลคลิกที่ภาพทันทีจะก่อให้เกิดข้อผิดพลาดในเชลล์ของ Windows (aka "explorer.exe") ในบางส่วนในฟังก์ชั่นที่มีชื่อว่าgdiplus.dll
GpJpegDecoder::read_jpeg_marker()
ฟังก์ชันนี้เรียกใช้สำหรับเครื่องหมายแต่ละตัวในรูปภาพเพียงแค่: อ่านขนาดเซ็กเมนต์ของเครื่องหมายจัดสรรบัฟเฟอร์ที่มีความยาวเป็นขนาดเซ็กเมนต์และคัดลอกเนื้อหาของส่วนไปยังบัฟเฟอร์ที่จัดสรรใหม่นี้
นี่คือจุดเริ่มต้นของฟังก์ชัน:
.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
เมื่อเสร็จแล้วขนาดเซ็กเมนต์จะถูกใช้เพื่อจัดสรรบัฟเฟอร์ตามการคำนวณนี้:
จัดสรรขนาด = 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 _GpMalloc@4 ; GpMalloc(x)
ในกรณีของเราเป็นขนาดของกลุ่มเป็น 0 ขนาดจัดสรรสำหรับบัฟเฟอร์คือ 2 ไบต์
ช่องโหว่เกิดขึ้นทันทีหลังจากการจัดสรร:
.text:70E19A37 call _GpMalloc@4 ; 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 บิต บัฟเฟอร์ต้นทางถูกควบคุม (เนื้อหาของรูปภาพ) และปลายทางคือบัฟเฟอร์บนฮีป
เขียนเงื่อนไข
สำเนาจะทริกเกอร์ข้อยกเว้นการละเมิดการเข้าถึง (AV) เมื่อมาถึงส่วนท้ายของหน้าหน่วยความจำ (อาจมาจากตัวชี้ต้นทางหรือตัวชี้ปลายทาง) เมื่อ AV ถูกทริกเกอร์ฮีปจะอยู่ในสถานะที่มีช่องโหว่แล้วเนื่องจากสำเนาได้เขียนทับบล็อกฮีพที่ตามมาทั้งหมดแล้วจนกว่าจะพบเพจที่ไม่ได้แมป
สิ่งที่ทำให้สามารถใช้ประโยชน์จากข้อผิดพลาดนี้ได้ก็คือ 3 SEH (Structured Exception Handler; นี่คือการลอง / ยกเว้นในระดับต่ำ) กำลังจับข้อยกเว้นในส่วนนี้ของโค้ด อย่างแม่นยำยิ่งขึ้น SEH แรกจะคลายสแต็กเพื่อให้มันกลับมาเพื่อแยกวิเคราะห์เครื่องหมาย JPEG อื่นดังนั้นจึงข้ามเครื่องหมายที่ทำให้เกิดข้อยกเว้นโดยสิ้นเชิง
หากไม่มี SEH รหัสจะทำให้ทั้งโปรแกรมล้มเหลว ดังนั้นโค้ดจะข้ามเซ็กเมนต์ COM และแยกวิเคราะห์ส่วนอื่น ดังนั้นเราจึงกลับไปGpJpegDecoder::read_jpeg_marker()
ที่ส่วนใหม่และเมื่อรหัสจัดสรรบัฟเฟอร์ใหม่:
.text:70E19A33 lea eax, [esi+2] ; alloc_size = semgent_size + 2
.text:70E19A36 push eax ; dwBytes
.text:70E19A37 call _GpMalloc@4 ; 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!!!
ตอนนี้เราสามารถเขียนสิ่งที่เราต้องการที่เราต้องการ ...