Reworked Program To Support Analysing The "Cryptexes" Partition

Starting with macOS Ventura (13), Apple has made some changes to where the sharedcache is located. As a result, the program has been rewritten to support obtain symbols from multiple locations. A nice consequence of this change is that we now grab the symbols for DriverKit as well.

For anyone interested in more details about the cryptexes, i recommend readling the following article: https://threedots.ovh/blog/2022/06/a-quick-look-at-macos-rapid-security-response/
This commit is contained in:
Thomas A 2022-12-30 13:21:07 -08:00
parent 3ce72546cb
commit 4794e1387c
11 changed files with 415 additions and 276 deletions

2
.gitignore vendored
View File

@ -9,7 +9,7 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
temp/
tmp/
#
# macOS GitIgnore

View File

@ -1,46 +0,0 @@
use std::path::{Path,PathBuf};
use clap::{Parser};
#[derive(Debug)]
pub struct Arguments {
pub results_path: String,
pub base_path: String
}
#[derive(Parser)]
#[command(version, author = "Thomas A.", about = "Extracts library symbols from Apple's framework")]
struct RawArguments {
/// The normal root directory in macOS, iOS, etc.
/// If no argument is provided, the root path will be used.
#[arg(long, value_name = "PATH")]
standard_path: Option<String>,
/// Where the symbols will be saved at.
#[arg(value_name = "RESULT FOLDER")]
results_path: String
}
impl Arguments {
pub fn new() -> Arguments {
let raw_arguments = RawArguments::parse();
Self::into_arguments(raw_arguments)
}
fn into_arguments(raw_arguments: RawArguments) -> Arguments {
let base_path = raw_arguments.standard_path.unwrap_or(String::from("/"));
Arguments {
results_path: raw_arguments.results_path,
base_path
}
}
pub fn path_from_base<P: AsRef<Path>>(&self, location: P) -> PathBuf {
let base_path = Path::new(self.base_path.as_str());
base_path.join(location)
}
pub fn path_from_results<P: AsRef<Path>>(&self, location: P) -> PathBuf {
let results_path = Path::new(self.results_path.as_str());
results_path.join(location)
}
}

1
src/arguments.rs Normal file
View File

@ -0,0 +1 @@
pub mod cli;

65
src/arguments/cli.rs Normal file
View File

@ -0,0 +1,65 @@
use std::path::{Path,PathBuf};
use clap::{Parser};
#[derive(Debug)]
pub struct CliArguments {
pub results_path: PathBuf,
pub base_path: PathBuf,
pub cryptexes_os_path: Option<PathBuf>,
}
#[derive(Parser)]
#[command(version, author = "Thomas A.", about = "Extracts library symbols from Apple's framework/libraries")]
struct RawArguments {
/// The normal root directory in macOS, iOS, etc.
/// If no argument is provided, the root path will be used.
#[arg(long, value_name = "PATH")]
standard_path: Option<String>,
/// Path to cryptexes OS directory.
/// If no argument is provided, the program will first check
/// if "/System/Cryptexes/OS" exists, if it doesn't exist, the
/// option will be ignored.
#[arg(long, value_name = "PATH")]
cryptexes_os_path: Option<String>,
/// Where the symbols will be saved at.
#[arg(value_name = "RESULT FOLDER")]
results_path: String,
}
impl CliArguments {
pub fn new() -> CliArguments {
let raw_arguments = RawArguments::parse();
Self::into_arguments(raw_arguments)
}
fn into_arguments(raw_arguments: RawArguments) -> CliArguments {
let base_path = raw_arguments.standard_path.unwrap_or_else(|| {
println!("Standard path not provided. Falling back to root directory ('/')");
String::from("/")
});
let cryptexes_os_path = raw_arguments.cryptexes_os_path.or_else(|| {
let temp = String::from("/System/Cryptexes/OS");
let temp_path = Path::new(temp.as_str());
println!("Cryptexes OS path not provided. Checking if path '{}' exists", temp);
if temp_path.exists() {
println!("Found '{}' path", temp);
Some(temp)
} else {
println!("Unable to find '{}' path", temp);
None
}
});
let results_path = PathBuf::from(raw_arguments.results_path);
let base_path = PathBuf::from(&base_path);
let cryptexes_os_path = cryptexes_os_path.map(|path| { PathBuf::from(path) });
CliArguments {
results_path,
base_path,
cryptexes_os_path,
}
}
}

View File

@ -2,18 +2,14 @@ use std::fs::remove_dir_all;
use crate::location::ResultsLocation;
pub struct Cleanup {}
impl Cleanup {
pub fn remove_saved_symbols(results_locations: &ResultsLocation) {
if let Ok(_) = remove_dir_all(&results_locations.unique_version_path) {
println!("Deleted {:?}", results_locations.unique_version_path);
}
pub fn remove_saved_symbols(results_locations: &ResultsLocation) {
if let Ok(_) = remove_dir_all(&results_locations.os_version_path) {
println!("Deleted {:?}", results_locations.os_version_path);
}
}
pub fn remove_temp(results_locations: &ResultsLocation) {
pub fn remove_temp(results_locations: &ResultsLocation) {
if let Ok(_) = remove_dir_all(&results_locations.temp_path) {
println!("Cleaned up temp data");
}
}
}

View File

@ -1,9 +1,11 @@
pub mod system;
use std::fs::{read_dir, ReadDir};
use std::iter::Enumerate;
use std::path::{Path,PathBuf};
use crate::argument::Arguments;
use crate::program::SystemVersionDefaults;
use crate::arguments::cli::{CliArguments};
use crate::program::{SystemVersionDefaults};
pub(crate) fn walk_directory<P: AsRef<Path>>(location: P, valid_file: fn(&PathBuf) -> bool) -> Vec<PathBuf> {
let mut current_directory: Vec<Enumerate<ReadDir>> = Vec::new();
@ -40,53 +42,25 @@ pub(crate) fn walk_directory<P: AsRef<Path>>(location: P, valid_file: fn(&PathBu
}
#[derive(Debug)]
pub struct BaseLocation {
pub system_version_path: PathBuf,
pub dyld_shardcache_macos_path: PathBuf,
pub dyld_shardcache_iphoneos_path: PathBuf,
}
impl BaseLocation {
pub fn new(arguments: &Arguments) -> BaseLocation {
let system_version_path = Path::new("System/Library/CoreServices/SystemVersion");
let dyld_shardcache_macos = Path::new("System/Library/dyld");
let dyld_shardcache_iphoneos = Path::new("System/Library/Caches/com.apple.dyld");
BaseLocation {
system_version_path: arguments.path_from_base(system_version_path),
dyld_shardcache_macos_path: arguments.path_from_base(dyld_shardcache_macos),
dyld_shardcache_iphoneos_path: arguments.path_from_base(dyld_shardcache_iphoneos),
}
}
}
#[derive(Debug)]
pub struct ResultsLocation {
pub shared_cache_path: PathBuf,
pub unique_version_path: PathBuf,
pub os_version_path: PathBuf,
pub temp_path: PathBuf,
pub temp_shared_cache_path: PathBuf
}
impl ResultsLocation {
pub fn new(arguments: &Arguments, system_version: &SystemVersionDefaults) -> ResultsLocation {
const SHARED_CACHE_DIR: &str = "shared_cache";
const TEMP_DIR: &str = "temp";
pub fn new(arguments: &CliArguments, system_version: SystemVersionDefaults) -> ResultsLocation {
const TEMP_DIR: &str = "tmp";
let version_folder = format!("{} ({})", system_version.product_version, system_version.product_build_version);
let system_version = &system_version.product_name;
let unique_version_path = arguments.path_from_results(Path::new(system_version.as_str())).join(version_folder);
let shared_cache_path = unique_version_path.join(SHARED_CACHE_DIR);
let temp_path = unique_version_path.join(TEMP_DIR);
let temp_shared_cache_path = temp_path.join(SHARED_CACHE_DIR);
let os_version_path = arguments.results_path.as_path().join(system_version).join(version_folder);
let temp_path = os_version_path.join(TEMP_DIR);
ResultsLocation {
shared_cache_path,
unique_version_path,
Self {
os_version_path,
temp_path,
temp_shared_cache_path
}
}
}

174
src/location/system.rs Normal file
View File

@ -0,0 +1,174 @@
use std::{path::{PathBuf, Path}, fs::{read_dir, ReadDir}, collections::{VecDeque}};
use crate::{arguments::cli::CliArguments, program::DyldSharedCacheExtractor};
use super::ResultsLocation;
const SYSTEM_FOLDER_BLACKLIST: [&str; 5] = ["Cryptexes","Library","Developer","Applications","Volumes"];
// where we searched for the library files (either directly on the filesystem
// or extracted from a sharedcache container).
pub const STANDARD_DIR: &str = "standard";
pub const SHAREDCACHE_DIR: &str = "sharedcache";
#[derive(Debug)]
pub enum SymbolsLocationType {
Standard,
SharedCache,
Unknown
}
// The filesystem for where we will start the search.
const ROOT_DIR: &str = "root";
const CRYPTEXES_DIR: &str = "cryptexes";
#[derive(Debug)]
pub enum VolumeType {
Root,
Cryptexes,
Unknown
}
#[derive(Debug)]
pub struct SystemPathParser {
pub save_path: PathBuf,
pub symbol_folders: Vec<PathBuf>,
}
impl SystemPathParser {
pub fn new(arguments: &CliArguments, results_location: &ResultsLocation) -> Vec<SystemPathParser> {
let mut parser_paths: Vec<SystemPathParser> = Vec::new();
parser_paths.append(&mut Self::new_from_filesystem(ROOT_DIR,results_location,&arguments.base_path));
if let Some(cryptexes_os_path) = &arguments.cryptexes_os_path {
parser_paths.append(&mut Self::new_from_filesystem(CRYPTEXES_DIR,results_location,cryptexes_os_path));
}
parser_paths
}
pub fn breakdown_save_path(&self) -> (SymbolsLocationType,VolumeType) {
let mut components = self.save_path.components();
let symbol_location = if let Some(symbol_location_path) = components.next() {
let symbol_location_path = symbol_location_path.as_os_str();
if symbol_location_path == STANDARD_DIR {
SymbolsLocationType::Standard
} else if symbol_location_path == SHAREDCACHE_DIR {
SymbolsLocationType::SharedCache
} else {
SymbolsLocationType::Unknown
}
} else { SymbolsLocationType::Unknown };
let volume = if let Some(volume_path) = components.next() {
let volume_path = volume_path.as_os_str();
if volume_path == ROOT_DIR {
VolumeType::Root
} else if volume_path == CRYPTEXES_DIR {
VolumeType::Cryptexes
} else {
VolumeType::Unknown
}
} else { VolumeType::Unknown };
(symbol_location,volume)
}
fn new_from_filesystem<P: AsRef<Path>>(filesystem_identifier: &str, results_location: &ResultsLocation, filesystem_path: &P) -> Vec<SystemPathParser> {
let mut parser_paths: Vec<SystemPathParser> = Vec::new();
let save_path = Path::new(STANDARD_DIR).join(filesystem_identifier);
let mut paths_to_sharedcache = Self::create_system_path_parser(&mut parser_paths,filesystem_path,save_path);
while !paths_to_sharedcache.is_empty() {
if let Some(sharedcache_folder) = paths_to_sharedcache.pop_front() {
if let Ok(relative_path) = sharedcache_folder.strip_prefix(&filesystem_path) {
let system_identifier =
if relative_path.starts_with("System/DriverKit") {"driverkit"}
else if relative_path.starts_with("System/Library") {"library"}
else { panic!("Unexpected filepath {:?}", relative_path) };
let save_path = Path::new(SHAREDCACHE_DIR).join(filesystem_identifier).join(system_identifier);
let sharedcache_extracted_path = results_location.temp_path.join(save_path.as_os_str());
let dyld_shared_cache_extractor = DyldSharedCacheExtractor::new(&sharedcache_extracted_path, &sharedcache_folder);
for extracted_path in dyld_shared_cache_extractor.extracted_paths {
let save_path = save_path.join(extracted_path.as_path().file_name().unwrap());
let unexpected_sharedcache_locations = Self::create_system_path_parser(&mut parser_paths,&extracted_path,save_path);
assert!(unexpected_sharedcache_locations.is_empty(),"Unexpected sharedcache files");
}
}
}
}
parser_paths
}
fn create_system_path_parser<P: AsRef<Path>>(parser_paths: &mut Vec<SystemPathParser>, filesystem_path: &P, save_path: PathBuf) -> VecDeque<PathBuf>{
let mut paths_to_sharedcache: VecDeque<PathBuf> = VecDeque::new();
let mut paths_to_analyse: VecDeque<PathBuf> = VecDeque::new();
let mut symbol_folders: Vec<PathBuf> = Vec::new();
paths_to_analyse.push_back(PathBuf::from(filesystem_path.as_ref()));
while !paths_to_analyse.is_empty() {
if let Some(path) = paths_to_analyse.pop_front() {
Self::append_path_if_exists(&mut symbol_folders,&path,"usr/lib");
let system_path = path.join("System");
if system_path.exists() {
Self::append_path_if_exists(&mut symbol_folders,&system_path,"Library/Frameworks");
Self::append_path_if_exists(&mut symbol_folders,&system_path,"Library/PrivateFrameworks");
let system_folders = read_dir(&system_path).unwrap();
Self::analyse_system_subfolders(system_folders,&mut paths_to_analyse);
if let Some(sharedcache_folder) = Self::determine_sharedcache_folder(&system_path) {
paths_to_sharedcache.push_back(sharedcache_folder)
};
}
}
}
parser_paths.push(SystemPathParser { save_path, symbol_folders });
return paths_to_sharedcache;
}
fn append_path_if_exists<P: AsRef<Path>>(list_of_path: &mut Vec<PathBuf>, path: &P, path_to_append: &str) {
let evaluate_path = path.as_ref().join(path_to_append);
if evaluate_path.exists() { list_of_path.push(evaluate_path) }
}
fn determine_sharedcache_folder<P: AsRef<Path>>(path: &P) -> Option<PathBuf> {
let mut dyld_shardcache_path: Option<PathBuf> = None;
let dyld_shardcache_macos = path.as_ref().join(Path::new("Library/dyld"));
let dyld_shardcache_iphoneos = path.as_ref().join(Path::new("Library/Caches/com.apple.dyld"));
let mut found_paths: u8 = 0;
found_paths += if dyld_shardcache_macos.exists() {1} else {0};
found_paths += if dyld_shardcache_iphoneos.exists() {1} else {0};
assert!(found_paths <= 1, "Both {dyld_shardcache_macos:?} and {dyld_shardcache_iphoneos:?} exist");
if dyld_shardcache_macos.exists() {
dyld_shardcache_path = Some(dyld_shardcache_macos)
} else if dyld_shardcache_iphoneos.exists() {
dyld_shardcache_path = Some(dyld_shardcache_iphoneos)
}
return dyld_shardcache_path;
}
fn analyse_system_subfolders(system_folders: ReadDir, paths_to_sharedcache: &mut VecDeque<PathBuf>) {
for system_folder in system_folders {
let system_folder = system_folder.unwrap();
let folder_name = system_folder.file_name();
let file_type = system_folder.file_type().unwrap();
if !SYSTEM_FOLDER_BLACKLIST.contains(&folder_name.to_str().unwrap()) && file_type.is_dir() {
paths_to_sharedcache.push_back(system_folder.path());
}
}
}
}

View File

@ -1,48 +1,96 @@
mod argument;
mod arguments;
mod clean;
mod location;
mod program;
mod symbols;
use std::path::Path;
use std::{path::{Path, PathBuf}, fs::{File, create_dir_all}, io::Write};
fn analyse_system_path<P: AsRef<Path>,Q: AsRef<Path>>(path: &P, whoami_username: &program::WhoAmIUserName, result_location: &Q, unique_folder :Option<&str>) {
let parse_filesystem_symbols_list = symbols::ParseBaseFilesystem::new(path);
for parse_filesystem_symbols in parse_filesystem_symbols_list {
parse_filesystem_symbols.traverse(result_location, unique_folder, path, &whoami_username);
use location::system::SystemPathParser;
#[derive(Debug)]
struct GlobalMainVariables {
arguments: arguments::cli::CliArguments,
system_path_parser: Vec<location::system::SystemPathParser>,
results_location: location::ResultsLocation,
whoami_username: program::WhoAmIUserName,
}
impl GlobalMainVariables {
pub fn new() -> GlobalMainVariables {
let arguments = arguments::cli::CliArguments::new();
let system_version_path = arguments.base_path.join(Path::new("System/Library/CoreServices/SystemVersion.plist"));
assert!(system_version_path.exists(), "Unable to find SystemVersion.plist");
let system_version = program::SystemVersionDefaults::new(system_version_path.to_str().unwrap());
let whoami_username = program::WhoAmIUserName::new();
let results_location = location::ResultsLocation::new(&arguments, system_version);
clean::remove_saved_symbols(&results_location);
let system_path_parser = location::system::SystemPathParser::new(&arguments, &results_location);
GlobalMainVariables {
arguments,
system_path_parser,
results_location,
whoami_username,
}
}
pub fn parse_for_symbols(&self) {
for path_to_parse in &self.system_path_parser {
let filesystem_path = Self::determine_filesystem_path(&self, path_to_parse);
for symbol_folder in &path_to_parse.symbol_folders {
let macho_executables = location::walk_directory(symbol_folder, symbols::macho::is_file_macho);
for macho_executable in macho_executables {
let relative_path = macho_executable.strip_prefix(&filesystem_path).unwrap().parent().unwrap();
let macho_executable_file_name = format!{"{}.symboldir",macho_executable.file_name().unwrap().to_str().unwrap()};
let macho_executable_dir = self.results_location.os_version_path.join(&path_to_parse.save_path).join(relative_path).join(macho_executable_file_name);
create_dir_all(&macho_executable_dir).unwrap();
let nm_program = program::NmLibrarySymbols::new(&macho_executable);
let mut nm_output_file = File::create(macho_executable_dir.join("nm.txt")).unwrap();
nm_output_file.write_all(nm_program.raw_output.as_bytes()).expect("Unable to save `nm` output");
let otool_program = program::OtoolLibrarySymbols::new(&macho_executable, &self.whoami_username);
let mut otool_output_file = File::create(macho_executable_dir.join("otool.txt")).unwrap();
otool_output_file.write_all(otool_program.raw_output.as_bytes()).expect("Unable to save `otool` output");
}
}
}
}
fn determine_filesystem_path(&self, path_to_parse: &SystemPathParser) -> PathBuf{
let (symbol_location,volume) = path_to_parse.breakdown_save_path();
let arguments = &self.arguments;
let results_location = &self.results_location;
let starting_path = match symbol_location {
location::system::SymbolsLocationType::Standard => {
match volume {
location::system::VolumeType::Root => { arguments.base_path.clone() }
location::system::VolumeType::Cryptexes => { arguments.cryptexes_os_path.clone().unwrap() }
location::system::VolumeType::Unknown => { panic!("Unknown volumn location detected"); }
}
}
location::system::SymbolsLocationType::SharedCache => {
results_location.temp_path.join(&path_to_parse.save_path)
}
location::system::SymbolsLocationType::Unknown => { panic!("Unknown symbol location detected"); }
};
starting_path
}
pub fn clean(&self) {
clean::remove_temp(&self.results_location);
}
}
fn main() {
let arguments = argument::Arguments::new();
let base_locations = location::BaseLocation::new(&arguments);
let system_version = program::SystemVersionDefaults::new(base_locations.system_version_path.to_str().unwrap());
let results_location = location::ResultsLocation::new(&arguments, &system_version);
let whoami_username = program::WhoAmIUserName::new();
clean::Cleanup::remove_saved_symbols(&results_location);
let dyld_shared_cache_extractor = program::DyldSharedCacheExtractor::new(&base_locations, &results_location);
for path in dyld_shared_cache_extractor.extracted_paths.iter() {
let shared_cache_folder = path.as_path().file_name().expect("Unable to obtain shared cache folder name").to_str();
analyse_system_path(path,&whoami_username,&results_location.shared_cache_path,shared_cache_folder);
}
clean::Cleanup::remove_temp(&results_location);
{
let path = Path::new(arguments.base_path.as_str());
let unique_folder = Some("standard");
analyse_system_path(&path,&whoami_username,&results_location.unique_version_path,unique_folder);
clean::Cleanup::remove_temp(&results_location);
}
println!{"{:#?}",arguments}
println!{"{:#?}",system_version}
println!{"{:#?}",base_locations}
println!{"{:#?}",results_location}
println!{"{:#?}",dyld_shared_cache_extractor}
println!("{:#?}",whoami_username)
let execution: GlobalMainVariables = GlobalMainVariables::new();
execution.parse_for_symbols();
execution.clean();
}

View File

@ -1,6 +1,6 @@
use std::{process::{Command, Output}, fs::{read_dir, ReadDir, File}, path::{Path, PathBuf}, io::Read};
use std::{process::{Command, Output}, fs::{read_dir, ReadDir}, path::{Path, PathBuf}};
use crate::location::{BaseLocation, ResultsLocation};
use crate::{symbols::macho::is_file_dyld_sharedcache};
fn parse_stdout(output: Output) -> Vec<String> {
let raw_string = String::from_utf8(output.stdout).expect("Unable to save output");
@ -53,36 +53,31 @@ pub struct DyldSharedCacheExtractor {
}
impl DyldSharedCacheExtractor {
pub fn new(base_location: &BaseLocation, results_location: &ResultsLocation) -> DyldSharedCacheExtractor {
pub fn new<P: AsRef<Path>, Q: AsRef<Path>>(sharedcache_extracted_path: &Q, dyld_sharedcache_folder: &P) -> DyldSharedCacheExtractor {
let mut instance = DyldSharedCacheExtractor {
extracted_paths: Vec::new()
};
if let Ok(read_dir) = read_dir(&base_location.dyld_shardcache_macos_path) {
println!("Inspecting {:?} for shared cache", base_location.dyld_shardcache_macos_path);
instance.extract_shared_library(results_location,read_dir);
}
if let Ok(read_dir) = read_dir(&base_location.dyld_shardcache_iphoneos_path) {
println!("Inspecting {:?} for shared cache", base_location.dyld_shardcache_iphoneos_path);
instance.extract_shared_library(results_location,read_dir);
if let Ok(sharedcache_readdir) = read_dir(dyld_sharedcache_folder) {
println!("Inspecting {:?} directory for shared cache", dyld_sharedcache_folder.as_ref());
instance.extract_shared_library(sharedcache_extracted_path,sharedcache_readdir);
}
instance
}
fn extract_shared_library(&mut self, results_location: &ResultsLocation, read_dir: ReadDir) {
for dir_entry in read_dir {
let dir_entry = dir_entry.unwrap();
let file_path = dir_entry.path();
fn extract_shared_library<P: AsRef<Path>>(&mut self, sharedcache_extracted_path: &P, sharedcache_readdir: ReadDir) {
for sharedcache_direntry in sharedcache_readdir {
let sharedcache_direntry = sharedcache_direntry.unwrap();
let sharedcache_file_path = sharedcache_direntry.path();
if self.is_shared_cache_file(file_path.as_path()) {
let filename = file_path.as_path().file_name().expect("Unable to get file name");
let temp_path = results_location.temp_shared_cache_path.join(filename);
DyldSharedCacheExtractor::launch_program(file_path.as_path(), &temp_path);
if is_file_dyld_sharedcache(sharedcache_file_path.as_path()) {
let file_name = sharedcache_file_path.file_name().unwrap();
let temp_path = sharedcache_extracted_path.as_ref().join(&format!("{}.dir",file_name.to_str().unwrap()));
// If the path doesn't exist after `dyld-shared-cache-extractor` finishes executing, it means that
// the application was not able to extract anything from it.
DyldSharedCacheExtractor::launch_program(sharedcache_file_path.as_path(), &temp_path);
if temp_path.is_dir() {
self.extracted_paths.push(temp_path);
}
@ -90,19 +85,6 @@ impl DyldSharedCacheExtractor {
}
}
fn is_shared_cache_file(&self, file_path: &Path) -> bool {
if file_path.is_file() {
if let Ok(mut file) = File::open(file_path) {
let mut magic: [u8; 5] = [0; 5];
if let Ok(_) = file.read(&mut magic) {
return [b'd',b'y',b'l',b'd',b'_'] == magic;
}
}
}
return false
}
fn launch_program(shared_cache_path: &Path, temp_path: &Path) {
let _ = Command::new("dyld-shared-cache-extractor")
.args([shared_cache_path, temp_path])

View File

@ -1,110 +1 @@
use std::{path::{PathBuf, Path}, fs::{File, self}, io::{Read, Write}};
use crate::{location, program};
const SYSTEM_LIBRARY_FRAMEWORK_PATH: &str = "System/Library/Frameworks";
const SYSTEM_LIBRARY_PRIVATEFRAMEWORK_PATH: &str = "System/Library/PrivateFrameworks";
const USR_LIB_PATH: &str = "usr/lib";
const SYSTEM_IOSSUPPORT: &str = "System/iOSSupport";
const MACHO_32BIT_MH_MAGIC: u32 = 0xfeedface;
const MACHO_32BIT_MH_CIGAM: u32 = 0xcefaedfe;
const MACHO_64BIT_MH_MAGIC: u32 = 0xfeedfacf;
const MACHO_64BIT_MH_CIGAM: u32 = 0xcffaedfe;
const MACHO_FAT_MAGIC: u32 = 0xcafebabe;
const MACHO_FAT_CIGAM: u32 = 0xbebafeca;
fn is_file_macho(path: &PathBuf) -> bool {
if !path.is_file() {
return false
}
let macho_filetype: u32;
if let Ok(mut file) = File::open(path) {
let mut temp_buf: [u8; 4] = [0; 4];
if let Ok(size) = file.read(&mut temp_buf) {
if size != 4 { return false; }
}
macho_filetype = u32::from_le_bytes(temp_buf);
} else {
return false;
}
let macho_32bit: bool;
let macho_64bit: bool;
let macho_fat: bool;
macho_32bit = macho_filetype == MACHO_32BIT_MH_MAGIC
|| macho_filetype == MACHO_32BIT_MH_CIGAM;
macho_64bit = macho_filetype == MACHO_64BIT_MH_MAGIC
|| macho_filetype == MACHO_64BIT_MH_CIGAM;
macho_fat = macho_filetype == MACHO_FAT_MAGIC
|| macho_filetype == MACHO_FAT_CIGAM;
macho_32bit || macho_64bit || macho_fat
}
pub struct ParseBaseFilesystem {
framework_path: PathBuf,
privateframework_path: PathBuf,
usr_lib_path: PathBuf
}
const NM_TEXTFILE_NAME: &str = "nm.txt";
const OTOOL_TEXTFILE_NAME: &str = "otool.txt";
impl ParseBaseFilesystem {
pub fn new<P: AsRef<Path>>(location: &P) -> Vec<ParseBaseFilesystem> {
let mut base_filesystems: Vec<ParseBaseFilesystem> = Vec::new();
base_filesystems.push(ParseBaseFilesystem::build_parse_base_filesystem(location));
let iossupport_path = location.as_ref().join(SYSTEM_IOSSUPPORT);
if iossupport_path.is_dir() {
println!("Found iOSSupport Directory");
base_filesystems.push(ParseBaseFilesystem::build_parse_base_filesystem(iossupport_path));
}
base_filesystems
}
fn build_parse_base_filesystem<P: AsRef<Path>>(location: P) -> ParseBaseFilesystem {
let framework_path = location.as_ref().join(SYSTEM_LIBRARY_FRAMEWORK_PATH);
let privateframework_path = location.as_ref().join(SYSTEM_LIBRARY_PRIVATEFRAMEWORK_PATH);
let usr_lib_path = location.as_ref().join(USR_LIB_PATH);
ParseBaseFilesystem {
framework_path,
privateframework_path,
usr_lib_path
}
}
pub fn traverse<P: AsRef<Path>, Q: AsRef<Path>>(&self, result_location: P, unique_folder: Option<&str>, base_location: Q, whoami: &program::WhoAmIUserName) {
let mut macho_paths: Vec<PathBuf> = Vec::new();
macho_paths.append(&mut location::walk_directory(&self.framework_path, is_file_macho));
macho_paths.append(&mut location::walk_directory(&self.privateframework_path, is_file_macho));
macho_paths.append(&mut location::walk_directory(&self.usr_lib_path, is_file_macho));
let macho_paths = macho_paths;
for macho_path in macho_paths.iter() {
let relative_location = macho_path.strip_prefix(base_location.as_ref()).unwrap();
let result_dir: PathBuf = if let Some(unique_folder) = unique_folder {
result_location.as_ref().join(unique_folder).join(relative_location)
} else {
result_location.as_ref().join(relative_location)
};
println!("Parsing {:?}", macho_path);
fs::create_dir_all(&result_dir).expect("Unable to create directory");
let mut nm_textfile = File::create(result_dir.as_path().join(NM_TEXTFILE_NAME)).expect("Unable to create file");
let nm = program::NmLibrarySymbols::new(&macho_path);
nm_textfile.write(nm.raw_output.as_bytes()).expect("Unable to save log information into file");
let mut otool_textfile = File::create(result_dir.as_path().join(OTOOL_TEXTFILE_NAME)).expect("Unable to create file");
let otool = program::OtoolLibrarySymbols::new(&macho_path,whoami);
otool_textfile.write(otool.raw_output.as_bytes()).expect("Unable to save log information into file");
}
}
}
pub mod macho;

54
src/symbols/macho.rs Normal file
View File

@ -0,0 +1,54 @@
use std::{path::Path, fs::File, io::Read};
const MACHO_32BIT_MH_MAGIC: u32 = 0xfeedface;
const MACHO_32BIT_MH_CIGAM: u32 = 0xcefaedfe;
const MACHO_64BIT_MH_MAGIC: u32 = 0xfeedfacf;
const MACHO_64BIT_MH_CIGAM: u32 = 0xcffaedfe;
const MACHO_FAT_MAGIC: u32 = 0xcafebabe;
const MACHO_FAT_CIGAM: u32 = 0xbebafeca;
pub(crate) fn is_file_macho<P: AsRef<Path>>(path: &P) -> bool {
let path = path.as_ref();
if !path.is_file() {
return false
}
let macho_filetype: u32;
if let Ok(mut file) = File::open(path) {
let mut temp_buf: [u8; 4] = [0; 4];
if let Ok(size) = file.read(&mut temp_buf) {
if size != 4 { return false; }
}
macho_filetype = u32::from_le_bytes(temp_buf);
} else {
return false;
}
let macho_32bit: bool;
let macho_64bit: bool;
let macho_fat: bool;
macho_32bit = macho_filetype == MACHO_32BIT_MH_MAGIC
|| macho_filetype == MACHO_32BIT_MH_CIGAM;
macho_64bit = macho_filetype == MACHO_64BIT_MH_MAGIC
|| macho_filetype == MACHO_64BIT_MH_CIGAM;
macho_fat = macho_filetype == MACHO_FAT_MAGIC
|| macho_filetype == MACHO_FAT_CIGAM;
macho_32bit || macho_64bit || macho_fat
}
pub(crate) fn is_file_dyld_sharedcache(file_path: &Path) -> bool {
if file_path.is_file() {
if let Ok(mut file) = File::open(file_path) {
let mut magic: [u8; 5] = [0; 5];
if let Ok(_) = file.read(&mut magic) {
// The magic header for sharedcache is "dyld_"
return [b'd',b'y',b'l',b'd',b'_'] == magic;
}
}
}
return false
}