├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── DyldEx ├── CMakeLists.txt ├── config.h.in ├── dyldex.cpp ├── dyldex_all.cpp ├── dyldex_all_multiprocess.cpp └── dyldex_info.cpp ├── DyldExtractor ├── CMakeLists.txt ├── Converter │ ├── Linkedit │ │ ├── Encoder │ │ │ ├── BindingV1.cpp │ │ │ ├── BindingV1.h │ │ │ ├── Chained.cpp │ │ │ ├── Chained.h │ │ │ ├── Encoder.h │ │ │ ├── LegacyGenerator.cpp │ │ │ ├── LegacyGenerator.h │ │ │ ├── RebaseV1.cpp │ │ │ └── RebaseV1.h │ │ ├── Linkedit.h │ │ ├── MetadataGenerator.cpp │ │ ├── MetadataGenerator.h │ │ ├── Optimizer.cpp │ │ └── Optimizer.h │ ├── Objc │ │ ├── Atoms.h │ │ ├── Objc.cpp │ │ ├── Objc.h │ │ ├── Placer.cpp │ │ ├── Placer.h │ │ ├── SelectorFixer.cpp │ │ ├── SelectorFixer.h │ │ ├── Walker.cpp │ │ └── Walker.h │ ├── OffsetOptimizer.cpp │ ├── OffsetOptimizer.h │ ├── Slide.cpp │ ├── Slide.h │ └── Stubs │ │ ├── Arm64Fixer.cpp │ │ ├── Arm64Fixer.h │ │ ├── Arm64Utils.cpp │ │ ├── Arm64Utils.h │ │ ├── ArmFixer.cpp │ │ ├── ArmFixer.h │ │ ├── ArmUtils.cpp │ │ ├── ArmUtils.h │ │ ├── Fixer.cpp │ │ ├── Fixer.h │ │ ├── Stubs.h │ │ ├── SymbolPointerCache.cpp │ │ └── SymbolPointerCache.h ├── Dyld │ ├── Context.cpp │ └── Context.h ├── Macho │ ├── Context.cpp │ ├── Context.h │ └── Loader.h ├── Objc │ └── Abstraction.h ├── Provider │ ├── Accelerator.h │ ├── ActivityLogger.cpp │ ├── ActivityLogger.h │ ├── BindInfo.cpp │ ├── BindInfo.h │ ├── Disassembler.cpp │ ├── Disassembler.h │ ├── ExtraData.cpp │ ├── ExtraData.h │ ├── FunctionTracker.cpp │ ├── FunctionTracker.h │ ├── LinkeditTracker.cpp │ ├── LinkeditTracker.h │ ├── PointerTracker.cpp │ ├── PointerTracker.h │ ├── README.md │ ├── SymbolTableTracker.cpp │ ├── SymbolTableTracker.h │ ├── Symbolizer.cpp │ ├── Symbolizer.h │ ├── Validator.cpp │ └── Validator.h └── Utils │ ├── Architectures.h │ ├── ExtractionContext.cpp │ ├── ExtractionContext.h │ ├── Leb128.cpp │ ├── Leb128.h │ └── Utils.h ├── External └── headers │ ├── dyld │ ├── Trie.hpp │ └── dyld_cache_format.h │ └── mach-o │ ├── fixup-chains.h │ ├── loader.h │ └── nlist.h ├── LICENSE ├── README.md └── Tests └── IntegrationTests ├── RunAllCaches_AllImages.py └── RunAllCaches_SingleImage.py /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | AlignTrailingComments: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | .vscode 3 | binaries 4 | build 5 | out 6 | CMakeSettings.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "External/spdlog"] 2 | path = External/spdlog 3 | url = https://github.com/gabime/spdlog.git 4 | [submodule "External/argparse"] 5 | path = External/argparse 6 | url = https://github.com/p-ranav/argparse.git 7 | [submodule "External/fmt"] 8 | path = External/fmt 9 | url = https://github.com/fmtlib/fmt.git 10 | [submodule "External/capstone"] 11 | path = External/capstone 12 | url = https://github.com/capstone-engine/capstone.git 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project("DyldExtractorC" VERSION 0.0.1) 3 | 4 | # Check endianness 5 | include(TestBigEndian) 6 | TEST_BIG_ENDIAN(IS_BIG_ENDIAN) 7 | 8 | if(IS_BIG_ENDIAN) 9 | message(FATAL_ERROR "Big Endian hosts are not supported.") 10 | endif() 11 | 12 | # Set c++ 20 13 | set(CMAKE_CXX_STANDARD 20) 14 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 15 | 16 | if(WIN32) 17 | # We have to set _WIN32_WINNT for boost 18 | if(${CMAKE_SYSTEM_VERSION} EQUAL 10) # Windows 10 19 | add_definitions(-D _WIN32_WINNT=0x0A00) 20 | elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.3) # Windows 8.1 21 | add_definitions(-D _WIN32_WINNT=0x0603) 22 | elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.2) # Windows 8 23 | add_definitions(-D _WIN32_WINNT=0x0602) 24 | elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.1) # Windows 7 25 | add_definitions(-D _WIN32_WINNT=0x0601) 26 | elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.0) # Windows Vista 27 | add_definitions(-D _WIN32_WINNT=0x0600) 28 | else() # Windows XP (5.1) 29 | add_definitions(-D _WIN32_WINNT=0x0501) 30 | endif() 31 | endif() 32 | 33 | # Boost 34 | set(Boost_NO_WARN_NEW_VERSIONS ON) 35 | find_package(Boost REQUIRED COMPONENTS iostreams filesystem) 36 | include_directories(${Boost_INCLUDE_DIRS}) 37 | 38 | # External 39 | include_directories(External/headers) 40 | add_subdirectory(External/fmt) 41 | 42 | add_compile_definitions(SPDLOG_ACTIVE_LEVEL=0) # Allow all logging, define at runtime 43 | add_subdirectory(External/spdlog) 44 | 45 | set(ARGPARSE_LONG_VERSION_ARG_ONLY ON) 46 | add_subdirectory(External/argparse) 47 | 48 | set(CAPSTONE_ARCHITECTURE_DEFAULT OFF) 49 | set(CAPSTONE_ARM_SUPPORT ON) 50 | set(CAPSTONE_ARM64_SUPPORT ON) 51 | set(CAPSTONE_X86_SUPPORT ON) 52 | add_subdirectory(External/capstone) 53 | set_target_properties(capstone PROPERTIES COMPILE_OPTIONS "-w") 54 | 55 | # Include sub-projects. 56 | add_subdirectory(DyldExtractor) 57 | add_subdirectory(DyldEx) -------------------------------------------------------------------------------- /DyldEx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | configure_file(config.h.in config.h) 4 | 5 | add_executable(dyldex dyldex.cpp) 6 | target_link_libraries(dyldex PRIVATE DyldExtractor) 7 | target_link_libraries(dyldex PRIVATE spdlog::spdlog) 8 | target_link_libraries(dyldex PRIVATE argparse::argparse) 9 | target_link_libraries(dyldex PRIVATE fmt::fmt) 10 | target_link_libraries(dyldex PRIVATE capstone::capstone) 11 | target_include_directories(dyldex PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 12 | 13 | add_executable(dyldex_info dyldex_info.cpp) 14 | target_link_libraries(dyldex_info PRIVATE DyldExtractor) 15 | target_link_libraries(dyldex_info PRIVATE spdlog::spdlog) 16 | target_link_libraries(dyldex_info PRIVATE argparse::argparse) 17 | target_link_libraries(dyldex_info PRIVATE fmt::fmt) 18 | target_link_libraries(dyldex_info PRIVATE capstone::capstone) 19 | target_include_directories(dyldex_info PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 20 | 21 | add_executable(dyldex_all dyldex_all.cpp) 22 | target_link_libraries(dyldex_all PRIVATE DyldExtractor) 23 | target_link_libraries(dyldex_all PRIVATE spdlog::spdlog) 24 | target_link_libraries(dyldex_all PRIVATE argparse::argparse) 25 | target_link_libraries(dyldex_all PRIVATE fmt::fmt) 26 | target_link_libraries(dyldex_all PRIVATE capstone::capstone) 27 | target_include_directories(dyldex_all PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 28 | 29 | add_executable(dyldex_all_multiprocess dyldex_all_multiprocess.cpp) 30 | target_link_libraries(dyldex_all_multiprocess PRIVATE DyldExtractor) 31 | target_link_libraries(dyldex_all_multiprocess PRIVATE spdlog::spdlog) 32 | target_link_libraries(dyldex_all_multiprocess PRIVATE argparse::argparse) 33 | target_link_libraries(dyldex_all_multiprocess PRIVATE fmt::fmt) 34 | target_link_libraries(dyldex_all_multiprocess PRIVATE capstone::capstone) 35 | target_link_libraries(dyldex_all_multiprocess PRIVATE ${Boost_LIBRARIES}) 36 | target_include_directories(dyldex_all_multiprocess PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 37 | -------------------------------------------------------------------------------- /DyldEx/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef __DYLDEX_CONFIG__ 2 | #define __DYLDEX_CONFIG__ 3 | 4 | #define DYLDEXTRACTORC_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ 5 | #define DYLDEXTRACTORC_VERSION_MINOR @PROJECT_VERSION_MINOR@ 6 | #define DYLDEXTRACTORC_VERSION_PATCH @PROJECT_VERSION_PATCH@ 7 | 8 | #define DYLDEXTRACTORC_VERSION "@PROJECT_VERSION@" 9 | 10 | #if DYLDEXTRACTORC_VERSION_MAJOR > 255 11 | #error "Major version is too high" 12 | #elif DYLDEXTRACTORC_VERSION_MINOR > 255 13 | #error "Minor version is too high" 14 | #elif DYLDEXTRACTORC_VERSION_PATCH > 255 15 | #error "Patch version is too high" 16 | #endif 17 | 18 | #include 19 | /** 20 | * 0000 0000 00000000 00000000 00000000 21 | * Reserved Type Major Minor Patch 22 | * 23 | * Type 24 | * 0 = python 25 | * 1 = C++ 26 | */ 27 | static const uint32_t DYLDEXTRACTORC_VERSION_DATA = 28 | (1 << 24) | (DYLDEXTRACTORC_VERSION_MAJOR << 16) | 29 | (DYLDEXTRACTORC_VERSION_MINOR << 8) | (DYLDEXTRACTORC_VERSION_PATCH); 30 | 31 | #endif // __DYLDEX_CONFIG__ -------------------------------------------------------------------------------- /DyldEx/dyldex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "config.h" 19 | 20 | namespace fs = std::filesystem; 21 | using namespace DyldExtractor; 22 | 23 | struct ProgramArguments { 24 | fs::path cache_path; 25 | bool verbose; 26 | bool listImages; 27 | std::optional listFilter; 28 | std::optional extractImage; 29 | std::optional outputPath; 30 | bool imbedVersion; 31 | 32 | union { 33 | uint32_t raw; 34 | struct { 35 | uint32_t processSlideInfo : 1, optimizeLinkedit : 1, fixStubs : 1, 36 | fixObjc : 1, generateMetadata : 1, unused : 27; 37 | }; 38 | } modulesDisabled; 39 | }; 40 | 41 | ProgramArguments parseArgs(int argc, char *argv[]) { 42 | argparse::ArgumentParser program("dyldex", DYLDEXTRACTORC_VERSION); 43 | 44 | program.add_argument("cache_path") 45 | .help("The path to the shared cache. If there are subcaches, give the " 46 | "main one (typically without the file extension)."); 47 | 48 | program.add_argument("-v", "--verbose") 49 | .help("Enables debug logging messages.") 50 | .default_value(false) 51 | .implicit_value(true); 52 | 53 | program.add_argument("-l", "--list-images") 54 | .help("Lists the images in the shared cache.") 55 | .default_value(false) 56 | .implicit_value(true); 57 | 58 | program.add_argument("-f", "--filter").help("Filter images when listing."); 59 | 60 | program.add_argument("-e", "--extract") 61 | .help("Extract the image. Specify more of the path for conflicts in " 62 | "image names"); 63 | 64 | program.add_argument("-o", "--output") 65 | .help("The output path for the extracted image. Required for extraction"); 66 | 67 | program.add_argument("-s", "--skip-modules") 68 | .help("Skip certain modules. Most modules depend on each other, so use " 69 | "with caution. Useful for development. 1=processSlideInfo, " 70 | "2=optimizeLinkedit, 4=fixStubs, 8=fixObjc, 16=generateMetadata") 71 | .scan<'d', int>() 72 | .default_value(0); 73 | 74 | program.add_argument("--imbed-version") 75 | .help("Imbed this tool's version number into the mach_header_64's " 76 | "reserved field. Only supports 64 bit images.") 77 | .default_value(false) 78 | .implicit_value(true); 79 | 80 | ProgramArguments args; 81 | try { 82 | program.parse_args(argc, argv); 83 | 84 | args.cache_path = fs::path(program.get("cache_path")); 85 | args.verbose = program.get("--verbose"); 86 | args.listImages = program.get("--list-images"); 87 | args.listFilter = program.present("--filter"); 88 | args.extractImage = program.present("--extract"); 89 | args.outputPath = program.present("--output"); 90 | args.modulesDisabled.raw = program.get("--skip-modules"); 91 | args.imbedVersion = program.get("--imbed-version"); 92 | } catch (const std::runtime_error &err) { 93 | std::cerr << "Argument parsing error: " << err.what() << std::endl; 94 | std::exit(1); 95 | } 96 | 97 | if (args.extractImage && !args.outputPath) { 98 | std::cerr << "Output path is required for extraction" << std::endl; 99 | std::exit(1); 100 | } 101 | 102 | return args; 103 | } 104 | 105 | /// Retrieve images in the cache, with an optional filter. 106 | std::vector> 107 | getImages(Dyld::Context &dCtx, std::optional filter) { 108 | std::vector> images; 109 | images.reserve(dCtx.images.size()); 110 | 111 | for (int i = 0; i < dCtx.images.size(); i++) { 112 | auto imagePath = 113 | std::string((const char *)dCtx.file + dCtx.images[i]->pathFileOffset); 114 | 115 | if (filter) { 116 | auto it = std::search(imagePath.begin(), imagePath.end(), filter->begin(), 117 | filter->end(), [](char ch1, char ch2) { 118 | return std::tolower(ch1) == std::tolower(ch2); 119 | }); 120 | if (it == imagePath.end()) { 121 | continue; 122 | } 123 | } 124 | 125 | images.emplace_back(i, imagePath); 126 | } 127 | 128 | return images; 129 | } 130 | 131 | template 132 | void extractImage(Dyld::Context &dCtx, ProgramArguments args) { 133 | using P = A::P; 134 | 135 | // Get the image info of the extraction target 136 | assert(args.extractImage != std::nullopt); 137 | 138 | auto extractionTargetFilter = *args.extractImage; 139 | auto possibleTargets = getImages(dCtx, args.extractImage); 140 | if (possibleTargets.size() == 0) { 141 | std::cerr << fmt::format("Unable to find image '{}'", 142 | extractionTargetFilter) 143 | << std::endl; 144 | return; 145 | } 146 | 147 | auto &[imageIndex, imagePath] = possibleTargets[0]; 148 | auto imageInfo = dCtx.images[imageIndex]; 149 | std::cout << fmt::format("Extracting '{}'", imagePath) << std::endl; 150 | 151 | auto mCtx = dCtx.createMachoCtx(imageInfo); 152 | 153 | // validate 154 | try { 155 | Provider::Validator

(mCtx).validate(); 156 | } catch (const std::exception &e) { 157 | std::cout << std::format("Validation Error: {}", e.what()); 158 | return; 159 | } 160 | 161 | // Setup context and extract 162 | Provider::ActivityLogger activity("DyldEx", std::cout, true); 163 | auto logger = activity.getLogger(); 164 | logger->set_pattern("[%T:%e %-8l %s:%#] %v"); 165 | if (args.verbose) { 166 | logger->set_level(spdlog::level::trace); 167 | } else { 168 | logger->set_level(spdlog::level::info); 169 | } 170 | activity.update("DyldEx", "Starting up"); 171 | 172 | Provider::Accelerator

accelerator; 173 | Utils::ExtractionContext eCtx(dCtx, mCtx, accelerator, activity); 174 | 175 | // Process 176 | if (!args.modulesDisabled.processSlideInfo) { 177 | Converter::processSlideInfo(eCtx); 178 | } 179 | if (!args.modulesDisabled.optimizeLinkedit) { 180 | Converter::optimizeLinkedit(eCtx); 181 | } 182 | if (!args.modulesDisabled.fixStubs) { 183 | Converter::fixStubs(eCtx); 184 | } 185 | if (!args.modulesDisabled.fixObjc) { 186 | Converter::fixObjc(eCtx); 187 | } 188 | if (!args.modulesDisabled.generateMetadata) { 189 | Converter::generateMetadata(eCtx); 190 | } 191 | 192 | if (args.imbedVersion) { 193 | if constexpr (!std::is_same_v) { 194 | SPDLOG_LOGGER_ERROR( 195 | logger, "Unable to imbed version info in a non 64 bit image."); 196 | } else { 197 | mCtx.header->reserved = DYLDEXTRACTORC_VERSION_DATA; 198 | } 199 | } 200 | auto writeProcedures = Converter::optimizeOffsets(eCtx); 201 | 202 | // Write 203 | fs::create_directories(args.outputPath->parent_path()); 204 | std::ofstream outFile(*args.outputPath, std::ios_base::binary); 205 | if (!outFile.good()) { 206 | SPDLOG_LOGGER_ERROR(logger, "Unable to open output file."); 207 | return; 208 | } 209 | 210 | for (auto procedure : writeProcedures) { 211 | outFile.seekp(procedure.writeOffset); 212 | outFile.write((const char *)procedure.source, procedure.size); 213 | } 214 | outFile.close(); 215 | 216 | activity.update("DyldEx", "Done"); 217 | activity.stopActivity(); 218 | } 219 | 220 | int main(int argc, char *argv[]) { 221 | ProgramArguments args = parseArgs(argc, argv); 222 | 223 | try { 224 | Dyld::Context dCtx(args.cache_path); 225 | 226 | if (args.listImages) { 227 | std::cout << "Index | Path\n------------\n"; 228 | for (auto &[i, path] : getImages(dCtx, args.listFilter)) { 229 | std::cout << fmt::format("{:0>5} | {}\n", i, path); 230 | } 231 | std::cout << std::endl; 232 | 233 | return 0; 234 | } else if (args.extractImage) { 235 | // use dyld's magic to select arch 236 | if (strcmp(dCtx.header->magic, "dyld_v1 x86_64") == 0) 237 | extractImage(dCtx, args); 238 | else if (strcmp(dCtx.header->magic, "dyld_v1 x86_64h") == 0) 239 | extractImage(dCtx, args); 240 | else if (strcmp(dCtx.header->magic, "dyld_v1 armv7") == 0) 241 | extractImage(dCtx, args); 242 | else if (strncmp(dCtx.header->magic, "dyld_v1 armv7", 14) == 0) 243 | extractImage(dCtx, args); 244 | else if (strcmp(dCtx.header->magic, "dyld_v1 arm64") == 0) 245 | extractImage(dCtx, args); 246 | else if (strcmp(dCtx.header->magic, "dyld_v1 arm64e") == 0) 247 | extractImage(dCtx, args); 248 | else if (strcmp(dCtx.header->magic, "dyld_v1arm64_32") == 0) 249 | extractImage(dCtx, args); 250 | else if (strcmp(dCtx.header->magic, "dyld_v1 i386") == 0 || 251 | strcmp(dCtx.header->magic, "dyld_v1 armv5") == 0 || 252 | strcmp(dCtx.header->magic, "dyld_v1 armv6") == 0) { 253 | std::cerr << "Unsupported Architecture type."; 254 | return 1; 255 | } else { 256 | std::cerr << "Unrecognized dyld shared cache magic.\n"; 257 | return 1; 258 | } 259 | } 260 | } catch (const std::exception &e) { 261 | std::cerr << "An error has occurred: " << e.what() << std::endl; 262 | return 1; 263 | } 264 | return 0; 265 | } -------------------------------------------------------------------------------- /DyldEx/dyldex_all.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "config.h" 20 | 21 | namespace fs = std::filesystem; 22 | using namespace DyldExtractor; 23 | 24 | #pragma region Arguments 25 | struct ProgramArguments { 26 | fs::path cache_path; 27 | std::optional outputDir; 28 | bool verbose; 29 | bool disableOutput; 30 | bool onlyValidate; 31 | bool imbedVersion; 32 | 33 | union { 34 | uint32_t raw; 35 | struct { 36 | uint32_t processSlideInfo : 1, optimizeLinkedit : 1, fixStubs : 1, 37 | fixObjc : 1, generateMetadata : 1, unused : 27; 38 | }; 39 | } modulesDisabled; 40 | }; 41 | 42 | ProgramArguments parseArgs(int argc, char *argv[]) { 43 | argparse::ArgumentParser program("dyldex_all", DYLDEXTRACTORC_VERSION); 44 | 45 | program.add_argument("cache_path") 46 | .help("The path to the shared cache. If there are subcaches, give the " 47 | "main one (typically without the file extension)."); 48 | 49 | program.add_argument("-o", "--output-dir") 50 | .help("The output directory for the extracted images. Required for " 51 | "extraction"); 52 | 53 | program.add_argument("-v", "--verbose") 54 | .help("Enables debug logging messages.") 55 | .default_value(false) 56 | .implicit_value(true); 57 | 58 | program.add_argument("-d", "--disable-output") 59 | .help("Disables writing output. Useful for development.") 60 | .default_value(false) 61 | .implicit_value(true); 62 | 63 | program.add_argument("--only-validate") 64 | .help("Only validate images.") 65 | .default_value(false) 66 | .implicit_value(true); 67 | 68 | program.add_argument("-s", "--skip-modules") 69 | .help("Skip certain modules. Most modules depend on each other, so use " 70 | "with caution. Useful for development. 1=processSlideInfo, " 71 | "2=optimizeLinkedit, 4=fixStubs, 8=fixObjc, 16=generateMetadata") 72 | .scan<'d', int>() 73 | .default_value(0); 74 | 75 | program.add_argument("--imbed-version") 76 | .help("Imbed this tool's version number into the mach_header_64's " 77 | "reserved field. Only supports 64 bit images.") 78 | .default_value(false) 79 | .implicit_value(true); 80 | 81 | ProgramArguments args; 82 | try { 83 | program.parse_args(argc, argv); 84 | 85 | args.cache_path = fs::path(program.get("cache_path")); 86 | args.outputDir = program.present("--output-dir"); 87 | args.verbose = program.get("--verbose"); 88 | args.disableOutput = program.get("--disable-output"); 89 | args.onlyValidate = program.get("--only-validate"); 90 | args.modulesDisabled.raw = program.get("--skip-modules"); 91 | args.imbedVersion = program.get("--imbed-version"); 92 | 93 | } catch (const std::runtime_error &err) { 94 | std::cerr << "Argument parsing error: " << err.what() << std::endl; 95 | std::exit(1); 96 | } 97 | 98 | if (!args.disableOutput && !args.outputDir) { 99 | std::cerr << "Output directory is required for extraction" << std::endl; 100 | std::exit(1); 101 | } 102 | 103 | return args; 104 | } 105 | #pragma endregion Arguments 106 | 107 | template 108 | void runImage(Dyld::Context &dCtx, 109 | Provider::Accelerator &accelerator, 110 | const dyld_cache_image_info *imageInfo, 111 | const std::string imagePath, const std::string imageName, 112 | const ProgramArguments &args, std::ostream &logStream) { 113 | 114 | // validate 115 | auto mCtx = dCtx.createMachoCtx(imageInfo); 116 | try { 117 | Provider::Validator(mCtx).validate(); 118 | } catch (const std::exception &e) { 119 | std::cout << std::format("Validation Error: {}", e.what()); 120 | return; 121 | } 122 | 123 | if (args.onlyValidate) { 124 | return; 125 | } 126 | 127 | // Setup context 128 | Provider::ActivityLogger activity("DyldEx_" + imageName, logStream, false); 129 | auto logger = activity.getLogger(); 130 | logger->set_pattern("[%-8l %s:%#] %v"); 131 | if (args.verbose) { 132 | logger->set_level(spdlog::level::trace); 133 | } else { 134 | logger->set_level(spdlog::level::info); 135 | } 136 | 137 | Utils::ExtractionContext eCtx(dCtx, mCtx, accelerator, activity); 138 | 139 | if (!args.modulesDisabled.processSlideInfo) { 140 | Converter::processSlideInfo(eCtx); 141 | } 142 | if (!args.modulesDisabled.optimizeLinkedit) { 143 | Converter::optimizeLinkedit(eCtx); 144 | } 145 | if (!args.modulesDisabled.fixStubs) { 146 | Converter::fixStubs(eCtx); 147 | } 148 | if (!args.modulesDisabled.fixObjc) { 149 | Converter::fixObjc(eCtx); 150 | } 151 | if (!args.modulesDisabled.generateMetadata) { 152 | Converter::generateMetadata(eCtx); 153 | } 154 | if (args.imbedVersion) { 155 | if constexpr (!std::is_same_v) { 156 | SPDLOG_LOGGER_ERROR( 157 | logger, "Unable to imbed version info in a non 64 bit image."); 158 | } else { 159 | mCtx.header->reserved = DYLDEXTRACTORC_VERSION_DATA; 160 | } 161 | } 162 | 163 | if (!args.disableOutput) { 164 | auto writeProcedures = Converter::optimizeOffsets(eCtx); 165 | 166 | auto outputPath = *args.outputDir / imagePath.substr(1); // remove leading / 167 | fs::create_directories(outputPath.parent_path()); 168 | std::ofstream outFile(outputPath, std::ios_base::binary); 169 | if (!outFile.good()) { 170 | SPDLOG_LOGGER_ERROR(logger, "Unable to open output file."); 171 | return; 172 | } 173 | 174 | for (auto procedure : writeProcedures) { 175 | outFile.seekp(procedure.writeOffset); 176 | outFile.write((const char *)procedure.source, procedure.size); 177 | } 178 | outFile.close(); 179 | } 180 | } 181 | 182 | template 183 | void runAllImages(Dyld::Context &dCtx, ProgramArguments &args) { 184 | Provider::ActivityLogger activity("DyldEx_All", std::cout, true); 185 | auto logger = activity.getLogger(); 186 | logger->set_pattern("[%T:%e %-8l %s:%#] %v"); 187 | if (args.verbose) { 188 | logger->set_level(spdlog::level::trace); 189 | } else { 190 | logger->set_level(spdlog::level::info); 191 | } 192 | activity.update("DyldEx All", "Starting up"); 193 | int imagesProcessed = 0; 194 | std::ostringstream summaryStream; 195 | 196 | Provider::Accelerator accelerator; 197 | 198 | const int numberOfImages = (int)dCtx.images.size(); 199 | for (int i = 0; i < numberOfImages; i++) { 200 | const auto imageInfo = dCtx.images[i]; 201 | std::string imagePath((char *)(dCtx.file + imageInfo->pathFileOffset)); 202 | std::string imageName = imagePath.substr(imagePath.rfind("/") + 1); 203 | 204 | imagesProcessed++; 205 | activity.update(std::nullopt, fmt::format("[{:4}/{}] {}", imagesProcessed, 206 | numberOfImages, imageName)); 207 | 208 | std::ostringstream loggerStream; 209 | runImage(dCtx, accelerator, imageInfo, imagePath, imageName, args, 210 | loggerStream); 211 | 212 | // update summary and UI. 213 | auto logs = loggerStream.str(); 214 | activity.getLoggerStream() 215 | << fmt::format("processed {}", imageName) << std::endl 216 | << logs << std::endl; 217 | if (logs.length()) { 218 | summaryStream << "* " << imageName << std::endl << logs << std::endl; 219 | } 220 | } 221 | 222 | activity.update(std::nullopt, "Done"); 223 | activity.stopActivity(); 224 | activity.getLoggerStream() 225 | << std::endl 226 | << "==== Summary ====" << std::endl 227 | << summaryStream.str() << "=================" << std::endl; 228 | } 229 | 230 | int main(int argc, char *argv[]) { 231 | ProgramArguments args = parseArgs(argc, argv); 232 | 233 | try { 234 | Dyld::Context dCtx(args.cache_path); 235 | 236 | // use dyld's magic to select arch 237 | if (strcmp(dCtx.header->magic, "dyld_v1 x86_64") == 0) 238 | runAllImages(dCtx, args); 239 | else if (strcmp(dCtx.header->magic, "dyld_v1 x86_64h") == 0) 240 | runAllImages(dCtx, args); 241 | else if (strcmp(dCtx.header->magic, "dyld_v1 armv7") == 0) 242 | runAllImages(dCtx, args); 243 | else if (strncmp(dCtx.header->magic, "dyld_v1 armv7", 14) == 0) 244 | runAllImages(dCtx, args); 245 | else if (strcmp(dCtx.header->magic, "dyld_v1 arm64") == 0) 246 | runAllImages(dCtx, args); 247 | else if (strcmp(dCtx.header->magic, "dyld_v1 arm64e") == 0) 248 | runAllImages(dCtx, args); 249 | else if (strcmp(dCtx.header->magic, "dyld_v1arm64_32") == 0) 250 | runAllImages(dCtx, args); 251 | else if (strcmp(dCtx.header->magic, "dyld_v1 i386") == 0 || 252 | strcmp(dCtx.header->magic, "dyld_v1 armv5") == 0 || 253 | strcmp(dCtx.header->magic, "dyld_v1 armv6") == 0) { 254 | std::cerr << "Unsupported Architecture type."; 255 | return 1; 256 | } else { 257 | std::cerr << "Unrecognized dyld shared cache magic.\n"; 258 | return 1; 259 | } 260 | 261 | } catch (const std::exception &e) { 262 | std::cerr << "An error has occurred: " << e.what() << std::endl; 263 | return 1; 264 | } 265 | return 0; 266 | } -------------------------------------------------------------------------------- /DyldEx/dyldex_info.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "config.h" 10 | 11 | namespace fs = std::filesystem; 12 | using namespace DyldExtractor; 13 | 14 | struct ProgramArguments { 15 | fs::path cache_path; 16 | uint64_t address; 17 | bool findAddress; 18 | bool resolveChain; 19 | }; 20 | 21 | ProgramArguments parseArgs(int argc, char *argv[]) { 22 | argparse::ArgumentParser program("dyldex_info", DYLDEXTRACTORC_VERSION); 23 | 24 | program.add_argument("cache_path") 25 | .help("The path to the shared cache. If there are subcaches, give the " 26 | "main one (typically without the file extension)."); 27 | 28 | program.add_argument("-a", "--address") 29 | .help( 30 | "Input an address. Hexadecimal numbers must contains the 0x prefix.") 31 | .scan<'i', uint64_t>() 32 | .default_value(0); 33 | 34 | program.add_argument("--find-address") 35 | .help("Find the image that contains the address.") 36 | .default_value(false) 37 | .implicit_value(true); 38 | 39 | program.add_argument("--resolve-chain") 40 | .help("Resolve a stub chain") 41 | .default_value(false) 42 | .implicit_value(true); 43 | 44 | ProgramArguments args; 45 | try { 46 | program.parse_args(argc, argv); 47 | 48 | args.cache_path = fs::path(program.get("cache_path")); 49 | args.address = program.get("--address"); 50 | args.findAddress = program.get("--find-address"); 51 | args.resolveChain = program.get("--resolve-chain"); 52 | 53 | } catch (const std::runtime_error &err) { 54 | std::cerr << "Argument parsing error: " << err.what() << std::endl; 55 | std::exit(1); 56 | } 57 | 58 | return args; 59 | } 60 | 61 | template 62 | std::string 63 | formatStubFormat(typename Converter::Stubs::Arm64Utils::StubFormat format) { 64 | switch (format) { 65 | case Converter::Stubs::Arm64Utils::StubFormat::StubNormal: 66 | return "StubNormal"; 67 | break; 68 | case Converter::Stubs::Arm64Utils::StubFormat::StubOptimized: 69 | return "StubOptimized"; 70 | break; 71 | case Converter::Stubs::Arm64Utils::StubFormat::AuthStubNormal: 72 | return "AuthStubNormal"; 73 | break; 74 | case Converter::Stubs::Arm64Utils::StubFormat::AuthStubOptimized: 75 | return "AuthStubOptimized"; 76 | break; 77 | case Converter::Stubs::Arm64Utils::StubFormat::AuthStubResolver: 78 | return "AuthStubResolver"; 79 | break; 80 | case Converter::Stubs::Arm64Utils::StubFormat::Resolver: 81 | return "Resolver"; 82 | break; 83 | 84 | default: 85 | Utils::unreachable(); 86 | } 87 | } 88 | 89 | template void program(Dyld::Context &dCtx, ProgramArguments &args) { 90 | if (args.findAddress) { 91 | bool found = false; 92 | for (auto imageInfo : dCtx.images) { 93 | auto mCtx = dCtx.createMachoCtx(imageInfo); 94 | if (mCtx.containsAddr(args.address)) { 95 | // Find the specific segment 96 | for (const auto &seg : mCtx.segments) { 97 | if (args.address >= seg.command->vmaddr && 98 | args.address < seg.command->vmaddr + seg.command->vmsize) { 99 | found = true; 100 | auto imagePath = 101 | (const char *)(dCtx.file + imageInfo->pathFileOffset); 102 | std::cout << fmt::format("{}: {}", imagePath, seg.command->segname) 103 | << std::endl; 104 | break; 105 | } 106 | } 107 | break; 108 | } 109 | } 110 | 111 | if (!found) { 112 | std::cerr 113 | << fmt::format( 114 | "Unable to find an image that contains the address {:#x}", 115 | args.address) 116 | << std::endl; 117 | } 118 | } 119 | 120 | if (args.resolveChain) { 121 | if constexpr (std::is_same_v) { 122 | Provider::Accelerator accelerator; 123 | Provider::PointerTracker ptrTracker(dCtx); 124 | Converter::Stubs::Arm64Utils arm64Utils(dCtx, accelerator, ptrTracker); 125 | 126 | auto currentAddr = args.address; 127 | while (true) { 128 | auto data = arm64Utils.resolveStub(currentAddr); 129 | if (!data) { 130 | break; 131 | } 132 | 133 | auto [newAddr, format] = *data; 134 | std::cout << std::format("{}: {:#x} -> {:#x}", 135 | formatStubFormat(format), currentAddr, 136 | newAddr) 137 | << std::endl; 138 | if (currentAddr == newAddr) { 139 | break; 140 | } else { 141 | currentAddr = newAddr; 142 | } 143 | } 144 | } else { 145 | std::cerr << "Not implemented for architectures other than arm64." 146 | << std::endl; 147 | } 148 | } 149 | } 150 | 151 | int main(int argc, char *argv[]) { 152 | ProgramArguments args = parseArgs(argc, argv); 153 | 154 | try { 155 | Dyld::Context dCtx(args.cache_path); 156 | 157 | // use dyld's magic to select arch 158 | if (strcmp(dCtx.header->magic, "dyld_v1 x86_64") == 0) 159 | program(dCtx, args); 160 | else if (strcmp(dCtx.header->magic, "dyld_v1 x86_64h") == 0) 161 | program(dCtx, args); 162 | else if (strcmp(dCtx.header->magic, "dyld_v1 armv7") == 0) 163 | program(dCtx, args); 164 | else if (strncmp(dCtx.header->magic, "dyld_v1 armv7", 14) == 0) 165 | program(dCtx, args); 166 | else if (strcmp(dCtx.header->magic, "dyld_v1 arm64") == 0) 167 | program(dCtx, args); 168 | else if (strcmp(dCtx.header->magic, "dyld_v1 arm64e") == 0) 169 | program(dCtx, args); 170 | else if (strcmp(dCtx.header->magic, "dyld_v1arm64_32") == 0) 171 | program(dCtx, args); 172 | else if (strcmp(dCtx.header->magic, "dyld_v1 i386") == 0 || 173 | strcmp(dCtx.header->magic, "dyld_v1 armv5") == 0 || 174 | strcmp(dCtx.header->magic, "dyld_v1 armv6") == 0) { 175 | std::cerr << "Unsupported Architecture type."; 176 | return 1; 177 | } else { 178 | std::cerr << "Unrecognized dyld shared cache magic.\n"; 179 | return 1; 180 | } 181 | 182 | } catch (const std::exception &e) { 183 | std::cerr << "An error has occurred: " << e.what() << std::endl; 184 | return 1; 185 | } 186 | return 0; 187 | } -------------------------------------------------------------------------------- /DyldExtractor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | add_library(DyldExtractor 4 | Converter/Linkedit/Encoder/BindingV1.cpp 5 | Converter/Linkedit/Encoder/Chained.cpp 6 | Converter/Linkedit/Encoder/LegacyGenerator.cpp 7 | Converter/Linkedit/Encoder/RebaseV1.cpp 8 | Converter/Linkedit/MetadataGenerator.cpp 9 | Converter/Linkedit/Optimizer.cpp 10 | Converter/Objc/Objc.cpp 11 | Converter/Objc/Placer.cpp 12 | Converter/Objc/Walker.cpp 13 | Converter/Stubs/Arm64Fixer.cpp 14 | Converter/Stubs/Arm64Utils.cpp 15 | Converter/Stubs/ArmFixer.cpp 16 | Converter/Stubs/ArmUtils.cpp 17 | Converter/Stubs/Fixer.cpp 18 | Converter/Stubs/SymbolPointerCache.cpp 19 | Converter/OffsetOptimizer.cpp 20 | Converter/Slide.cpp 21 | Dyld/Context.cpp 22 | Macho/Context.cpp 23 | Provider/ActivityLogger.cpp 24 | Provider/BindInfo.cpp 25 | Provider/Disassembler.cpp 26 | Provider/ExtraData.cpp 27 | Provider/FunctionTracker.cpp 28 | Provider/LinkeditTracker.cpp 29 | Provider/PointerTracker.cpp 30 | Provider/Symbolizer.cpp 31 | Provider/SymbolTableTracker.cpp 32 | Provider/Validator.cpp 33 | Utils/ExtractionContext.cpp 34 | Utils/Leb128.cpp 35 | ) 36 | 37 | target_link_libraries(DyldExtractor PUBLIC ${Boost_LIBRARIES}) 38 | target_include_directories(DyldExtractor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 39 | 40 | target_include_directories(DyldExtractor PUBLIC .) 41 | 42 | target_link_libraries(DyldExtractor PRIVATE spdlog::spdlog) 43 | target_link_libraries(DyldExtractor PRIVATE fmt::fmt) 44 | target_link_libraries(DyldExtractor PRIVATE capstone::capstone) 45 | -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/Encoder/BindingV1.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_LINKEDIT_ENCODER_BINDINGV1__ 2 | #define __CONVERTER_LINKEDIT_ENCODER_BINDINGV1__ 3 | 4 | #include 5 | 6 | namespace DyldExtractor::Converter::Linkedit::Encoder { 7 | 8 | struct BindingV1Info { 9 | BindingV1Info(uint8_t _type, uint8_t _flags, uint16_t _threadedBindOrdinal, 10 | int _libraryOrdinal, const char *_symbolName, uint64_t 11 | _address, int64_t _addend); 12 | BindingV1Info(uint8_t t, int ord, const char *sym, bool weak_import, 13 | uint64_t addr, int64_t add); 14 | BindingV1Info(uint8_t t, const char *sym, bool non_weak_definition, 15 | uint64_t addr, int64_t add); 16 | 17 | uint8_t _type; 18 | uint8_t _flags; 19 | uint16_t _threadedBindOrdinal; 20 | int _libraryOrdinal; 21 | const char *_symbolName; 22 | uint64_t _address; 23 | int64_t _addend; 24 | 25 | // for sorting 26 | int operator<(const BindingV1Info &rhs) const; 27 | }; 28 | 29 | template 30 | std::vector encodeBindingV1(std::vector &info, 31 | const Macho::Context &mCtx); 32 | 33 | } // namespace DyldExtractor::Converter::Linkedit::Encoder 34 | 35 | #endif // __CONVERTER_LINKEDIT_ENCODER_BINDINGV1__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/Encoder/Chained.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_LINKEDIT_ENCODER_CHAINED__ 2 | #define __CONVERTER_LINKEDIT_ENCODER_CHAINED__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace DyldExtractor::Converter::Linkedit::Encoder { 9 | 10 | class Atom { 11 | public: 12 | const char *name; 13 | uint32_t libOrdinal; 14 | bool weakImport; 15 | }; 16 | 17 | class ChainedFixupBinds { 18 | public: 19 | using EnumerationCallback = std::function; 21 | 22 | void ensureTarget(const Atom *atom, bool authPtr, uint64_t addend); 23 | uint32_t count() const; 24 | bool hasLargeAddends() const; 25 | bool hasHugeAddends() const; 26 | bool hasHugeSymbolStrings() const; 27 | void forEachBind(EnumerationCallback callback); 28 | uint32_t ordinal(const Atom *atom, uint64_t addend) const; 29 | void setMaxRebase(uint64_t max) { _maxRebase = max; } 30 | uint64_t maxRebase() const { return _maxRebase; } 31 | 32 | private: 33 | struct AtomAndAddend { 34 | const Atom *atom; 35 | uint64_t addend; 36 | }; 37 | std::unordered_map _bindOrdinalsWithNoAddend; 38 | std::vector _bindsTargets; 39 | uint64_t _maxRebase = 0; 40 | bool _hasLargeAddends = false; 41 | bool _hasHugeAddends = false; 42 | }; 43 | 44 | struct ChainedFixupPageInfo { 45 | std::vector fixupOffsets; 46 | std::vector chainOverflows; 47 | }; 48 | 49 | struct ChainedFixupSegInfo { 50 | const char *name; 51 | uint64_t startAddr; 52 | uint64_t endAddr; 53 | uint32_t fileOffset; 54 | uint32_t pageSize; 55 | uint32_t pointerFormat; 56 | std::vector pages; 57 | }; 58 | 59 | /// @brief Generated rebase and bind info using the new chained pointers method, 60 | /// only supports arm64 and arm64e. This can not be used for arm and arm64_32 61 | /// because pointers are too big to fit. 62 | class ChainedEncoder { 63 | using A = Utils::Arch::arm64; 64 | using P = A::P; 65 | using PtrT = P::PtrT; 66 | 67 | using LETrackerTag = Provider::LinkeditTracker

::Tag; 68 | 69 | public: 70 | ChainedEncoder(Utils::ExtractionContext &eCtx); 71 | 72 | void generateMetadata(); 73 | 74 | private: 75 | /// @brief fixup pointers and set their targets 76 | void fixupPointers(); 77 | 78 | /// @brief Builds chains info 79 | void buildChainedFixupInfo(); 80 | 81 | /// @brief Encodes linkedit data. 82 | /// @return The linkedit data, pointer aligned 83 | std::vector encodeChainedInfo(); 84 | 85 | /// @brief applies and chains pointers together, must be ran after 86 | /// `encodeChainedInfo` and linkedit data is added. 87 | void applyChainedFixups(); 88 | 89 | uint16_t chainedPointerFormat() const; 90 | void fixup64(); 91 | void fixup64e(); 92 | 93 | Macho::Context &mCtx; 94 | Provider::ActivityLogger &activity; 95 | std::shared_ptr logger; 96 | Provider::PointerTracker

&ptrTracker; 97 | Provider::LinkeditTracker

&leTracker; 98 | Provider::SymbolTableTracker

&stTracker; 99 | std::optional> &exObjc; 100 | 101 | ChainedFixupBinds chainedFixupBinds; 102 | std::vector chainedFixupSegments; 103 | 104 | // Map of symbolic info to atoms 105 | std::map, Atom> atomMap; 106 | // Map of bind address to atoms 107 | std::map bindToAtoms; 108 | }; 109 | 110 | } // namespace DyldExtractor::Converter::Linkedit::Encoder 111 | 112 | #endif // __CONVERTER_LINKEDIT_ENCODER_CHAINED__ 113 | -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/Encoder/Encoder.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_LINKEDIT_ENCODER__ 2 | #define __CONVERTER_LINKEDIT_ENCODER__ 3 | 4 | #include "Chained.h" 5 | #include "LegacyGenerator.h" 6 | 7 | #endif // __CONVERTER_LINKEDIT_ENCODER__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/Encoder/LegacyGenerator.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_LINKEDIT_LEGACYGENERATOR__ 2 | #define __CONVERTER_LINKEDIT_LEGACYGENERATOR__ 3 | 4 | #include 5 | 6 | namespace DyldExtractor::Converter::Linkedit::Encoder { 7 | 8 | /// @brief Generate rebase and bind info using the legacy opcode method. 9 | template 10 | void generateLegacyMetadata(Utils::ExtractionContext &eCtx); 11 | 12 | } // namespace DyldExtractor::Converter::Linkedit::Encoder 13 | 14 | #endif // __CONVERTER_LINKEDIT_LEGACYGENERATOR__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/Encoder/RebaseV1.cpp: -------------------------------------------------------------------------------- 1 | #include "RebaseV1.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace DyldExtractor; 7 | using namespace Converter; 8 | using namespace Linkedit; 9 | using namespace Encoder; 10 | 11 | RebaseV1Info::RebaseV1Info(uint8_t t, uint64_t addr) 12 | : _type(t), _address(addr) {} 13 | 14 | struct rebase_tmp { 15 | rebase_tmp(uint8_t op, uint64_t p1, uint64_t p2 = 0) 16 | : opcode(op), operand1(p1), operand2(p2) {} 17 | uint8_t opcode; 18 | uint64_t operand1; 19 | uint64_t operand2; 20 | }; 21 | 22 | template 23 | std::vector 24 | Encoder::encodeRebaseV1(const std::vector &info, 25 | const Macho::Context &mCtx) { 26 | using PtrT = P::PtrT; 27 | 28 | // convert to temp encoding that can be more easily optimized 29 | std::vector mid; 30 | uint64_t curSegStart = 0; 31 | uint64_t curSegEnd = 0; 32 | uint32_t curSegIndex = 0; 33 | uint8_t type = 0; 34 | uint64_t address = (uint64_t)(-1); 35 | for (auto it = info.begin(); it != info.end(); ++it) { 36 | if (type != it->_type) { 37 | mid.push_back(rebase_tmp(REBASE_OPCODE_SET_TYPE_IMM, it->_type)); 38 | type = it->_type; 39 | } 40 | if (address != it->_address) { 41 | if ((it->_address < curSegStart) || (it->_address >= curSegEnd)) { 42 | 43 | // Find segment containing address 44 | bool found = false; 45 | for (int segI = 0; segI < mCtx.segments.size(); segI++) { 46 | const auto &seg = mCtx.segments.at(segI); 47 | 48 | if ((it->_address < seg.command->vmaddr) || 49 | (it->_address >= (seg.command->vmaddr + seg.command->vmsize))) { 50 | curSegStart = seg.command->vmaddr; 51 | curSegEnd = seg.command->vmaddr + seg.command->vmsize; 52 | curSegIndex = segI; 53 | found = true; 54 | break; 55 | } 56 | } 57 | if (!found) 58 | throw "binding address outside range of any segment"; 59 | 60 | mid.push_back(rebase_tmp(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB, 61 | curSegIndex, it->_address - curSegStart)); 62 | } else { 63 | mid.push_back( 64 | rebase_tmp(REBASE_OPCODE_ADD_ADDR_ULEB, it->_address - address)); 65 | } 66 | address = it->_address; 67 | } 68 | mid.push_back(rebase_tmp(REBASE_OPCODE_DO_REBASE_ULEB_TIMES, 1)); 69 | address += sizeof(PtrT); 70 | if (address >= curSegEnd) 71 | address = 0; 72 | } 73 | mid.push_back(rebase_tmp(REBASE_OPCODE_DONE, 0)); 74 | 75 | // optimize phase 1, compress packed runs of pointers 76 | rebase_tmp *dst = &mid[0]; 77 | for (const rebase_tmp *src = &mid[0]; src->opcode != REBASE_OPCODE_DONE; 78 | ++src) { 79 | if ((src->opcode == REBASE_OPCODE_DO_REBASE_ULEB_TIMES) && 80 | (src->operand1 == 1)) { 81 | *dst = *src++; 82 | while (src->opcode == REBASE_OPCODE_DO_REBASE_ULEB_TIMES) { 83 | dst->operand1 += src->operand1; 84 | ++src; 85 | } 86 | --src; 87 | ++dst; 88 | } else { 89 | *dst++ = *src; 90 | } 91 | } 92 | dst->opcode = REBASE_OPCODE_DONE; 93 | 94 | // optimize phase 2, combine rebase/add pairs 95 | dst = &mid[0]; 96 | for (const rebase_tmp *src = &mid[0]; src->opcode != REBASE_OPCODE_DONE; 97 | ++src) { 98 | if ((src->opcode == REBASE_OPCODE_DO_REBASE_ULEB_TIMES) && 99 | (src->operand1 == 1) && 100 | (src[1].opcode == REBASE_OPCODE_ADD_ADDR_ULEB)) { 101 | dst->opcode = REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB; 102 | dst->operand1 = src[1].operand1; 103 | ++src; 104 | ++dst; 105 | } else { 106 | *dst++ = *src; 107 | } 108 | } 109 | dst->opcode = REBASE_OPCODE_DONE; 110 | 111 | // optimize phase 3, compress packed runs of 112 | // REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB with same addr delta into one 113 | // REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB 114 | dst = &mid[0]; 115 | for (const rebase_tmp *src = &mid[0]; src->opcode != REBASE_OPCODE_DONE; 116 | ++src) { 117 | uint64_t delta = src->operand1; 118 | if ((src->opcode == REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB) && 119 | (src[1].opcode == REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB) && 120 | (src[2].opcode == REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB) && 121 | (src[1].operand1 == delta) && (src[2].operand1 == delta)) { 122 | // found at least three in a row, this is worth compressing 123 | dst->opcode = REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB; 124 | dst->operand1 = 1; 125 | dst->operand2 = delta; 126 | ++src; 127 | while ((src->opcode == REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB) && 128 | (src->operand1 == delta)) { 129 | dst->operand1++; 130 | ++src; 131 | } 132 | --src; 133 | ++dst; 134 | } else { 135 | *dst++ = *src; 136 | } 137 | } 138 | dst->opcode = REBASE_OPCODE_DONE; 139 | 140 | // optimize phase 4, use immediate encodings 141 | for (rebase_tmp *p = &mid[0]; p->opcode != REBASE_OPCODE_DONE; ++p) { 142 | if ((p->opcode == REBASE_OPCODE_ADD_ADDR_ULEB) && 143 | (p->operand1 < (15 * sizeof(PtrT))) && 144 | ((p->operand1 % sizeof(PtrT)) == 0)) { 145 | p->opcode = REBASE_OPCODE_ADD_ADDR_IMM_SCALED; 146 | p->operand1 = p->operand1 / sizeof(PtrT); 147 | } else if ((p->opcode == REBASE_OPCODE_DO_REBASE_ULEB_TIMES) && 148 | (p->operand1 < 15)) { 149 | p->opcode = REBASE_OPCODE_DO_REBASE_IMM_TIMES; 150 | } 151 | } 152 | 153 | // convert to compressed encoding 154 | std::vector encodedData; 155 | encodedData.reserve(info.size() * 2); 156 | bool done = false; 157 | for (auto it = mid.begin(); !done && it != mid.end(); ++it) { 158 | switch (it->opcode) { 159 | case REBASE_OPCODE_DONE: 160 | 161 | done = true; 162 | break; 163 | case REBASE_OPCODE_SET_TYPE_IMM: 164 | 165 | encodedData.push_back( 166 | (uint8_t)(REBASE_OPCODE_SET_TYPE_IMM | it->operand1)); 167 | break; 168 | case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: 169 | 170 | encodedData.push_back( 171 | (uint8_t)(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | it->operand1)); 172 | Utils::appendUleb128(encodedData, it->operand2); 173 | break; 174 | case REBASE_OPCODE_ADD_ADDR_ULEB: 175 | 176 | encodedData.push_back(REBASE_OPCODE_ADD_ADDR_ULEB); 177 | Utils::appendUleb128(encodedData, it->operand1); 178 | break; 179 | case REBASE_OPCODE_ADD_ADDR_IMM_SCALED: 180 | 181 | encodedData.push_back( 182 | (uint8_t)(REBASE_OPCODE_ADD_ADDR_IMM_SCALED | it->operand1)); 183 | break; 184 | case REBASE_OPCODE_DO_REBASE_IMM_TIMES: 185 | 186 | encodedData.push_back( 187 | (uint8_t)(REBASE_OPCODE_DO_REBASE_IMM_TIMES | it->operand1)); 188 | break; 189 | case REBASE_OPCODE_DO_REBASE_ULEB_TIMES: 190 | 191 | encodedData.push_back(REBASE_OPCODE_DO_REBASE_ULEB_TIMES); 192 | Utils::appendUleb128(encodedData, it->operand1); 193 | break; 194 | case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: 195 | 196 | encodedData.push_back(REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB); 197 | Utils::appendUleb128(encodedData, it->operand1); 198 | break; 199 | case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB: 200 | 201 | encodedData.push_back(REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB); 202 | Utils::appendUleb128(encodedData, it->operand1); 203 | Utils::appendUleb128(encodedData, it->operand2); 204 | break; 205 | } 206 | } 207 | 208 | // align to pointer size 209 | encodedData.resize(Utils::align(encodedData.size(), sizeof(PtrT))); 210 | return encodedData; 211 | } 212 | 213 | template std::vector Encoder::encodeRebaseV1( 214 | const std::vector &info, 215 | const Macho::Context &mCtx); 216 | template std::vector Encoder::encodeRebaseV1( 217 | const std::vector &info, 218 | const Macho::Context &mCtx); -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/Encoder/RebaseV1.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_LINKEDIT_ENCODER_REBASEV1__ 2 | #define __CONVERTER_LINKEDIT_ENCODER_REBASEV1__ 3 | 4 | #include 5 | 6 | namespace DyldExtractor::Converter::Linkedit::Encoder { 7 | 8 | struct RebaseV1Info { 9 | RebaseV1Info(uint8_t t, uint64_t addr); 10 | uint8_t _type; 11 | uint64_t _address; 12 | }; 13 | 14 | template 15 | std::vector encodeRebaseV1(const std::vector &info, 16 | const Macho::Context &mCtx); 17 | 18 | } // namespace DyldExtractor::Converter::Linkedit::Encoder 19 | 20 | #endif // __CONVERTER_LINKEDIT_ENCODER_REBASEV1__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/Linkedit.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_LINKEDIT_LINKEDIT__ 2 | #define __CONVERTER_LINKEDIT_LINKEDIT__ 3 | 4 | #include "MetadataGenerator.h" 5 | #include "Optimizer.h" 6 | 7 | #endif // __CONVERTER_LINKEDIT_LINKEDIT__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/MetadataGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "MetadataGenerator.h" 2 | 3 | #include "Encoder/Encoder.h" 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace DyldExtractor; 9 | using namespace Converter; 10 | using namespace Linkedit; 11 | 12 | /// @brief Writes strings and symbol tables in the tracker 13 | /// @tparam A 14 | /// @param eCtx 15 | template void writeSymbols(Utils::ExtractionContext &eCtx) { 16 | using P = A::P; 17 | using PtrT = P::PtrT; 18 | using LETrackerMetadata = Provider::LinkeditTracker

::Metadata; 19 | using LETrackerTag = Provider::LinkeditTracker

::Tag; 20 | using STTrackerSymbolType = Provider::SymbolTableTracker

::SymbolType; 21 | 22 | eCtx.activity->update(std::nullopt, "Writing symbols"); 23 | 24 | auto &mCtx = *eCtx.mCtx; 25 | auto logger = eCtx.logger; 26 | auto &leTracker = eCtx.leTracker.value(); 27 | auto &stTracker = eCtx.stTracker.value(); 28 | 29 | auto symtab = mCtx.getFirstLC(); 30 | auto dysymtab = mCtx.getFirstLC(); 31 | 32 | // Create string pool 33 | // Get size of pool and assign indicies 34 | auto &strings = stTracker.getStrings(); 35 | 36 | uint32_t strSize = 0; 37 | uint32_t currentI = 1; // first string is \x00 38 | std::map strIndicies; 39 | for (auto it = strings.cbegin(); it != strings.cend(); it++) { 40 | strIndicies[&(*it)] = currentI; 41 | strSize += (uint32_t)it->size(); 42 | currentI += (uint32_t)it->size() + 1; // include null terminator 43 | } 44 | 45 | // add null terminator for each string, and 1 for the beginning \x00 46 | strSize += (uint32_t)strings.size() + 1; 47 | std::vector strBuf(strSize, 0x0); 48 | auto strBufData = strBuf.data(); 49 | 50 | // Copy strings 51 | for (auto &[it, offset] : strIndicies) { 52 | memcpy(strBufData + offset, it->c_str(), it->size()); 53 | } 54 | 55 | // Create symbol table 56 | auto &syms = stTracker.getSymbolCaches(); 57 | auto _symTypeOffset = [&syms](auto symType) -> uint32_t { 58 | switch (symType) { 59 | case STTrackerSymbolType::other: 60 | return 0; 61 | case STTrackerSymbolType::local: 62 | return (uint32_t)syms.other.size(); 63 | case STTrackerSymbolType::external: 64 | return (uint32_t)(syms.other.size() + syms.local.size()); 65 | case STTrackerSymbolType::undefined: 66 | return (uint32_t)(syms.other.size() + syms.local.size() + 67 | syms.external.size()); 68 | default: 69 | Utils::unreachable(); 70 | } 71 | }; 72 | 73 | const uint32_t nlistSize = (uint32_t)sizeof(Macho::Loader::nlist

); 74 | uint32_t nSyms = (uint32_t)(syms.other.size() + syms.local.size() + 75 | syms.external.size() + syms.undefined.size()); 76 | std::vector> symsBuf; 77 | symsBuf.reserve(nSyms); 78 | 79 | // Write symbols 80 | auto _writeSyms = [&](auto &syms) { 81 | for (auto &[strIt, sym] : syms) { 82 | symsBuf.push_back(sym); 83 | symsBuf.back().n_un.n_strx = strIndicies[&(*strIt)]; 84 | } 85 | }; 86 | _writeSyms(syms.other); 87 | _writeSyms(syms.local); 88 | _writeSyms(syms.external); 89 | _writeSyms(syms.undefined); 90 | 91 | // Create indirect symbol table 92 | auto &indirectSymtab = stTracker.indirectSyms; 93 | std::vector indirectSymtabBuf; 94 | indirectSymtabBuf.reserve(indirectSymtab.size()); 95 | for (const auto &sym : indirectSymtab) { 96 | indirectSymtabBuf.push_back(_symTypeOffset(sym.first) + sym.second); 97 | } 98 | 99 | // Add new data to tracking 100 | LETrackerMetadata stringPoolMeta( 101 | LETrackerTag::stringPool, nullptr, 102 | Utils::align(strSize, (uint32_t)sizeof(PtrT)), 103 | reinterpret_cast(symtab)); 104 | if (!leTracker.addData(stringPoolMeta, strBufData, strSize).second) { 105 | SPDLOG_LOGGER_ERROR(logger, "Not enough space to add string pool."); 106 | return; 107 | } 108 | symtab->strsize = strSize; 109 | 110 | LETrackerMetadata symtabMeta( 111 | LETrackerTag::symtab, nullptr, 112 | Utils::align(nSyms * nlistSize, (uint32_t)sizeof(PtrT)), 113 | reinterpret_cast(symtab)); 114 | if (!leTracker 115 | .addData(symtabMeta, reinterpret_cast(symsBuf.data()), 116 | nSyms * nlistSize) 117 | .second) { 118 | SPDLOG_LOGGER_ERROR(logger, "Not enough space to add symbol table."); 119 | return; 120 | } 121 | symtab->nsyms = nSyms; 122 | 123 | LETrackerMetadata indirectSymtabMeta( 124 | LETrackerTag::indirectSymtab, nullptr, 125 | Utils::align((uint32_t)(indirectSymtabBuf.size() * sizeof(uint32_t)), 126 | (uint32_t)sizeof(PtrT)), 127 | reinterpret_cast(dysymtab)); 128 | if (!leTracker 129 | .addData(indirectSymtabMeta, 130 | reinterpret_cast(indirectSymtabBuf.data()), 131 | (uint32_t)(indirectSymtabBuf.size() * sizeof(uint32_t))) 132 | .second) { 133 | SPDLOG_LOGGER_ERROR(logger, 134 | "Not enough space to add indirect symbol table."); 135 | return; 136 | } 137 | dysymtab->nindirectsyms = (uint32_t)indirectSymtabBuf.size(); 138 | 139 | // Set symbol indicies 140 | dysymtab->ilocalsym = _symTypeOffset(STTrackerSymbolType::local); 141 | dysymtab->nlocalsym = (uint32_t)syms.local.size(); 142 | dysymtab->iextdefsym = _symTypeOffset(STTrackerSymbolType::external); 143 | dysymtab->nextdefsym = (uint32_t)syms.external.size(); 144 | dysymtab->iundefsym = _symTypeOffset(STTrackerSymbolType::undefined); 145 | dysymtab->nundefsym = (uint32_t)syms.undefined.size(); 146 | } 147 | 148 | template 149 | void Converter::generateMetadata(Utils::ExtractionContext &eCtx) { 150 | using P = A::P; 151 | 152 | eCtx.activity->update("Metadata Generator", "Starting Up"); 153 | auto dyldInfo = eCtx.mCtx->getFirstLC(); 154 | 155 | if (!eCtx.leTracker || !eCtx.stTracker) { 156 | SPDLOG_LOGGER_ERROR(eCtx.logger, 157 | "Metadata Generator depends on Linkedit Optimizer."); 158 | return; 159 | } 160 | 161 | // Check if new-style encoding can be used 162 | if constexpr (std::is_same_v) { 163 | if (!dyldInfo) { 164 | Encoder::ChainedEncoder(eCtx).generateMetadata(); 165 | writeSymbols(eCtx); 166 | eCtx.activity->update(std::nullopt, "Done"); 167 | return; 168 | } 169 | } 170 | 171 | Encoder::generateLegacyMetadata(eCtx); 172 | writeSymbols(eCtx); 173 | eCtx.activity->update(std::nullopt, "Done"); 174 | } 175 | 176 | #define X(T) \ 177 | template void Converter::generateMetadata(Utils::ExtractionContext & \ 178 | eCtx); 179 | X(Utils::Arch::x86_64) 180 | X(Utils::Arch::arm) 181 | X(Utils::Arch::arm64) 182 | X(Utils::Arch::arm64_32) 183 | #undef X -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/MetadataGenerator.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_LINKEDIT_METADATAGENERATOR__ 2 | #define __CONVERTER_LINKEDIT_METADATAGENERATOR__ 3 | 4 | #include 5 | 6 | namespace DyldExtractor::Converter { 7 | 8 | template void generateMetadata(Utils::ExtractionContext &eCtx); 9 | 10 | } // namespace DyldExtractor::Converter 11 | 12 | #endif // __CONVERTER_LINKEDIT_METADATAGENERATOR__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Linkedit/Optimizer.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_LINKEDIT_OPTIMIZER__ 2 | #define __CONVERTER_LINKEDIT_OPTIMIZER__ 3 | 4 | #include 5 | 6 | namespace DyldExtractor::Converter { 7 | 8 | bool isRedactedIndirect(uint32_t entry); 9 | template void optimizeLinkedit(Utils::ExtractionContext &eCtx); 10 | 11 | } // namespace DyldExtractor::Converter 12 | 13 | #endif // __CONVERTER_LINKEDIT_OPTIMIZER__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Objc/Objc.cpp: -------------------------------------------------------------------------------- 1 | #include "Objc.h" 2 | #include "Placer.h" 3 | #include "Walker.h" 4 | 5 | using namespace DyldExtractor; 6 | using namespace Converter; 7 | using namespace ObjcFixer; 8 | 9 | template void fix(Utils::ExtractionContext &eCtx) { 10 | auto &mCtx = *eCtx.mCtx; 11 | Walker objcWalker(eCtx); 12 | Placer objcPlacer(eCtx, objcWalker); 13 | 14 | Objc::image_info *objcImageInfo; 15 | if (auto sect = mCtx.getSection(nullptr, "__objc_imageinfo").second; sect) { 16 | objcImageInfo = (Objc::image_info *)mCtx.convertAddrP(sect->addr); 17 | if (!(objcImageInfo->flags & Objc::image_info::OptimizedByDyld)) { 18 | return; // Image not optimized 19 | } 20 | } else { 21 | // no objc 22 | return; 23 | } 24 | 25 | // Walk classes 26 | if (!objcWalker.walkAll()) { 27 | return; 28 | } 29 | 30 | if (auto exData = objcPlacer.placeAll(); exData) { 31 | eCtx.exObjc.emplace(std::move(*exData)); 32 | } else { 33 | return; 34 | } 35 | 36 | // clear optimized by Dyld flag 37 | objcImageInfo->flags &= ~Objc::image_info::OptimizedByDyld; 38 | } 39 | 40 | template void Converter::fixObjc(Utils::ExtractionContext &eCtx) { 41 | // Load Providers 42 | eCtx.bindInfo.load(); 43 | 44 | if (!eCtx.symbolizer || !eCtx.leTracker || !eCtx.stTracker) { 45 | SPDLOG_LOGGER_ERROR(eCtx.logger, 46 | "ObjC Fixer depends on Linkedit Optimizer"); 47 | return; 48 | } 49 | 50 | eCtx.activity->update("ObjC Fixer"); 51 | fix(eCtx); 52 | } 53 | 54 | #define X(T) \ 55 | template void Converter::fixObjc(Utils::ExtractionContext & eCtx); 56 | X(Utils::Arch::x86_64) 57 | X(Utils::Arch::arm) X(Utils::Arch::arm64) X(Utils::Arch::arm64_32) 58 | #undef X -------------------------------------------------------------------------------- /DyldExtractor/Converter/Objc/Objc.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_OBJC_OBJC__ 2 | #define __CONVERTER_OBJC_OBJC__ 3 | 4 | #include 5 | 6 | namespace DyldExtractor::Converter { 7 | 8 | template void fixObjc(Utils::ExtractionContext &eCtx); 9 | 10 | } // namespace DyldExtractor::Converter 11 | 12 | #endif // __CONVERTER_OBJC_OBJC__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Objc/Placer.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_OBJC_PLACER__ 2 | #define __CONVERTER_OBJC_PLACER__ 3 | 4 | #include "Walker.h" 5 | 6 | namespace DyldExtractor::Converter::ObjcFixer { 7 | 8 | template class Placer { 9 | using P = A::P; 10 | using PtrT = P::PtrT; 11 | 12 | public: 13 | Placer(Utils::ExtractionContext &eCtx, Walker &walker); 14 | 15 | std::optional> placeAll(); 16 | 17 | private: 18 | /// @brief Inserts a new segment for the extra data 19 | /// @return The name of the segment that the extra data extends and the 20 | /// address of the extra data or 0 if it couldn't be allocated. 21 | std::pair allocateDataRegion(); 22 | 23 | /// @brief Gives addresses to all atoms 24 | /// @returns The size of the extra data section 25 | PtrT placeAtoms(const PtrT exDataAddr); 26 | /// @brief Propagate all atoms 27 | void propagateAtoms(); 28 | /// @brief Update data fields and write 29 | void writeAtoms(Provider::ExtraData

&exData); 30 | /// @brief Adds pointers to tracking 31 | void trackAtoms(Provider::ExtraData

&exData); 32 | 33 | /// @brief Checks if a bind has a symbol entry 34 | void checkBind(const std::shared_ptr &bind); 35 | 36 | Macho::Context &mCtx; 37 | std::shared_ptr logger; 38 | Provider::PointerTracker

&ptrTracker; 39 | Provider::LinkeditTracker

&leTracker; 40 | Provider::SymbolTableTracker

&stTracker; 41 | 42 | Walker &walker; 43 | }; 44 | 45 | } // namespace DyldExtractor::Converter::ObjcFixer 46 | 47 | #endif // __CONVERTER_OBJC_PLACER__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Objc/SelectorFixer.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arandomdev/DyldExtractorC/89ad09dcdba885e8286105cc515c73bc3756aa39/DyldExtractor/Converter/Objc/SelectorFixer.cpp -------------------------------------------------------------------------------- /DyldExtractor/Converter/Objc/SelectorFixer.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_OBJC_SELECTORFIXER__ 2 | #define __CONVERTER_OBJC_SELECTORFIXER__ 3 | 4 | namespace DyldExtractor::Converter::ObjcFixer { 5 | 6 | template class SelectorFixer {}; 7 | 8 | } // namespace DyldExtractor::Converter::ObjcFixer 9 | 10 | #endif // __CONVERTER_OBJC_SELECTORFIXER__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Objc/Walker.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_OBJC_WALKER__ 2 | #define __CONVERTER_OBJC_WALKER__ 3 | 4 | #include "Atoms.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace DyldExtractor::Converter::ObjcFixer { 10 | template class Placer; 11 | 12 | template class Walker { 13 | using P = A::P; 14 | using PtrT = P::PtrT; 15 | 16 | // allow placer to access atoms 17 | friend class Placer; 18 | 19 | public: 20 | Walker(Utils::ExtractionContext &eCtx); 21 | bool walkAll(); 22 | 23 | private: 24 | bool parseOptInfo(); 25 | 26 | ClassAtom *walkClass(const PtrT addr); 27 | ClassDataAtom *walkClassData(const PtrT addr); 28 | IvarLayoutAtom

*walkIvarLayout(const PtrT addr); 29 | StringAtom

*walkString(const PtrT addr); 30 | MethodListAtom

*walkMethodList(const PtrT addr); 31 | SmallMethodListAtom

*walkSmallMethodList(const PtrT addr, 32 | Objc::method_list_t data); 33 | LargeMethodListAtom

*walkLargeMethodList(const PtrT addr, 34 | Objc::method_list_t data); 35 | ProtocolListAtom *walkProtocolList(const PtrT addr); 36 | ProtocolAtom *walkProtocol(const PtrT addr); 37 | PropertyListAtom

*walkPropertyList(const PtrT addr); 38 | ExtendedMethodTypesAtom

*walkExtendedMethodTypes(const PtrT addr, 39 | const uint32_t count); 40 | IvarListAtom *walkIvarList(const PtrT addr); 41 | IvarOffsetAtom *walkIvarOffset(const PtrT addr); 42 | CategoryAtom *walkCategory(const PtrT addr); 43 | ImpAtom

*walkImp(const PtrT addr); 44 | 45 | PointerAtom> *makeSmallMethodSelRef(const PtrT stringAddr); 46 | 47 | /// @brief Find the list in a relative_list_list_t with the same image index. 48 | /// @param addr VM address of the relative_list_list_t 49 | /// @return Address of the list 50 | std::optional findInImageRelList(const PtrT addr) const; 51 | 52 | const Dyld::Context &dCtx; 53 | Macho::Context &mCtx; 54 | Provider::ActivityLogger &activity; 55 | std::shared_ptr logger; 56 | 57 | Provider::BindInfo

&bindInfo; 58 | Provider::PointerTracker

&ptrTracker; 59 | Provider::Symbolizer &symbolizer; 60 | 61 | uint16_t imageIndex; 62 | bool hasCategoryClassProperties = false; 63 | std::optional relMethodSelBaseAddr; 64 | 65 | /// @brief Cache of atoms, keys are the original addresses 66 | template using CacheT = std::map; 67 | struct { 68 | CacheT> classes; 69 | CacheT> classData; 70 | CacheT> ivarLayouts; 71 | CacheT> strings; 72 | CacheT> smallMethodLists; 73 | CacheT> largeMethodLists; 74 | CacheT> protocolLists; 75 | CacheT> protocols; 76 | CacheT> propertyLists; 77 | CacheT> extendedMethodTypes; 78 | CacheT> ivarLists; 79 | CacheT> ivarOffsets; 80 | CacheT> categories; 81 | CacheT> imps; 82 | 83 | // Key is the same as the string 84 | CacheT>> smallMethodSelRefs; 85 | } atoms; 86 | 87 | /// @brief Cache of pointers that need to be fixed after placing atoms 88 | struct { 89 | CacheT>> classes; 90 | CacheT>> categories; 91 | CacheT>> protocols; 92 | CacheT>> selectorRefs; 93 | CacheT>> protocolRefs; 94 | CacheT>> classRefs; 95 | CacheT>> superRefs; 96 | } pointers; 97 | }; 98 | 99 | } // namespace DyldExtractor::Converter::ObjcFixer 100 | 101 | #endif // __CONVERTER_OBJC_WALKER__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/OffsetOptimizer.cpp: -------------------------------------------------------------------------------- 1 | #include "OffsetOptimizer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace DyldExtractor; 9 | using namespace Converter; 10 | 11 | /// @brief Checks and makes room for the extra objc segment 12 | /// @return A pair of pointers, first is the extra data if available, the second 13 | /// is the linkedit data. 14 | template bool checkExtraObjc(Utils::ExtractionContext &eCtx) { 15 | const auto &mCtx = *eCtx.mCtx; 16 | if (!eCtx.exObjc) { 17 | return false; 18 | } 19 | const auto &exObjc = eCtx.exObjc.value(); 20 | 21 | // sort segments by address and find segments 22 | auto segs = mCtx.segments; 23 | std::sort(segs.begin(), segs.end(), [](const auto &a, const auto &b) { 24 | return a.command->vmaddr < b.command->vmaddr; 25 | }); 26 | 27 | auto extendsSegIt = 28 | std::find_if(segs.begin(), segs.end(), [&exObjc](const auto &a) { 29 | return strncmp(a.command->segname, exObjc.getExtendsSeg().c_str(), 30 | 16) == 0; 31 | }); 32 | auto linkeditIt = std::find_if(segs.begin(), segs.end(), [](const auto &a) { 33 | return strncmp(a.command->segname, SEG_LINKEDIT, 16) == 0; 34 | }); 35 | 36 | assert(extendsSegIt != segs.end()); // Found extends seg 37 | assert(std::next(extendsSegIt) != segs.end()); // It's not the linkedit 38 | assert(linkeditIt != segs.end()); // Found linkedit 39 | 40 | // Make more room if necessary 41 | auto exObjcEndAddr = exObjc.getEndAddr(); 42 | auto nextSegIt = std::next(extendsSegIt); 43 | if (exObjcEndAddr > nextSegIt->command->vmaddr) { 44 | if (nextSegIt == linkeditIt) { 45 | // Can move the linkedit to make room 46 | linkeditIt->command->vmaddr = 47 | Utils::align(exObjcEndAddr, SEGMENT_ALIGNMENT); 48 | } else { 49 | SPDLOG_LOGGER_ERROR(eCtx.logger, 50 | "Unable to make room for the extra ObjC segment."); 51 | return false; 52 | } 53 | } 54 | 55 | return true; 56 | } 57 | 58 | template 59 | std::vector 60 | Converter::optimizeOffsets(Utils::ExtractionContext &eCtx) { 61 | eCtx.activity->update("Offset Optimizer", "Updating Offsets"); 62 | auto &mCtx = *eCtx.mCtx; 63 | 64 | std::vector procedures; 65 | 66 | if (!eCtx.leTracker) { 67 | SPDLOG_LOGGER_ERROR( 68 | eCtx.logger, 69 | "Offset optimizer and output depends on linkedit optimizer."); 70 | return procedures; // empty 71 | } 72 | 73 | // verify sizes 74 | for (const auto seg : mCtx.segments) { 75 | if (seg.command->fileoff > UINT32_MAX || 76 | seg.command->filesize > UINT32_MAX) { 77 | SPDLOG_LOGGER_ERROR(eCtx.logger, 78 | "Segment has too big of a fileoff or filesize, " 79 | "likely a malformed segment command."); 80 | return procedures; // empty 81 | } 82 | } 83 | 84 | bool writeExObjc = checkExtraObjc(eCtx); 85 | uint32_t dataHead = 0; 86 | for (auto &seg : mCtx.segments) { 87 | bool isLinkedit = strncmp(seg.command->segname, SEG_LINKEDIT, 16) == 0; 88 | 89 | // create procedure 90 | if (isLinkedit) { 91 | procedures.emplace_back(dataHead, eCtx.leTracker->getData(), 92 | seg.command->filesize); 93 | } else { 94 | procedures.emplace_back(dataHead, mCtx.convertAddrP(seg.command->vmaddr), 95 | seg.command->filesize); 96 | 97 | if (writeExObjc && eCtx.exObjc && 98 | strncmp(eCtx.exObjc->getExtendsSeg().c_str(), seg.command->segname, 99 | 16) == 0) { 100 | // add procedure for ExObjc and increase size of segment 101 | auto &exObjc = eCtx.exObjc.value(); 102 | auto exObjcStart = exObjc.getBaseAddr(); 103 | auto exObjcEnd = exObjc.getEndAddr(); 104 | procedures.emplace_back(dataHead + (exObjcStart - seg.command->vmaddr), 105 | exObjc.getData(), exObjcEnd - exObjcStart); 106 | 107 | auto newSize = exObjcEnd - seg.command->vmaddr; 108 | seg.command->vmsize = newSize; 109 | seg.command->filesize = newSize; 110 | } 111 | } 112 | 113 | // shift the segment and sections 114 | int32_t shiftDelta = dataHead - (uint32_t)seg.command->fileoff; 115 | seg.command->fileoff += shiftDelta; 116 | for (auto §ion : seg.sections) { 117 | section->offset += shiftDelta; 118 | } 119 | 120 | if (isLinkedit) { 121 | eCtx.leTracker->changeOffset((uint32_t)seg.command->fileoff); 122 | } 123 | 124 | // update and page align dataHead 125 | dataHead += (uint32_t)seg.command->filesize; 126 | Utils::align(&dataHead, SEGMENT_ALIGNMENT); 127 | } 128 | 129 | return procedures; 130 | } 131 | 132 | #define X(T) \ 133 | template std::vector Converter::optimizeOffsets( \ 134 | Utils::ExtractionContext & eCtx); 135 | X(Utils::Arch::x86_64) 136 | X(Utils::Arch::arm) 137 | X(Utils::Arch::arm64) 138 | X(Utils::Arch::arm64_32) 139 | #undef X -------------------------------------------------------------------------------- /DyldExtractor/Converter/OffsetOptimizer.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_OFFSETOPTIMIZER__ 2 | #define __CONVERTER_OFFSETOPTIMIZER__ 3 | 4 | #include 5 | #include 6 | 7 | #define SEGMENT_ALIGNMENT 0x4000 8 | 9 | namespace DyldExtractor::Converter { 10 | 11 | struct OffsetWriteProcedure { 12 | uint64_t writeOffset; 13 | const uint8_t *source; 14 | uint64_t size; 15 | }; 16 | 17 | /// @brief Optimize a mach-o file's offsets for output. 18 | /// @returns A vector of write procedures. 19 | template 20 | std::vector 21 | optimizeOffsets(Utils::ExtractionContext &eCtx); 22 | 23 | }; // namespace DyldExtractor::Converter 24 | 25 | #endif // __CONVERTER_OFFSETOPTIMIZER__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Slide.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_SLIDE__ 2 | #define __CONVERTER_SLIDE__ 3 | 4 | #include 5 | #include 6 | 7 | namespace DyldExtractor::Converter { 8 | 9 | template void processSlideInfo(Utils::ExtractionContext &eCtx); 10 | 11 | } // namespace DyldExtractor::Converter 12 | 13 | #endif // __CONVERTER_SLIDE__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Stubs/Arm64Fixer.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_STUBS_ARM64FIXER__ 2 | #define __CONVERTER_STUBS_ARM64FIXER__ 3 | 4 | #include "Arm64Utils.h" 5 | #include "SymbolPointerCache.h" 6 | 7 | namespace DyldExtractor::Converter::Stubs { 8 | 9 | template class Fixer; 10 | 11 | template class Arm64Fixer { 12 | using P = A::P; 13 | using PtrT = P::PtrT; 14 | using SPtrT = P::SPtrT; 15 | 16 | using SPointerType = SymbolPointerCache::PointerType; 17 | using AStubFormat = Arm64Utils::StubFormat; 18 | 19 | public: 20 | Arm64Fixer(Fixer &delegate); 21 | void fix(); 22 | 23 | std::map stubMap; 24 | 25 | private: 26 | struct StubInfo { 27 | AStubFormat format; 28 | PtrT target; // The target function of the stub 29 | PtrT addr; 30 | uint8_t *loc; // Writable location of the stub 31 | uint32_t size; // Size in bytes of the stub 32 | }; 33 | 34 | void fixStubHelpers(); 35 | void scanStubs(); 36 | void fixPass1(); 37 | void fixPass2(); 38 | void fixCallsites(); 39 | 40 | void addStubInfo(PtrT sAddr, Provider::SymbolicInfo info); 41 | 42 | Fixer &delegate; 43 | Macho::Context &mCtx; 44 | Provider::ActivityLogger &activity; 45 | std::shared_ptr logger; 46 | Provider::BindInfo

&bindInfo; 47 | Provider::Disassembler &disasm; 48 | Provider::PointerTracker

&ptrTracker; 49 | Provider::Symbolizer &symbolizer; 50 | Provider::SymbolTableTracker

&stTracker; 51 | 52 | SymbolPointerCache &pointerCache; 53 | Arm64Utils &arm64Utils; 54 | 55 | std::map, std::set, 56 | std::less> 57 | reverseStubMap; 58 | 59 | std::list brokenStubs; 60 | }; 61 | 62 | } // namespace DyldExtractor::Converter::Stubs 63 | 64 | #endif // __CONVERTER_STUBS_ARM64FIXER__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Stubs/Arm64Utils.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_STUBS_ARM64UTILS__ 2 | #define __CONVERTER_STUBS_ARM64UTILS__ 3 | 4 | #include 5 | #include 6 | 7 | namespace DyldExtractor::Converter::Stubs { 8 | 9 | template class Arm64Utils { 10 | using P = A::P; 11 | using PtrT = P::PtrT; 12 | using SPtrT = P::SPtrT; 13 | 14 | public: 15 | enum class StubFormat { 16 | // Non optimized stub with a symbol pointer and a stub helper. 17 | StubNormal, 18 | // Optimized stub with a symbol pointer and a stub helper. 19 | StubOptimized, 20 | // Non optimized auth stub with a symbol pointer. 21 | AuthStubNormal, 22 | // Optimized auth stub with a branch to a function. 23 | AuthStubOptimized, 24 | // Non optimized auth stub with a symbol pointer and a resolver. 25 | AuthStubResolver, 26 | // A special stub helper with a branch to a function. 27 | Resolver 28 | }; 29 | 30 | struct ResolverData { 31 | PtrT targetFunc; 32 | PtrT targetPtr; 33 | PtrT size; 34 | }; 35 | 36 | Arm64Utils(const Dyld::Context &dCtx, Provider::Accelerator

&accelerator, 37 | const Provider::PointerTracker

&ptrTracker); 38 | 39 | /// @brief Check if it is a stub binder 40 | /// @param addr Address to the bind, usually start of the __stub_helper sect. 41 | /// @returns If it is or not. 42 | bool isStubBinder(const PtrT addr) const; 43 | 44 | /// @brief Get data for a stub resolver 45 | /// A stub resolver is a special helper that branches to a function that 46 | /// should be in the same image. 47 | /// 48 | /// @param addr The address of the resolver 49 | /// @returns Optional resolver data 50 | std::optional getResolverData(const PtrT addr) const; 51 | 52 | /// @brief Get a stub's target and its format 53 | /// @param addr The address of the stub 54 | /// @returns An optional pair of the stub's target and its format. 55 | std::optional> resolveStub(const PtrT addr) const; 56 | 57 | /// @brief Resolve a stub chain 58 | /// @param addr The address of the first stub. 59 | /// @returns The address to the final target, usually a function but can be 60 | /// addr or an address to a stub if the format is not known. 61 | PtrT resolveStubChain(const PtrT addr); 62 | 63 | /// @brief Resolve a stub chain with extended chain information 64 | /// @param addr The address of the first stub 65 | /// @return A vector of stubs in the chain, the first in the pair is the 66 | /// target of the stub and the second is the format of the stub. 67 | std::vector> 68 | resolveStubChainExtended(const PtrT addr); 69 | 70 | /// @brief Get the offset data of a stub helper. 71 | /// @param addr The address of the stub helper 72 | /// @returns The offset data or nullopt if it's not a regular stub helper. 73 | std::optional getStubHelperData(const PtrT addr) const; 74 | 75 | /// @brief Get the address of the symbol pointer for a normal stub. 76 | /// @param addr The address of the stub 77 | /// @returns The address of the pointer, or nullopt. 78 | std::optional getStubLdrAddr(const PtrT addr) const; 79 | 80 | /// @brief Get the address of the symbol pointer for a normal auth stub. 81 | /// @param addr The address of the stub 82 | /// @returns The address of the pointer, or nullopt. 83 | std::optional getAuthStubLdrAddr(const PtrT addr) const; 84 | 85 | /// @brief Write a normal stub at the location. 86 | /// @param loc Where to write the stub 87 | /// @param stubAddr The address of the stub 88 | /// @param ldrAddr The address for the target load 89 | void writeNormalStub(uint8_t *loc, const PtrT stubAddr, 90 | const PtrT ldrAddr) const; 91 | 92 | /// @brief Write a normal auth stub at the location. 93 | /// @param loc Where to write the stub 94 | /// @param stubAddr The address of the stub 95 | /// @param ldrAddr The address for the target load 96 | void writeNormalAuthStub(uint8_t *loc, const PtrT stubAddr, 97 | const PtrT ldrAddr) const; 98 | 99 | /// @brief Sign extend a number 100 | /// @tparam T The type of the number 101 | /// @tparam B The number of bits 102 | /// @returns The number sign extended. 103 | template static inline T signExtend(const T x) { 104 | struct { 105 | T x : B; 106 | } s; 107 | return s.x = x; 108 | }; 109 | 110 | private: 111 | friend class Provider::Accelerator

; 112 | 113 | const Dyld::Context &dCtx; 114 | Provider::Accelerator

&accelerator; 115 | const Provider::PointerTracker

&ptrTracker; 116 | 117 | using ResolverT = typename std::function(PtrT)>; 118 | std::map stubResolvers; 119 | 120 | std::optional getStubNormalTarget(const PtrT addr) const; 121 | std::optional getStubOptimizedTarget(const PtrT addr) const; 122 | std::optional getAuthStubNormalTarget(const PtrT addr) const; 123 | std::optional getAuthStubOptimizedTarget(const PtrT addr) const; 124 | std::optional getAuthStubResolverTarget(const PtrT addr) const; 125 | std::optional getResolverTarget(const PtrT addr) const; 126 | 127 | static PtrT getLdrOffset(const uint32_t ldrI); 128 | }; 129 | 130 | }; // namespace DyldExtractor::Converter::Stubs 131 | 132 | #endif // __CONVERTER_STUBS_ARM64UTILS__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Stubs/ArmFixer.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_STUBS_ARMFIXER__ 2 | #define __CONVERTER_STUBS_ARMFIXER__ 3 | 4 | #include "ArmUtils.h" 5 | #include "SymbolPointerCache.h" 6 | 7 | namespace DyldExtractor::Converter::Stubs { 8 | 9 | template class Fixer; 10 | 11 | class ArmFixer { 12 | using A = Utils::Arch::arm; 13 | using P = A::P; 14 | using PtrT = P::PtrT; 15 | 16 | using SPointerType = SymbolPointerCache::PointerType; 17 | using AStubFormat = ArmUtils::StubFormat; 18 | 19 | public: 20 | ArmFixer(Fixer &delegate); 21 | 22 | void fix(); 23 | 24 | std::map stubMap; 25 | 26 | private: 27 | struct StubInfo { 28 | AStubFormat format; 29 | PtrT target; // The target function of the stub 30 | PtrT addr; 31 | uint8_t *loc; // Writable location of the stub 32 | }; 33 | 34 | void fixStubHelpers(); 35 | void scanStubs(); 36 | void fixPass1(); 37 | void fixPass2(); 38 | void fixCallsites(); 39 | 40 | void addStubInfo(PtrT sAddr, Provider::SymbolicInfo info); 41 | 42 | Fixer &delegate; 43 | Macho::Context &mCtx; 44 | Provider::ActivityLogger &activity; 45 | std::shared_ptr logger; 46 | Provider::BindInfo

&bindInfo; 47 | Provider::Disassembler &disasm; 48 | Provider::PointerTracker

&ptrTracker; 49 | Provider::Symbolizer &symbolizer; 50 | Provider::SymbolTableTracker

&stTracker; 51 | 52 | SymbolPointerCache &pointerCache; 53 | ArmUtils &armUtils; 54 | 55 | std::map, std::set, 56 | std::less> 57 | reverseStubMap; 58 | 59 | std::list brokenStubs; 60 | }; 61 | 62 | } // namespace DyldExtractor::Converter::Stubs 63 | 64 | #endif // __CONVERTER_STUBS_ARMFIXER__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Stubs/ArmUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "ArmUtils.h" 2 | 3 | using namespace DyldExtractor; 4 | using namespace Converter; 5 | using namespace Stubs; 6 | 7 | ArmUtils::ArmUtils(const Dyld::Context &dCtx, 8 | Provider::Accelerator

&accelerator, 9 | const Provider::PointerTracker

&ptrTracker) 10 | : dCtx(dCtx), accelerator(accelerator), ptrTracker(ptrTracker) { 11 | stubResolvers = { 12 | {StubFormat::normalV4, [this](PtrT a) { return getNormalV4Target(a); }}, 13 | {StubFormat::optimizedV5, 14 | [this](PtrT a) { return getOptimizedV5Target(a); }}, 15 | {StubFormat::resolver, [this](PtrT a) { return getResolverTarget(a); }}}; 16 | } 17 | 18 | std::optional 19 | ArmUtils::isStubBinder(const PtrT addr) const { 20 | /** 21 | * 29d8b2bc 04 c0 2d e5 str r12,[sp,#local_4]! 22 | * 29d8b2c0 10 c0 9f e5 ldr r12,[DAT_29d8b2d8] 23 | * 29d8b2c4 0c c0 8f e0 add r12,pc,r12 24 | * 29d8b2c8 04 c0 2d e5 str r12=>PTR_2ef24004,[sp,#local_8]! 25 | * 29d8b2cc 08 c0 9f e5 ldr r12,[DAT_29d8b2dc] 26 | * 29d8b2d0 0c c0 8f e0 add r12,pc,r12 27 | * 29d8b2d4 00 f0 9c e5 ldr pc=>__nl_symbol_ptr::dyld_stub_binder,[r12,#0x0] 28 | * 29d8b2d8 38 8d 19 05 undefined4 05198D38h -> __dyld_private 29 | * 29d8b2dc 28 8d 19 05 undefined4 05198D28h -> dyld_stub_binder 30 | */ 31 | 32 | auto plainAddr = addr & -4; 33 | const auto p = (const uint32_t *)dCtx.convertAddrP(plainAddr); 34 | if (p == nullptr) { 35 | return std::nullopt; 36 | } 37 | 38 | const auto str = p[0]; 39 | const auto ldr = p[1]; 40 | const auto add = p[2]; 41 | const auto str2 = p[3]; 42 | const auto ldr2 = p[4]; 43 | const auto add2 = p[5]; 44 | const auto ldr3 = p[6]; 45 | if ((str & 0x0E500000) != 0x04000000 || (ldr & 0x0F7F0000) != 0x051F0000 || 46 | (add & 0x0FE00010) != 0x00800000 || (str2 & 0x0E500000) != 0x04000000 || 47 | (ldr2 & 0x0F7F0000) != 0x051F0000 || (add2 & 0x0FE00010) != 0x00800000 || 48 | (ldr3 & 0x0E500000) != 0x04100000) { 49 | return std::nullopt; 50 | } else { 51 | const auto privPtrOffset = p[7]; 52 | return StubBinderInfo{plainAddr + 8 + 8 + privPtrOffset, 0x24}; 53 | } 54 | } 55 | 56 | std::optional 57 | ArmUtils::getStubHelperData(const PtrT addr) const { 58 | /** 59 | * 29d8b2f8 00 c0 9f e5 ldr r12,[DAT_29d8b300] 60 | * 29d8b2fc ee ff ff ea b stub_helpers 61 | * 29d8b300 39 00 00 00 undefined4 00000039h 62 | */ 63 | 64 | const auto p = (const uint32_t *)dCtx.convertAddrP(addr & -4); 65 | if (p == nullptr) { 66 | return std::nullopt; 67 | } 68 | 69 | const auto ldr = p[0]; 70 | const auto b = p[1]; 71 | if (ldr != 0x0E59FC000 || (b & 0x0F000000) != 0x0A000000) { 72 | return std::nullopt; 73 | } 74 | 75 | return p[2]; 76 | } 77 | 78 | std::optional 79 | ArmUtils::getResolverData(const PtrT addr) const { 80 | /** 81 | * 20b159f8 0f402de9 stmdb sp!,{r0 r1 r2 r3 lr} 82 | * 20b159fc d858fefa blx _vDSP_FFTCSBFirst4S 83 | * 20b15a00 10c09fe5 ldr r12,[DAT_20b15a18] 84 | * 20b15a04 0cc08fe0 add r12,pc,r12 85 | * 20b15a08 00008ce5 str r0,[r12,#0x0]=>__la_symbol_ptr:: 86 | * 20b15a0c 00c0a0e1 cpy r12,r0 87 | * 20b15a10 0f40bde8 ldmia sp!,{r0 r1 r2 r3 lr}=>local_14 88 | * 20b15a14 1cff2fe1 bx r12 89 | * 20b15a18 7877c20c UNK 0CC27778h 90 | */ 91 | 92 | auto plainAddr = addr & -4; 93 | const auto p = (const uint32_t *)dCtx.convertAddrP(plainAddr); 94 | if (p == nullptr) { 95 | return std::nullopt; 96 | } 97 | 98 | const auto stmdb = p[0]; 99 | const auto blx = p[1]; 100 | const auto ldr = p[2]; 101 | const auto add = p[3]; 102 | const auto str = p[4]; 103 | const auto cpy = p[5]; 104 | const auto ldmia = p[6]; 105 | const auto bx = p[7]; 106 | if ((stmdb & 0x0FD00000) != 0x09000000 || (blx & 0xFE000000) != 0xFA000000 || 107 | (ldr & 0x0E500000) != 0x04100000 || (add & 0x0FE00010) != 0x00800000 || 108 | (str & 0x0E500000) != 0x04000000 || (cpy & 0x0FEF0FF0) != 0x01A00000 || 109 | (ldmia & 0x0FD00000) != 0x08900000 || (bx & 0x0FFFFFF0) != 0x012FFF10) { 110 | return std::nullopt; 111 | } 112 | const auto resolverData = (int32_t)p[8]; 113 | 114 | // Get target function 115 | PtrT targetFunc; 116 | { 117 | const uint32_t imm24 = blx & 0x00FFFFFF; 118 | const bool H = (blx & 0x01000000) >> 24; 119 | const int32_t imm32 = signExtend((imm24 << 2) | (H << 1)); 120 | targetFunc = plainAddr + 4 + 8 + imm32; 121 | } 122 | 123 | PtrT targetPtr = plainAddr + 12 + 8 + resolverData; 124 | PtrT size = 0x24; 125 | return ResolverData{targetFunc, targetPtr, size}; 126 | } 127 | 128 | ArmUtils::PtrT ArmUtils::resolveStubChain(const PtrT addr) { 129 | if (accelerator.armResolvedChains.contains(addr)) { 130 | return accelerator.armResolvedChains[addr]; 131 | } 132 | 133 | PtrT target = addr; 134 | while (true) { 135 | if (auto stubData = resolveStub(target); stubData != std::nullopt) { 136 | target = stubData->first; 137 | } else { 138 | break; 139 | } 140 | } 141 | 142 | accelerator.armResolvedChains[addr] = target; 143 | 144 | return target; 145 | } 146 | 147 | std::optional> 148 | ArmUtils::resolveStub(const PtrT addr) const { 149 | for (auto &[format, resolver] : stubResolvers) { 150 | if (auto res = resolver(addr); res != std::nullopt) { 151 | return std::make_pair(*res, format); 152 | } 153 | } 154 | 155 | return std::nullopt; 156 | } 157 | 158 | std::optional 159 | ArmUtils::getNormalV4LdrAddr(const PtrT addr) const { 160 | // Reference getNormalV4Target 161 | 162 | auto plainAddr = addr & -4; 163 | const auto p = (const uint32_t *)dCtx.convertAddrP(plainAddr); 164 | if (p == nullptr) { 165 | return std::nullopt; 166 | } 167 | 168 | const auto ldr = p[0]; 169 | const auto add = p[1]; 170 | const auto ldr2 = p[2]; 171 | if (ldr != 0xE59FC004 || add != 0xE08FC00C || ldr2 != 0xE59CF000) { 172 | return std::nullopt; 173 | } 174 | 175 | const auto stubData = p[3]; 176 | return plainAddr + 12 + stubData; 177 | } 178 | 179 | void ArmUtils::writeNormalV4Stub(uint8_t *loc, const PtrT stubAddr, 180 | const PtrT ldrAddr) const { 181 | /** 182 | * ldr ip, pc + 12 183 | * add ip, pc, ip 184 | * ldr pc, [ip] 185 | * stub data 186 | */ 187 | 188 | auto p = (uint32_t *)loc; 189 | p[0] = 0xE59FC004; 190 | p[1] = 0xE08FC00C; 191 | p[2] = 0xE59CF000; 192 | *(int32_t *)(p + 3) = (int32_t)ldrAddr - stubAddr - 12; 193 | } 194 | 195 | std::optional 196 | ArmUtils::getNormalV4Target(const PtrT addr) const { 197 | /** 198 | * 04 c0 9f e5 ldr r12,[DAT_00007f18] 199 | * 0c c0 8f e0 add r12,pc,r12 200 | * 00 f0 9c e5 ldr pc=>__stub_helper::_NSLog,[r12,#0x0] 201 | * f0 00 00 00 UNK 000000F0h 202 | */ 203 | 204 | auto plainAddr = addr & -4; 205 | const auto p = (const uint32_t *)dCtx.convertAddrP(plainAddr); 206 | if (p == nullptr) { 207 | return std::nullopt; 208 | } 209 | 210 | const auto ldr = p[0]; 211 | const auto add = p[1]; 212 | const auto ldr2 = p[2]; 213 | if (ldr != 0xE59FC004 || add != 0xE08FC00C || ldr2 != 0xE59CF000) { 214 | return std::nullopt; 215 | } 216 | 217 | const auto stubData = p[3]; 218 | return ptrTracker.slideP(plainAddr + 12 + stubData); 219 | } 220 | 221 | std::optional 222 | ArmUtils::getOptimizedV5Target(const PtrT addr) const { 223 | /** 224 | * 576a3a28 00 c0 9f e5 ldr r12,[DAT_576a3a30] 225 | * 576a3a2c 0c f0 8f e0 add pc=>LAB_4692f7c4,pc,r12 226 | * 576a3a30 91 bd 28 ef UNK EF28BD91h 227 | * 576a3a34 fe de ff e7 udf #0xfdee TRAP 228 | */ 229 | 230 | auto plainAddr = addr & -4; 231 | const auto p = (const uint32_t *)dCtx.convertAddrP(plainAddr); 232 | if (p == nullptr) { 233 | return std::nullopt; 234 | } 235 | 236 | const auto ldr = p[0]; 237 | const auto add = p[1]; 238 | const auto trap = p[3]; 239 | if (ldr != 0xE59FC000 || add != 0xE08FF00C || trap != 0xE7FFDEFE) { 240 | return std::nullopt; 241 | } 242 | 243 | const auto stubData = p[2]; 244 | 245 | return (plainAddr + 12 + stubData); 246 | } 247 | 248 | std::optional 249 | ArmUtils::getResolverTarget(const PtrT addr) const { 250 | if (const auto res = getResolverData(addr); res) { 251 | return res->targetFunc; 252 | } else { 253 | return std::nullopt; 254 | } 255 | } -------------------------------------------------------------------------------- /DyldExtractor/Converter/Stubs/ArmUtils.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_STUBS_ARMUTILS__ 2 | #define __CONVERTER_STUBS_ARMUTILS__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace DyldExtractor::Converter::Stubs { 9 | 10 | class ArmUtils { 11 | using A = Utils::Arch::arm; 12 | using P = Utils::Arch::Pointer32; 13 | using PtrT = P::PtrT; 14 | 15 | public: 16 | enum class StubFormat { 17 | normalV4, // __picsymbolstub4, not optimized 18 | optimizedV5, // __picsymbolstub5, optimized 19 | resolver // __stub_helper 20 | }; 21 | 22 | struct ResolverData { 23 | PtrT targetFunc; 24 | PtrT targetPtr; 25 | PtrT size; 26 | }; 27 | 28 | struct StubBinderInfo { 29 | PtrT privatePtr; 30 | PtrT size; 31 | }; 32 | 33 | ArmUtils(const Dyld::Context &dCtx, Provider::Accelerator

&accelerator, 34 | const Provider::PointerTracker

&ptrTracker); 35 | 36 | /// @brief Sign extend a number 37 | /// @tparam T The type of the number 38 | /// @tparam B The number of bits 39 | /// @returns The number sign extended. 40 | template static inline T signExtend(const T x) { 41 | struct { 42 | T x : B; 43 | } s; 44 | return s.x = x; 45 | }; 46 | 47 | /// @brief Check if it is a stub binder 48 | /// @param addr Address to the bind, usually start of the __stub_helper sect. 49 | /// @returns If it is or not. 50 | std::optional isStubBinder(const PtrT addr) const; 51 | 52 | /// @brief Get the stub helper data 53 | /// @param addr The address of the stub helper 54 | /// @returns The stub data, or nullopt if the format is incorrect. 55 | std::optional getStubHelperData(const PtrT addr) const; 56 | 57 | /// @brief Get resolver data 58 | /// Resolver data is a special helper that should branch to a function within 59 | /// its own image. 60 | /// @param addr the address of the stub helper 61 | /// @returns Optional resolver data 62 | std::optional getResolverData(const PtrT addr) const; 63 | 64 | /// @brief Resolve a stub chain 65 | /// @param addr The address of the beginning of the chain 66 | /// @returns The last known node of the chain. Can fail to properly resolve 67 | /// if the format is not known. 68 | PtrT resolveStubChain(const PtrT addr); 69 | 70 | /// @brief Get a stub's target and its format 71 | /// @param addr The address of the stub 72 | /// @returns An optional pair of the stub's target and its format. 73 | std::optional> resolveStub(const PtrT addr) const; 74 | 75 | /// @brief Get the ldr address of a normal V4 stub 76 | /// @param addr The address of the stub 77 | /// @returns The target address or nullopt 78 | std::optional getNormalV4LdrAddr(const PtrT addr) const; 79 | 80 | /// @brief Write a normal V4 stub at the location. 81 | /// @param loc Where to write the stub 82 | /// @param stubAddr The address of the stub 83 | /// @param ldrAddr The address for the target load 84 | void writeNormalV4Stub(uint8_t *loc, const PtrT stubAddr, 85 | const PtrT ldrAddr) const; 86 | 87 | private: 88 | std::optional getNormalV4Target(const PtrT addr) const; 89 | std::optional getOptimizedV5Target(const PtrT addr) const; 90 | std::optional getResolverTarget(const PtrT addr) const; 91 | 92 | const Dyld::Context &dCtx; 93 | Provider::Accelerator

&accelerator; 94 | const Provider::PointerTracker

&ptrTracker; 95 | 96 | using ResolverT = typename std::function(PtrT)>; 97 | std::map stubResolvers; 98 | }; 99 | 100 | } // namespace DyldExtractor::Converter::Stubs 101 | 102 | #endif // __CONVERTER_STUBS_ARMUTILS__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Stubs/Fixer.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_STUBS_FIXER__ 2 | #define __CONVERTER_STUBS_FIXER__ 3 | 4 | #include "Arm64Fixer.h" 5 | #include "ArmFixer.h" 6 | #include "SymbolPointerCache.h" 7 | #include 8 | 9 | namespace DyldExtractor::Converter::Stubs { 10 | 11 | template class Fixer { 12 | friend class SymbolPointerCache; 13 | friend class Arm64Fixer; 14 | friend class ArmFixer; 15 | using P = A::P; 16 | using PtrT = P::PtrT; 17 | using LETrackerTag = Provider::LinkeditTracker

::Tag; 18 | using STSymbolType = Provider::SymbolTableTracker

::SymbolType; 19 | 20 | public: 21 | Fixer(Utils::ExtractionContext &eCtx); 22 | void fix(); 23 | 24 | private: 25 | void checkIndirectEntries(); 26 | void fixIndirectEntries(); 27 | void bindPointers(); 28 | 29 | bool isInCodeRegions(PtrT addr); 30 | 31 | Utils::ExtractionContext &eCtx; 32 | const Dyld::Context &dCtx; 33 | Macho::Context &mCtx; 34 | Provider::Accelerator

&accelerator; 35 | Provider::ActivityLogger &activity; 36 | std::shared_ptr logger; 37 | Provider::BindInfo

&bindInfo; 38 | Provider::Disassembler &disasm; 39 | Provider::PointerTracker

&ptrTracker; 40 | Provider::Symbolizer &symbolizer; 41 | Provider::LinkeditTracker

&leTracker; 42 | Provider::SymbolTableTracker

&stTracker; 43 | 44 | SymbolPointerCache ptrCache; 45 | 46 | std::optional> arm64Utils; 47 | std::optional> arm64Fixer; 48 | 49 | std::optional armUtils; 50 | std::optional armFixer; 51 | }; 52 | 53 | } // namespace DyldExtractor::Converter::Stubs 54 | 55 | #endif // __CONVERTER_STUBS_FIXER__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Stubs/Stubs.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_STUBS_STUBS__ 2 | #define __CONVERTER_STUBS_STUBS__ 3 | 4 | namespace DyldExtractor::Converter { 5 | 6 | template void fixStubs(Utils::ExtractionContext &eCtx); 7 | 8 | } // namespace DyldExtractor::Converter 9 | 10 | #endif // __CONVERTER_STUBS_STUBS__ -------------------------------------------------------------------------------- /DyldExtractor/Converter/Stubs/SymbolPointerCache.cpp: -------------------------------------------------------------------------------- 1 | #include "SymbolPointerCache.h" 2 | #include 3 | 4 | using namespace DyldExtractor; 5 | using namespace Converter::Stubs; 6 | 7 | template 8 | SymbolPointerCache::SymbolPointerCache( 9 | Macho::Context &mCtx, Provider::ActivityLogger &activity, 10 | std::shared_ptr logger, 11 | const Provider::PointerTracker

&ptrTracker, 12 | const Provider::Symbolizer &symbolizer, 13 | const Provider::SymbolTableTracker

&stTracker, 14 | std::optional> &arm64Utils, std::optional &armUtils) 15 | : mCtx(mCtx), logger(logger), activity(activity), ptrTracker(ptrTracker), 16 | symbolizer(symbolizer), stTracker(stTracker), arm64Utils(arm64Utils), 17 | armUtils(armUtils) {} 18 | 19 | template 20 | SymbolPointerCache::PointerType 21 | SymbolPointerCache::getPointerType(const auto sect) const { 22 | const auto sectType = sect->flags & SECTION_TYPE; 23 | const bool isAuth = 24 | strstr(sect->segname, "AUTH") || strstr(sect->sectname, "auth"); 25 | 26 | if (sectType == S_LAZY_SYMBOL_POINTERS) { 27 | if (isAuth) { 28 | SPDLOG_LOGGER_ERROR(logger, "Unknown section type combination."); 29 | } else { 30 | return PointerType::lazy; 31 | } 32 | } else if (sectType == S_NON_LAZY_SYMBOL_POINTERS) { 33 | if (isAuth) { 34 | return PointerType::auth; 35 | } else { 36 | return PointerType::normal; 37 | } 38 | } else { 39 | SPDLOG_LOGGER_ERROR(logger, "Unexpected section type {:#x}.", sectType); 40 | } 41 | 42 | return PointerType::normal; 43 | } 44 | 45 | template void SymbolPointerCache::scanPointers() { 46 | activity.update(std::nullopt, "Scanning Symbol Pointers"); 47 | 48 | mCtx.enumerateSections( 49 | [](auto seg, auto sect) { 50 | return (sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS || 51 | (sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS; 52 | }, 53 | [this](auto seg, auto sect) { 54 | auto pType = getPointerType(sect); 55 | 56 | uint32_t indirectI = sect->reserved1; 57 | for (PtrT pAddr = sect->addr; pAddr < sect->addr + sect->size; 58 | pAddr += sizeof(PtrT), indirectI++) { 59 | activity.update(); 60 | 61 | std::set symbols; 62 | 63 | // Check indirect entry 64 | if (indirectI >= stTracker.indirectSyms.size()) { 65 | SPDLOG_LOGGER_WARN(logger, 66 | "Unable to symbolize stub via indirect symbols " 67 | "as the index overruns the entries."); 68 | } else { 69 | const auto &[strIt, entry] = 70 | stTracker.getSymbol(stTracker.indirectSyms.at(indirectI)); 71 | uint64_t ordinal = GET_LIBRARY_ORDINAL(entry.n_desc); 72 | symbols.insert({*strIt, ordinal, std::nullopt}); 73 | } 74 | 75 | // The pointer's target function 76 | if (const auto pTarget = ptrTracker.slideP(pAddr); pTarget) { 77 | PtrT pFunc; 78 | if constexpr (std::is_same_v || 79 | std::is_same_v) { 80 | pFunc = arm64Utils->resolveStubChain(pTarget); 81 | } else if constexpr (std::is_same_v) { 82 | pFunc = armUtils->resolveStubChain(pTarget); 83 | } 84 | if (const auto set = symbolizer.symbolizeAddr(pFunc & -4); set) { 85 | symbols.insert(set->symbols.begin(), set->symbols.end()); 86 | } 87 | } 88 | 89 | if (!symbols.empty()) { 90 | addPointerInfo( 91 | pType, pAddr, 92 | Provider::SymbolicInfo(std::move(symbols), 93 | Provider::SymbolicInfo::Encoding::None)); 94 | } else { 95 | // Add to unnamed 96 | switch (pType) { 97 | case PointerType::normal: 98 | unnamed.normal.insert(pAddr); 99 | break; 100 | 101 | case PointerType::lazy: 102 | unnamed.lazy.insert(pAddr); 103 | break; 104 | 105 | case PointerType::auth: 106 | unnamed.auth.insert(pAddr); 107 | break; 108 | 109 | default: 110 | Utils::unreachable(); 111 | } 112 | } 113 | } 114 | 115 | return true; 116 | }); 117 | } 118 | 119 | template 120 | bool SymbolPointerCache::isAvailable(PointerType pType, PtrT addr) { 121 | switch (pType) { 122 | case PointerType::normal: 123 | return ptr.normal.contains(addr) && !used.normal.contains(addr); 124 | break; 125 | 126 | case PointerType::lazy: 127 | return ptr.lazy.contains(addr) && !used.lazy.contains(addr); 128 | break; 129 | 130 | case PointerType::auth: 131 | return ptr.auth.contains(addr) && !used.auth.contains(addr); 132 | break; 133 | 134 | default: 135 | Utils::unreachable(); 136 | } 137 | } 138 | 139 | template 140 | void SymbolPointerCache::namePointer(PointerType pType, PtrT addr, 141 | const Provider::SymbolicInfo &info) { 142 | switch (pType) { 143 | case PointerType::normal: 144 | unnamed.normal.erase(addr); 145 | break; 146 | case PointerType::lazy: 147 | unnamed.lazy.erase(addr); 148 | break; 149 | case PointerType::auth: 150 | unnamed.auth.erase(addr); 151 | break; 152 | 153 | default: 154 | Utils::unreachable(); 155 | } 156 | 157 | addPointerInfo(pType, addr, info); 158 | } 159 | 160 | template 161 | const Provider::SymbolicInfo * 162 | SymbolPointerCache::getPointerInfo(PointerType pType, PtrT addr) const { 163 | switch (pType) { 164 | case PointerType::normal: 165 | if (ptr.normal.contains(addr)) { 166 | return ptr.normal.at(addr).get(); 167 | } else { 168 | return nullptr; 169 | } 170 | 171 | case PointerType::lazy: 172 | if (ptr.lazy.contains(addr)) { 173 | return ptr.lazy.at(addr).get(); 174 | } else { 175 | return nullptr; 176 | } 177 | 178 | case PointerType::auth: 179 | if (ptr.auth.contains(addr)) { 180 | return ptr.auth.at(addr).get(); 181 | } else { 182 | return nullptr; 183 | } 184 | 185 | default: 186 | Utils::unreachable(); 187 | } 188 | } 189 | 190 | template 191 | void SymbolPointerCache::addPointerInfo(PointerType pType, PtrT pAddr, 192 | const Provider::SymbolicInfo &info) { 193 | PtrMapT *pointers; 194 | ReverseMapT *reversePtrs; 195 | switch (pType) { 196 | case PointerType::normal: 197 | pointers = &ptr.normal; 198 | reversePtrs = &reverse.normal; 199 | break; 200 | 201 | case PointerType::lazy: 202 | pointers = &ptr.lazy; 203 | reversePtrs = &reverse.lazy; 204 | break; 205 | 206 | case PointerType::auth: 207 | pointers = &ptr.auth; 208 | reversePtrs = &reverse.auth; 209 | break; 210 | 211 | default: 212 | Utils::unreachable(); 213 | } 214 | 215 | // Add to normal cache 216 | Provider::SymbolicInfo *newInfo; 217 | if (pointers->contains(pAddr)) { 218 | newInfo = pointers->at(pAddr).get(); 219 | newInfo->symbols.insert(info.symbols.begin(), info.symbols.end()); 220 | } else { 221 | newInfo = 222 | pointers->emplace(pAddr, std::make_shared(info)) 223 | .first->second.get(); 224 | } 225 | 226 | // add to reverse cache 227 | for (auto &sym : newInfo->symbols) { 228 | (*reversePtrs)[sym.name].insert(pAddr); 229 | } 230 | } 231 | 232 | template class SymbolPointerCache; 233 | template class SymbolPointerCache; 234 | template class SymbolPointerCache; 235 | -------------------------------------------------------------------------------- /DyldExtractor/Converter/Stubs/SymbolPointerCache.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONVERTER_STUBS_SYMBOLPOINTERCACHE__ 2 | #define __CONVERTER_STUBS_SYMBOLPOINTERCACHE__ 3 | 4 | #include "Arm64Utils.h" 5 | #include "ArmUtils.h" 6 | #include 7 | #include 8 | 9 | namespace DyldExtractor::Converter::Stubs { 10 | 11 | template class SymbolPointerCache { 12 | using P = A::P; 13 | using PtrT = P::PtrT; 14 | 15 | public: 16 | enum class PointerType { 17 | normal, // Commonly in __got 18 | lazy, // Commonly in __la_symbol_ptr 19 | auth // Commonly in __auth_got 20 | }; 21 | 22 | SymbolPointerCache(Macho::Context &mCtx, 23 | Provider::ActivityLogger &activity, 24 | std::shared_ptr logger, 25 | const Provider::PointerTracker

&ptrTracker, 26 | const Provider::Symbolizer &symbolizer, 27 | const Provider::SymbolTableTracker

&stTracker, 28 | std::optional> &arm64Utils, 29 | std::optional &armUtils); 30 | PointerType getPointerType(const auto sect) const; 31 | void scanPointers(); 32 | 33 | /// @brief Check if a pointer is free to use 34 | bool isAvailable(PointerType pType, PtrT addr); 35 | /// @brief Provide symbolic info for a unnamed pointer 36 | void namePointer(PointerType pType, PtrT addr, 37 | const Provider::SymbolicInfo &info); 38 | const Provider::SymbolicInfo *getPointerInfo(PointerType pType, 39 | PtrT addr) const; 40 | 41 | /// TODO: Add weak type 42 | using PtrMapT = std::map>; 43 | struct { 44 | PtrMapT normal; 45 | PtrMapT lazy; 46 | PtrMapT auth; 47 | } ptr; 48 | 49 | using ReverseMapT = std::map, 50 | std::set, std::less>; 51 | struct { 52 | ReverseMapT normal; 53 | ReverseMapT lazy; 54 | ReverseMapT auth; 55 | } reverse; 56 | 57 | struct { 58 | std::set normal; 59 | std::set lazy; 60 | std::set auth; 61 | } unnamed; 62 | 63 | struct { 64 | std::set normal; 65 | std::set lazy; 66 | std::set auth; 67 | } used; 68 | 69 | private: 70 | void addPointerInfo(PointerType pType, PtrT pAddr, 71 | const Provider::SymbolicInfo &info); 72 | 73 | Macho::Context &mCtx; 74 | Provider::ActivityLogger &activity; 75 | std::shared_ptr logger; 76 | const Provider::PointerTracker

&ptrTracker; 77 | const Provider::Symbolizer &symbolizer; 78 | const Provider::SymbolTableTracker

&stTracker; 79 | 80 | std::optional> &arm64Utils; 81 | std::optional &armUtils; 82 | }; 83 | 84 | } // namespace DyldExtractor::Converter::Stubs 85 | 86 | #endif // __CONVERTER_STUBS_SYMBOLPOINTERCACHE__ -------------------------------------------------------------------------------- /DyldExtractor/Dyld/Context.cpp: -------------------------------------------------------------------------------- 1 | #include "Context.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace DyldExtractor; 8 | using namespace Dyld; 9 | 10 | Context::Context(fs::path sharedCachePath, const uint8_t *subCacheUUID) 11 | : cachePath(sharedCachePath), cacheOpen(true) { 12 | cacheFile.open(cachePath.string(), bio::mapped_file::mapmode::readonly); 13 | file = (uint8_t *)cacheFile.const_data(); 14 | 15 | preflightCache(subCacheUUID); 16 | 17 | if (!subCacheUUID) { 18 | // open subcaches if there are any 19 | if (!headerContainsMember( 20 | offsetof(dyld_cache_header, subCacheArrayCount))) { 21 | return; 22 | } 23 | 24 | bool _usesNewerSubCacheInfo = 25 | headerContainsMember(offsetof(dyld_cache_header, cacheSubType)); 26 | const std::string pathBase = sharedCachePath.string(); 27 | for (uint32_t i = 0; i < header->subCacheArrayCount; i++) { 28 | std::string fullPath; 29 | const uint8_t *subCacheUUID; 30 | if (_usesNewerSubCacheInfo) { 31 | auto subCacheInfo = 32 | (dyld_subcache_entry *)(file + header->subCacheArrayOffset) + i; 33 | subCacheUUID = subCacheInfo->uuid; 34 | fullPath = pathBase + std::string(subCacheInfo->fileSuffix); 35 | } else { 36 | auto subCacheInfo = 37 | (dyld_subcache_entry_v1 *)(file + header->subCacheArrayOffset) + i; 38 | subCacheUUID = subCacheInfo->uuid; 39 | fullPath = pathBase + fmt::format(".{}", i + 1); 40 | } 41 | subcaches.emplace_back(fullPath, subCacheUUID); 42 | } 43 | 44 | // symbols cache 45 | if (headerContainsMember(offsetof(dyld_cache_header, symbolFileUUID))) { 46 | // Check for null uuid 47 | uint8_t summary = 0; 48 | for (int i = 0; i < 16; i++) { 49 | summary |= header->symbolFileUUID[i]; 50 | } 51 | if (summary == 0) { 52 | return; 53 | } 54 | 55 | subcaches.emplace_back(pathBase + ".symbols", header->symbolFileUUID); 56 | } 57 | } 58 | } 59 | 60 | Context::~Context() { 61 | if (cacheOpen && cacheFile.is_open()) { 62 | cacheFile.close(); 63 | cacheOpen = false; 64 | } 65 | } 66 | 67 | Context::Context(Context &&other) 68 | : file(other.file), header(other.header), 69 | cacheFile(std::move(other.cacheFile)), 70 | cachePath(std::move(other.cachePath)), cacheOpen(other.cacheOpen), 71 | subcaches(std::move(other.subcaches)), 72 | mappings(std::move(other.mappings)) { 73 | other.file = nullptr; 74 | other.header = nullptr; 75 | other.cacheOpen = false; 76 | } 77 | 78 | Context &Context::operator=(Context &&other) { 79 | this->file = other.file; 80 | this->header = other.header; 81 | this->cacheOpen = other.cacheOpen; 82 | 83 | this->cacheFile = std::move(other.cacheFile); 84 | this->cachePath = std::move(other.cachePath); 85 | this->subcaches = std::move(other.subcaches); 86 | this->mappings = std::move(other.mappings); 87 | 88 | other.file = nullptr; 89 | other.header = nullptr; 90 | other.cacheOpen = false; 91 | 92 | return *this; 93 | } 94 | 95 | std::pair Context::convertAddr(uint64_t addr) const { 96 | for (auto const &mapping : mappings) { 97 | if (addr >= mapping->address && addr < mapping->address + mapping->size) { 98 | return std::make_pair((addr - mapping->address) + mapping->fileOffset, 99 | this); 100 | } 101 | } 102 | 103 | for (auto const &subcache : subcaches) { 104 | auto convert = subcache.convertAddr(addr); 105 | if (convert.second != nullptr) { 106 | return convert; 107 | } 108 | } 109 | 110 | return std::make_pair(0, nullptr); 111 | } 112 | 113 | const uint8_t *Context::convertAddrP(uint64_t addr) const { 114 | auto [offset, ctx] = convertAddr(addr); 115 | return ctx ? ctx->file + offset : nullptr; 116 | } 117 | 118 | bool Context::headerContainsMember(std::size_t memberOffset) const { 119 | // Use mapping offset as the cutoff point. 120 | return memberOffset < header->mappingOffset; 121 | } 122 | 123 | template 124 | Macho::Context 125 | Context::createMachoCtx(const dyld_cache_image_info *imageInfo) const { 126 | auto getMappings = [](std::vector info) { 127 | std::vector mappings(info.size()); 128 | for (auto i : info) { 129 | mappings.emplace_back(i); 130 | } 131 | return mappings; 132 | }; 133 | 134 | auto [imageOffset, mainCache] = convertAddr(imageInfo->address); 135 | auto mainMappings = getMappings(mainCache->mappings); 136 | 137 | if constexpr (ro) { 138 | // Make a read only macho context with the files already open 139 | std::vector>> 140 | subFiles; 141 | subFiles.reserve(subcaches.size()); 142 | 143 | // Add this cache if necessary 144 | if (file != mainCache->file) { 145 | subFiles.emplace_back(cacheFile, getMappings(mappings)); 146 | } 147 | 148 | // Add subcaches 149 | for (auto &cache : subcaches) { 150 | if (cache.file != mainCache->file) { 151 | subFiles.emplace_back(cache.cacheFile, getMappings(cache.mappings)); 152 | } 153 | } 154 | 155 | return Macho::Context(imageOffset, mainCache->cacheFile, 156 | mainMappings, subFiles); 157 | } else { 158 | // Make a writable macho context by giving the paths 159 | std::vector>> subFiles; 160 | subFiles.reserve(subcaches.size()); 161 | 162 | // Add this cache if necessary 163 | if (file != mainCache->file) { 164 | subFiles.emplace_back(cachePath, getMappings(mappings)); 165 | } 166 | 167 | // Add subcaches 168 | for (auto &cache : subcaches) { 169 | if (cache.file != mainCache->file) { 170 | subFiles.emplace_back(cache.cachePath, getMappings(cache.mappings)); 171 | } 172 | } 173 | 174 | return Macho::Context(imageOffset, mainCache->cachePath, 175 | mainMappings, subFiles); 176 | } 177 | } 178 | 179 | template Macho::Context 180 | Context::createMachoCtx( 181 | const dyld_cache_image_info *imageInfo) const; 182 | template Macho::Context 183 | Context::createMachoCtx( 184 | const dyld_cache_image_info *imageInfo) const; 185 | template Macho::Context 186 | Context::createMachoCtx( 187 | const dyld_cache_image_info *imageInfo) const; 188 | template Macho::Context 189 | Context::createMachoCtx( 190 | const dyld_cache_image_info *imageInfo) const; 191 | 192 | const Context *Context::getSymbolsCache() const { 193 | if (!subcaches.size()) { 194 | return this; 195 | } 196 | 197 | for (auto &cache : subcaches) { 198 | if (memcmp(header->symbolFileUUID, cache.header->uuid, 16) == 0) { 199 | return &cache; 200 | } 201 | } 202 | 203 | return nullptr; 204 | } 205 | 206 | void Context::preflightCache(const uint8_t *subCacheUUID) { 207 | // validate cache 208 | if (cacheFile.size() < sizeof(dyld_cache_header)) { 209 | throw std::invalid_argument("Cache file is too small."); 210 | } 211 | 212 | header = (dyld_cache_header *)file; 213 | 214 | if (memcmp("dyld", header->magic, 4)) { 215 | throw std::invalid_argument("Magic does not start with dyld."); 216 | } 217 | if (subCacheUUID) { 218 | if (memcmp(subCacheUUID, header->uuid, 16) != 0) { 219 | throw std::invalid_argument("Subcache UUID Mismatch."); 220 | } 221 | } 222 | 223 | // get additional info 224 | mappings.reserve(header->mappingCount); 225 | for (uint32_t i = 0; i < header->mappingCount; i++) { 226 | mappings.emplace_back( 227 | (dyld_cache_mapping_info *)(file + header->mappingOffset + 228 | (i * sizeof(dyld_cache_mapping_info)))); 229 | } 230 | 231 | bool usesNewerImages = 232 | headerContainsMember(offsetof(dyld_cache_header, imagesOffset)); 233 | uint32_t imagesOffset = 234 | usesNewerImages ? header->imagesOffset : header->imagesOffsetOld; 235 | uint32_t imagesCount = 236 | usesNewerImages ? header->imagesCount : header->imagesCountOld; 237 | images.reserve(imagesCount); 238 | for (uint32_t i = 0; i < imagesCount; i++) { 239 | images.emplace_back( 240 | (dyld_cache_image_info *)(file + imagesOffset + 241 | (i * sizeof(dyld_cache_image_info)))); 242 | } 243 | } -------------------------------------------------------------------------------- /DyldExtractor/Dyld/Context.h: -------------------------------------------------------------------------------- 1 | #ifndef __DYLD_CONTEXT__ 2 | #define __DYLD_CONTEXT__ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace DyldExtractor::Dyld { 11 | 12 | namespace bio = boost::iostreams; 13 | namespace fs = std::filesystem; 14 | 15 | class Context { 16 | public: 17 | const uint8_t *file; 18 | const dyld_cache_header *header; 19 | std::vector images; 20 | std::vector subcaches; 21 | 22 | Context(fs::path sharedCachePath, const uint8_t *subCacheUUID = nullptr); 23 | ~Context(); 24 | Context(const Context &other) = delete; 25 | Context(Context &&other); 26 | Context &operator=(const Context &other) = delete; 27 | Context &operator=(Context &&other); 28 | 29 | /// @brief Convert a vmaddr to it's file offset. 30 | /// 31 | /// If the offset could not be found, a pair with 0 and 32 | /// nullptr will be returned. 33 | /// 34 | /// @param addr The virtual address to convert. 35 | /// @returns A pair of the file offset and its Context. 36 | std::pair convertAddr(uint64_t addr) const; 37 | 38 | /// @brief Convert a vmaddr to it's file offset. 39 | /// 40 | /// If the offset could not be found, nullptr will be 41 | /// returned. 42 | /// 43 | /// @param addr The virtual address to convert. 44 | /// @returns A file based pointer to the address 45 | const uint8_t *convertAddrP(uint64_t addr) const; 46 | 47 | /// @brief Determine if a member is contained in the header 48 | /// @param memberOffset The offset to the member. 49 | /// @returns A boolean on whether the header contains the member. 50 | bool headerContainsMember(std::size_t memberOffset) const; 51 | 52 | /// @brief Create a macho context 53 | /// 54 | /// Use the template boolean to set if the macho has readonly access or 55 | /// private access. 56 | /// 57 | /// @param imageInfo The image info of the MachO file. 58 | template 59 | Macho::Context 60 | createMachoCtx(const dyld_cache_image_info *imageInfo) const; 61 | 62 | /// @brief Get the cache file for local symbols 63 | const Context *getSymbolsCache() const; 64 | 65 | private: 66 | bio::mapped_file cacheFile; 67 | fs::path cachePath; 68 | // False when the cacheFile is not constructed, closed, or moved. 69 | bool cacheOpen = false; 70 | 71 | std::vector mappings; 72 | 73 | void preflightCache(const uint8_t *subCacheUUID = nullptr); 74 | }; 75 | 76 | }; // namespace DyldExtractor::Dyld 77 | 78 | #endif // __DYLD_CONTEXT__ -------------------------------------------------------------------------------- /DyldExtractor/Macho/Context.cpp: -------------------------------------------------------------------------------- 1 | #include "Context.h" 2 | 3 | #include 4 | 5 | using namespace DyldExtractor; 6 | using namespace Macho; 7 | 8 | MappingInfo::MappingInfo(const dyld_cache_mapping_info *info) 9 | : address(info->address), size(info->size), fileOffset(info->fileOffset) {} 10 | 11 | template 12 | SegmentContext::SegmentContext(SegmentCommandT *segment) 13 | : command(segment) { 14 | auto *sectStart = 15 | (typename c_const::T *)segment + sizeof(SegmentCommandT); 16 | for (uint32_t i = 0; i < segment->nsects; i++) { 17 | auto sect = (SectionT *)(sectStart + (i * sizeof(SectionT))); 18 | sections.emplace_back(sect); 19 | } 20 | } 21 | 22 | template class SegmentContext; 23 | template class SegmentContext; 24 | template class SegmentContext; 25 | template class SegmentContext; 26 | 27 | template 28 | Context::Context( 29 | uint64_t fileOffset, bio::mapped_file mainFile, 30 | std::vector mainMappings, 31 | std::vector>> 32 | subFiles) 33 | : headerOffset(fileOffset) { 34 | // Store files 35 | if constexpr (ro) { 36 | file = (FileT *)mainFile.const_data(); 37 | } else { 38 | file = (FileT *)mainFile.data(); 39 | } 40 | 41 | files.emplace_back(file, mainMappings); 42 | fileMaps.push_back(std::move(mainFile)); 43 | for (auto &[fileMap, mapping] : subFiles) { 44 | FileT *subFile; 45 | if constexpr (ro) { 46 | subFile = (FileT *)fileMap.const_data(); 47 | } else { 48 | subFile = (FileT *)fileMap.data(); 49 | } 50 | 51 | files.emplace_back(subFile, mapping); 52 | fileMaps.push_back(std::move(fileMap)); 53 | } 54 | if (ownFiles) { 55 | filesOpen = true; 56 | } 57 | 58 | reloadHeader(); 59 | } 60 | 61 | template 62 | Context::Context( 63 | uint64_t fileOffset, fs::path mainPath, 64 | std::vector mainMappings, 65 | std::vector>> subFiles) 66 | : Context(fileOffset, openFile(mainPath), mainMappings, 67 | openFiles(subFiles)) { 68 | ownFiles = true; 69 | } 70 | 71 | template Context::~Context() { 72 | if (filesOpen) { 73 | for (auto &file : fileMaps) { 74 | file.close(); 75 | } 76 | filesOpen = false; 77 | } 78 | } 79 | 80 | template 81 | Context::Context(Context &&other) 82 | : file(other.file), header(other.header), ownFiles(other.ownFiles), 83 | filesOpen(other.filesOpen), fileMaps(std::move(other.fileMaps)), 84 | files(std::move(other.files)) { 85 | other.file = nullptr; 86 | other.header = nullptr; 87 | other.ownFiles = false; 88 | other.filesOpen = false; 89 | } 90 | 91 | template 92 | Context &Context::operator=(Context &&other) { 93 | this->file = other.file; 94 | this->header = other.header; 95 | this->ownFiles = other.ownFiles; 96 | this->filesOpen = other.filesOpen; 97 | 98 | this->fileMaps = std::move(other.fileMaps); 99 | this->files = std::move(other.files); 100 | 101 | other.file = nullptr; 102 | other.header = nullptr; 103 | other.ownFiles = false; 104 | other.filesOpen = false; 105 | return *this; 106 | } 107 | 108 | template void Context::reloadHeader() { 109 | loadCommands.clear(); 110 | segments.clear(); 111 | 112 | header = (HeaderT *)(file + headerOffset); 113 | 114 | if (header->magic != HeaderT::MAGIC && header->magic != HeaderT::CIGAM) { 115 | throw std::invalid_argument("Mach-o header has an invalid magic."); 116 | } else if (header->magic == HeaderT::CIGAM) { 117 | throw std::invalid_argument( 118 | "Host system endianness incompatible with mach-o file."); 119 | } 120 | 121 | loadCommands.reserve(header->ncmds); 122 | FileT *cmdStart = (FileT *)header + sizeof(HeaderT); 123 | for (uint32_t cmdOff = 0; cmdOff < header->sizeofcmds;) { 124 | auto cmd = (LoadCommandT *)(cmdStart + cmdOff); 125 | loadCommands.emplace_back(cmd); 126 | cmdOff += cmd->cmdsize; 127 | } 128 | 129 | for (auto const seg : getAllLCs>()) { 130 | segments.emplace_back(seg); 131 | } 132 | } 133 | 134 | template 135 | std::pair::FileT *> 136 | Context::convertAddr(uint64_t addr) const { 137 | for (auto &[file, mappings] : files) { 138 | for (auto &mapping : mappings) { 139 | if (addr >= mapping.address && addr < mapping.address + mapping.size) { 140 | return std::make_pair((addr - mapping.address) + mapping.fileOffset, 141 | file); 142 | } 143 | } 144 | } 145 | 146 | return std::make_pair(0, nullptr); 147 | } 148 | 149 | template 150 | typename Context::FileT * 151 | Context::convertAddrP(uint64_t addr) const { 152 | auto [offset, file] = convertAddr(addr); 153 | return file ? file + offset : nullptr; 154 | } 155 | 156 | template 157 | const SegmentContext * 158 | Context::getSegment(const char *segName) const { 159 | auto nameSize = strlen(segName) + 1; 160 | if (nameSize > 16) { 161 | throw std::invalid_argument("Segment name is too long."); 162 | } 163 | 164 | for (auto &seg : segments) { 165 | if (memcmp(segName, seg.command->segname, nameSize) == 0) { 166 | return &seg; 167 | } 168 | } 169 | 170 | return nullptr; 171 | } 172 | 173 | template 174 | std::pair *, 175 | const typename SegmentContext::SectionT *> 176 | Context::getSection(const char *segName, const char *sectName) const { 177 | std::size_t segSize = 0; 178 | if (segName != nullptr) { 179 | segSize = strlen(segName); 180 | if (segSize < 16) { 181 | // Include null terminator 182 | segSize++; 183 | } else if (segSize > 16) { 184 | throw std::invalid_argument("Segment name is too long."); 185 | } 186 | } 187 | 188 | std::size_t sectSize = strlen(sectName); 189 | if (sectSize < 16) { 190 | // Include null terminator 191 | sectSize++; 192 | } else if (sectSize > 16) { 193 | throw std::invalid_argument("Section name is too long."); 194 | } 195 | 196 | for (auto &seg : segments) { 197 | if (segSize == 0 || memcmp(segName, seg.command->segname, segSize) == 0) { 198 | for (auto sect : seg.sections) { 199 | if (memcmp(sectName, sect->sectname, sectSize) == 0) { 200 | return std::make_pair(&seg, sect); 201 | } 202 | } 203 | } 204 | } 205 | 206 | return std::make_pair(nullptr, nullptr); 207 | } 208 | 209 | template 210 | bool Context::containsAddr(const uint64_t addr) const { 211 | /// TODO: Binary search? 212 | for (auto &seg : segments) { 213 | if (addr >= seg.command->vmaddr && 214 | addr < seg.command->vmaddr + seg.command->vmsize) { 215 | return true; 216 | } 217 | } 218 | 219 | return false; 220 | } 221 | 222 | template 223 | std::vector::LoadCommandT *> 224 | Context::_getAllLCs(const uint32_t (&targetCmds)[], 225 | std::size_t ncmds) const { 226 | auto matchLoadCommands = [&targetCmds, ncmds](uint32_t cmd) { 227 | // magic value for load_command, match all. 228 | if (ncmds == 2 && targetCmds[0] == 0x00 && targetCmds[1] == 0x00) { 229 | return true; 230 | } 231 | 232 | for (int i = 0; i < ncmds; i++) { 233 | if (cmd == targetCmds[i]) { 234 | return true; 235 | } 236 | } 237 | return false; 238 | }; 239 | 240 | std::vector lcs; 241 | for (auto lc : loadCommands) { 242 | if (matchLoadCommands(lc->cmd)) { 243 | lcs.push_back(lc); 244 | } 245 | } 246 | 247 | return lcs; 248 | } 249 | 250 | template 251 | typename Context::LoadCommandT * 252 | Context::_getFirstLC(const uint32_t (&targetCmds)[], 253 | std::size_t ncmds) const { 254 | auto matchLoadCommands = [&targetCmds, ncmds](uint32_t cmd) { 255 | // magic value for load_command, match all. 256 | if (ncmds == 2 && targetCmds[0] == 0x00 && targetCmds[1] == 0x00) { 257 | return true; 258 | } 259 | 260 | for (int i = 0; i < ncmds; i++) { 261 | if (cmd == targetCmds[i]) { 262 | return true; 263 | } 264 | } 265 | return false; 266 | }; 267 | 268 | for (auto lc : loadCommands) { 269 | if (matchLoadCommands(lc->cmd)) { 270 | return lc; 271 | } 272 | } 273 | 274 | return nullptr; 275 | } 276 | 277 | template 278 | void Context::enumerateSections(EnumerationCallback pred, 279 | EnumerationCallback callback) { 280 | for (auto &seg : segments) { 281 | for (auto sect : seg.sections) { 282 | if (pred(seg, sect)) { 283 | if (!callback(seg, sect)) { 284 | return; 285 | } 286 | } 287 | } 288 | } 289 | } 290 | 291 | template 292 | void Context::enumerateSections(EnumerationCallback callback) { 293 | enumerateSections([](...) { return true; }, callback); 294 | } 295 | 296 | template 297 | bio::mapped_file Context::openFile(fs::path path) { 298 | return bio::mapped_file(path.string(), bio::mapped_file::mapmode::priv); 299 | } 300 | 301 | template 302 | std::vector>> 303 | Context::openFiles( 304 | std::vector>> paths) { 305 | std::vector>> files( 306 | paths.size()); 307 | for (auto &[path, mappings] : paths) { 308 | files.emplace_back( 309 | bio::mapped_file(path.string(), bio::mapped_file::mapmode::priv), 310 | mappings); 311 | } 312 | 313 | return files; 314 | } 315 | 316 | template class Context; 317 | template class Context; 318 | template class Context; 319 | template class Context; -------------------------------------------------------------------------------- /DyldExtractor/Macho/Context.h: -------------------------------------------------------------------------------- 1 | #ifndef __MACHO_CONTEXT__ 2 | #define __MACHO_CONTEXT__ 3 | 4 | #include 5 | #include 6 | 7 | #include "Loader.h" 8 | #include 9 | #include 10 | 11 | namespace DyldExtractor::Macho { 12 | 13 | namespace bio = boost::iostreams; 14 | namespace fs = std::filesystem; 15 | 16 | // Conditional const 17 | template struct c_const { 18 | using T = _T; 19 | }; 20 | template struct c_const { 21 | using T = const _T; 22 | }; 23 | 24 | struct MappingInfo { 25 | uint64_t address; 26 | uint64_t size; 27 | uint64_t fileOffset; 28 | 29 | MappingInfo() = default; 30 | MappingInfo(const dyld_cache_mapping_info *); 31 | }; 32 | 33 | template class SegmentContext { 34 | public: 35 | using SegmentCommandT = c_const>::T; 36 | using SectionT = c_const>::T; 37 | 38 | SegmentCommandT *command; 39 | std::vector sections; 40 | 41 | SegmentContext(SegmentCommandT *segment); 42 | }; 43 | 44 | /// A wrapper around a MachO file in the DSC. 45 | /// The template boolean determines if it is read only. 46 | template class Context { 47 | public: 48 | using FileT = c_const::T; 49 | using LoadCommandT = c_const::T; 50 | using HeaderT = c_const>::T; 51 | using SegmentT = SegmentContext; 52 | using EnumerationCallback = 53 | std::function; 54 | 55 | // The file containing the header 56 | FileT *file; 57 | HeaderT *header; 58 | 59 | std::vector loadCommands; 60 | std::vector segments; 61 | 62 | /// @brief A wrapper around a MachO file. 63 | /// 64 | /// The access permissions is based on the main file provided. Calling this 65 | /// directly also implies that the context does not manage the file maps. 66 | /// 67 | /// @param fileOffset The file offset to the mach header. 68 | /// @param mainFile The memory map that contains the header. 69 | /// @param mainMapping The mapping info for mainFile 70 | /// @param subFiles A vector of tuples of all other memory maps and their 71 | /// mapping info. 72 | Context(uint64_t fileOffset, bio::mapped_file mainFile, 73 | std::vector mainMappings, 74 | std::vector>> 75 | subFiles); 76 | /// @brief A writable wrapper around a MachO file. 77 | /// 78 | /// The files will be opened with private (copy on write) access. 79 | /// 80 | /// @param fileOffset The file offset to the mach header. 81 | /// @param mainPath The file path of the file that contains the header. 82 | /// @param mainMapping The mapping info for mainFile 83 | /// @param subFiles A vector of tuples of all other file paths and their 84 | /// mapping info. 85 | Context(uint64_t fileOffset, fs::path mainPath, 86 | std::vector mainMappings, 87 | std::vector>> subFiles); 88 | 89 | /// @brief Reload the header and load commands 90 | void reloadHeader(); 91 | 92 | /// @brief Convert a vmaddr to it's file offset. 93 | /// 94 | /// If the offset could not be found, a pair with 0 and 95 | /// nullptr will be returned. 96 | /// 97 | /// @param addr The virtual address to convert. 98 | /// @returns A pair of the file offset and its file. 99 | std::pair convertAddr(uint64_t addr) const; 100 | 101 | /// @brief Convert a vmaddr to it's file offset. 102 | /// 103 | /// If the offset could not be found, nullptr will be 104 | /// returned. 105 | /// 106 | /// @param addr The virtual address to convert. 107 | /// @returns A file based pointer to the address 108 | FileT *convertAddrP(uint64_t addr) const; 109 | 110 | /// @brief Get the first load command with a custom filter 111 | /// @tparam lc The type of load command 112 | /// @param cmds The custom ID filter 113 | /// @return A pointer to the load command, nullptr if not found 114 | template 115 | inline c_const::T *getFirstLC(const uint32_t (&cmds)[_s]) const { 116 | return reinterpret_cast::T *>(_getFirstLC(cmds, _s)); 117 | } 118 | 119 | /// @brief Get the first load command 120 | /// @tparam lc The type of load command 121 | /// @return A pointer to the load command, nullptr if not found 122 | template inline c_const::T *getFirstLC() const { 123 | return getFirstLC(lc::CMDS); 124 | } 125 | 126 | /// @brief Get all load commands with a custom filter 127 | /// @tparam lc The type of load command 128 | /// @param cmds The custom ID filter 129 | /// @return A list of pointers to load commands 130 | template 131 | inline std::vector::T *> 132 | getAllLCs(const uint32_t (&cmds)[_s]) const { 133 | return reinterpret_cast::T *> &>( 134 | _getAllLCs(cmds, _s)); 135 | } 136 | 137 | /// @brief Get all load commands 138 | /// @tparam lc The type of load command 139 | /// @return A list of pointer to load commands 140 | template 141 | inline std::vector::T *> getAllLCs() const { 142 | return getAllLCs(lc::CMDS); 143 | } 144 | 145 | /// @brief Search for a segment 146 | /// 147 | /// @param segName The name of the segment. 148 | /// @returns The segment context. nullptr if not found. 149 | const SegmentT *getSegment(const char *segName) const; 150 | 151 | /// @brief Search for a section 152 | /// @param segName The name of the segment, or nullptr. 153 | /// @param sectName The name of the section. 154 | /// @returns The segment and the section 155 | std::pair 156 | getSection(const char *segName, const char *sectName) const; 157 | 158 | /// @brief Enumerate all segments 159 | /// @param pred The predicate used to filter. 160 | /// @param callback The function to call for each section. Return false to 161 | /// stop. 162 | void enumerateSections(EnumerationCallback pred, 163 | EnumerationCallback callback); 164 | 165 | /// @brief Enumerate all segments 166 | /// @param callback The function to call for each section. Return false to 167 | /// stop. 168 | void enumerateSections(EnumerationCallback callback); 169 | 170 | /// @brief Check if the address is in the macho file 171 | /// @param addr 172 | /// @returns If the file contains the address 173 | bool containsAddr(const uint64_t addr) const; 174 | 175 | ~Context(); 176 | Context(const Context &other) = delete; 177 | Context(Context &&other); 178 | Context &operator=(const Context &other) = delete; 179 | Context &operator=(Context &&other); 180 | 181 | private: 182 | uint64_t headerOffset; 183 | // Determines if the file need to be closed during destruction. 184 | bool ownFiles = false; 185 | // Indicates if the files are open, false if ownFiles is false. 186 | bool filesOpen = false; 187 | 188 | // Contains all file maps 189 | std::vector fileMaps; 190 | // Contains all files and mappings 191 | std::vector>> files; 192 | 193 | std::vector _getAllLCs(const uint32_t (&targetCmds)[], 194 | std::size_t ncmds) const; 195 | LoadCommandT *_getFirstLC(const uint32_t (&targetCmds)[], 196 | std::size_t ncmds) const; 197 | 198 | // Convenience method to open files with private access. 199 | static bio::mapped_file openFile(fs::path path); 200 | static std::vector>> 201 | openFiles(std::vector>> paths); 202 | }; 203 | 204 | }; // namespace DyldExtractor::Macho 205 | 206 | #endif // __MACHO_CONTEXT__ -------------------------------------------------------------------------------- /DyldExtractor/Macho/Loader.h: -------------------------------------------------------------------------------- 1 | #ifndef __MACHO_LOADER__ 2 | #define __MACHO_LOADER__ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define CPU_SUBTYPE_MASK 0xff000000 10 | #define CPU_SUBTYPE_ARM64E 2 11 | 12 | namespace DyldExtractor::Macho::Loader { 13 | 14 | template struct mach_header {}; 15 | template <> struct mach_header : public ::mach_header { 16 | enum { MAGIC = MH_MAGIC, CIGAM = MH_CIGAM }; 17 | }; 18 | template <> 19 | struct mach_header : public ::mach_header_64 { 20 | enum { MAGIC = MH_MAGIC_64, CIGAM = MH_CIGAM_64 }; 21 | }; 22 | 23 | struct load_command : public ::load_command { 24 | constexpr static uint32_t CMDS[] = {0x00, 0x00}; // magic value for all lcs 25 | }; 26 | 27 | template struct segment_command {}; 28 | template <> 29 | struct segment_command : public ::segment_command { 30 | constexpr static uint32_t CMDS[] = {LC_SEGMENT}; 31 | }; 32 | template <> 33 | struct segment_command : public ::segment_command_64 { 34 | constexpr static uint32_t CMDS[] = {LC_SEGMENT_64}; 35 | }; 36 | 37 | template struct section {}; 38 | template <> struct section : public ::section {}; 39 | template <> struct section : public ::section_64 {}; 40 | 41 | struct symtab_command : public ::symtab_command { 42 | constexpr static uint32_t CMDS[] = {LC_SYMTAB}; 43 | }; 44 | 45 | struct dysymtab_command : public ::dysymtab_command { 46 | constexpr static uint32_t CMDS[] = {LC_DYSYMTAB}; 47 | }; 48 | 49 | struct linkedit_data_command : public ::linkedit_data_command { 50 | constexpr static uint32_t CMDS[] = { 51 | LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, 52 | LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS, LC_LINKER_OPTIMIZATION_HINT, 53 | LC_DYLD_EXPORTS_TRIE, LC_DYLD_CHAINED_FIXUPS}; 54 | }; 55 | 56 | struct dyld_info_command : public ::dyld_info_command { 57 | constexpr static uint32_t CMDS[] = {LC_DYLD_INFO, LC_DYLD_INFO_ONLY}; 58 | }; 59 | 60 | template struct nlist {}; 61 | template <> struct nlist : public ::nlist {}; 62 | template <> struct nlist : public ::nlist_64 {}; 63 | 64 | struct dylib_command : public ::dylib_command { 65 | constexpr static uint32_t CMDS[] = {LC_ID_DYLIB, LC_LOAD_DYLIB, 66 | LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB, 67 | LC_LOAD_UPWARD_DYLIB, LC_LAZY_LOAD_DYLIB}; 68 | }; 69 | 70 | }; // namespace DyldExtractor::Macho::Loader 71 | 72 | #endif // __MACHO_LOADER__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/Accelerator.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_ACCELERATOR__ 2 | #define __PROVIDER_ACCELERATOR__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #pragma warning(push) 11 | #pragma warning(disable : 4267) 12 | #include 13 | #pragma warning(pop) 14 | 15 | namespace DyldExtractor::Provider { 16 | 17 | namespace AcceleratorTypes { 18 | 19 | /// Intermediate representation of a export, should not be used. 20 | struct SymbolizerExportEntry { 21 | uint64_t address; 22 | ExportInfoTrie::Entry entry; 23 | 24 | /// @brief This constructor should only be used for searching 25 | SymbolizerExportEntry(std::string n) : address(0), entry(n, ExportInfo()) {} 26 | SymbolizerExportEntry(uint64_t a, ExportInfoTrie::Entry e) 27 | : address(a), entry(e) {} 28 | 29 | struct Hash { 30 | std::size_t operator()(const SymbolizerExportEntry &e) const { 31 | return std::hash{}(e.entry.name); 32 | } 33 | }; 34 | 35 | struct KeyEqual { 36 | bool operator()(const SymbolizerExportEntry &a, 37 | const SymbolizerExportEntry &b) const { 38 | return a.entry.name == b.entry.name; 39 | } 40 | }; 41 | }; 42 | 43 | using SymbolizerExportEntryMapT = 44 | std::unordered_multiset; 46 | 47 | }; // namespace AcceleratorTypes 48 | 49 | /// Accelerate modules when processing more than one image. Single threaded. 50 | template class Accelerator { 51 | using PtrT = P::PtrT; 52 | 53 | public: 54 | // Provider::Symbolizer 55 | std::map pathToImage; 56 | std::map 57 | exportsCache; 58 | 59 | // Converter::Stubs::Arm64Utils, Converter::Stubs::ArmUtils 60 | std::map arm64ResolvedChains; 61 | std::map armResolvedChains; 62 | 63 | // Converter::Stubs::Fixer 64 | struct CodeRegion { 65 | PtrT start; 66 | PtrT end; 67 | auto operator<=>(const auto &o) const { return start <=> o.start; } 68 | }; 69 | std::set codeRegions; 70 | 71 | Accelerator() = default; 72 | Accelerator(const Accelerator &) = delete; 73 | Accelerator &operator=(const Accelerator &) = delete; 74 | }; 75 | 76 | }; // namespace DyldExtractor::Provider 77 | 78 | #endif // __PROVIDER_ACCELERATOR__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/ActivityLogger.cpp: -------------------------------------------------------------------------------- 1 | #include "ActivityLogger.h" 2 | 3 | using namespace DyldExtractor; 4 | using namespace Provider; 5 | 6 | ActivityLogger::StreamBuffer::StreamBuffer(std::streambuf *buffer) 7 | : buffer(buffer) {} 8 | 9 | int ActivityLogger::StreamBuffer::sync() { return buffer->pubsync(); } 10 | 11 | int ActivityLogger::StreamBuffer::overflow(int c) { 12 | if (c != std::char_traits::eof()) { 13 | if (needPrefix && 14 | prefix.size() != buffer->sputn(prefix.c_str(), prefix.size())) { 15 | return std::char_traits::eof(); 16 | } 17 | needPrefix = (c == '\n'); 18 | } 19 | 20 | return buffer->sputc(c); 21 | } 22 | 23 | ActivityLogger::ActivityLogger(std::string name, std::ostream &output, 24 | bool enableActivity) 25 | : activityStream(output), loggerStream(&streamBuffer), 26 | streamBuffer(output.rdbuf()), enableActivity(enableActivity), 27 | lastActivityUpdate(std::chrono::high_resolution_clock::now()), 28 | lastElapsedTime(0), startTime(std::chrono::high_resolution_clock::now()) { 29 | std::shared_ptr streamSink; 30 | if (enableActivity) { 31 | // Create a logger with the special buffer 32 | streamSink = std::make_shared(loggerStream); 33 | 34 | // preload activity 35 | update(currentModule, currentMessage, true); 36 | } else { 37 | streamSink = std::make_shared(output); 38 | } 39 | 40 | logger = std::make_shared(name, streamSink); 41 | } 42 | 43 | void ActivityLogger::update(std::optional moduleName, 44 | std::optional message, 45 | bool fullUpdate) { 46 | if (!enableActivity) { 47 | return; 48 | } 49 | 50 | // Format, [(/) Elapsed Time] Module - Text 51 | unsigned int updateLevel = fullUpdate ? INT_MAX : 0; 52 | 53 | // Spinner 54 | auto currentTime = std::chrono::high_resolution_clock::now(); 55 | if (std::chrono::duration_cast(currentTime - 56 | lastActivityUpdate) 57 | .count() > 150) { 58 | lastActivityUpdate = currentTime; 59 | currentActivityState = (currentActivityState + 1) % activityStates.size(); 60 | updateLevel |= 0b1; 61 | } 62 | 63 | // elapsed time 64 | auto elapsedTime = std::chrono::duration_cast( 65 | std::chrono::high_resolution_clock::now() - startTime); 66 | if (elapsedTime != lastElapsedTime) { 67 | lastElapsedTime = elapsedTime; 68 | updateLevel |= 0b10; 69 | } 70 | 71 | // text 72 | if (moduleName) { 73 | currentModule = moduleName.value(); 74 | updateLevel |= 0b100; 75 | } 76 | if (message) { 77 | currentMessage = message.value(); 78 | updateLevel |= 0b100; 79 | } 80 | 81 | // Update 82 | std::string output; 83 | if (updateLevel >= 0b100) { 84 | output = fmt::format( 85 | "\033[2K[({}) {}] {} - {}", activityStates[currentActivityState], 86 | _formatTime(elapsedTime), currentModule, currentMessage); 87 | } else if (updateLevel >= 0b10) { 88 | output = fmt::format("[({}) {}", activityStates[currentActivityState], 89 | _formatTime(elapsedTime)); 90 | } else if (updateLevel >= 0b1) { 91 | output = fmt::format("[({}", activityStates[currentActivityState]); 92 | } 93 | 94 | if (output.length()) { 95 | activityStream << output + "\r" << std::flush; 96 | } 97 | } 98 | 99 | void ActivityLogger::stopActivity() { 100 | if (enableActivity) { 101 | enableActivity = false; 102 | activityStream << "\n"; 103 | } 104 | } 105 | 106 | std::shared_ptr ActivityLogger::getLogger() { return logger; } 107 | 108 | std::ostream &ActivityLogger::getLoggerStream() { return loggerStream; } 109 | 110 | std::string ActivityLogger::_formatTime(std::chrono::seconds seconds) { 111 | auto minutes = std::chrono::duration_cast(seconds); 112 | seconds -= minutes; 113 | 114 | return fmt::format("{:02d}:{:02d}", minutes.count(), seconds.count()); 115 | } -------------------------------------------------------------------------------- /DyldExtractor/Provider/ActivityLogger.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_ACTIVITYLOGGER__ 2 | #define __PROVIDER_ACTIVITYLOGGER__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace DyldExtractor::Provider { 10 | 11 | class ActivityLogger { 12 | /// @brief A wrapper for a streambuf that allows an activity indicator. 13 | /// 14 | /// Essentially does new line, moves line up, and insert line, before 15 | /// every line. 16 | class StreamBuffer : public std::streambuf { 17 | public: 18 | StreamBuffer(std::streambuf *buffer); 19 | 20 | private: 21 | std::streambuf *buffer; 22 | 23 | /// Thanks to alkis-pap from github.com/p-ranav/indicators/issues/107 24 | /// Move up, insert line 25 | const std::string prefix = "\n\033[A\033[1L"; 26 | bool needPrefix; 27 | 28 | int sync(); 29 | int overflow(int c); 30 | }; 31 | 32 | public: 33 | /// @brief Create a logger with an optional activity indicator. 34 | /// @param name The name of the logger. 35 | /// @param output The output stream. 36 | /// @param enableActivity Enable or disable the activity indicator. 37 | ActivityLogger(std::string name, std::ostream &output, bool enableActivity); 38 | 39 | ActivityLogger(const ActivityLogger &) = delete; 40 | ActivityLogger(const ActivityLogger &&) = delete; 41 | ActivityLogger &operator=(ActivityLogger &) = delete; 42 | ActivityLogger &operator=(ActivityLogger &&) = delete; 43 | 44 | /// Update the activity indicator. 45 | /// 46 | /// @param module The name of the module. 47 | /// @param message The message. 48 | void update(std::optional moduleName = std::nullopt, 49 | std::optional message = std::nullopt, 50 | bool fullUpdate = false); 51 | 52 | /// @brief Stop the activity indicator 53 | void stopActivity(); 54 | 55 | /// @brief Get the spdlog logger 56 | std::shared_ptr getLogger(); 57 | 58 | /// @brief Get the logger stream that won't interfere with 59 | /// the activity indicator. 60 | std::ostream &getLoggerStream(); 61 | 62 | private: 63 | std::shared_ptr logger; 64 | std::ostream &activityStream; 65 | std::ostream loggerStream; 66 | StreamBuffer streamBuffer; 67 | 68 | bool enableActivity; 69 | std::string currentModule = "---"; 70 | std::string currentMessage = "---"; 71 | int currentActivityState = 0; 72 | const std::vector activityStates = {"|", "/", "-", "\\"}; 73 | std::chrono::time_point 74 | lastActivityUpdate; 75 | 76 | std::chrono::seconds lastElapsedTime; 77 | const std::chrono::time_point startTime; 78 | 79 | std::string _formatTime(std::chrono::seconds seconds); 80 | }; 81 | 82 | } // namespace DyldExtractor::Provider 83 | 84 | #endif // __PROVIDER_ACTIVITYLOGGER__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/BindInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "BindInfo.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace DyldExtractor; 9 | using namespace Provider; 10 | 11 | struct IntermediateBindRecord { 12 | uint8_t segIndex = 0; 13 | uint64_t segOffset = 0; 14 | uint8_t type = 0; 15 | uint8_t flags = 0; 16 | int libOrdinal = 0; 17 | char *symbolName = nullptr; 18 | int64_t addend = 0; 19 | }; 20 | 21 | /// @brief Bind info reader implementation 22 | /// @param start Pointer to start of opcode stream. 23 | /// @param end Pointer to end of opcode stream. 24 | /// @param callback Called for each bind record. The first argument is the 25 | /// offset of the bind record from the start of the stream, which only makes 26 | /// sense for lazy bind info. Return false to stop reading. 27 | template 28 | void readBindStream( 29 | const uint8_t *const start, const uint8_t *const end, bool stopAtDone, 30 | std::function callback) { 31 | const uint8_t *currentRecordStart = start; 32 | IntermediateBindRecord currentRecord; 33 | 34 | const uint32_t ptrSize = sizeof(P::PtrT); 35 | const uint8_t *p = start; 36 | 37 | while (p < end) { 38 | const auto opcode = *p & BIND_OPCODE_MASK; 39 | const auto imm = *p & BIND_IMMEDIATE_MASK; 40 | p++; 41 | 42 | switch (opcode) { 43 | case BIND_OPCODE_DONE: 44 | if (stopAtDone) { 45 | return; 46 | } else { 47 | // Resets and starts a new record 48 | currentRecord = IntermediateBindRecord(); 49 | currentRecordStart = p; 50 | } 51 | break; 52 | 53 | case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: 54 | currentRecord.libOrdinal = imm; 55 | break; 56 | 57 | case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: 58 | currentRecord.libOrdinal = (int)Utils::readUleb128(p, end); 59 | break; 60 | 61 | case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: 62 | // the special ordinals are negative numbers 63 | if (imm == 0) 64 | currentRecord.libOrdinal = 0; 65 | else { 66 | int8_t signExtended = BIND_OPCODE_MASK | imm; 67 | currentRecord.libOrdinal = signExtended; 68 | } 69 | break; 70 | 71 | case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: 72 | currentRecord.flags = imm; 73 | currentRecord.symbolName = (char *)p; 74 | while (*p != '\0') 75 | p++; 76 | p++; 77 | break; 78 | 79 | case BIND_OPCODE_SET_TYPE_IMM: 80 | currentRecord.type = imm; 81 | break; 82 | 83 | case BIND_OPCODE_SET_ADDEND_SLEB: 84 | currentRecord.addend = Utils::readSleb128(p, end); 85 | break; 86 | 87 | case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: 88 | currentRecord.segIndex = imm; 89 | currentRecord.segOffset = Utils::readUleb128(p, end); 90 | break; 91 | 92 | case BIND_OPCODE_ADD_ADDR_ULEB: 93 | currentRecord.segOffset += Utils::readUleb128(p, end); 94 | break; 95 | 96 | case BIND_OPCODE_DO_BIND: 97 | if (!callback((uint32_t)(currentRecordStart - start), currentRecord)) { 98 | return; 99 | } 100 | 101 | currentRecord.segOffset += ptrSize; 102 | break; 103 | 104 | case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: 105 | if (!callback((uint32_t)(currentRecordStart - start), currentRecord)) { 106 | return; 107 | } 108 | 109 | currentRecord.segOffset += Utils::readUleb128(p, end) + ptrSize; 110 | break; 111 | 112 | case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: 113 | if (!callback((uint32_t)(currentRecordStart - start), currentRecord)) { 114 | return; 115 | } 116 | 117 | currentRecord.segOffset += imm * ptrSize + ptrSize; 118 | break; 119 | 120 | case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: { 121 | const auto count = Utils::readUleb128(p, end); 122 | const auto skip = Utils::readUleb128(p, end); 123 | for (uint32_t i = 0; i < count; i++) { 124 | if (!callback((uint32_t)(currentRecordStart - start), currentRecord)) { 125 | return; 126 | } 127 | 128 | currentRecord.segOffset += skip + ptrSize; 129 | } 130 | break; 131 | } 132 | 133 | default: 134 | throw std::invalid_argument( 135 | fmt::format("Unknown bind opcode 0x{:02x}.", *p)); 136 | } 137 | } 138 | } 139 | 140 | template 141 | BindInfo

::BindInfo(const Macho::Context &mCtx, 142 | Provider::ActivityLogger &activity) 143 | : mCtx(&mCtx), activity(&activity) {} 144 | 145 | template void BindInfo

::load() { 146 | if (dataLoaded) { 147 | return; 148 | } 149 | 150 | const uint8_t *linkeditFile = 151 | mCtx->convertAddr(mCtx->getSegment(SEG_LINKEDIT)->command->vmaddr).second; 152 | const dyld_info_command *dyldInfo = 153 | mCtx->getFirstLC(); 154 | 155 | activity->update("BindInfo", "Starting up"); 156 | if (dyldInfo && dyldInfo->bind_size) { 157 | activity->update(std::nullopt, "Reading Binding Info"); 158 | auto bindStart = linkeditFile + dyldInfo->bind_off; 159 | auto bindEnd = bindStart + dyldInfo->bind_size; 160 | readBindStream

(bindStart, bindEnd, true, 161 | [this](uint32_t, IntermediateBindRecord record) { 162 | binds.emplace_back( 163 | mCtx->segments.at(record.segIndex).command->vmaddr + 164 | record.segOffset, 165 | record.type, record.flags, record.libOrdinal, 166 | record.symbolName, record.addend); 167 | return true; 168 | }); 169 | } 170 | 171 | if (dyldInfo && dyldInfo->weak_bind_size) { 172 | activity->update(std::nullopt, "Reading Weak Binding Info"); 173 | auto bindStart = linkeditFile + dyldInfo->weak_bind_off; 174 | auto bindEnd = bindStart + dyldInfo->weak_bind_size; 175 | readBindStream

(bindStart, bindEnd, true, 176 | [this](uint32_t, IntermediateBindRecord record) { 177 | weakBinds.emplace_back( 178 | mCtx->segments.at(record.segIndex).command->vmaddr + 179 | record.segOffset, 180 | record.type, record.flags, record.libOrdinal, 181 | record.symbolName, record.addend); 182 | return true; 183 | }); 184 | } 185 | 186 | if (dyldInfo && dyldInfo->lazy_bind_size) { 187 | activity->update(std::nullopt, "Reading Lazy Binding Info"); 188 | auto bindStart = linkeditFile + dyldInfo->lazy_bind_off; 189 | auto bindEnd = bindStart + dyldInfo->lazy_bind_size; 190 | readBindStream

( 191 | bindStart, bindEnd, false, 192 | [this](uint32_t off, IntermediateBindRecord record) { 193 | lazyBinds.emplace( 194 | off, 195 | BindRecord{mCtx->segments.at(record.segIndex).command->vmaddr + 196 | record.segOffset, 197 | record.type, record.flags, record.libOrdinal, 198 | record.symbolName, record.addend}); 199 | return true; 200 | }); 201 | } 202 | 203 | _hasLazyBinds = dyldInfo != nullptr && dyldInfo->lazy_bind_size != 0; 204 | dataLoaded = true; 205 | } 206 | 207 | template 208 | const std::vector &BindInfo

::getBinds() const { 209 | return binds; 210 | } 211 | 212 | template 213 | const std::vector &BindInfo

::getWeakBinds() const { 214 | return weakBinds; 215 | } 216 | 217 | template 218 | const std::map &BindInfo

::getLazyBinds() const { 219 | return lazyBinds; 220 | } 221 | 222 | template 223 | const BindRecord *BindInfo

::getLazyBind(uint32_t offset) const { 224 | if (lazyBinds.contains(offset)) { 225 | return &lazyBinds.at(offset); 226 | } else { 227 | return nullptr; 228 | } 229 | } 230 | 231 | template bool BindInfo

::hasLazyBinds() const { 232 | return _hasLazyBinds; 233 | } 234 | 235 | template class BindInfo; 236 | template class BindInfo; -------------------------------------------------------------------------------- /DyldExtractor/Provider/BindInfo.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_BINDINFO__ 2 | #define __PROVIDER_BINDINFO__ 3 | 4 | #include "ActivityLogger.h" 5 | #include 6 | #include 7 | 8 | namespace DyldExtractor::Provider { 9 | 10 | struct BindRecord { 11 | uint64_t address = 0; 12 | uint8_t type = 0; 13 | uint8_t flags = 0; 14 | int libOrdinal = 0; 15 | char *symbolName = nullptr; 16 | int64_t addend = 0; 17 | }; 18 | 19 | template class BindInfo { 20 | public: 21 | BindInfo(const Macho::Context &mCtx, 22 | Provider::ActivityLogger &activity); 23 | BindInfo(const BindInfo &) = delete; 24 | BindInfo &operator=(const BindInfo &) = delete; 25 | 26 | /// @brief Read and load all binds 27 | void load(); 28 | 29 | /// @brief Get all regular bind records. 30 | const std::vector &getBinds() const; 31 | 32 | /// @brief Get all weak bind records. 33 | const std::vector &getWeakBinds() const; 34 | 35 | /// @brief Get all lazy bind records. 36 | const std::map &getLazyBinds() const; 37 | 38 | /// @brief Get a lazy bind record. 39 | /// @param offset The offset to the bind record. 40 | /// @return The bind record. 41 | const BindRecord *getLazyBind(uint32_t offset) const; 42 | 43 | bool hasLazyBinds() const; 44 | 45 | private: 46 | const Macho::Context *mCtx; 47 | Provider::ActivityLogger *activity; 48 | 49 | std::vector binds; 50 | std::vector weakBinds; 51 | std::map lazyBinds; 52 | bool _hasLazyBinds; 53 | 54 | bool dataLoaded = false; 55 | }; 56 | 57 | } // namespace DyldExtractor::Provider 58 | 59 | #endif // __PROVIDER_BINDINFO__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/Disassembler.cpp: -------------------------------------------------------------------------------- 1 | #include "Disassembler.h" 2 | 3 | #include 4 | 5 | using namespace DyldExtractor; 6 | using namespace Provider; 7 | 8 | template 9 | Disassembler::Instruction::Instruction(PtrT addr, uint8_t size) 10 | : address(addr), id(DISASM_INVALID_INSN), size(size) {} 11 | 12 | template 13 | Disassembler::Instruction::Instruction(cs_insn *raw) 14 | : address((PtrT)raw->address), id(raw->id), size((uint8_t)raw->size), 15 | opStr(raw->op_str) {} 16 | 17 | template 18 | Disassembler::Disassembler(const Macho::Context &mCtx, 19 | Provider::ActivityLogger &activity, 20 | std::shared_ptr logger, 21 | Provider::FunctionTracker

&funcTracker) 22 | : mCtx(&mCtx), activity(&activity), logger(logger), 23 | funcTracker(&funcTracker) { 24 | // Create Capstone engine 25 | cs_err err; 26 | if constexpr (std::is_same_v) { 27 | // x86_64 not supported but allow construction 28 | return; 29 | } else if constexpr (std::is_same_v) { 30 | err = cs_open(CS_ARCH_ARM, CS_MODE_THUMB, &handle); 31 | } else if constexpr (std::is_same_v || 32 | std::is_same_v) { 33 | err = cs_open(CS_ARCH_ARM64, CS_MODE_ARM, &handle); 34 | } else { 35 | Utils::unreachable(); 36 | } 37 | 38 | if (err != CS_ERR_OK) { 39 | throw std::runtime_error("Unable to open Capstone engine."); 40 | } 41 | } 42 | 43 | template Disassembler::~Disassembler() { cs_close(&handle); } 44 | 45 | template 46 | Disassembler::Disassembler(Disassembler &&o) 47 | : mCtx(o.mCtx), activity(o.activity), logger(std::move(o.logger)), 48 | funcTracker(o.funcTracker), instructions(std::move(o.instructions)), 49 | textData(o.textData), textAddr(o.textAddr), disassembled(o.disassembled), 50 | handle(o.handle) { 51 | o.mCtx = nullptr; 52 | o.activity = nullptr; 53 | o.funcTracker = nullptr; 54 | o.textData = nullptr; 55 | o.textAddr = 0; 56 | o.disassembled = false; 57 | o.handle = 0; 58 | } 59 | 60 | template 61 | Disassembler &Disassembler::operator=(Disassembler &&o) { 62 | this->mCtx = o.mCtx; 63 | this->activity = o.activity; 64 | this->logger = std::move(o.logger); 65 | this->funcTracker = o.funcTracker; 66 | this->instructions = std::move(o.instructions); 67 | this->textData = o.textData; 68 | this->textAddr = o.textAddr; 69 | this->disassembled = o.disassembled; 70 | this->handle = o.handle; 71 | 72 | o.mCtx = nullptr; 73 | o.activity = nullptr; 74 | o.funcTracker = nullptr; 75 | o.textData = nullptr; 76 | o.textAddr = 0; 77 | o.disassembled = false; 78 | o.handle = 0; 79 | return *this; 80 | } 81 | 82 | template void Disassembler::load() { 83 | if constexpr (std::is_same_v) { 84 | throw std::runtime_error("X86_64 disassembly not supported."); 85 | } 86 | 87 | if (disassembled) { 88 | return; 89 | } 90 | disassembled = true; 91 | activity->update("Disassembler", "disassembling (will appear frozen)"); 92 | 93 | // Get data about text 94 | auto textSeg = mCtx->getSegment(SEG_TEXT)->command; 95 | textData = mCtx->convertAddrP(textSeg->vmaddr); 96 | textAddr = textSeg->vmaddr; 97 | 98 | // read all data in code entries 99 | auto dataInCodeCmd = 100 | mCtx->getFirstLC({LC_DATA_IN_CODE}); 101 | if (dataInCodeCmd) { 102 | auto leFile = 103 | mCtx->convertAddr(mCtx->getSegment(SEG_LINKEDIT)->command->vmaddr) 104 | .second; 105 | data_in_code_entry *start = 106 | reinterpret_cast(leFile + dataInCodeCmd->dataoff); 107 | data_in_code_entry *end = 108 | start + (dataInCodeCmd->datasize / sizeof(data_in_code_entry)); 109 | dataInCodeEntries = {start, end}; 110 | } 111 | 112 | // Arm64 should not have data in code 113 | if constexpr (std::is_same_v || 114 | std::is_same_v) { 115 | if (dataInCodeEntries.size() != 0) { 116 | SPDLOG_LOGGER_WARN(logger, "Unexpected data in code entries for arm64."); 117 | } 118 | } 119 | 120 | // Process all functions 121 | funcTracker->load(); 122 | for (const auto &func : funcTracker->getFunctions()) { 123 | disasmFunc((uint32_t)(func.address - textAddr), (uint32_t)func.size); 124 | } 125 | } 126 | 127 | template 128 | Disassembler::ConstInstructionIt 129 | Disassembler::instructionAtAddr(PtrT addr) const { 130 | if (addr & 0x3) { 131 | // Must be 4 byte aligned 132 | return instructions.cend(); 133 | } 134 | 135 | if constexpr (std::is_same_v) { 136 | // Must use binary search as instruction sizes are mixed 137 | auto it = std::lower_bound(instructions.cbegin(), instructions.cend(), 138 | Instruction(addr, 4), 139 | [](const Instruction &a, const Instruction &b) { 140 | return a.address < b.address; 141 | }); 142 | if (it == instructions.cend() || it->address != addr) { 143 | return instructions.cend(); 144 | } else { 145 | return it; 146 | } 147 | 148 | } else if constexpr (std::is_same_v || 149 | std::is_same_v) { 150 | PtrT index = (addr - instructions.cbegin()->address) / 4; 151 | if (index >= instructions.size()) { 152 | return instructions.cend(); 153 | } 154 | 155 | return instructions.cbegin() + index; 156 | } else { 157 | Utils::unreachable(); 158 | } 159 | } 160 | 161 | template 162 | Disassembler::ConstInstructionIt Disassembler::instructionsBegin() const { 163 | return instructions.cbegin(); 164 | } 165 | 166 | template 167 | Disassembler::ConstInstructionIt Disassembler::instructionsEnd() const { 168 | return instructions.cend(); 169 | } 170 | 171 | template 172 | void Disassembler::disasmFunc(uint32_t offset, uint32_t size) { 173 | // Find data in code entries in the function 174 | auto dataInCodeBegin = dataInCodeEntries.lower_bound({offset, 0, 0}); 175 | auto dataInCodeEnd = dataInCodeEntries.lower_bound({offset + size, 0, 0}); 176 | 177 | uint32_t currOff = offset; 178 | for (auto it = dataInCodeBegin; it != dataInCodeEnd; it++) { 179 | uint32_t chunkSize = it->offset - currOff; 180 | disasmChunk(currOff, chunkSize); 181 | 182 | // Add invalid instruction for data in code 183 | instructions.emplace_back(textAddr + it->offset, (uint8_t)it->length); 184 | currOff += chunkSize + it->length; 185 | } 186 | 187 | // Disassemble to the end of the function 188 | disasmChunk(currOff, size - (currOff - offset)); 189 | } 190 | 191 | template 192 | void Disassembler::disasmChunk(uint32_t offset, uint32_t size) { 193 | uint32_t currOff = offset; 194 | while (currOff < offset + size) { 195 | cs_insn *rawInsn; 196 | auto count = 197 | cs_disasm(handle, textData + currOff, size - (currOff - offset), 198 | textAddr + currOff, 0, &rawInsn); 199 | if (count == 0) { 200 | // Recover and try again 201 | currOff += recover(currOff); 202 | continue; 203 | } 204 | 205 | for (int i = 0; i < count; i++) { 206 | instructions.emplace_back(rawInsn + i); 207 | currOff += (rawInsn + i)->size; 208 | } 209 | 210 | cs_free(rawInsn, count); 211 | } 212 | } 213 | 214 | template uint32_t Disassembler::recover(uint32_t offset) { 215 | if constexpr (std::is_same_v) { 216 | instructions.emplace_back(textAddr + offset, 2); 217 | return 2; 218 | } else if constexpr (std::is_same_v || 219 | std::is_same_v) { 220 | instructions.emplace_back(textAddr + offset, 4); 221 | return 4; 222 | } else { 223 | Utils::unreachable(); 224 | } 225 | } 226 | 227 | template class Disassembler; 228 | template class Disassembler; 229 | template class Disassembler; 230 | template class Disassembler; -------------------------------------------------------------------------------- /DyldExtractor/Provider/Disassembler.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_DISASSEMBLER__ 2 | #define __PROVIDER_DISASSEMBLER__ 3 | 4 | #include "ActivityLogger.h" 5 | #include "FunctionTracker.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define DISASM_INVALID_INSN 0 12 | 13 | namespace DyldExtractor::Provider { 14 | 15 | template class Disassembler { 16 | using P = A::P; 17 | using PtrT = P::PtrT; 18 | 19 | public: 20 | struct Instruction { 21 | PtrT address; 22 | unsigned int id; 23 | uint8_t size; 24 | 25 | std::string opStr; 26 | 27 | /// @brief Create an invalid instruction 28 | Instruction(PtrT addr, uint8_t size); 29 | Instruction(cs_insn *raw); 30 | }; 31 | 32 | using InstructionCacheT = std::vector; 33 | using ConstInstructionIt = InstructionCacheT::const_iterator; 34 | 35 | Disassembler(const Macho::Context &mCtx, 36 | Provider::ActivityLogger &activity, 37 | std::shared_ptr logger, 38 | Provider::FunctionTracker

&funcTracker); 39 | ~Disassembler(); 40 | Disassembler(const Disassembler &) = delete; 41 | Disassembler(Disassembler &&o); 42 | Disassembler &operator=(const Disassembler &) = delete; 43 | Disassembler &operator=(Disassembler &&o); 44 | 45 | void load(); 46 | ConstInstructionIt instructionAtAddr(PtrT addr) const; 47 | ConstInstructionIt instructionsBegin() const; 48 | ConstInstructionIt instructionsEnd() const; 49 | 50 | private: 51 | /// @brief Disassemble an entire function 52 | /// @param offset Byte offset from the text seg 53 | /// @param size Size of function 54 | void disasmFunc(uint32_t offset, uint32_t size); 55 | 56 | /// @brief Disassemble part of a function 57 | /// @param offset Byte offset from the text seg 58 | /// @param size Size of chunk 59 | void disasmChunk(uint32_t offset, uint32_t size); 60 | 61 | /// @brief Recover from failed disassembly 62 | /// @return The size of the recovered instruction 63 | uint32_t recover(uint32_t offset); 64 | 65 | const Macho::Context *mCtx; 66 | Provider::ActivityLogger *activity; 67 | std::shared_ptr logger; 68 | Provider::FunctionTracker

*funcTracker; 69 | 70 | InstructionCacheT instructions; 71 | uint8_t *textData = nullptr; // text segment data 72 | PtrT textAddr = 0; // text segment address 73 | bool disassembled = false; 74 | csh handle; 75 | 76 | static inline auto dataInCodeComp = [](const auto &rhs, const auto &lhs) { 77 | return rhs.offset < lhs.offset; 78 | }; 79 | std::set dataInCodeEntries; 80 | }; 81 | 82 | } // namespace DyldExtractor::Provider 83 | 84 | #endif // __PROVIDER_DISASSEMBLER__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/ExtraData.cpp: -------------------------------------------------------------------------------- 1 | #include "ExtraData.h" 2 | 3 | using namespace DyldExtractor; 4 | using namespace Provider; 5 | 6 | template 7 | ExtraData

::ExtraData(std::string extendsSeg, ExtraData

::PtrT addr, 8 | PtrT size) 9 | : extendsSeg(extendsSeg), baseAddr(addr), store(size) {} 10 | 11 | template ExtraData

::PtrT ExtraData

::getBaseAddr() const { 12 | return baseAddr; 13 | } 14 | 15 | template ExtraData

::PtrT ExtraData

::getEndAddr() const { 16 | return baseAddr + (PtrT)store.size(); 17 | } 18 | 19 | template uint8_t *ExtraData

::getData() { return store.data(); } 20 | template const uint8_t *ExtraData

::getData() const { 21 | return store.data(); 22 | } 23 | 24 | template const std::string &ExtraData

::getExtendsSeg() const { 25 | return extendsSeg; 26 | } 27 | 28 | template class ExtraData; 29 | template class ExtraData; 30 | -------------------------------------------------------------------------------- /DyldExtractor/Provider/ExtraData.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_EXTRADATA__ 2 | #define __PROVIDER_EXTRADATA__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace DyldExtractor::Provider { 10 | 11 | /// @brief Contains in memory data that is added to the image. Extends a 12 | /// segment. 13 | template class ExtraData { 14 | using PtrT = P::PtrT; 15 | 16 | public: 17 | ExtraData(std::string extendsSeg, PtrT addr, PtrT size); 18 | ExtraData(const ExtraData &) = delete; 19 | ExtraData(ExtraData &&) = default; 20 | ExtraData &operator=(const ExtraData &) = delete; 21 | ExtraData &operator=(ExtraData &&) = default; 22 | 23 | /// @brief Get the beginning address. 24 | PtrT getBaseAddr() const; 25 | 26 | /// @brief Get the end address. 27 | PtrT getEndAddr() const; 28 | 29 | uint8_t *getData(); 30 | const uint8_t *getData() const; 31 | 32 | /// @brief Get the name of the segment that this extends. 33 | const std::string &getExtendsSeg() const; 34 | 35 | private: 36 | std::string extendsSeg; 37 | PtrT baseAddr; 38 | std::vector store; 39 | }; 40 | 41 | } // namespace DyldExtractor::Provider 42 | 43 | #endif // __PROVIDER_EXTRADATA__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/FunctionTracker.cpp: -------------------------------------------------------------------------------- 1 | #include "FunctionTracker.h" 2 | 3 | #include 4 | 5 | using namespace DyldExtractor; 6 | using namespace Provider; 7 | 8 | template 9 | FunctionTracker

::FunctionTracker(const Macho::Context &mCtx, 10 | std::shared_ptr logger) 11 | : mCtx(&mCtx), logger(logger) {} 12 | 13 | template void FunctionTracker

::load() { 14 | if (loaded) { 15 | return; 16 | } 17 | loaded = true; 18 | 19 | auto [textSeg, textSect] = mCtx->getSection(SEG_TEXT, SECT_TEXT); 20 | auto leSeg = mCtx->getSegment(SEG_LINKEDIT)->command; 21 | 22 | auto funcStartsCmd = mCtx->getFirstLC( 23 | {LC_FUNCTION_STARTS}); 24 | const uint8_t *leFile = mCtx->convertAddr(leSeg->vmaddr).second; 25 | const uint8_t *p = leFile + funcStartsCmd->dataoff; 26 | const uint8_t *const end = p + funcStartsCmd->datasize; 27 | 28 | PtrT funcAddr = textSeg->command->vmaddr + (PtrT)Utils::readUleb128(p, end); 29 | if (funcAddr == textSeg->command->vmaddr) { 30 | return; 31 | } 32 | 33 | while (*p) { 34 | PtrT next = (PtrT)Utils::readUleb128(p, end); 35 | functions.emplace_back(funcAddr, next); 36 | funcAddr += next; 37 | } 38 | 39 | // Add last function 40 | functions.emplace_back(funcAddr, textSect->addr + textSect->size - funcAddr); 41 | } 42 | 43 | template 44 | const std::vector::Function> & 45 | FunctionTracker

::getFunctions() const { 46 | return functions; 47 | } 48 | 49 | template class FunctionTracker; 50 | template class FunctionTracker; -------------------------------------------------------------------------------- /DyldExtractor/Provider/FunctionTracker.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_FUNCTIONTRACKER__ 2 | #define __PROVIDER_FUNCTIONTRACKER__ 3 | 4 | #include 5 | #include 6 | 7 | namespace DyldExtractor::Provider { 8 | 9 | /// @brief Reads and stores function starts. 10 | template class FunctionTracker { 11 | using PtrT = P::PtrT; 12 | 13 | public: 14 | class Function { 15 | public: 16 | PtrT address; 17 | PtrT size; 18 | }; 19 | 20 | FunctionTracker(const Macho::Context &mCtx, 21 | std::shared_ptr logger); 22 | FunctionTracker(const FunctionTracker &) = delete; 23 | FunctionTracker(FunctionTracker &&o) = default; 24 | FunctionTracker &operator=(const FunctionTracker &) = delete; 25 | FunctionTracker &operator=(FunctionTracker &&o) = default; 26 | 27 | void load(); 28 | const std::vector &getFunctions() const; 29 | 30 | private: 31 | const Macho::Context *mCtx; 32 | std::shared_ptr logger; 33 | 34 | bool loaded = false; 35 | std::vector functions; 36 | }; 37 | 38 | } // namespace DyldExtractor::Provider 39 | 40 | #endif // __PROVIDER_FUNCTIONTRACKER__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/LinkeditTracker.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_LINKEDITTRACKER__ 2 | #define __PROVIDER_LINKEDITTRACKER__ 3 | 4 | #include 5 | 6 | namespace DyldExtractor::Provider { 7 | 8 | /// @brief Manages the linkedit region during extraction. Keeps the load 9 | /// command offsets synced with the tracked data, unless changeOffset is called. 10 | template class LinkeditTracker { 11 | using PtrT = P::PtrT; 12 | 13 | public: 14 | enum class Tag { 15 | chained = 0, // Chained fixups 16 | detachedExportTrie = 1, // Detached export trie 17 | rebase = 2, // Rebase info 18 | binding = 3, // Binding info 19 | weakBinding = 4, // Weak binding info 20 | lazyBinding = 5, // Lazy binding info 21 | exportTrie = 6, // Export trie in dyld_info_command 22 | functionStarts = 7, // Function starts 23 | dataInCode = 8, // Data in code 24 | symtab = 9, // Symbol table entries 25 | indirectSymtab = 10, // Indirect symbol table 26 | stringPool = 11, // String pool 27 | }; 28 | 29 | /// @brief Describes tracked data 30 | struct Metadata { 31 | Tag tag; 32 | uint8_t *data; 33 | uint32_t dataSize; 34 | uint32_t *offsetField; 35 | 36 | Metadata(Tag tag, uint8_t *data, uint32_t dataSize, 37 | Macho::Loader::load_command *lc); 38 | uint8_t *end() const; 39 | }; 40 | 41 | using MetadataIt = std::vector::iterator; 42 | 43 | /// @brief Create a tracker with a set of tracked data 44 | /// 45 | /// Throws an exception in any of the following cases. 46 | /// * Data is not pointer aligned. 47 | /// * Data does not make up a continuous range. 48 | /// * Data is outside the linkedit or load command regions. 49 | /// 50 | /// @param mCtx The macho context 51 | /// @param linkeditSize The max size of the linkedit region 52 | /// @param initialData The initial set of data to manage 53 | LinkeditTracker(Macho::Context &mCtx, uint64_t linkeditSize, 54 | std::vector initialData); 55 | LinkeditTracker(const LinkeditTracker

&) = delete; 56 | LinkeditTracker(LinkeditTracker

&&) = default; 57 | LinkeditTracker

&operator=(const LinkeditTracker

&) = delete; 58 | LinkeditTracker

&operator=(LinkeditTracker

&&) = default; 59 | 60 | MetadataIt metadataBegin(); 61 | MetadataIt metadataEnd(); 62 | Macho::Loader::load_command *lcBegin(); 63 | Macho::Loader::load_command *lcEnd(); 64 | 65 | /// @brief Get a pointer to the beginning of Linkedit data. 66 | const uint8_t *getData() const; 67 | 68 | /// @brief Find metadata with a tag 69 | /// @param tag The tag to search for 70 | /// @return An iterator to the metadata or the past end iterator 71 | MetadataIt findTag(Tag tag); 72 | 73 | /// @brief Resize the given tracked data 74 | /// @param metaIt Iterator to the metadata, is still valid after the operation 75 | /// @param newSize The new size, must be pointer aligned 76 | /// @returns If there was enough space for the operation. 77 | bool resizeData(MetadataIt metaIt, uint32_t newSize); 78 | 79 | /// @brief Add data into the linkedit 80 | /// @param meta The metadata for the data, the data size must be pointer 81 | /// aligned. Data pointer and offset field does not have to be valid. 82 | /// @param data Pointer to the source of data 83 | /// @param copySize The size of data to copy into the region. Must be less 84 | /// than or equal to size in data. 85 | /// @return An iterator to the new tracked data, and a boolean indicating if 86 | /// there was enough space for the operation. 87 | std::pair addData(Metadata meta, const uint8_t *const data, 88 | uint32_t copySize); 89 | 90 | /// @brief Remove data from the linkedit 91 | /// @param pos The data to remove. 92 | void removeData(MetadataIt pos); 93 | 94 | /// @brief Insert a load command into the header, triggers a reload on the 95 | /// MachOContext. 96 | /// @param pos The position of the new load command. 97 | /// @param lc The load command to insert 98 | /// @return An pointer to the inserted load command, and a boolean indicating 99 | /// if there was enough space for the operation. 100 | std::pair 101 | insertLC(Macho::Loader::load_command *pos, Macho::Loader::load_command *lc); 102 | 103 | /// @brief Remove a load command from the header, triggers a reload on the 104 | /// MachOContext. 105 | /// @param lc The load command to remove. 106 | void removeLC(Macho::Loader::load_command *lc); 107 | 108 | /// @brief Get the amount of bytes available for new load commands. 109 | uint32_t freeLCSpace() const; 110 | 111 | /// @brief Change the linkedit region offset, doesn't shift any data but load 112 | /// commands are updated. Causes de-sync with the load commands, meaning 113 | /// that all offsets in the load commands are invalidated! 114 | /// @param offset The new offset 115 | void changeOffset(uint32_t offset); 116 | 117 | private: 118 | Macho::Context *mCtx; 119 | std::vector metadata; 120 | 121 | Macho::Loader::segment_command

*leSeg; 122 | uint64_t leOffset; // file offset to the start of linkedit data region 123 | uint8_t *leData; // pointer to start of linkedit data region 124 | uint8_t *leDataEnd; // pointer to end of the entire data region 125 | 126 | uint8_t *cmdsData; // pointer to start of load commands data region 127 | uint8_t *cmdsDataEnd; // pointer to the past the end load command 128 | uint64_t cmdsMaxSize; // Maximum space allowed for load commands 129 | 130 | static uint32_t lcOffsetForTag(Tag tag); 131 | }; 132 | 133 | } // namespace DyldExtractor::Provider 134 | 135 | #endif // __PROVIDER_LINKEDITTRACKER__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/PointerTracker.cpp: -------------------------------------------------------------------------------- 1 | #include "PointerTracker.h" 2 | 3 | #include 4 | 5 | using namespace DyldExtractor; 6 | using namespace Provider; 7 | 8 | template 9 | bool PointerTracker

::MappingSlideInfo::containsAddr( 10 | const uint64_t addr) const { 11 | return addr >= address && addr < address + size; 12 | } 13 | 14 | template 15 | const uint8_t * 16 | PointerTracker

::MappingSlideInfo::convertAddr(const uint64_t addr) const { 17 | return (addr - address) + data; 18 | } 19 | 20 | template 21 | PointerTracker

::PointerTracker( 22 | const Dyld::Context &dCtx, 23 | std::optional> logger) 24 | : dCtx(&dCtx), logger(logger) { 25 | fillMappings(); 26 | } 27 | 28 | template 29 | PointerTracker

::PtrT PointerTracker

::slideP(const PtrT addr) const { 30 | for (auto &map : mappings) { 31 | if (!map.containsAddr(addr)) { 32 | continue; 33 | } 34 | auto ptr = map.convertAddr(addr); 35 | 36 | switch (map.slideInfoVersion) { 37 | case 1: { 38 | return *(PtrT *)ptr; 39 | } 40 | case 2: { 41 | auto slideInfo = (dyld_cache_slide_info2 *)map.slideInfo; 42 | auto val = *(PtrT *)ptr & ~slideInfo->delta_mask; 43 | if (val != 0) { 44 | val += slideInfo->value_add; 45 | } 46 | return (PtrT)val; 47 | } 48 | case 3: { 49 | auto ptrInfo = (dyld_cache_slide_pointer3 *)ptr; 50 | if (ptrInfo->auth.authenticated) { 51 | auto slideInfo = (dyld_cache_slide_info3 *)map.slideInfo; 52 | return (PtrT)ptrInfo->auth.offsetFromSharedCacheBase + 53 | (PtrT)slideInfo->auth_value_add; 54 | } else { 55 | uint64_t value51 = ptrInfo->plain.pointerValue; 56 | uint64_t top8Bits = value51 & 0x0007F80000000000ULL; 57 | uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFFULL; 58 | return (PtrT)(top8Bits << 13) | (PtrT)bottom43Bits; 59 | } 60 | } 61 | case 4: { 62 | auto slideInfo = (dyld_cache_slide_info4 *)map.slideInfo; 63 | auto newValue = *(uint32_t *)ptr & ~(slideInfo->delta_mask); 64 | return (PtrT)newValue + (PtrT)slideInfo->value_add; 65 | } 66 | default: { 67 | if (logger) { 68 | SPDLOG_LOGGER_ERROR(*logger, "Unknown slide info version {}.", 69 | map.slideInfoVersion); 70 | } 71 | return 0; 72 | } 73 | } 74 | } 75 | 76 | return 0; 77 | } 78 | 79 | template 80 | void PointerTracker

::add(const PtrT addr, const PtrT target) { 81 | pointers[addr] = target; 82 | } 83 | 84 | template 85 | void PointerTracker

::addAuth(const PtrT addr, AuthData data) { 86 | authData[addr] = data; 87 | } 88 | 89 | template 90 | void PointerTracker

::copyAuth(const PtrT addr, const PtrT sAddr) { 91 | for (const auto mapI : authMappings) { 92 | const auto &map = mappings.at(mapI); 93 | if (map.containsAddr(sAddr)) { 94 | 95 | auto p = (dyld_cache_slide_pointer3 *)map.convertAddr(sAddr); 96 | if (p->auth.authenticated) { 97 | addAuth(addr, 98 | {(uint16_t)p->auth.diversityData, 99 | (bool)p->auth.hasAddressDiversity, (uint8_t)p->auth.key}); 100 | } 101 | break; 102 | } 103 | } 104 | } 105 | 106 | template 107 | void PointerTracker

::removePointers(const PtrT start, const PtrT end) { 108 | // Remove from pointers, auth, and bind 109 | pointers.erase(pointers.lower_bound(start), pointers.upper_bound(end)); 110 | authData.erase(authData.lower_bound(start), authData.upper_bound(end)); 111 | bindData.erase(bindData.lower_bound(start), bindData.upper_bound(end)); 112 | } 113 | 114 | template 115 | void PointerTracker

::addBind(const PtrT addr, 116 | std::shared_ptr data) { 117 | bindData[addr] = data; 118 | } 119 | 120 | template 121 | const std::vector::MappingSlideInfo> & 122 | PointerTracker

::getMappings() const { 123 | return mappings; 124 | } 125 | 126 | template 127 | std::vector::MappingSlideInfo *> 128 | PointerTracker

::getSlideMappings() const { 129 | std::vector results; 130 | results.reserve(mappings.size()); 131 | for (const auto &mapI : slideMappings) { 132 | results.emplace_back(&mappings[mapI]); 133 | } 134 | return results; 135 | } 136 | 137 | template 138 | const std::map::PtrT, 139 | typename PointerTracker

::PtrT> & 140 | PointerTracker

::getPointers() const { 141 | return pointers; 142 | } 143 | 144 | template 145 | const std::map::PtrT, 146 | typename PointerTracker

::AuthData> & 147 | PointerTracker

::getAuths() const { 148 | return authData; 149 | } 150 | 151 | template 152 | const std::map::PtrT, 153 | std::shared_ptr> & 154 | PointerTracker

::getBinds() const { 155 | return bindData; 156 | } 157 | 158 | template uint32_t PointerTracker

::getPageSize() const { 159 | const auto slideMaps = getSlideMappings(); 160 | if (!slideMaps.size()) { 161 | if (logger) { 162 | SPDLOG_LOGGER_ERROR(*logger, "No slide info to infer pagesize."); 163 | } 164 | return 0x1000; 165 | } 166 | 167 | const auto map = *slideMaps.begin(); 168 | switch (map->slideInfoVersion) { 169 | case 1: 170 | // Assume 0x1000 171 | return 0x1000; 172 | case 2: 173 | case 3: 174 | case 4: { 175 | // page size in second uint32_t field 176 | auto pageSize = reinterpret_cast(map->slideInfo)[1]; 177 | return pageSize; 178 | } 179 | default: { 180 | if (logger) { 181 | SPDLOG_LOGGER_WARN(*logger, "Unknown slide info version {}.", 182 | map->slideInfoVersion); 183 | } 184 | return 0x1000; 185 | } 186 | } 187 | } 188 | 189 | template void PointerTracker

::fillMappings() { 190 | if (dCtx->header->slideInfoOffsetUnused) { 191 | // Assume legacy case with no sub caches, and only one slide info 192 | auto maps = 193 | (dyld_cache_mapping_info *)(dCtx->file + dCtx->header->mappingOffset); 194 | 195 | // First mapping doesn't have slide info 196 | mappings.emplace_back(dCtx->file + maps->fileOffset, maps->address, 197 | maps->size, 0, nullptr); 198 | 199 | // slide info corresponds to the second mapping 200 | auto map2 = maps + 1; 201 | auto slideInfo = dCtx->file + dCtx->header->slideInfoOffsetUnused; 202 | uint32_t slideVer = *(uint32_t *)slideInfo; 203 | mappings.emplace_back(dCtx->file + map2->fileOffset, map2->address, 204 | map2->size, slideVer, slideInfo); 205 | slideMappings.push_back(1); 206 | if (slideVer == 3) { 207 | authMappings.push_back(1); 208 | } 209 | 210 | // Add other mappings 211 | for (uint32_t i = 2; i < dCtx->header->mappingCount; i++) { 212 | auto map = maps + i; 213 | mappings.emplace_back(dCtx->file + map->address, map->address, map->size, 214 | 0, nullptr); 215 | } 216 | return; 217 | } 218 | 219 | if (!dCtx->headerContainsMember( 220 | offsetof(dyld_cache_header, mappingWithSlideOffset))) { 221 | if (logger) { 222 | SPDLOG_LOGGER_ERROR(*logger, "Unable to get mapping and slide info."); 223 | } 224 | return; 225 | } 226 | 227 | // Get all mappings from all caches 228 | auto extendInfo = [this](const Dyld::Context &ctx) { 229 | if (!ctx.header->mappingWithSlideCount) { 230 | return; 231 | } 232 | auto start = (dyld_cache_mapping_and_slide_info 233 | *)(ctx.file + ctx.header->mappingWithSlideOffset); 234 | auto end = start + ctx.header->mappingWithSlideCount; 235 | for (auto i = start; i < end; i++) { 236 | if (i->slideInfoFileOffset) { 237 | auto slideInfo = ctx.file + i->slideInfoFileOffset; 238 | auto slideVer = *(uint32_t *)slideInfo; 239 | mappings.emplace_back(ctx.file + i->fileOffset, i->address, i->size, 240 | slideVer, slideInfo); 241 | } else { 242 | mappings.emplace_back(ctx.file + i->fileOffset, i->address, i->size, 0, 243 | nullptr); 244 | } 245 | } 246 | }; 247 | 248 | extendInfo(*dCtx); 249 | for (auto &ctx : dCtx->subcaches) { 250 | extendInfo(ctx); 251 | } 252 | 253 | // fill other mappings as mappings should be constant now 254 | for (int i = 0; i < mappings.size(); i++) { 255 | const auto &map = mappings.at(i); 256 | if (map.slideInfo != nullptr) { 257 | slideMappings.push_back(i); 258 | } 259 | if (map.slideInfoVersion == 3) { 260 | authMappings.push_back(i); 261 | } 262 | } 263 | } 264 | 265 | template class PointerTracker; 266 | template class PointerTracker; -------------------------------------------------------------------------------- /DyldExtractor/Provider/PointerTracker.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_POINTERTRACKER__ 2 | #define __PROVIDER_POINTERTRACKER__ 3 | 4 | #include "Symbolizer.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace DyldExtractor::Provider { 14 | 15 | template class PointerTracker { 16 | using PtrT = P::PtrT; 17 | 18 | public: 19 | struct AuthData { 20 | uint16_t diversity; 21 | bool hasAddrDiv; 22 | uint8_t key; 23 | }; 24 | 25 | struct MappingSlideInfo { 26 | const uint8_t *data; 27 | const uint64_t address; 28 | const uint64_t size; 29 | 30 | const uint32_t slideInfoVersion; 31 | const uint8_t *slideInfo; 32 | 33 | /// @brief Check if the mapping contains the address. 34 | /// @param addr The address to check 35 | /// @return If the mapping contains the address 36 | bool containsAddr(const uint64_t addr) const; 37 | 38 | /// @brief Convert the address to a pointer 39 | /// @param addr The address to convert 40 | /// @return A pointer regardless if the mapping contains it. 41 | const uint8_t *convertAddr(const uint64_t addr) const; 42 | }; 43 | 44 | PointerTracker( 45 | const Dyld::Context &dCtx, 46 | std::optional> logger = std::nullopt); 47 | PointerTracker(const PointerTracker &) = delete; 48 | PointerTracker &operator=(const PointerTracker &) = delete; 49 | 50 | /// @brief Slide the pointer at the address 51 | /// @param address The address of the pointer. 52 | /// @returns The slid pointer value. 53 | PtrT slideP(const PtrT addr) const; 54 | 55 | /// @brief Slide the struct at the address. 56 | /// @tparam T The type of struct. 57 | /// @param address The address of the struct. 58 | /// @returns The slid struct. 59 | template T slideS(const PtrT address) const { 60 | T data = *reinterpret_cast(dCtx->convertAddrP(address)); 61 | for (auto offset : T::PTRS()) { 62 | *(PtrT *)((uint8_t *)&data + (PtrT)offset) = 63 | slideP(address + (PtrT)offset); 64 | } 65 | return data; 66 | } 67 | 68 | /// @brief Add a pointer to tracking, overwriting if already added. 69 | /// @param addr Address of the pointer. 70 | /// @param target The target address. 71 | void add(const PtrT addr, const PtrT target); 72 | 73 | /// @brief Add a struct to tracking 74 | /// @tparam T The type of data 75 | /// @param addr The address of the struct 76 | /// @param data The new targets of the pointers 77 | template void addS(const PtrT addr, const T &data) { 78 | for (auto offset : T::PTRS()) { 79 | add(addr + (PtrT)offset, *(PtrT *)((uint8_t *)&data + offset)); 80 | } 81 | } 82 | 83 | /// @brief Add pointer auth data for a pointer. 84 | /// @param addr Address of the pointer 85 | /// @param data The auth data 86 | void addAuth(const PtrT addr, AuthData data); 87 | 88 | /// @brief Copy and add auth data for a pointer 89 | /// @param addr The address of the pointer 90 | /// @param sAddr The address of the pointer to copy auth data from 91 | void copyAuth(const PtrT addr, const PtrT sAddr); 92 | 93 | /// @brief Copy and add auth data for a struct 94 | /// @tparam T The type of struct 95 | /// @param addr The address of the struct 96 | /// @param sAddr The address to copy auth data from 97 | template void copyAuthS(PtrT addr, PtrT sAddr) { 98 | // Check if the source address is within an auth mapping 99 | for (const auto mapI : authMappings) { 100 | const auto &map = mappings.at(mapI); 101 | if (map.containsAddr(sAddr)) { 102 | // Copy auth data for each pointer if needed 103 | auto sLoc = map.convertAddr(sAddr); 104 | for (auto offset : T::PTRS()) { 105 | auto p = (dyld_cache_slide_pointer3 *)(sLoc + offset); 106 | if (p->auth.authenticated) { 107 | addAuth(addr + (PtrT)offset, 108 | {(uint16_t)p->auth.diversityData, 109 | (bool)p->auth.hasAddressDiversity, (uint8_t)p->auth.key}); 110 | } 111 | } 112 | break; 113 | } 114 | } 115 | } 116 | 117 | /// @brief Remove pointers from tracking, and all maps 118 | /// @param start The first pointer to remove 119 | /// @param end The last pointer to remove, exclusive 120 | void removePointers(const PtrT start, const PtrT end); 121 | 122 | /// @brief Add bind data for a pointer 123 | /// @param addr The address of the pointer 124 | /// @param data Symbolic info for the bind 125 | void addBind(const PtrT addr, std::shared_ptr data); 126 | 127 | /// @brief Get all mappings 128 | const std::vector &getMappings() const; 129 | 130 | /// @brief Get all mappings with slide info 131 | std::vector getSlideMappings() const; 132 | 133 | const std::map &getPointers() const; 134 | 135 | const std::map &getAuths() const; 136 | 137 | const std::map> &getBinds() const; 138 | 139 | /// @brief Get the page size. 140 | uint32_t getPageSize() const; 141 | 142 | private: 143 | void fillMappings(); 144 | 145 | const Dyld::Context *dCtx; 146 | std::optional> logger; 147 | 148 | std::vector mappings; 149 | std::vector slideMappings; 150 | std::vector authMappings; 151 | 152 | std::map pointers; 153 | std::map authData; 154 | std::map> bindData; 155 | }; 156 | 157 | }; // namespace DyldExtractor::Provider 158 | 159 | #endif // __PROVIDER_POINTERTRACKER__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/README.md: -------------------------------------------------------------------------------- 1 | # Providers 2 | Providers are classes that provide services, like pointer tracking or disassembly. 3 | 4 | ## Specification 5 | * Constructed and contained within an ExtractionContext. 6 | * Should be movable if it is contained in the extraction context. 7 | * Copy constructors and assignments should be deleted 8 | * Cannot take ExtractionContext as an argument. 9 | * Data can be preloaded or loaded manually. With a `load` method. 10 | * Should protect against multiple calls to the load function 11 | 12 | ## Notes 13 | Be very careful of holding pointers to load commands, and data into the linkedit. Pointers may be invalidated unexpectedly. -------------------------------------------------------------------------------- /DyldExtractor/Provider/SymbolTableTracker.cpp: -------------------------------------------------------------------------------- 1 | #include "SymbolTableTracker.h" 2 | 3 | #include 4 | 5 | using namespace DyldExtractor; 6 | using namespace Provider; 7 | 8 | template 9 | const std::string &SymbolTableTracker

::addString(const std::string &str) { 10 | return *strings.insert(str).first; 11 | } 12 | 13 | template 14 | SymbolTableTracker

::SymbolIndex 15 | SymbolTableTracker

::addSym(SymbolType type, const std::string &str, 16 | const Macho::Loader::nlist

&sym) { 17 | auto it = strings.find(str); 18 | uint32_t index; 19 | switch (type) { 20 | case SymbolType::other: 21 | index = (uint32_t)syms.other.size(); 22 | syms.other.emplace_back(it, sym); 23 | break; 24 | case SymbolType::local: 25 | index = (uint32_t)syms.local.size(); 26 | syms.local.emplace_back(it, sym); 27 | break; 28 | case SymbolType::external: 29 | index = (uint32_t)syms.external.size(); 30 | syms.external.emplace_back(it, sym); 31 | break; 32 | case SymbolType::undefined: 33 | index = (uint32_t)syms.undefined.size(); 34 | syms.undefined.emplace_back(it, sym); 35 | break; 36 | default: 37 | Utils::unreachable(); 38 | } 39 | 40 | return std::make_pair(type, index); 41 | } 42 | 43 | template 44 | const std::pair::const_iterator, Macho::Loader::nlist

> 45 | &SymbolTableTracker

::getSymbol(const SymbolIndex &index) const { 46 | switch (index.first) { 47 | case SymbolType::other: 48 | return syms.other.at(index.second); 49 | case SymbolType::local: 50 | return syms.local.at(index.second); 51 | case SymbolType::external: 52 | return syms.external.at(index.second); 53 | case SymbolType::undefined: 54 | return syms.undefined.at(index.second); 55 | default: 56 | Utils::unreachable(); 57 | } 58 | } 59 | 60 | template 61 | const typename SymbolTableTracker

::StringCache & 62 | SymbolTableTracker

::getStrings() const { 63 | return strings; 64 | } 65 | 66 | template 67 | const typename SymbolTableTracker

::SymbolCaches & 68 | SymbolTableTracker

::getSymbolCaches() const { 69 | return syms; 70 | } 71 | 72 | template 73 | typename SymbolTableTracker

::SymbolIndex & 74 | SymbolTableTracker

::getOrMakeRedactedSymIndex() { 75 | if (redactedSymIndex) { 76 | return *redactedSymIndex; 77 | } 78 | 79 | auto &str = addString(""); 80 | Macho::Loader::nlist

sym = {0}; 81 | sym.n_type = 1; 82 | return redactedSymIndex.emplace(addSym(SymbolType::other, str, sym)); 83 | } 84 | 85 | template 86 | const std::optional::SymbolIndex> & 87 | SymbolTableTracker

::getRedactedSymIndex() const { 88 | return redactedSymIndex; 89 | } 90 | 91 | template class SymbolTableTracker; 92 | template class SymbolTableTracker; -------------------------------------------------------------------------------- /DyldExtractor/Provider/SymbolTableTracker.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER__SYMBOLTABLETRACKER__ 2 | #define __PROVIDER__SYMBOLTABLETRACKER__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace DyldExtractor::Provider { 11 | 12 | /// @brief Represents a dynamic symbol table 13 | template class SymbolTableTracker { 14 | public: 15 | enum class SymbolType { other, local, external, undefined }; 16 | using StringCache = std::set; 17 | using SymbolIndex = std::pair; 18 | 19 | struct SymbolCaches { 20 | using SymbolCacheT = std::vector< 21 | std::pair>>; 22 | SymbolCacheT other; 23 | SymbolCacheT local; 24 | SymbolCacheT external; 25 | SymbolCacheT undefined; 26 | }; 27 | 28 | SymbolTableTracker() = default; 29 | SymbolTableTracker(const SymbolTableTracker &) = delete; 30 | SymbolTableTracker(SymbolTableTracker &&) = default; 31 | SymbolTableTracker &operator=(const SymbolTableTracker &) = delete; 32 | SymbolTableTracker &operator=(SymbolTableTracker &&) = default; 33 | 34 | /// @brief Add a string 35 | const std::string &addString(const std::string &str); 36 | 37 | /// @brief Add a symbol 38 | /// @param type The type of symbol, string index does not have to be valid 39 | /// @param str The string associated with the symbol, must already be added 40 | /// @param sym The symbol metadata 41 | /// @returns The symbol type and index pair. 42 | SymbolIndex addSym(SymbolType type, const std::string &str, 43 | const Macho::Loader::nlist

&sym); 44 | 45 | const std::pair> & 46 | getSymbol(const SymbolIndex &index) const; 47 | 48 | /// @brief Get tracked strings 49 | const StringCache &getStrings() const; 50 | 51 | /// @brief Get tracked symbols 52 | const SymbolCaches &getSymbolCaches() const; 53 | 54 | /// @brief Get or create a " symbol with n_type=1" 55 | /// @return The symbol index 56 | SymbolIndex &getOrMakeRedactedSymIndex(); 57 | const std::optional &getRedactedSymIndex() const; 58 | 59 | // indirect symbols, the second pair element is the index into that group of 60 | // symbols 61 | std::vector indirectSyms; 62 | 63 | private: 64 | StringCache strings; 65 | SymbolCaches syms; 66 | 67 | std::optional redactedSymIndex; 68 | }; 69 | 70 | } // namespace DyldExtractor::Provider 71 | 72 | #endif // __PROVIDER__SYMBOLTABLETRACKER__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/Symbolizer.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_SYMBOLIZER__ 2 | #define __PROVIDER_SYMBOLIZER__ 3 | 4 | #include "ActivityLogger.h" 5 | #include "SymboltableTracker.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace DyldExtractor::Provider { 12 | 13 | /// Provides symbolic info for a single address 14 | class SymbolicInfo { 15 | public: 16 | struct Symbol { 17 | /// The name of the symbol 18 | std::string name; 19 | /// The ordinal of the dylib the symbol comes from 20 | uint64_t ordinal; 21 | /// Export flags if the symbol was generated from export info 22 | std::optional exportFlags; 23 | 24 | /// @brief If the symbol is a ReExport 25 | /// @returns false if exportFlags is not given 26 | bool isReExport() const; 27 | 28 | std::strong_ordering operator<=>(const Symbol &rhs) const; 29 | }; 30 | 31 | enum class Encoding : uint8_t { 32 | None = 0b00, 33 | Arm = 0b00, 34 | Thumb = 0b01, 35 | Jazelle = 0b10, 36 | ThumbEE = 0b11 37 | }; 38 | 39 | /// @brief Construct with one symbol 40 | SymbolicInfo(Symbol first, Encoding encoding); 41 | /// @brief Construct by copying symbols, must not be empty 42 | SymbolicInfo(std::set &symbols, Encoding encoding); 43 | /// @brief Construct by moving symbols, must not be empty 44 | SymbolicInfo(std::set &&symbols, Encoding encoding); 45 | 46 | void addSymbol(Symbol sym); 47 | 48 | /// @brief Get the preferred symbol 49 | const Symbol &preferredSymbol() const; 50 | 51 | std::set symbols; 52 | 53 | /// @brief The instruction set encoding. set by the first 2 LSBits. Only 54 | /// applies to ARMv7 55 | Encoding encoding; 56 | }; 57 | 58 | template class Symbolizer { 59 | using P = A::P; 60 | using PtrT = P::PtrT; 61 | 62 | public: 63 | Symbolizer(const Dyld::Context &dCtx, Macho::Context &mCtx, 64 | Provider::Accelerator

&accelerator, 65 | Provider::ActivityLogger &activity, 66 | std::shared_ptr logger, 67 | const Provider::SymbolTableTracker

&stTracker); 68 | Symbolizer(const Symbolizer &) = delete; 69 | Symbolizer &operator=(const Symbolizer &) = delete; 70 | 71 | /// @brief Look for a symbol 72 | /// @param addr The address of the symbol. Without instruction bits. 73 | /// @return A pointer to the symbolic info or a nullptr 74 | const SymbolicInfo *symbolizeAddr(PtrT addr) const; 75 | 76 | /// @brief Check if an address has symbolic info 77 | /// @param addr The address without instruction bits 78 | /// @return If there is symbolic info 79 | bool containsAddr(PtrT addr) const; 80 | 81 | /// @brief Get a shared pointer for a symbolic info 82 | /// @param addr The address without instruction bits 83 | /// @return A new shared pointer 84 | std::shared_ptr shareInfo(PtrT addr) const; 85 | 86 | private: 87 | void enumerateExports(); 88 | void enumerateSymbols(); 89 | void processSymbolCache( 90 | const typename Provider::SymbolTableTracker

::SymbolCaches::SymbolCacheT 91 | &symCache); 92 | 93 | using ExportEntry = Provider::AcceleratorTypes::SymbolizerExportEntry; 94 | using EntryMapT = Provider::AcceleratorTypes::SymbolizerExportEntryMapT; 95 | EntryMapT & 96 | processDylibCmd(const Macho::Loader::dylib_command *dylibCmd) const; 97 | std::vector 98 | readExports(const std::string &dylibPath, 99 | const Macho::Context &dylibCtx) const; 100 | 101 | const Dyld::Context *dCtx; 102 | Macho::Context *mCtx; 103 | Provider::Accelerator

*accelerator; 104 | Provider::ActivityLogger *activity; 105 | std::shared_ptr logger; 106 | const Provider::SymbolTableTracker

*stTracker; 107 | 108 | std::map> symbols; 109 | 110 | bool dataLoaded = false; 111 | }; 112 | 113 | } // namespace DyldExtractor::Provider 114 | 115 | #endif // __PROVIDER_SYMBOLIZER__ -------------------------------------------------------------------------------- /DyldExtractor/Provider/Validator.cpp: -------------------------------------------------------------------------------- 1 | #include "Validator.h" 2 | #include 3 | 4 | using namespace DyldExtractor; 5 | using namespace Provider; 6 | 7 | template 8 | Validator

::Validator(const Macho::Context &mCtx) : mCtx(&mCtx) {} 9 | 10 | template void Validator

::validate() { 11 | if (!mCtx->getSegment(SEG_LINKEDIT)) { 12 | throw std::exception("Missing Linkedit segment."); 13 | } 14 | 15 | if (!mCtx->getSegment(SEG_TEXT)) { 16 | throw std::exception("Missing Text segment."); 17 | } 18 | 19 | if (!mCtx->getSection(SEG_TEXT, SECT_TEXT).second) { 20 | throw std::exception("Missing text section."); 21 | } 22 | 23 | if (!mCtx->getFirstLC()) { 24 | throw std::exception("Missing symtab command."); 25 | } 26 | 27 | if (!mCtx->getFirstLC()) { 28 | throw std::exception("Missing dysymtab command."); 29 | } 30 | 31 | if (memcmp(mCtx->segments.back().command->segname, SEG_LINKEDIT, 32 | sizeof(SEG_LINKEDIT)) != 0) { 33 | throw std::exception( 34 | "Linkedit segment is not the last segment load command."); 35 | } 36 | 37 | { 38 | // Linkedit highest addr 39 | PtrT maxSegAddr = 0; 40 | PtrT leAddr = 0; 41 | for (const auto &seg : mCtx->segments) { 42 | if (memcmp(seg.command->segname, SEG_LINKEDIT, sizeof(SEG_LINKEDIT)) == 43 | 0) { 44 | leAddr = seg.command->vmaddr; 45 | } else { 46 | if (seg.command->vmaddr > maxSegAddr) { 47 | maxSegAddr = seg.command->vmaddr; 48 | } 49 | } 50 | } 51 | 52 | if (maxSegAddr > leAddr) { 53 | throw std::exception( 54 | "Linkedit segment does not have the highest address."); 55 | } 56 | 57 | if (leAddr % 0x4000) { 58 | throw std::exception( 59 | "Linkedit segment is not address aligned to 0x4000."); 60 | } 61 | } 62 | 63 | if (!mCtx->getFirstLC( 64 | {LC_FUNCTION_STARTS})) { 65 | throw std::exception("Missing function starts command."); 66 | } 67 | } 68 | 69 | template class Validator; 70 | template class Validator; -------------------------------------------------------------------------------- /DyldExtractor/Provider/Validator.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROVIDER_VALIDATOR__ 2 | #define __PROVIDER_VALIDATOR__ 3 | 4 | #include 5 | 6 | namespace DyldExtractor::Provider { 7 | 8 | /*** 9 | * @brief Validate assumptions used in converters and providers. Should be ran 10 | * before creating the ExtractionContext 11 | * 12 | * If any of the following conditions are not met an exception is thrown. 13 | * * MachO Context 14 | * * Contains a linkedit segment 15 | * * Contains a text segment and section 16 | * * Contains a symtab command 17 | * * Contains a dysymtab command 18 | * * Linkedit segment 19 | * * Is the last load command 20 | * * Highest in address 21 | * * Address is aligned to 0x4000 22 | * * Contains a function starts command 23 | * */ 24 | template class Validator { 25 | using PtrT = P::PtrT; 26 | 27 | public: 28 | Validator(const Macho::Context &mCtx); 29 | 30 | void validate(); 31 | 32 | private: 33 | const Macho::Context *mCtx; 34 | }; 35 | 36 | } // namespace DyldExtractor::Provider 37 | 38 | #endif // __PROVIDER_VALIDATOR__ -------------------------------------------------------------------------------- /DyldExtractor/Utils/Architectures.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTILS_ARCHITECTURES__ 2 | #define __UTILS_ARCHITECTURES__ 3 | 4 | #include 5 | 6 | namespace DyldExtractor::Utils { 7 | 8 | namespace Arch { 9 | 10 | class Pointer32 { 11 | public: 12 | using PtrT = uint32_t; 13 | using SPtrT = int32_t; 14 | }; 15 | 16 | class Pointer64 { 17 | public: 18 | using PtrT = uint64_t; 19 | using SPtrT = int64_t; 20 | }; 21 | 22 | struct x86_64 { 23 | using P = Pointer64; 24 | }; 25 | 26 | struct arm { 27 | using P = Pointer32; 28 | }; 29 | 30 | struct arm64 { 31 | using P = Pointer64; 32 | }; 33 | 34 | struct arm64_32 { 35 | using P = Pointer32; 36 | }; 37 | 38 | } // namespace Arch 39 | } // namespace DyldExtractor::Utils 40 | 41 | #endif // __UTILS_ARCHITECTURES__ -------------------------------------------------------------------------------- /DyldExtractor/Utils/ExtractionContext.cpp: -------------------------------------------------------------------------------- 1 | #include "ExtractionContext.h" 2 | #include "Architectures.h" 3 | 4 | using namespace DyldExtractor; 5 | using namespace Utils; 6 | 7 | template 8 | ExtractionContext::ExtractionContext(const Dyld::Context &dCtx, 9 | Macho::Context &mCtx, 10 | Provider::Accelerator

&accelerator, 11 | Provider::ActivityLogger &activity) 12 | : dCtx(&dCtx), mCtx(&mCtx), accelerator(&accelerator), activity(&activity), 13 | logger(activity.getLogger()), bindInfo(mCtx, activity), 14 | disasm(mCtx, activity, logger, funcTracker), 15 | funcTracker(mCtx, logger), ptrTracker(dCtx, logger) {} 16 | 17 | template class ExtractionContext; 18 | template class ExtractionContext; 19 | template class ExtractionContext; 20 | template class ExtractionContext; -------------------------------------------------------------------------------- /DyldExtractor/Utils/ExtractionContext.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTILS_EXTRACTIONCONTEXT__ 2 | #define __UTILS_EXTRACTIONCONTEXT__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace DyldExtractor::Utils { 19 | 20 | template class ExtractionContext { 21 | using P = A::P; 22 | 23 | public: 24 | const Dyld::Context *dCtx; 25 | Macho::Context *mCtx; 26 | Provider::Accelerator

*accelerator; 27 | Provider::ActivityLogger *activity; 28 | std::shared_ptr logger; 29 | 30 | Provider::BindInfo

bindInfo; 31 | Provider::Disassembler disasm; 32 | Provider::FunctionTracker

funcTracker; 33 | Provider::PointerTracker

ptrTracker; 34 | 35 | std::optional> symbolizer; 36 | std::optional> leTracker; 37 | std::optional> stTracker; 38 | std::optional> exObjc; 39 | 40 | ExtractionContext(const Dyld::Context &dCtx, Macho::Context &mCtx, 41 | Provider::Accelerator

&accelerator, 42 | Provider::ActivityLogger &activity); 43 | ExtractionContext(const ExtractionContext &other) = delete; 44 | ExtractionContext &operator=(const ExtractionContext &other) = delete; 45 | ExtractionContext(ExtractionContext &&other) = delete; 46 | ExtractionContext &operator=(ExtractionContext &&other) = delete; 47 | }; 48 | 49 | }; // namespace DyldExtractor::Utils 50 | 51 | #endif // __UTILS_EXTRACTIONCONTEXT__ -------------------------------------------------------------------------------- /DyldExtractor/Utils/Leb128.cpp: -------------------------------------------------------------------------------- 1 | #include "Leb128.h" 2 | 3 | #include 4 | 5 | using namespace DyldExtractor; 6 | 7 | uint64_t Utils::readUleb128(const uint8_t *&p, const uint8_t *end) { 8 | uint64_t result = 0; 9 | int bit = 0; 10 | do { 11 | if (p == end) { 12 | throw std::invalid_argument("malformed uleb128."); 13 | } 14 | uint64_t slice = *p & 0x7f; 15 | 16 | if (bit > 63) { 17 | throw std::invalid_argument("uleb128 too big for uint64."); 18 | } else { 19 | result |= (slice << bit); 20 | bit += 7; 21 | } 22 | } while (*p++ & 0x80); 23 | return result; 24 | } 25 | 26 | int64_t Utils::readSleb128(const uint8_t *&p, const uint8_t *end) { 27 | int64_t result = 0; 28 | int bit = 0; 29 | uint8_t byte = 0; 30 | do { 31 | if (p == end) { 32 | throw std::invalid_argument("malformed sleb128."); 33 | } 34 | byte = *p++; 35 | result |= (((int64_t)(byte & 0x7f)) << bit); 36 | bit += 7; 37 | } while (byte & 0x80); 38 | // sign extend negative numbers 39 | if (((byte & 0x40) != 0) && (bit < 64)) 40 | result |= (~0ULL) << bit; 41 | return result; 42 | } 43 | 44 | void Utils::appendUleb128(std::vector &out, uint64_t value) { 45 | uint8_t byte; 46 | do { 47 | byte = value & 0x7F; 48 | value &= ~0x7F; 49 | if (value != 0) 50 | byte |= 0x80; 51 | out.push_back(byte); 52 | value = value >> 7; 53 | } while (byte >= 0x80); 54 | } 55 | 56 | void Utils::appendSleb128(std::vector &out, int64_t value) { 57 | bool isNeg = (value < 0); 58 | uint8_t byte; 59 | bool more; 60 | do { 61 | byte = value & 0x7F; 62 | value = value >> 7; 63 | if (isNeg) 64 | more = ((value != -1) || ((byte & 0x40) == 0)); 65 | else 66 | more = ((value != 0) || ((byte & 0x40) != 0)); 67 | if (more) 68 | byte |= 0x80; 69 | out.push_back(byte); 70 | } while (more); 71 | } -------------------------------------------------------------------------------- /DyldExtractor/Utils/Leb128.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTILS_LEB128__ 2 | #define __UTILS_LEB128__ 3 | 4 | #include 5 | #include 6 | 7 | namespace DyldExtractor::Utils { 8 | 9 | uint64_t readUleb128(const uint8_t *&p, const uint8_t *end); 10 | int64_t readSleb128(const uint8_t *&p, const uint8_t *end); 11 | void appendUleb128(std::vector &out, uint64_t value); 12 | void appendSleb128(std::vector &out, int64_t value); 13 | 14 | } // namespace DyldExtractor::Utils 15 | 16 | #endif // __UTILS_LEB128__ -------------------------------------------------------------------------------- /DyldExtractor/Utils/Utils.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTILS__UTILS__ 2 | #define __UTILS__UTILS__ 3 | #include 4 | namespace DyldExtractor::Utils { 5 | 6 | /// @brief Align n up to a stride k 7 | /// @param n The number to align. 8 | /// @param k The stride to align to. 9 | template inline void align(T1 *n, T2 k) { 10 | *n = (T1)((*n + k - 1) / k * k); 11 | } 12 | 13 | /// @brief Align n up to a stride k 14 | /// @param n The number to align. 15 | /// @param k The stride to align to. 16 | /// @returns The aligned number. 17 | template inline T1 align(T1 n, T2 k) { 18 | return (T1)((n + k - 1) / k * k); 19 | } 20 | 21 | /// Mark a code path as unreachable, ported from c++23 22 | [[noreturn]] inline void unreachable() { 23 | #ifdef __GNUC__ 24 | __builtin_unreachable(); 25 | #elif _MSC_VER 26 | __assume(false); 27 | #endif 28 | 29 | #ifndef NDEBUG 30 | abort(); 31 | #endif 32 | } 33 | 34 | } // namespace DyldExtractor::Utils 35 | 36 | #endif // __UTILS__UTILS__ 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 arandomdev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DyldExtractorC 2 | Extract Binaries from Apple's Dyld Shared Cache to be useful in a disassembler. 3 | 4 | This is a alternative version of the main project [DyldExtractor](https://github.com/arandomdev/DyldExtractor). Changing the language to c++ allows copying Apple's code for better output. Though for now, the main version should be used. 5 | 6 | This tool requires the host machine to use Little Endian, but that shouldn't affect anyone. -------------------------------------------------------------------------------- /Tests/IntegrationTests/RunAllCaches_AllImages.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import pathlib 4 | import subprocess 5 | import sys 6 | import multiprocessing 7 | from typing import Generator, Optional 8 | 9 | RETRY_MESSAGE = "The paging file is too small" 10 | RETRY_CORE_COUNT = str(multiprocessing.cpu_count() // 2) 11 | BASE_PARAMS = ( 12 | "--disable-output", 13 | "--verbose", 14 | "--quiet") 15 | 16 | 17 | class Arguments: 18 | executable_path: pathlib.Path 19 | caches_path: pathlib.Path 20 | cache_filters: Optional[list[str]] 21 | pause: bool 22 | skip_modules: int 23 | only_validate: bool 24 | pass 25 | 26 | 27 | def getArguments() -> Arguments: 28 | parser = argparse.ArgumentParser("RunAllCaches_AllImages") 29 | parser.add_argument("--executable-path", type=pathlib.Path, 30 | default=os.environ.get( 31 | "TESTING_DYLDEX_ALL_MULTIPROCESS_PATH"), 32 | help="Path to dyldex, can be set with the " 33 | "environmental variable " 34 | "'TESTING_DYLDEX_ALL_MULTIPROCESS_PATH'.") 35 | parser.add_argument("--caches-path", type=pathlib.Path, 36 | default=os.environ.get("TESTING_CACHES_PATH"), 37 | help="The folder containing the caches to test, " 38 | "can be set with the environmental variable " 39 | "'TESTING_CACHES_PATH'.") 40 | parser.add_argument("--cache-filters", nargs="+", type=str, 41 | help="A list of keywords to filter out caches that " 42 | "should not be processed.") 43 | parser.add_argument("--pause", action=argparse.BooleanOptionalAction, 44 | default=False, 45 | help="Pause before running the next cache.") 46 | parser.add_argument("--skip-modules", type=int, 47 | default=0, help="Modules to skip") 48 | parser.add_argument("--only-validate", 49 | action=argparse.BooleanOptionalAction, default=False, 50 | help="Only validate images instead of processing them") 51 | 52 | args = parser.parse_args(namespace=Arguments()) 53 | if not args.executable_path: 54 | print("--executable-path or TESTING_DYLDEX_ALL_MULTIPROCESS_PATH needs" 55 | " to be set.\n", file=sys.stderr) 56 | parser.print_help() 57 | parser.exit() 58 | if not args.caches_path: 59 | print("--caches-path or TESTING_CACHES_PATH needs to be set.\n", 60 | file=sys.stderr) 61 | parser.print_help() 62 | parser.exit() 63 | return args 64 | 65 | 66 | def getCachePaths( 67 | cachePath: pathlib.Path, 68 | filters: Optional[list[str]] 69 | ) -> Generator[pathlib.Path, None, None]: 70 | for arch in cachePath.iterdir(): 71 | if not arch.is_dir(): 72 | continue 73 | 74 | for cache in arch.iterdir(): 75 | if cache.is_dir(): 76 | path = next(c for c in cache.iterdir() if c.suffix == "") 77 | else: 78 | path = cache 79 | 80 | if filters and next((f for f in filters if f in str(path)), None): 81 | continue 82 | yield path 83 | 84 | 85 | def runCache( 86 | exe: pathlib.Path, 87 | cachePath: pathlib.Path, 88 | skipModules: int, 89 | validateOnly: bool 90 | ) -> bool: 91 | print(f"\nRunning {cachePath}") 92 | params = (exe, cachePath) + BASE_PARAMS 93 | params += ("--skip-modules", str(skipModules)) 94 | if validateOnly: 95 | params += ("--only-validate",) 96 | 97 | proc = subprocess.Popen(params, stderr=subprocess.PIPE, encoding="utf-8") 98 | assert proc.stderr is not None 99 | 100 | errorMessages = "" 101 | while True: 102 | errorFrag = proc.stderr.read() 103 | if errorFrag == "" and proc.poll() is not None: 104 | break 105 | if errorFrag: 106 | errorMessages += errorFrag 107 | sys.stdout.write(errorFrag) 108 | sys.stdout.flush() 109 | 110 | if proc.returncode != 0 and RETRY_MESSAGE in errorMessages: 111 | print("Re-processing with lower thread count") 112 | proc2 = subprocess.run(params + ("-j", RETRY_CORE_COUNT)) 113 | if proc2.returncode != 0: 114 | return False 115 | elif proc.returncode != 0 and RETRY_MESSAGE not in errorMessages: 116 | return False 117 | 118 | return True 119 | 120 | 121 | def main(): 122 | args = getArguments() 123 | 124 | for cachePath in getCachePaths(args.caches_path, args.cache_filters): 125 | if args.pause: 126 | try: 127 | input(f"Press enter to process {cachePath}") 128 | except KeyboardInterrupt: 129 | return 130 | 131 | if not runCache( 132 | args.executable_path, 133 | cachePath, 134 | args.skip_modules, 135 | args.only_validate): 136 | return 137 | pass 138 | pass 139 | 140 | 141 | if __name__ == "__main__": 142 | main() 143 | -------------------------------------------------------------------------------- /Tests/IntegrationTests/RunAllCaches_SingleImage.py: -------------------------------------------------------------------------------- 1 | """Run a single image though all caches 2 | """ 3 | 4 | import argparse 5 | import os 6 | import subprocess as sp 7 | import sys 8 | from pathlib import Path 9 | from typing import Optional 10 | 11 | DEFAULT_IMAGES = ( 12 | "/System/Library/PrivateFrameworks/PreferencesUI.framework/PreferencesUI", 13 | "/System/Library/PrivateFrameworks/RunningBoard.framework/RunningBoard", 14 | "/System/iOSSupport/System/Library/PrivateFrameworks/WeatherUI.framework/Versions/A/WeatherUI", # noqa 15 | "/System/Library/PrivateFrameworks/DigitalAccess.framework/DigitalAccess", 16 | "/System/Library/PrivateFrameworks/AccountSettings.framework/AccountSettings" # noqa 17 | ) 18 | 19 | 20 | class Arguments: 21 | executable_path: Optional[Path] 22 | caches_path: Optional[Path] 23 | output_dir: Optional[Path] 24 | images: tuple[str, ...] 25 | pass 26 | 27 | 28 | def findCaches(cachesPath: Path) -> tuple[Path, ...]: 29 | caches: list[Path] = [] 30 | for arch in cachesPath.iterdir(): 31 | for cache in arch.iterdir(): 32 | if cache.is_dir(): 33 | caches.append(next(c for c in cache.iterdir() if not c.suffix)) 34 | pass 35 | else: 36 | caches.append(cache) 37 | pass 38 | pass 39 | 40 | return tuple(caches) 41 | 42 | 43 | def runCache( 44 | executablePath: Path, 45 | cachePath: Path, 46 | cacheName: str, 47 | outputDir: Path, 48 | images: tuple[str, ...] 49 | ) -> None: 50 | for image in images: 51 | args = [ 52 | executablePath, 53 | "-v", 54 | "-e", 55 | image, 56 | "-o", 57 | outputDir / f"{cacheName}_{image.split('/')[-1]}", 58 | cachePath 59 | ] 60 | proc = sp.run(args, stdout=sp.PIPE, stderr=sp.STDOUT) 61 | if f"Unable to find image '{image}'" in str(proc.stdout): 62 | continue 63 | else: 64 | print(f"------: {cacheName} :------") 65 | sys.stdout.buffer.write(proc.stdout) 66 | sys.stdout.flush() 67 | print("------------\n") 68 | return 69 | 70 | print(f"Unable to find suitable image for {cacheName}.\n", file=sys.stderr) 71 | pass 72 | 73 | 74 | def main() -> None: 75 | argParser = argparse.ArgumentParser("RunAllCaches_SingleImage") 76 | argParser.add_argument("--executable-path", type=Path, 77 | default=os.environ.get("TESTING_DYLDEX_PATH"), 78 | help="Path to dyldex, can be set with the " 79 | "environmental variable 'TESTING_DYLDEX_PATH'.") 80 | argParser.add_argument("--caches-path", type=Path, 81 | default=os.environ.get("TESTING_CACHES_PATH"), 82 | help="The folder containing the caches to test, " 83 | "can be set with the environmental variable " 84 | "'TESTING_CACHES_PATH'.") 85 | argParser.add_argument("--output-dir", type=Path, 86 | default=os.environ.get("TESTING_OUTPUT_DIR"), 87 | help="The output directory, can be set with " 88 | "TESTING_OUTPUT_DIR") 89 | argParser.add_argument("--images", type=tuple, default=DEFAULT_IMAGES, 90 | help="Images to test with a cache, tried in order " 91 | "if a cache does not contain a image.") 92 | 93 | args = argParser.parse_args(namespace=Arguments()) 94 | if not args.executable_path: 95 | print("--executable-path or TESTING_DYLDEX_PATH needs to be " 96 | "set.\n", file=sys.stderr) 97 | argParser.print_help() 98 | argParser.exit() 99 | if not args.caches_path: 100 | print("--caches-path or TESTING_CACHES_PATH needs to be set.\n", 101 | file=sys.stderr) 102 | argParser.print_help() 103 | argParser.exit() 104 | if not args.output_dir: 105 | print("--output-dir or TESTING_OUTPUT_DIR needs to be set.\n", 106 | file=sys.stderr) 107 | argParser.print_help() 108 | argParser.exit() 109 | 110 | for cache in findCaches(args.caches_path): 111 | cacheName = f"{cache.parent.name}_{cache.name}" 112 | runCache(args.executable_path, cache, cacheName, args.output_dir, 113 | args.images) 114 | pass 115 | pass 116 | 117 | 118 | if __name__ == "__main__": 119 | main() 120 | pass 121 | --------------------------------------------------------------------------------