mirror of
https://github.com/darlinghq/LibrarySymbols.git
synced 2024-11-23 04:59:47 +00:00
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:
parent
3ce72546cb
commit
4794e1387c
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,7 +9,7 @@ Cargo.lock
|
|||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
temp/
|
tmp/
|
||||||
|
|
||||||
#
|
#
|
||||||
# macOS GitIgnore
|
# macOS GitIgnore
|
||||||
|
@ -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
1
src/arguments.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod cli;
|
65
src/arguments/cli.rs
Normal file
65
src/arguments/cli.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/clean.rs
18
src/clean.rs
@ -2,18 +2,14 @@ use std::fs::remove_dir_all;
|
|||||||
|
|
||||||
use crate::location::ResultsLocation;
|
use crate::location::ResultsLocation;
|
||||||
|
|
||||||
pub struct Cleanup {}
|
pub fn remove_saved_symbols(results_locations: &ResultsLocation) {
|
||||||
|
if let Ok(_) = remove_dir_all(&results_locations.os_version_path) {
|
||||||
impl Cleanup {
|
println!("Deleted {:?}", results_locations.os_version_path);
|
||||||
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_temp(results_locations: &ResultsLocation) {
|
pub fn remove_temp(results_locations: &ResultsLocation) {
|
||||||
if let Ok(_) = remove_dir_all(&results_locations.temp_path) {
|
if let Ok(_) = remove_dir_all(&results_locations.temp_path) {
|
||||||
println!("Cleaned up temp data");
|
println!("Cleaned up temp data");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,11 @@
|
|||||||
|
pub mod system;
|
||||||
|
|
||||||
use std::fs::{read_dir, ReadDir};
|
use std::fs::{read_dir, ReadDir};
|
||||||
use std::iter::Enumerate;
|
use std::iter::Enumerate;
|
||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
|
|
||||||
use crate::argument::Arguments;
|
use crate::arguments::cli::{CliArguments};
|
||||||
use crate::program::SystemVersionDefaults;
|
use crate::program::{SystemVersionDefaults};
|
||||||
|
|
||||||
pub(crate) fn walk_directory<P: AsRef<Path>>(location: P, valid_file: fn(&PathBuf) -> bool) -> Vec<PathBuf> {
|
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();
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct ResultsLocation {
|
pub struct ResultsLocation {
|
||||||
pub shared_cache_path: PathBuf,
|
pub os_version_path: PathBuf,
|
||||||
pub unique_version_path: PathBuf,
|
|
||||||
pub temp_path: PathBuf,
|
pub temp_path: PathBuf,
|
||||||
pub temp_shared_cache_path: PathBuf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResultsLocation {
|
impl ResultsLocation {
|
||||||
pub fn new(arguments: &Arguments, system_version: &SystemVersionDefaults) -> ResultsLocation {
|
pub fn new(arguments: &CliArguments, system_version: SystemVersionDefaults) -> ResultsLocation {
|
||||||
const SHARED_CACHE_DIR: &str = "shared_cache";
|
const TEMP_DIR: &str = "tmp";
|
||||||
const TEMP_DIR: &str = "temp";
|
|
||||||
|
|
||||||
let version_folder = format!("{} ({})", system_version.product_version, system_version.product_build_version);
|
let version_folder = format!("{} ({})", system_version.product_version, system_version.product_build_version);
|
||||||
let system_version = &system_version.product_name;
|
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 os_version_path = arguments.results_path.as_path().join(system_version).join(version_folder);
|
||||||
|
let temp_path = os_version_path.join(TEMP_DIR);
|
||||||
|
|
||||||
let temp_path = unique_version_path.join(TEMP_DIR);
|
Self {
|
||||||
let temp_shared_cache_path = temp_path.join(SHARED_CACHE_DIR);
|
os_version_path,
|
||||||
|
|
||||||
ResultsLocation {
|
|
||||||
shared_cache_path,
|
|
||||||
unique_version_path,
|
|
||||||
temp_path,
|
temp_path,
|
||||||
temp_shared_cache_path
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
174
src/location/system.rs
Normal file
174
src/location/system.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
src/main.rs
124
src/main.rs
@ -1,48 +1,96 @@
|
|||||||
mod argument;
|
mod arguments;
|
||||||
mod clean;
|
mod clean;
|
||||||
mod location;
|
mod location;
|
||||||
mod program;
|
mod program;
|
||||||
mod symbols;
|
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>) {
|
use location::system::SystemPathParser;
|
||||||
let parse_filesystem_symbols_list = symbols::ParseBaseFilesystem::new(path);
|
|
||||||
for parse_filesystem_symbols in parse_filesystem_symbols_list {
|
#[derive(Debug)]
|
||||||
parse_filesystem_symbols.traverse(result_location, unique_folder, path, &whoami_username);
|
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() {
|
fn main() {
|
||||||
let arguments = argument::Arguments::new();
|
let execution: GlobalMainVariables = GlobalMainVariables::new();
|
||||||
let base_locations = location::BaseLocation::new(&arguments);
|
execution.parse_for_symbols();
|
||||||
|
execution.clean();
|
||||||
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)
|
|
||||||
}
|
|
@ -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> {
|
fn parse_stdout(output: Output) -> Vec<String> {
|
||||||
let raw_string = String::from_utf8(output.stdout).expect("Unable to save output");
|
let raw_string = String::from_utf8(output.stdout).expect("Unable to save output");
|
||||||
@ -53,36 +53,31 @@ pub struct DyldSharedCacheExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
let mut instance = DyldSharedCacheExtractor {
|
||||||
extracted_paths: Vec::new()
|
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) {
|
if let Ok(sharedcache_readdir) = read_dir(dyld_sharedcache_folder) {
|
||||||
println!("Inspecting {:?} for shared cache", base_location.dyld_shardcache_iphoneos_path);
|
println!("Inspecting {:?} directory for shared cache", dyld_sharedcache_folder.as_ref());
|
||||||
instance.extract_shared_library(results_location,read_dir);
|
instance.extract_shared_library(sharedcache_extracted_path,sharedcache_readdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_shared_library(&mut self, results_location: &ResultsLocation, read_dir: ReadDir) {
|
fn extract_shared_library<P: AsRef<Path>>(&mut self, sharedcache_extracted_path: &P, sharedcache_readdir: ReadDir) {
|
||||||
for dir_entry in read_dir {
|
for sharedcache_direntry in sharedcache_readdir {
|
||||||
let dir_entry = dir_entry.unwrap();
|
let sharedcache_direntry = sharedcache_direntry.unwrap();
|
||||||
let file_path = dir_entry.path();
|
let sharedcache_file_path = sharedcache_direntry.path();
|
||||||
|
|
||||||
if self.is_shared_cache_file(file_path.as_path()) {
|
if is_file_dyld_sharedcache(sharedcache_file_path.as_path()) {
|
||||||
let filename = file_path.as_path().file_name().expect("Unable to get file name");
|
let file_name = sharedcache_file_path.file_name().unwrap();
|
||||||
let temp_path = results_location.temp_shared_cache_path.join(filename);
|
let temp_path = sharedcache_extracted_path.as_ref().join(&format!("{}.dir",file_name.to_str().unwrap()));
|
||||||
DyldSharedCacheExtractor::launch_program(file_path.as_path(), &temp_path);
|
|
||||||
|
|
||||||
// If the path doesn't exist after `dyld-shared-cache-extractor` finishes executing, it means that
|
// 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.
|
// 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() {
|
if temp_path.is_dir() {
|
||||||
self.extracted_paths.push(temp_path);
|
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) {
|
fn launch_program(shared_cache_path: &Path, temp_path: &Path) {
|
||||||
let _ = Command::new("dyld-shared-cache-extractor")
|
let _ = Command::new("dyld-shared-cache-extractor")
|
||||||
.args([shared_cache_path, temp_path])
|
.args([shared_cache_path, temp_path])
|
||||||
|
111
src/symbols.rs
111
src/symbols.rs
@ -1,110 +1 @@
|
|||||||
use std::{path::{PathBuf, Path}, fs::{File, self}, io::{Read, Write}};
|
pub mod macho;
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
54
src/symbols/macho.rs
Normal file
54
src/symbols/macho.rs
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user