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
|
||||
**/*.rs.bk
|
||||
|
||||
temp/
|
||||
tmp/
|
||||
|
||||
#
|
||||
# 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;
|
||||
|
||||
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) {
|
||||
if let Ok(_) = remove_dir_all(&results_locations.temp_path) {
|
||||
println!("Cleaned up temp data");
|
||||
}
|
||||
pub fn remove_temp(results_locations: &ResultsLocation) {
|
||||
if let Ok(_) = remove_dir_all(&results_locations.temp_path) {
|
||||
println!("Cleaned up temp data");
|
||||
}
|
||||
}
|
@ -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 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);
|
||||
let temp_shared_cache_path = temp_path.join(SHARED_CACHE_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
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 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();
|
||||
}
|
@ -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])
|
||||
|
111
src/symbols.rs
111
src/symbols.rs
@ -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
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