XNU Image Fuzzer

Input Seed #2 PNG | https://github.com/xsscx/xnuimagefuzzer
>> Home » XNU Image Fuzzer

Estimated reading time: 5 minutes

Executive Summary

The Source Code contains a proof of concept implementation of an image fuzzer designed for XNU environments. It aims to demonstrate basic fuzzing techniques on image data to uncover potential vulnerabilities in image processing routines. The Objective-C Code implements 12 CGCreateBitmap & CGColorSpace Functions working with Raw Data and String Injection that are User Controllable Inputs.

whoami

I am David Hoyt and participated in the Apple Security Research Device Program for 2021 & 2022. Apple sent me an iPhone 11 & iPhone 12 for A/B testing, very helpful. This Project is some of the Code I wrote for debugging on the SRD.

This

I updated this Objective-C Code for A/B Testing along side Jackalope. The Results were so Interesting I increased the Fuzzer Coverage Envelope.

Start

Universal Binary

When you compile a Universal Binary that includes both arm64 and arm64e slices, iOS will preferentially execute the slice that best matches the device’s capabilities. On devices that support the arm64e architecture, this means the arm64e slice is used to take full advantage of the hardware’s security features and any other optimizations that the architecture may provide.

Specifying Architecture in Xcode

To specify the architecture in Xcode:

  1. Go to your project’s build settings.
  2. Look for the Architectures setting.
  3. You can select Standard Architectures to target the default architectures (arm64 for iOS), or you can manually specify arm64e by adding it to the list if you want to target newer devices specifically.

File

XNU Image Fuzzer:         Mach-O universal binary with 2 architectures: [arm64:Mach-O 64-bit executable arm64] [arm64e:Mach-O 64-bit executable arm64e]
XNU Image Fuzzer (for architecture arm64): Mach-O 64-bit executable arm64
XNU Image Fuzzer (for architecture arm64e): Mach-O 64-bit executable arm64e

Big Picture

Render

Seeds

Background

I had been using Jackalope for Fuzzing and to confirm that it could find easy to identify Bugs. Looking deeper at Jackalope, I found minor UAF, OOB, NPTR that impacted some results given the Seeding.

I wrote this Objective-C Code for A/B Testing along side Jackalope, and the iOsOnMac Interposing Code. The Results were so Interesting I increased this Fuzzer Scope, then wrote Interposing Code.

You can see the Example Code running At Scale using the iOS Interposing Code in iOSOnMac [https://github.com/xsscx/macos-research/blob/main/code/iOSOnMac/]. The iOsOnMac implementation is a more robust method for Fuzzing and Collecting the post-processed Images.

The example Code provides the ability to change a few Numbers in a Function() and further Modify the Program Behavior, perhaps you will get a good Crash.

Use Case

From a security standpoint, use your fuzzed files for analyzing & rendering to identify potential vulnerabilities in software that processes or displays PNG images.

The presence of unexpected or non-standard chunks could potentially be exploited to execute arbitrary code, cause denial-of-service (DoS) conditions, or lead to information disclosure if the software does not properly handle such anomalies.

Sample Crash in libAppleEXR

Discussion & Analysis

A XNU Platform Crash for all Apple Devices occurs when processing EXR files with subsampled channels in Apple’s libAppleEXR.dylib and the open-source libOpenEXR.

The crash in libAppleEXR.dylib involves functions like TileDecoder::ReadYccRGBAPixels and occurs during the decoding process in ImageIO and CoreGraphics frameworks. In libOpenEXR, the crash is in the processing of channels marked as ‘HALF (2, 2)’, indicating subsampling.

Sample EXR File Info

From the open EXR Project, there is a Sample File named Flowers.exr, below is the Header Info:

{‘channels’: {‘BY’: HALF (2, 2), ‘RY’: HALF (2, 2), ‘Y’: HALF (1, 1)}, ‘compression’: B44_COMPRESSION, ‘dataWindow’: (0, 0) – (783, 733), ‘displayWindow’: (0, 0) – (783, 733), ‘lineOrder’: INCREASING_Y, ‘owner’: b’Copyright 2006 Industrial Light & Magic’, ‘pixelAspectRatio’: 1.0, ‘screenWindowCenter’: (0.0, 0.0), ‘screenWindowWidth’: 1.0}

dict_keys

dict_keys(['BY', 'RY', 'Y']), {'BY': (143864,), 'RY': (143864,), 'Y': (575456,)})
  • The issue arises when BY and RY do not equal 25% of Y
  • This leads to memory management issues in a wide array of products, and services.

We can see that the subsampled channels (‘BY’ and ‘RY’) are not standard in size compared to the full-resolution ‘Y’ channel. These channels are a quarter of the total pixel count, potentially leading to incorrect buffer size allocations. The crashes are likely due to buffer overflows or memory mismanagement when handling these non-standard sizes.

iDOT

pngcheck BitmapContext32BitFloat4Component_fuzzed_image_series7.png
% pngcheck *
BitmapContext32BitFloat4Component_fuzzed_image_series7.png illegal (unless recently approved) unknown, public chunk iDOT

Why

We are reminded that the reference implementation indicates:

Tiled image files do not support subsampled chroma channels

Given this critical detail, we can now begin constructing the Seeds for Fuzzing the EXR Libraries.

Takeaway

Crash Analysis

For Crash Analysis, consider Reading https://srd.cx/xnu-crash-analysis/ and for arm64e Pointer Authentication Crashes, consider Reading https://srd.cx/possible-pointer-authentication-failure-data-abort/ for a quick snapshot of what may be Signal, or Noise.

Sample Functions

// Define constants for ALL and MAX_PERMUTATION
#define ALL -1
#define MAX_PERMUTATION 12

// Global variable to control verbosity
int verboseLogging = 0; // Set to 1 for detailed logging, 0 for minimal logging

// Function declarations
BOOL isValidImagePath(NSString *path);
UIImage *loadImageFromFile(NSString *path);
void processImage(UIImage *image, int permutation);
void Data(unsigned char *rawData, size_t width, size_t height, const char *message);
NSString *createUniqueDirectoryForSavingImages(void);

// Permutation functions
void createBitmapContextStandardRGB(CGImageRef cgImg, int permutation);
void createBitmapContextPremultipliedFirstAlpha(CGImageRef cgImg);
void createBitmapContextNonPremultipliedAlpha(CGImageRef cgImg);
void createBitmapContext16BitDepth(CGImageRef cgImg);
void createBitmapContextGrayscale(CGImageRef cgImg);
void createBitmapContextHDRFloatComponents(CGImageRef cgImg);
void createBitmapContextAlphaOnly(CGImageRef cgImg);
void createBitmapContext1BitMonochrome(CGImageRef cgImg);
void createBitmapContextBigEndian(CGImageRef cgImg);
void createBitmapContextLittleEndian(CGImageRef cgImg);
void createBitmapContext8BitInvertedColors(CGImageRef cgImg);
void createBitmapContext32BitFloat4Component(CGImageRef cgImg);
void applyFuzzingToBitmapContext(unsigned char *rawData, size_t width, size_t height);
void logPixelData(unsigned char *rawData, size_t width, size_t height, const char *message);
void applyEnhancedFuzzingToBitmapContext(unsigned char *rawData, size_t width, size_t height, BOOL verboseLogging);

createBitmapContextStandardRGB()

void createBitmapContextStandardRGB(CGImageRef cgImg, int permutation) {
NSLog(@"Creating bitmap context with Standard RGB settings and applying fuzzing");
debugMemoryHandling();

if (!cgImg) {
NSLog(@"Invalid CGImageRef provided.");
return;
}

size_t width = CGImageGetWidth(cgImg);
size_t height = CGImageGetHeight(cgImg);
size_t bytesPerRow = width * 4; // 4 bytes per pixel (RGBA)

unsigned char *rawData = (unsigned char *)calloc(height * bytesPerRow, sizeof(unsigned char));
if (!rawData) {
NSLog(@"Failed to allocate memory for image processing");
debugMemoryHandling();
return;
}

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (!colorSpace) {
NSLog(@"Failed to create color space");
free(rawData);
debugMemoryHandling();
return;
}

CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
CGContextRef ctx = CGBitmapContextCreate(rawData, width, height, 8, bytesPerRow, colorSpace, bitmapInfo);

CGColorSpaceRelease(colorSpace);

if (!ctx) {
NSLog(@"Failed to create bitmap context");
free(rawData);
debugMemoryHandling();
return;
}

CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), cgImg);

NSLog(@"Applying enhanced fuzzing logic to the bitmap context");
applyEnhancedFuzzingToBitmapContext(rawData, width, height, verboseLogging);

CGImageRef newCgImg = CGBitmapContextCreateImage(ctx);
if (!newCgImg) {
NSLog(@"Failed to create CGImage from context");
} else {
UIImage *newImage = [UIImage imageWithCGImage:newCgImg];
CGImageRelease(newCgImg);

saveFuzzedImage(newImage, @"standard_rgb");

NSLog(@"Modified UIImage created and saved successfully.");
}

CGContextRelease(ctx);
free(rawData);
debugMemoryHandling();
}

Fuzzer Console Log

Starting up...
Loading file: Flowers.exr
Image path: /xnuimagefuzzer.app/Flowers.exr
UIImage created: <UIImage:0x107e0c750 anonymous {784, 734} renderingMode=automatic(original)>, Size: {width: 784.00, height: 734.00}, Scale: 1.000000, Orientation: 0
CGImage created from UIImage. Dimensions: 784 x 734
Case: Creating bitmap context with Standard RGB settings
Chunk @ 0x102de0000
Chunk @ 0x102df0000
...
Successfully unmapped chunk @ 0x10b620000
Successfully unmapped chunk @ 0x10b630000
Creating bitmap context with Standard RGB settings and applying fuzzing
Drawing image into the bitmap context
Before fuzzing - Basic pixel logging executed.
Applying secondary fuzzing logic to the bitmap context
After fuzzing - Basic pixel logging executed.
Creating CGImage from the modified bitmap context
...
Successfully injected all 4 strings.
Enhanced fuzzing on bitmap context completed.
Fuzzed image for '8Bit_InvertedColors' context saved to /var/mobile/Containers/Data/Application/.../Documents/fuzzed_image_8Bit_InvertedColors.png
Modified UIImage with createBitmapContext8BitInvertedColors settings created and saved successfully.
Completed image processing for permutation 11
Case: Creating bitmap context with 32-bit float, 4-component settings
Creating bitmap context with 32-bit float, 4-component settings
Applying enhanced fuzzing logic to the bitmap context
Starting enhanced fuzzing with injection string 1: XNU Image Fuzzer
Enhanced fuzzing with injection string 1: XNU Image Fuzzer completed
Starting enhanced fuzzing with injection string 2: https://xss.cx?xnuimagefuzzer
Enhanced fuzzing with injection string 2: https://xss.cx?xnuimagefuzzer completed
Starting enhanced fuzzing with injection string 3: DROP TABLES;
Enhanced fuzzing with injection string 3: DROP TABLES; completed
Starting enhanced fuzzing with injection string 4: console.log('domain');
Enhanced fuzzing with injection string 4: console.log('domain'); completed
All enhanced fuzzing processes completed.
Fuzzed image for '32bit_float4' context saved to /var/mobile/Containers/Data/Application/.../Documents/fuzzed_image_32bit_float4.png
Modified UIImage with 32-bit float, 4-component settings created and saved successfully.
Completed image processing for permutation 12
[*] COMM_PAGE_SIGNATURE: commpage 64-bit
[*] COMM_PAGE_VERSION: 3
[*] COMM_PAGE_NCPUS: 8
[*] COMM_PAGE_CPU_CAPABILITIES64:
MMX: false
SSE: false
SSE2: false
SSE3: true
Cache32: false
Cache64: false
Cache128: true
FastThreadLocalStorage: true
SupplementalSSE3: true
64Bit: true
SSE4_1: true
SSE4_2: true
AES: true
InOrderPipeline: true
Slow: true
UP: false
NumCPUs: 8
AVX1_0: true
RDRAND: true
F16C: true
ENFSTRG: true
FMA: true
AVX2_0: false
BMI1: false
BMI2: true
RTM: true
HLE: true
ADX: false
RDSEED: false
MPX: false
SGX: false
[*] Done dumping comm page.
Device Information:
Name: iPad
Model: iPad
System Name: iPadOS
System Version: 17.3.1
Identifier For Vendor: 666
Battery Level: 85.000000
Battery State: Unplugged
Kernel Version: 23.3.0
Hardware Model: J621AP
CPU Type: J621AP
XNU Image Fuzzer 1.3.1.i transferring to AppDelegate Version 1.0.5.i at 2024-03-01 at 10:36:55
End of Run