Talos Vulnerability Report


Apple Core Graphics BMP Framework img_decode_read Remote Code Execution Vulnerability

July 18, 2016

Report ID



An exploitable out of bounds write exists in the handling of BMP images on Apple OS X and iOS. A crafted BMP document can lead to an out of bounds write resulting in remote code execution. Vulnerability can be triggered via a saved BMP file delivered by other means when opened in any application using the Apple Core Graphics API.

Tested Versions

OS X El Capitan - 10.11.5

Product URLs


CVSSv3 Score

6.3 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N


This vulnerability is present in the Apple CoreGraphics framework which is used for path-based drawing, transformations, color management, offscreen rendering, patterns, gradients and shadings, image data management, image creation, masking, and PDF document creation, display, and parsing on OS X and iOS.

There exists a vulnerability in the parsing and handling of BMP images. A specially crafted BMP image file can lead to an out of bounds write and ultimately to remote code execution.

The BMP file format, also known as bitmap image file or device independent bitmap file format or simply a bitmap, is a raster graphics image file format used to store bitmap digital images, independently of the display device (such as a graphics adapter), especially on Microsoft Windows. BMP handles multiple different forms of compression as well making it a somewhat difficult graphics format to parse.

A dump of the files relevant header components is shown below:

<class bmp.BITMAPINFOHEADER> 'bmiHeader'
[e] <instance bmp.DWORD 'biSize'> 0x00000028 (40)
[12] <instance bmp.LONG 'biWidth'> 0x0000e803 (59395)
[16] <instance bmp.LONG 'biHeight'> 0x0000ff01 (65281)
[1a] <instance bmp.WORD 'biPlanes'> 0x0001 (1)
[1c] <instance bmp.WORD 'biBitCount'> 0x0004 (4)
[1e] <instance bmp.__biCompression 'biCompression'> BI_RLE4(0x2)
[22] <instance bmp.DWORD 'biSizeImage'> 0x00000200 (512)
[26] <instance bmp.LONG 'biXPelsPerMeter'> 0x00000b12 (2834)
[2a] <instance bmp.LONG 'biYPelsPerMeter'> 0x00000b12 (2834)
[2e] <instance bmp.DWORD 'biClrUsed'> 0x00000010 (16)
[32] <instance bmp.DWORD 'biClrImportant'> 0x00000010 (16)

And running the file through Qlmanage with guard malloc enabled shows us this crash:

rax = 0x0000001a1afd5500
rbx = 0x00000000fffffffe
rcx = 0x0000000000000002
rdx = 0x0000000000000000
rdi = 0x000000000000e804
rsi = 0x0000001e1b1f47b0
rbp = 0x0000001a7e877c70
rsp = 0x0000001a7e877c48
r8 = 0x0000001a1afd5500
r9 = 0x0000001e1b1f47b0
r10 = 0x0000000000000004
r11 = 0x0000000000000003
r12 = 0x0000000000000001
r13 = 0x000000000000e804
r14 = 0x000000000003a00c
r15 = 0x0000000000000002
rip = 0x00007fff8e2ba109  CoreGraphics`decode_byte_8bpc_3 + 369

    0x7fff8e2ba109 <+369>: mov    bl, byte ptr [r12 + rax]
    0x7fff8e2ba10d <+373>: mov    cl, byte ptr [r15 + rax]
    0x7fff8e2ba111 <+377>: mov    dl, byte ptr [r11 + rax]
    0x7fff8e2ba115 <+381>: mov    byte ptr [rsi], bl
    0x7fff8e2ba117 <+383>: mov    byte ptr [rsi + 0x1], cl
    0x7fff8e2ba11a <+386>: mov    byte ptr [rsi + 0x2], dl
    0x7fff8e2ba11d <+389>: add    rax, r10
    0x7fff8e2ba120 <+392>: add    edi, -0x1


* thread #12: tid = 0x5e16, 0x00007fff8e2ba109 CoreGraphics`decode_byte_8bpc_3 + 369, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0x1a1afd5501)
  * frame #0: 0x00007fff8e2ba109 CoreGraphics`decode_byte_8bpc_3 + 369
    frame #1: 0x00007fff8e25c3e2 CoreGraphics`decode_data + 16158
    frame #2: 0x00007fff8e257a58 CoreGraphics`img_decode_read + 378
    frame #3: 0x00007fff8e2577d7 CoreGraphics`img_colormatch_read + 363
    frame #4: 0x00007fff8e25571f CoreGraphics`img_data_lock + 8852
    frame #5: 0x00007fff8e2525af CoreGraphics`CGSImageDataLock + 151
    frame #6: 0x00007fff9e8f41d4 libRIP.A.dylib`ripc_AcquireImage + 972
    frame #7: 0x00007fff9e8f2c7e libRIP.A.dylib`ripc_DrawImage + 1011
    frame #8: 0x00007fff8e251b31 CoreGraphics`CGContextDrawImageWithOptions + 571
    frame #9: 0x00007fff8e2518da CoreGraphics`CGContextDrawImage + 51


Disassembling down from the backtrace we can spot where the vulnerability arises in img_decode_read.

__text:00000000000399F6 _img_decode_read:                       ; CODE XREF: _img_imagemask_read+1AAp
__text:00000000000399F6                                         ; _img_imagemask_read+266p ...
__text:00000000000399F6                 push    rbp
__text:00000000000399F7                 mov     rbp, rsp
__text:00000000000399FA                 push    r15
__text:00000000000399FC                 push    r14
__text:00000000000399FE                 push    r13
__text:0000000000039A00                 push    r12
__text:0000000000039A02                 push    rbx
__text:0000000000039A03                 sub     rsp, 98h
__text:0000000000039A0A                 mov     [rbp-68h], r8
__text:0000000000039A0E                 mov     r9, rcx
__text:0000000000039A11                 mov     r10d, edx
__text:0000000000039A14                 mov     r14d, esi          [0]


__text:0000000000039B0D                 call    _get_image_pointer [1]
__text:0000000000039B12                 test    rax, rax
__text:0000000000039B15                 jz      loc_39D19
__text:0000000000039B1B                 mov     esi, [r13+78h]
__text:0000000000039B1F                 mov     r8d, [r13+88h]
__text:0000000000039B26                 movsxd  rcx, r14d
__text:0000000000039B29                 imul    r14d, r8d
__text:0000000000039B2D                 mov     rdi, [r13+0A0h]
__text:0000000000039B34                 mov     [r13+58h], rcx
__text:0000000000039B38                 movsxd  rcx, r14d [2]
__text:0000000000039B3B                 add     rax, rcx

This function is used in a loop to continually grab more data from the image with an increasing value passed in via RSI (at [0]) until it reaches the image height value. When the value grows large enough a sign extension error occurs. In this case it is 0xff00 that causes the problem. A pointer to a valid image data buffer (at [1]) is returned and then some indexing is done on it to get to the proper element. The problem arises because the image height is not properly checked so when the value in R14 is promoted to a larger type stored in RCX the remaining bits gets filled in with 0xF causing an invalid index into the array. As shown previously the invalid index is calculated via the image height allowing this vulnerability to potentially be leveraged into an information leak or full remote code execution.

Crash Information

 Crashed thread log =
 : Dispatch queue: com.apple.root.default-qos
 0   com.apple.CoreGraphics        	0x00007fff98e73395 decode_byte_8bpc_3 + 369
 1   com.apple.CoreGraphics        	0x00007fff98e154fd decode_data + 16158
 2   com.apple.CoreGraphics        	0x00007fff98e10b70 img_decode_read + 378
 3   com.apple.CoreGraphics        	0x00007fff98e108ef img_colormatch_read + 363
 4   com.apple.CoreGraphics        	0x00007fff98e0e837 img_data_lock + 8852
 5   com.apple.CoreGraphics        	0x00007fff98e0b6c7 CGSImageDataLock + 151
 6   libRIP.A.dylib                	0x00007fff8d1021d4 ripc_AcquireImage + 972
 7   libRIP.A.dylib                	0x00007fff8d100c7e ripc_DrawImage + 1011
 8   com.apple.CoreGraphics        	0x00007fff98e0ac48 CGContextDrawImageWithOptions + 571
 9   com.apple.CoreGraphics        	0x00007fff98e0a9f1 CGContextDrawImage + 51
 10  com.apple.AppKit              	0x00007fff92b71856 __74-[NSImageRep drawInRect:fromRect:operation:fraction:respectFlipped:hints:]_block_invoke + 773
 11  com.apple.AppKit              	0x00007fff92b7135a -[NSImageRep drawInRect:fromRect:operation:fraction:respectFlipped:hints:] + 1110
 12  com.apple.AppKit              	0x00007fff92b7929d __71-[NSImage drawInRect:fromRect:operation:fraction:respectFlipped:hints:]_block_invoke1045 + 1119
 13  com.apple.AppKit              	0x00007fff92b38cb4 -[NSImage _usingBestRepresentationForRect:context:hints:body:] + 164
 14  com.apple.AppKit              	0x00007fff92b78d5c -[NSImage drawInRect:fromRect:operation:fraction:respectFlipped:hints:] + 2117
 15  com.apple.AppKit              	0x00007fff92fc6473 -[NSImage hitTestRect:withImageDestinationRect:context:hints:flipped:] + 547
 16  com.apple.qldisplay.Image     	0x000000013550a8e1 0x135506000 + 18657
 17  com.apple.qldisplay.Image     	0x000000013550a7d3 0x135506000 + 18387
 18  com.apple.qldisplay.Image     	0x0000000135508b23 0x135506000 + 11043
 19  libdispatch.dylib             	0x00007fff882dd93d _dispatch_call_block_and_release + 12
 20  libdispatch.dylib             	0x00007fff882d240b _dispatch_client_callout + 8
 21  libdispatch.dylib             	0x00007fff882d629b _dispatch_root_queue_drain + 1890
 22  libdispatch.dylib             	0x00007fff882d5b00 _dispatch_worker_thread3 + 91
 23  libsystem_pthread.dylib       	0x00007fff87d9e4de _pthread_wqthread + 1129
 24  libsystem_pthread.dylib       	0x00007fff87d9c341 start_wqthread + 13

 log name is: ./crashlogs/poo.crashlog.txt
 exception=EXC_BAD_ACCESS:signal=11:is_exploitable=yes:instruction_disassembly=movb	(%r12,%rax),%bl:instruction_address=0x00007fff98e73395:access_type=read:access_address=0x0000001a1bbae501:

Exploit Proof-of-Concept

Open attached BMP in Qlmanage with LibGmalloc and the preview flag passed in.


Tyler Bohan


2016-06-15 - Vendor Disclosure
2016-07-18 - Public Release