├── .gitignore ├── .vscode └── settings.json ├── ChOma.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── choma.xcscheme │ ├── choma_cli.xcscheme │ ├── dsc_extract.xcscheme │ └── dsc_tester.xcscheme ├── LICENSE ├── Makefile ├── README.md ├── cert.p12 ├── external └── ios │ ├── entitlements.plist │ ├── libcrypto.a │ └── libssl.a ├── include └── choma ├── src ├── Base64.c ├── Base64.h ├── BufferedStream.c ├── BufferedStream.h ├── CSBlob.c ├── CSBlob.h ├── CachePatching.h ├── CodeDirectory.c ├── CodeDirectory.h ├── DER.c ├── DER.h ├── DyldSharedCache.c ├── DyldSharedCache.h ├── Entitlements.c ├── Entitlements.h ├── Fat.c ├── Fat.h ├── FileStream.c ├── FileStream.h ├── Host.c ├── Host.h ├── MachO.c ├── MachO.h ├── MachOByteOrder.h ├── MachOLoadCommand.c ├── MachOLoadCommand.h ├── MemoryStream.c ├── MemoryStream.h ├── PatchFinder.c ├── PatchFinder.h ├── PatchFinder_arm64.c ├── PatchFinder_arm64.h ├── Util.c ├── Util.h ├── arm64.c ├── arm64.h ├── dyld_cache_format.h └── fixup-chains.h └── tests ├── choma_cli └── main.c ├── choma_sign └── main.c ├── ct_bypass ├── AppStoreCodeDirectory.h ├── CADetails.h ├── DERTemplate.h ├── TemplateSignatureBlob.h └── main.c ├── dsc_extract └── main.c ├── dsc_tester └── main.c ├── dyld_patch └── main.c ├── dylib_insert └── main.c ├── fat_create └── main.c └── kpf └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build/ 2 | build/ 3 | output/ 4 | data/ 5 | 6 | # Ignore .DS_Store 7 | .DS_Store 8 | 9 | # Ignore .vscode/ 10 | .vscode/* 11 | !.vscode/settings.json 12 | 13 | # Ignore nocommit-* files and dirs 14 | nocommit-* 15 | 16 | # Ignore Python scripts 17 | *.py 18 | 19 | # Miscellaneous 20 | ca.key 21 | *.tipa 22 | *.zip 23 | *.ipa 24 | *.app 25 | Payload/ 26 | 27 | # Ignore some Xcode internal files 28 | xcuserdata 29 | .idea/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.default.compilerPath": "/usr/bin/clang", 3 | "clangd.fallbackFlags": [ 4 | "-I${workspaceFolder}/src/external", 5 | "-I${workspaceFolder}/output/include", 6 | "-I/opt/homebrew/Cellar/openssl@3/3.2.1/include", 7 | ], 8 | "files.associations": { 9 | "*.ejs": "html", 10 | "codedirectory.h": "c", 11 | "templatesignatureblob.h": "c", 12 | "loader.h": "c", 13 | "__hash_table": "c", 14 | "__split_buffer": "c", 15 | "array": "c", 16 | "bitset": "c", 17 | "initializer_list": "c", 18 | "span": "c", 19 | "string": "c", 20 | "string_view": "c", 21 | "unordered_map": "c", 22 | "vector": "c" 23 | } 24 | } -------------------------------------------------------------------------------- /ChOma.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ChOma.xcodeproj/xcshareddata/xcschemes/choma.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /ChOma.xcodeproj/xcshareddata/xcschemes/choma_cli.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 61 | 63 | 69 | 70 | 71 | 72 | 74 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /ChOma.xcodeproj/xcshareddata/xcschemes/dsc_extract.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ChOma.xcodeproj/xcshareddata/xcschemes/dsc_tester.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 67 | 69 | 75 | 76 | 77 | 78 | 80 | 81 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lars Fröder 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. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC := clang 2 | 3 | CFLAGS ?= -Wall -Werror $(shell pkg-config --cflags libcrypto) -fPIC -Wno-pointer-to-int-cast -Wno-unused-command-line-argument -Wno-deprecated-declarations -framework CoreFoundation 4 | LDFLAGS ?= 5 | DYLIB_LDFLAGS ?= 6 | 7 | DEBUG ?= 0 8 | ifeq ($(DEBUG), 1) 9 | CFLAGS += -fsanitize=address 10 | endif 11 | DISABLE_SIGNING ?= 0 12 | DISABLE_TESTS ?= 0 13 | 14 | LIB_NAME := libchoma 15 | INSTALL_PATH ?= /usr/local/ 16 | 17 | ifeq ($(TARGET), ios) 18 | BUILD_DIR := build/ios 19 | OUTPUT_DIR := output/ios 20 | CFLAGS += -arch arm64 -arch arm64e -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=11.0 21 | ifeq ($(DISABLE_SIGNING), 0) 22 | CFLAGS += external/ios/libcrypto.a 23 | endif 24 | else 25 | BUILD_DIR := build 26 | OUTPUT_DIR := output 27 | ifeq ($(DISABLE_SIGNING), 0) 28 | CFLAGS += $(shell pkg-config --libs libcrypto) 29 | endif 30 | CFLAGS += -mmacosx-version-min=10.13 31 | endif 32 | 33 | ifeq ($(DISABLE_SIGNING), 1) 34 | CFLAGS += -DDISABLE_SIGNING=1 35 | endif 36 | 37 | SRC_DIR := src 38 | 39 | HEADER_OUTPUT_DIR := $(OUTPUT_DIR)/include 40 | TESTS_SRC_DIR := tests 41 | TESTS_BUILD_DIR := $(BUILD_DIR)/tests 42 | TESTS_OUTPUT_DIR := $(OUTPUT_DIR)/tests 43 | 44 | LIB_DIR := $(OUTPUT_DIR)/lib 45 | TESTS_DIR := build/tests 46 | 47 | STATIC_LIB := $(LIB_DIR)/$(LIB_NAME).a 48 | DYNAMIC_LIB := $(LIB_DIR)/$(LIB_NAME).dylib 49 | 50 | SRC_FILES := $(wildcard $(SRC_DIR)/*.c) 51 | OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRC_FILES)) 52 | 53 | ifeq ($(DISABLE_TESTS), 0) 54 | TESTS_SUBDIRS := $(wildcard $(TESTS_SRC_DIR)/*) 55 | TESTS_BINARIES := $(patsubst $(TESTS_SRC_DIR)/%,$(TESTS_OUTPUT_DIR)/%,$(TESTS_SUBDIRS)) 56 | endif 57 | 58 | CHOMA_HEADERS_SRC_DIR := $(SRC_DIR) 59 | CHOMA_HEADERS_DST_DIR := $(HEADER_OUTPUT_DIR)/choma 60 | 61 | CHOMA_HEADERS := $(shell find $(CHOMA_HEADERS_SRC_DIR) -type f -name "*.h") 62 | 63 | all: $(STATIC_LIB) $(DYNAMIC_LIB) copy-choma-headers clean-test $(TESTS_BINARIES) 64 | 65 | $(STATIC_LIB): $(OBJ_FILES) 66 | @mkdir -p $(LIB_DIR) 67 | libtool $^ -o $@ 68 | 69 | ifeq ($(TARGET), ios) 70 | $(DYNAMIC_LIB): $(OBJ_FILES) 71 | @mkdir -p $(LIB_DIR) 72 | $(CC) $(CFLAGS) $(LDFLAGS) $(DYLIB_LDFLAGS) -shared -o $@ $^ 73 | @ldid -S $@ 74 | else 75 | $(DYNAMIC_LIB): $(OBJ_FILES) 76 | @mkdir -p $(LIB_DIR) 77 | $(CC) $(CFLAGS) $(LDFLAGS) $(DYLIB_LDFLAGS) -shared -o $@ $^ 78 | endif 79 | 80 | $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c 81 | @mkdir -p $(dir $@) 82 | $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ 83 | 84 | ifeq ($(DISABLE_TESTS), 0) 85 | ifeq ($(TARGET), ios) 86 | $(TESTS_OUTPUT_DIR)/%: $(TESTS_SRC_DIR)/% 87 | @mkdir -p $(dir $@) 88 | @rm -rf $@ 89 | $(CC) $(CFLAGS) $(LDFLAGS) -I$(OUTPUT_DIR)/include -o $@ $ 2 | 3 | 4 | 5 | platform-application 6 | 7 | com.apple.private.security.no-container 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /external/ios/libcrypto.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/ChOma/7e1313c20a346b65864d44c973abafb86bdc4dd5/external/ios/libcrypto.a -------------------------------------------------------------------------------- /external/ios/libssl.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/ChOma/7e1313c20a346b65864d44c973abafb86bdc4dd5/external/ios/libssl.a -------------------------------------------------------------------------------- /include/choma: -------------------------------------------------------------------------------- 1 | ../src -------------------------------------------------------------------------------- /src/Base64.c: -------------------------------------------------------------------------------- 1 | #include "Base64.h" 2 | 3 | // https://stackoverflow.com/a/6782480 4 | static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 5 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 6 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 7 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 8 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 9 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 10 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', 11 | '4', '5', '6', '7', '8', '9', '+', '/'}; 12 | static int mod_table[] = {0, 2, 1}; 13 | 14 | 15 | char *base64_encode(const unsigned char *data, 16 | size_t input_length, 17 | size_t *output_length) { 18 | 19 | *output_length = 4 * ((input_length + 2) / 3); 20 | 21 | char *encoded_data = malloc(*output_length); 22 | if (encoded_data == NULL) return NULL; 23 | 24 | for (int i = 0, j = 0; i < input_length;) { 25 | 26 | uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; 27 | uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; 28 | uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; 29 | 30 | uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; 31 | 32 | encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; 33 | encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; 34 | encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; 35 | encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; 36 | } 37 | 38 | for (int i = 0; i < mod_table[input_length % 3]; i++) 39 | encoded_data[*output_length - 1 - i] = '='; 40 | 41 | return encoded_data; 42 | } -------------------------------------------------------------------------------- /src/Base64.h: -------------------------------------------------------------------------------- 1 | #ifndef BASE64_H 2 | #define BASE64_H 3 | 4 | #include 5 | #include 6 | 7 | char *base64_encode(const unsigned char *data, 8 | size_t input_length, 9 | size_t *output_length); 10 | 11 | #endif // BASE64_H -------------------------------------------------------------------------------- /src/BufferedStream.c: -------------------------------------------------------------------------------- 1 | #include "BufferedStream.h" 2 | #include "MemoryStream.h" 3 | 4 | #include 5 | 6 | static int buffered_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd); 7 | 8 | static int _buffered_stream_make_own_data(MemoryStream *stream) 9 | { 10 | BufferedStreamContext *context = stream->context; 11 | if ((stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) == 0) { 12 | void *newBuffer = malloc(context->subBufferSize); 13 | memcpy(newBuffer, context->buffer + context->subBufferStart, context->subBufferSize); 14 | context->buffer = newBuffer; 15 | stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; 16 | } 17 | return 0; 18 | } 19 | 20 | static int buffered_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf) 21 | { 22 | BufferedStreamContext *context = stream->context; 23 | if ((offset + size) > context->subBufferSize) { 24 | printf("Error: cannot read %zx bytes at %llx, maximum is %zx.\n", size, offset, context->subBufferSize); 25 | return -1; 26 | } 27 | 28 | memcpy(outBuf, context->buffer + context->subBufferStart + offset, size); 29 | return size; 30 | } 31 | 32 | static int buffered_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf) 33 | { 34 | BufferedStreamContext *context = stream->context; 35 | 36 | bool expandAllowed = (stream->flags & MEMORY_STREAM_FLAG_AUTO_EXPAND); 37 | bool needsExpand = (offset + size) > context->subBufferSize; 38 | 39 | if (needsExpand && !expandAllowed) { 40 | printf("Error: cannot write %zx bytes at %llx, maximum is %zx.\n", size, offset, context->subBufferSize); 41 | return -1; 42 | } 43 | 44 | if ((stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) == 0) { 45 | int r = _buffered_stream_make_own_data(stream); 46 | if (r != 0) return r; 47 | } 48 | 49 | if (needsExpand) { 50 | buffered_stream_expand(stream, 0, (offset + size) - context->subBufferSize); 51 | } 52 | 53 | memcpy(context->buffer + context->subBufferStart + offset, inBuf, size); 54 | return size; 55 | } 56 | 57 | static int buffered_stream_get_size(MemoryStream *stream, size_t *sizeOut) 58 | { 59 | BufferedStreamContext *context = stream->context; 60 | *sizeOut = context->subBufferSize; 61 | return 0; 62 | } 63 | 64 | static uint8_t *buffered_stream_get_raw_pointer(MemoryStream *stream) 65 | { 66 | BufferedStreamContext *context = stream->context; 67 | return &context->buffer[context->subBufferStart]; 68 | } 69 | 70 | static int buffered_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd) 71 | { 72 | BufferedStreamContext *context = stream->context; 73 | 74 | // TODO: bound checks 75 | context->subBufferStart += trimAtStart; 76 | context->subBufferSize -= (trimAtEnd + trimAtStart); 77 | 78 | return 0; 79 | } 80 | 81 | static int buffered_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd) 82 | { 83 | BufferedStreamContext *context = stream->context; 84 | 85 | size_t newSize = context->subBufferSize + expandAtStart + expandAtEnd; 86 | uint8_t *newBuffer = malloc(newSize); 87 | memset(newBuffer, 0, newSize); 88 | memcpy(&newBuffer[expandAtStart], &context->buffer[context->subBufferStart], context->subBufferSize); 89 | if (stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) { 90 | free(context->buffer); 91 | } 92 | context->buffer = newBuffer; 93 | context->bufferSize = newSize; 94 | context->subBufferStart = 0; 95 | context->subBufferSize = newSize; 96 | stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; 97 | 98 | return 0; 99 | } 100 | 101 | static MemoryStream *buffered_stream_softclone(MemoryStream *stream) 102 | { 103 | MemoryStream* clone = malloc(sizeof(MemoryStream)); 104 | if (!clone) return NULL; 105 | memset(clone, 0, sizeof(MemoryStream)); 106 | 107 | BufferedStreamContext *context = stream->context; 108 | BufferedStreamContext *contextCopy = malloc(sizeof(BufferedStreamContext)); 109 | 110 | contextCopy->buffer = context->buffer; 111 | contextCopy->bufferSize = context->bufferSize; 112 | contextCopy->subBufferStart = context->subBufferStart; 113 | contextCopy->subBufferSize = context->subBufferSize; 114 | clone->flags = stream->flags & ~(MEMORY_STREAM_FLAG_OWNS_DATA); 115 | 116 | clone->context = contextCopy; 117 | return clone; 118 | } 119 | 120 | static MemoryStream *buffered_stream_hardclone(MemoryStream *stream) 121 | { 122 | MemoryStream* clone = buffered_stream_softclone(stream); 123 | if (clone) { 124 | _buffered_stream_make_own_data(clone); 125 | } 126 | return clone; 127 | } 128 | 129 | static void buffered_stream_free(MemoryStream *stream) 130 | { 131 | BufferedStreamContext *context = stream->context; 132 | if (context->buffer) { 133 | if (stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) { 134 | free(context->buffer); 135 | } 136 | } 137 | free(context); 138 | } 139 | 140 | static int _buffered_stream_init(MemoryStream *stream) 141 | { 142 | stream->read = buffered_stream_read; 143 | stream->write = buffered_stream_write; 144 | stream->getSize = buffered_stream_get_size; 145 | stream->getRawPtr = buffered_stream_get_raw_pointer; 146 | 147 | stream->trim = buffered_stream_trim; 148 | stream->expand = buffered_stream_expand; 149 | 150 | stream->softclone = buffered_stream_softclone; 151 | stream->hardclone = buffered_stream_hardclone; 152 | stream->free = buffered_stream_free; 153 | 154 | stream->flags = MEMORY_STREAM_FLAG_MUTABLE; 155 | return 0; 156 | } 157 | 158 | MemoryStream *buffered_stream_init_from_buffer_nocopy(void *buffer, size_t bufferSize, uint32_t flags) 159 | { 160 | MemoryStream *stream = malloc(sizeof(MemoryStream)); 161 | if (!stream) return NULL; 162 | memset(stream, 0, sizeof(MemoryStream)); 163 | 164 | BufferedStreamContext *context = malloc(sizeof(BufferedStreamContext)); 165 | context->buffer = buffer; 166 | context->bufferSize = bufferSize; 167 | context->subBufferStart = 0; 168 | context->subBufferSize = bufferSize; 169 | 170 | stream->context = context; 171 | if (_buffered_stream_init(stream) != 0) goto fail; 172 | 173 | if (flags & BUFFERED_STREAM_FLAG_AUTO_EXPAND) { 174 | stream->flags |= MEMORY_STREAM_FLAG_AUTO_EXPAND; 175 | } 176 | 177 | return stream; 178 | 179 | fail: 180 | buffered_stream_free(stream); 181 | return NULL; 182 | } 183 | 184 | MemoryStream *buffered_stream_init_from_buffer(void *buffer, size_t bufferSize, uint32_t flags) 185 | { 186 | void *copy = malloc(bufferSize); 187 | if (!copy) return NULL; 188 | memcpy(copy, buffer, bufferSize); 189 | MemoryStream *stream = buffered_stream_init_from_buffer_nocopy(copy, bufferSize, flags); 190 | if (stream) { 191 | stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; 192 | } 193 | return stream; 194 | } -------------------------------------------------------------------------------- /src/BufferedStream.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFERED_STREAM_H 2 | #define BUFFERED_STREAM_H 3 | 4 | #include "MemoryStream.h" 5 | #include 6 | 7 | #define BUFFERED_STREAM_FLAG_AUTO_EXPAND (1 << 0) 8 | 9 | typedef struct BufferedStreamContext { 10 | uint8_t *buffer; 11 | size_t bufferSize; 12 | uint32_t subBufferStart; 13 | size_t subBufferSize; 14 | } BufferedStreamContext; 15 | 16 | MemoryStream *buffered_stream_init_from_buffer_nocopy(void *buffer, size_t bufferSize, uint32_t flags); 17 | MemoryStream *buffered_stream_init_from_buffer(void *buffer, size_t bufferSize, uint32_t flags); 18 | 19 | #endif // BUFFERED_STREAM_H -------------------------------------------------------------------------------- /src/CSBlob.h: -------------------------------------------------------------------------------- 1 | #ifndef CS_BLOB_H 2 | #define CS_BLOB_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Fat.h" 10 | #include "MachO.h" 11 | #include "MemoryStream.h" 12 | 13 | // Blob index 14 | typedef struct __BlobIndex { 15 | uint32_t type; 16 | uint32_t offset; 17 | } CS_BlobIndex; 18 | 19 | // CMS superblob 20 | typedef struct __SuperBlob { 21 | uint32_t magic; 22 | uint32_t length; 23 | uint32_t count; 24 | CS_BlobIndex index[]; 25 | } CS_SuperBlob; 26 | 27 | typedef struct __GenericBlob { 28 | uint32_t magic; /* magic number */ 29 | uint32_t length; /* total length of blob */ 30 | char data[]; 31 | } CS_GenericBlob; 32 | 33 | // CMS blob magic types 34 | typedef enum { 35 | CSMAGIC_REQUIREMENT = 0xfade0c00, 36 | CSMAGIC_REQUIREMENTS = 0xfade0c01, 37 | CSMAGIC_CODEDIRECTORY = 0xfade0c02, 38 | CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, 39 | CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, 40 | CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, 41 | CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172, 42 | CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, 43 | CSMAGIC_BLOBWRAPPER = 0xfade0b01, 44 | CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT = 0xfade8181, 45 | } CS_BlobMagic; 46 | 47 | typedef enum { 48 | CSSLOT_CODEDIRECTORY = 0, 49 | CSSLOT_INFOSLOT = 1, 50 | CSSLOT_REQUIREMENTS = 2, 51 | CSSLOT_RESOURCEDIR = 3, 52 | CSSLOT_APPLICATION = 4, 53 | CSSLOT_ENTITLEMENTS = 5, 54 | CSSLOT_DER_ENTITLEMENTS = 7, 55 | CSSLOT_LAUNCH_CONSTRAINT_SELF = 8, 56 | CSSLOT_LAUNCH_CONSTRAINT_PARENT = 9, 57 | CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE = 10, 58 | CSSLOT_LIBRARY_CONSTRAINT = 11, 59 | 60 | CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */ 61 | CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */ 62 | CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */ 63 | 64 | CSSLOT_SIGNATURESLOT = 0x10000, 65 | CSSLOT_IDENTIFICATIONSLOT = 0x10001, 66 | CSSLOT_TICKETSLOT = 0x10002, 67 | } CS_SlotType; 68 | 69 | typedef struct s_CS_DecodedBlob { 70 | struct s_CS_DecodedBlob *next; 71 | uint32_t type; 72 | MemoryStream *stream; 73 | } CS_DecodedBlob; 74 | 75 | typedef struct s_CS_DecodedSuperBlob { 76 | uint32_t magic; 77 | struct s_CS_DecodedBlob *firstBlob; 78 | } CS_DecodedSuperBlob; 79 | 80 | // Convert blob magic to readable blob type string 81 | const char *cs_blob_magic_to_string(uint32_t magic); 82 | const char *cs_slot_type_to_string(uint32_t slotType); 83 | 84 | // Extract Code Signature to file 85 | int macho_extract_cs_to_file(MachO *macho, CS_SuperBlob *superblob); 86 | 87 | int macho_find_code_signature_bounds(MachO *macho, uint32_t *offsetOut, uint32_t *sizeOut); 88 | 89 | CS_SuperBlob *macho_read_code_signature(MachO *macho); 90 | 91 | int macho_replace_code_signature(MachO *macho, CS_SuperBlob *superblob); 92 | 93 | CS_DecodedBlob *csd_blob_init(uint32_t type, CS_GenericBlob *blobData); 94 | int csd_blob_read(CS_DecodedBlob *blob, uint64_t offset, size_t size, void *outBuf); 95 | int csd_blob_write(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf); 96 | int csd_blob_insert(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf); 97 | int csd_blob_delete(CS_DecodedBlob *blob, uint64_t offset, size_t size); 98 | int csd_blob_read_string(CS_DecodedBlob *blob, uint64_t offset, char **outString); 99 | int csd_blob_write_string(CS_DecodedBlob *blob, uint64_t offset, const char *string); 100 | size_t csd_blob_get_size(CS_DecodedBlob *blob); 101 | uint32_t csd_blob_get_type(CS_DecodedBlob *blob); 102 | void csd_blob_set_type(CS_DecodedBlob *blob, uint32_t type); 103 | void csd_blob_free(CS_DecodedBlob *blob); 104 | 105 | CS_DecodedSuperBlob *csd_superblob_init(void); 106 | CS_DecodedSuperBlob *csd_superblob_decode(CS_SuperBlob *superblob); 107 | CS_SuperBlob *csd_superblob_encode(CS_DecodedSuperBlob *decodedSuperblob); 108 | CS_DecodedBlob *csd_superblob_find_blob(CS_DecodedSuperBlob *superblob, uint32_t type, uint32_t *indexOut); 109 | CS_DecodedBlob *csd_superblob_find_blob_by_magic(CS_DecodedSuperBlob *superblob, uint32_t magic, uint32_t *indexOut); 110 | int csd_superblob_insert_blob_after_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, CS_DecodedBlob *afterBlob); 111 | int csd_superblob_insert_blob_at_index(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, uint32_t atIndex); 112 | int csd_superblob_append_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToAppend); 113 | int csd_superblob_replace_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *newBlob); 114 | int csd_superblob_remove_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToRemove); // <- Important: When calling this, caller is responsible for freeing blobToRemove 115 | int csd_superblob_remove_blob_at_index(CS_DecodedSuperBlob *superblob, uint32_t atIndex); 116 | int csd_superblob_calculate_best_cdhash(CS_DecodedSuperBlob *decodedSuperblob, void *cdhashOut, int *cdhashType); 117 | int csd_superblob_print_content(CS_DecodedSuperBlob *decodedSuperblob, MachO *macho, bool printAllSlots, bool verifySlots); 118 | void csd_superblob_free(CS_DecodedSuperBlob *decodedSuperblob); 119 | 120 | #endif // CS_BLOB_H -------------------------------------------------------------------------------- /src/CachePatching.h: -------------------------------------------------------------------------------- 1 | /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- 2 | * 3 | * Copyright (c) 2003-2010 Apple Inc. All rights reserved. 4 | * 5 | * @APPLE_LICENSE_HEADER_START@ 6 | * 7 | * This file contains Original Code and/or Modifications of Original Code 8 | * as defined in and that are subject to the Apple Public Source License 9 | * Version 2.0 (the 'License'). You may not use this file except in 10 | * compliance with the License. Please obtain a copy of the License at 11 | * http://www.opensource.apple.com/apsl/ and read it before using this 12 | * file. 13 | * 14 | * The Original Code and all software distributed under the License are 15 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 16 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 17 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 19 | * Please see the License for the specific language governing rights and 20 | * limitations under the License. 21 | * 22 | * @APPLE_LICENSE_HEADER_END@ 23 | */ 24 | #ifndef _CACHE_PATCHING_H_ 25 | #define _CACHE_PATCHING_H_ 26 | 27 | #include 28 | 29 | // 30 | // MARK: --- V1 patching. This is for old caches, before Large/Split caches --- 31 | // 32 | 33 | struct dyld_cache_patch_info_v1 34 | { 35 | uint64_t patchTableArrayAddr; // (unslid) address of array for dyld_cache_image_patches for each image 36 | uint64_t patchTableArrayCount; // count of patch table entries 37 | uint64_t patchExportArrayAddr; // (unslid) address of array for patch exports for each image 38 | uint64_t patchExportArrayCount; // count of patch exports entries 39 | uint64_t patchLocationArrayAddr; // (unslid) address of array for patch locations for each patch 40 | uint64_t patchLocationArrayCount;// count of patch location entries 41 | uint64_t patchExportNamesAddr; // blob of strings of export names for patches 42 | uint64_t patchExportNamesSize; // size of string blob of export names for patches 43 | }; 44 | 45 | struct dyld_cache_image_patches_v1 46 | { 47 | uint32_t patchExportsStartIndex; 48 | uint32_t patchExportsCount; 49 | }; 50 | 51 | struct dyld_cache_patchable_export_v1 52 | { 53 | uint32_t cacheOffsetOfImpl; 54 | uint32_t patchLocationsStartIndex; 55 | uint32_t patchLocationsCount; 56 | uint32_t exportNameOffset; 57 | }; 58 | 59 | struct dyld_cache_patchable_location_v1 60 | { 61 | uint32_t cacheOffset; 62 | uint64_t high7 : 7, 63 | addend : 5, // 0..31 64 | authenticated : 1, 65 | usesAddressDiversity : 1, 66 | key : 2, 67 | discriminator : 16; 68 | }; 69 | 70 | // 71 | // MARK: --- V2 patching. This is for Large/Split caches and newer --- 72 | // 73 | struct dyld_cache_patch_info_v2 74 | { 75 | uint32_t patchTableVersion; // == 2 76 | uint32_t patchLocationVersion; // == 0 for now 77 | uint64_t patchTableArrayAddr; // (unslid) address of array for dyld_cache_image_patches_v2 for each image 78 | uint64_t patchTableArrayCount; // count of patch table entries 79 | uint64_t patchImageExportsArrayAddr; // (unslid) address of array for dyld_cache_image_export_v2 for each image 80 | uint64_t patchImageExportsArrayCount; // count of patch table entries 81 | uint64_t patchClientsArrayAddr; // (unslid) address of array for dyld_cache_image_clients_v2 for each image 82 | uint64_t patchClientsArrayCount; // count of patch clients entries 83 | uint64_t patchClientExportsArrayAddr; // (unslid) address of array for patch exports for each client image 84 | uint64_t patchClientExportsArrayCount; // count of patch exports entries 85 | uint64_t patchLocationArrayAddr; // (unslid) address of array for patch locations for each patch 86 | uint64_t patchLocationArrayCount; // count of patch location entries 87 | uint64_t patchExportNamesAddr; // blob of strings of export names for patches 88 | uint64_t patchExportNamesSize; // size of string blob of export names for patches 89 | }; 90 | 91 | struct dyld_cache_image_patches_v2 92 | { 93 | uint32_t patchClientsStartIndex; 94 | uint32_t patchClientsCount; 95 | uint32_t patchExportsStartIndex; // Points to dyld_cache_image_export_v2[] 96 | uint32_t patchExportsCount; 97 | }; 98 | 99 | struct dyld_cache_image_export_v2 100 | { 101 | uint32_t dylibOffsetOfImpl; // Offset from the dylib we used to find a dyld_cache_image_patches_v2 102 | uint32_t exportNameOffset : 28; 103 | uint32_t patchKind : 4; // One of DyldSharedCache::patchKind 104 | }; 105 | 106 | struct dyld_cache_image_clients_v2 107 | { 108 | uint32_t clientDylibIndex; 109 | uint32_t patchExportsStartIndex; // Points to dyld_cache_patchable_export_v2[] 110 | uint32_t patchExportsCount; 111 | }; 112 | 113 | struct dyld_cache_patchable_export_v2 114 | { 115 | uint32_t imageExportIndex; // Points to dyld_cache_image_export_v2 116 | uint32_t patchLocationsStartIndex; // Points to dyld_cache_patchable_location_v2[] 117 | uint32_t patchLocationsCount; 118 | }; 119 | 120 | struct dyld_cache_patchable_location_v2 121 | { 122 | uint32_t dylibOffsetOfUse; // Offset from the dylib we used to get a dyld_cache_image_clients_v2 123 | uint32_t high7 : 7, 124 | addend : 5, // 0..31 125 | authenticated : 1, 126 | usesAddressDiversity : 1, 127 | key : 2, 128 | discriminator : 16; 129 | }; 130 | 131 | // 132 | // MARK: --- V3 patching. This is V2 plus support for GOT combining --- 133 | // 134 | struct dyld_cache_patch_info_v3 135 | { 136 | struct dyld_cache_patch_info_v2 infoV2; 137 | // uint32_t patchTableVersion; // == 3 138 | // ... other fields from dyld_cache_patch_info_v2 139 | uint64_t gotClientsArrayAddr; // (unslid) address of array for dyld_cache_image_got_clients_v3 for each image 140 | uint64_t gotClientsArrayCount; // count of got clients entries. Should always match the patchTableArrayCount 141 | uint64_t gotClientExportsArrayAddr; // (unslid) address of array for patch exports for each GOT image 142 | uint64_t gotClientExportsArrayCount; // count of patch exports entries 143 | uint64_t gotLocationArrayAddr; // (unslid) address of array for patch locations for each GOT patch 144 | uint64_t gotLocationArrayCount; // count of patch location entries 145 | }; 146 | 147 | struct dyld_cache_image_got_clients_v3 148 | { 149 | uint32_t patchExportsStartIndex; // Points to dyld_cache_patchable_export_v3[] 150 | uint32_t patchExportsCount; 151 | }; 152 | 153 | struct dyld_cache_patchable_export_v3 154 | { 155 | uint32_t imageExportIndex; // Points to dyld_cache_image_export_v2 156 | uint32_t patchLocationsStartIndex; // Points to dyld_cache_patchable_location_v3[] 157 | uint32_t patchLocationsCount; 158 | }; 159 | 160 | struct dyld_cache_patchable_location_v3 161 | { 162 | uint64_t cacheOffsetOfUse; // Offset from the cache header 163 | uint32_t high7 : 7, 164 | addend : 5, // 0..31 165 | authenticated : 1, 166 | usesAddressDiversity : 1, 167 | key : 2, 168 | discriminator : 16; 169 | }; 170 | 171 | struct dyld_cache_patchable_location_v4 172 | { 173 | uint64_t dylibOffsetOfUse; // Offset from the cache header 174 | union { 175 | struct { 176 | uint32_t authenticated : 1, // == 1 177 | high7 : 7, 178 | isWeakImport : 1, 179 | addend : 5, // 0..31 180 | usesAddressDiversity : 1, 181 | keyIsD : 1, // B keys are not permitted. So this is just whether the A key is I or D (0 => I, 1 => D) 182 | discriminator : 16; 183 | } auth; 184 | struct { 185 | uint32_t authenticated : 1, // == 0 186 | high7 : 7, 187 | isWeakImport : 1, 188 | addend : 23; 189 | } regular; 190 | }; 191 | }; 192 | 193 | 194 | #endif /* _CACHE_PATCHING_H_ */ 195 | 196 | -------------------------------------------------------------------------------- /src/CodeDirectory.h: -------------------------------------------------------------------------------- 1 | #ifndef CODE_DIRECTORY_H 2 | #define CODE_DIRECTORY_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "MachO.h" 9 | #include "CSBlob.h" 10 | #include "Fat.h" 11 | #include "MachOByteOrder.h" 12 | #include "MachOLoadCommand.h" 13 | #include "MemoryStream.h" 14 | 15 | // Code directory blob header 16 | typedef struct __CodeDirectory { 17 | uint32_t magic; 18 | uint32_t length; 19 | uint32_t version; 20 | uint32_t flags; 21 | uint32_t hashOffset; 22 | uint32_t identOffset; 23 | uint32_t nSpecialSlots; 24 | uint32_t nCodeSlots; 25 | uint32_t codeLimit; 26 | uint8_t hashSize; 27 | uint8_t hashType; 28 | uint8_t platform; 29 | uint8_t pageSize; 30 | uint32_t spare2; 31 | 32 | /* Version 0x20100 */ 33 | uint32_t scatterOffset; 34 | 35 | /* Version 0x20200 */ 36 | uint32_t teamOffset; 37 | } CS_CodeDirectory 38 | __attribute__ ((aligned(1))); 39 | 40 | #define CS_CDHASH_LEN 20 41 | typedef uint8_t cdhash_t[CS_CDHASH_LEN]; 42 | void print_cdhash(cdhash_t cdhash); 43 | 44 | enum CS_HashType { 45 | CS_HASHTYPE_SHA160_160 = 1, 46 | CS_HASHTYPE_SHA256_256 = 2, 47 | CS_HASHTYPE_SHA256_160 = 3, 48 | CS_HASHTYPE_SHA384_384 = 4, 49 | }; 50 | 51 | char *csd_code_directory_copy_identifier(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut); 52 | char *csd_code_directory_copy_team_id(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut); 53 | int csd_code_directory_set_team_id(CS_DecodedBlob *codeDirBlob, char *newTeamID); 54 | int csd_code_directory_set_identifier(CS_DecodedBlob *codeDirBlob, char *newIdentifier); 55 | uint32_t csd_code_directory_get_flags(CS_DecodedBlob *codeDirBlob); 56 | void csd_code_directory_set_flags(CS_DecodedBlob *codeDirBlob, uint32_t flags); 57 | uint8_t csd_code_directory_get_hash_type(CS_DecodedBlob *codeDirBlob); 58 | void csd_code_directory_set_hash_type(CS_DecodedBlob *codeDirBlob, uint8_t hashType); 59 | unsigned csd_code_directory_calculate_rank(CS_DecodedBlob *codeDirBlob); 60 | int csd_code_directory_calculate_hash(CS_DecodedBlob *codeDirBlob, void *cdhashOut); 61 | int csd_code_directory_print_content(CS_DecodedBlob *codeDirBlob, MachO *macho, bool printSlots, bool verifySlots); 62 | void csd_code_directory_update_special_slots(CS_DecodedBlob *codeDirBlob, CS_DecodedBlob *xmlEntitlements, CS_DecodedBlob *derEntitlements, CS_DecodedBlob *requirements); 63 | void csd_code_directory_update(CS_DecodedBlob *codeDirBlob, MachO *macho); 64 | CS_DecodedBlob *csd_code_directory_init(MachO *macho, int hashType, bool alternate); 65 | 66 | #endif // CODE_DIRECTORY_H -------------------------------------------------------------------------------- /src/DER.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "MachOByteOrder.h" 8 | #include "DER.h" 9 | 10 | #define ASN1_CONSTRUCTED 0x20 11 | 12 | #define ASN1_SEQUENCE 0x10 13 | #define ASN1_SET 0x11 14 | 15 | #define ASN1_BOOLEAN 0x01 16 | #define ASN1_INTEGER 0x02 17 | #define ASN1_UTF8_STRING 0x0C 18 | 19 | // DER entitlements are a SET of SEQUENCE serialisations, each sequence being an entitlement 20 | 21 | void der_free_encoded_item(DEREncodedItem *item) { 22 | free(item->data); 23 | free(item); 24 | } 25 | 26 | DEREncodedItem *der_encode_length(uint32_t length) { 27 | if (length <= 0x7f) { 28 | uint8_t *data = malloc(1); 29 | if (data == NULL) { 30 | return NULL; 31 | } 32 | 33 | data[0] = length; 34 | 35 | DEREncodedItem *item = malloc(sizeof(DEREncodedItem)); 36 | if (item == NULL) { 37 | return NULL; 38 | } 39 | 40 | item->data = data; 41 | item->length = 1; 42 | 43 | return item; 44 | } else { 45 | uint32_t len = length; 46 | uint32_t lenBytes = 0; 47 | while (len) { 48 | len >>= 8; 49 | lenBytes++; 50 | } 51 | 52 | uint8_t *data = malloc(lenBytes + 1); 53 | if (data == NULL) { 54 | return NULL; 55 | } 56 | 57 | data[0] = 0x80 | lenBytes; 58 | for (uint32_t i = 1; i <= lenBytes; i++) { 59 | data[i] = (length >> (8 * (lenBytes - i))) & 0xff; 60 | } 61 | 62 | DEREncodedItem *item = malloc(sizeof(DEREncodedItem)); 63 | if (item == NULL) { 64 | free(data); 65 | return NULL; 66 | } 67 | 68 | item->data = data; 69 | item->length = lenBytes + 1; 70 | 71 | return item; 72 | } 73 | } 74 | 75 | DEREncodedItem *der_encode_item(DERItem *item) { 76 | DEREncodedItem *length = der_encode_length(item->length); 77 | if (length->data == NULL) { 78 | return NULL; 79 | } 80 | 81 | uint32_t totalLength = 1 + length->length + item->length; 82 | uint8_t *data = malloc(totalLength); 83 | if (data == NULL) { 84 | der_free_encoded_item(length); 85 | return NULL; 86 | } 87 | 88 | data[0] = item->tag; 89 | memcpy(&data[1], length->data, length->length); 90 | memcpy(&data[1 + length->length], item->data, item->length); 91 | 92 | DEREncodedItem *encodedItem = malloc(sizeof(DEREncodedItem)); 93 | if (encodedItem == NULL) { 94 | der_free_encoded_item(length); 95 | free(data); 96 | return NULL; 97 | } 98 | 99 | encodedItem->data = data; 100 | encodedItem->length = totalLength; 101 | 102 | der_free_encoded_item(length); 103 | 104 | return encodedItem; 105 | } 106 | 107 | DEREncodedItem *der_encode_boolean(bool value) { 108 | DERItem item; 109 | item.tag = ASN1_BOOLEAN; 110 | item.data = malloc(1); 111 | if (item.data == NULL) { 112 | return NULL; 113 | } 114 | 115 | item.data[0] = value; 116 | item.length = 1; 117 | 118 | return der_encode_item(&item); 119 | } 120 | 121 | DEREncodedItem *der_encode_integer(uint32_t value) { 122 | DERItem item; 123 | item.tag = ASN1_INTEGER; 124 | item.data = malloc(sizeof(uint32_t)); 125 | if (item.data == NULL) { 126 | return NULL; 127 | } 128 | 129 | // Convert to big-endian 130 | value = HOST_TO_BIG(value); 131 | memcpy(item.data, &value, sizeof(uint32_t)); 132 | item.length = 4; 133 | 134 | return der_encode_item(&item); 135 | } 136 | 137 | DEREncodedItem *der_encode_utf8_string(const char *string) { 138 | DERItem item; 139 | item.tag = ASN1_UTF8_STRING; 140 | item.data = (uint8_t *)string; 141 | item.length = strlen(string); 142 | 143 | return der_encode_item(&item); 144 | } 145 | 146 | DEREncodedItem *der_encode_sequence(DEREncodedItem *items[], uint32_t nItems) { 147 | uint8_t tag = ASN1_SEQUENCE | ASN1_CONSTRUCTED; 148 | 149 | uint32_t totalLength = 0; 150 | for (uint32_t i = 0; i < nItems; i++) { 151 | totalLength += items[i]->length; 152 | } 153 | 154 | DEREncodedItem *length = der_encode_length(totalLength); 155 | if (length == NULL) { 156 | return NULL; 157 | } 158 | 159 | uint32_t totalDataLength = 1 + length->length + totalLength; 160 | 161 | uint8_t *data = malloc(totalDataLength); 162 | if (data == NULL) { 163 | der_free_encoded_item(length); 164 | return NULL; 165 | } 166 | 167 | data[0] = tag; 168 | memcpy(&data[1], length->data, length->length); 169 | uint32_t offset = 1 + length->length; 170 | for (uint32_t i = 0; i < nItems; i++) { 171 | memcpy(&data[offset], items[i]->data, items[i]->length); 172 | offset += items[i]->length; 173 | } 174 | 175 | DEREncodedItem *encodedItem = malloc(sizeof(DEREncodedItem)); 176 | if (encodedItem == NULL) { 177 | der_free_encoded_item(length); 178 | free(data); 179 | return NULL; 180 | } 181 | 182 | encodedItem->data = data; 183 | encodedItem->length = totalDataLength; 184 | 185 | der_free_encoded_item(length); 186 | 187 | return encodedItem; 188 | } 189 | 190 | DEREncodedItem *der_encode_set(DEREncodedItem *items[], uint32_t nItems) { 191 | uint8_t tag = ASN1_SET | ASN1_CONSTRUCTED; 192 | 193 | uint32_t totalLength = 0; 194 | for (uint32_t i = 0; i < nItems; i++) { 195 | totalLength += items[i]->length; 196 | } 197 | 198 | DEREncodedItem *length = der_encode_length(totalLength); 199 | if (length == NULL) { 200 | return NULL; 201 | } 202 | 203 | uint32_t totalDataLength = 1 + length->length + totalLength; 204 | 205 | uint8_t *data = malloc(totalDataLength); 206 | if (data == NULL) { 207 | der_free_encoded_item(length); 208 | return NULL; 209 | } 210 | 211 | data[0] = tag; 212 | memcpy(&data[1], length->data, length->length); 213 | uint32_t offset = 1 + length->length; 214 | for (uint32_t i = 0; i < nItems; i++) { 215 | memcpy(&data[offset], items[i]->data, items[i]->length); 216 | offset += items[i]->length; 217 | } 218 | 219 | DEREncodedItem *encodedItem = malloc(sizeof(DEREncodedItem)); 220 | if (encodedItem == NULL) { 221 | der_free_encoded_item(length); 222 | free(data); 223 | return NULL; 224 | } 225 | 226 | encodedItem->data = data; 227 | encodedItem->length = totalDataLength; 228 | 229 | der_free_encoded_item(length); 230 | 231 | return encodedItem; 232 | } -------------------------------------------------------------------------------- /src/DER.h: -------------------------------------------------------------------------------- 1 | #ifndef DER_H 2 | #define DER_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | uint8_t tag; 8 | uint32_t length; 9 | uint8_t *data; 10 | } DERItem; 11 | 12 | typedef struct { 13 | uint8_t *data; 14 | uint32_t length; 15 | } DEREncodedItem; 16 | 17 | void der_free_encoded_item(DEREncodedItem *item); 18 | DEREncodedItem *der_encode_boolean(bool value); 19 | DEREncodedItem *der_encode_integer(uint32_t value); 20 | DEREncodedItem *der_encode_utf8_string(const char *string); 21 | DEREncodedItem *der_encode_sequence(DEREncodedItem *items[], uint32_t nItems); 22 | DEREncodedItem *der_encode_set(DEREncodedItem *items[], uint32_t nItems); 23 | 24 | #endif // DER_H -------------------------------------------------------------------------------- /src/DyldSharedCache.h: -------------------------------------------------------------------------------- 1 | #ifndef DYLD_SHARED_CACHE_H 2 | #define DYLD_SHARED_CACHE_H 3 | 4 | #include "dyld_cache_format.h" 5 | #include 6 | #include "CachePatching.h" 7 | #include 8 | #include 9 | typedef struct MachO MachO; 10 | typedef struct Fat Fat; 11 | 12 | typedef struct DyldSharedCacheFile { 13 | char *filepath; 14 | size_t filesize; 15 | int fd; 16 | struct dyld_cache_header header; 17 | } DyldSharedCacheFile; 18 | 19 | typedef struct DyldSharedCacheMapping { 20 | uint64_t vmaddr; 21 | uint64_t fileoff; 22 | void *ptr; 23 | uint64_t size; 24 | uint32_t maxProt; 25 | uint32_t initProt; 26 | uint64_t flags; 27 | // ABI stable until here 28 | void *slideInfoPtr; 29 | uint64_t slideInfoSize; 30 | struct DyldSharedCacheFile *file; 31 | } DyldSharedCacheMapping; 32 | 33 | typedef struct DyldSharedCacheImage { 34 | uint64_t address; 35 | uint64_t size; 36 | uint64_t index; 37 | uuid_t uuid; 38 | char *path; 39 | uint32_t nlistStartIndex; 40 | uint32_t nlistCount; 41 | Fat *fat; 42 | } DyldSharedCacheImage; 43 | 44 | typedef struct DyldSharedCache { 45 | unsigned fileCount; 46 | DyldSharedCacheFile **files; 47 | 48 | struct { 49 | bool loaded; 50 | unsigned index; 51 | void *nlist; 52 | uint32_t nlistCount; 53 | char *strings; 54 | uint32_t stringsSize; 55 | } symbolFile; 56 | 57 | unsigned mappingCount; 58 | DyldSharedCacheMapping *mappings; 59 | uint64_t baseAddress; 60 | uint32_t premapSlide; 61 | uint32_t cputype; 62 | uint32_t cpusubtype; 63 | 64 | uint64_t containedImageCount; 65 | DyldSharedCacheImage *containedImages; 66 | } DyldSharedCache; 67 | 68 | typedef struct DyldSharedCachePointer { 69 | uint64_t location; 70 | uint64_t target; 71 | 72 | bool authenticated; 73 | uint8_t key; 74 | uint16_t diversifier; 75 | bool hasAddressDiversity; 76 | } DyldSharedCachePointer; 77 | 78 | enum PAC_KEY { 79 | PAC_KEY_IA = 0, 80 | PAC_KEY_IB = 1, 81 | PAC_KEY_DA = 2, 82 | PAC_KEY_DB = 3, 83 | }; 84 | 85 | DyldSharedCache *dsc_init_from_path_premapped(const char *path, uint32_t premapSlide); 86 | DyldSharedCache *dsc_init_from_path(const char *path); 87 | bool dsc_is32bit(DyldSharedCache *sharedCache); 88 | void dsc_enumerate_files(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *filepath, size_t filesize, struct dyld_cache_header *header)); 89 | 90 | void dsc_enumerate_mappings(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCacheMapping *mapping, DyldSharedCacheFile *sourceFile, bool *stop)); 91 | DyldSharedCacheMapping *dsc_lookup_mapping(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size); 92 | void *dsc_find_buffer(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size); 93 | 94 | int dsc_read_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, size_t size, void *outBuf); 95 | int dsc_read_string_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, char **outString); 96 | uint64_t dsc_fileoff_to_vmaddr(DyldSharedCache *sharedCache, DyldSharedCacheFile *file, uint64_t fileoff); 97 | uint64_t dsc_vmaddr_to_fileoff(DyldSharedCache *sharedCache, uint64_t vmaddr, DyldSharedCacheFile **fileOut); 98 | 99 | void dsc_enumerate_images(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *path, DyldSharedCacheImage *imageHandle, MachO *imageMachO, bool *stop)); 100 | DyldSharedCacheImage *dsc_find_image_for_section_address(DyldSharedCache *sharedCache, uint64_t address); 101 | MachO *dsc_image_get_macho(DyldSharedCacheImage *image); 102 | DyldSharedCacheImage *dsc_lookup_image_by_address(DyldSharedCache *sharedCache, uint64_t address); 103 | MachO *dsc_lookup_macho_by_address(DyldSharedCache *sharedCache, uint64_t address, DyldSharedCacheImage **imageHandleOut); 104 | DyldSharedCacheImage *dsc_lookup_image_by_path(DyldSharedCache *sharedCache, const char *path); 105 | MachO *dsc_lookup_macho_by_path(DyldSharedCache *sharedCache, const char *path, DyldSharedCacheImage **imageHandleOut); 106 | int dsc_enumerate_chained_fixups(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop)); 107 | 108 | int dsc_image_enumerate_symbols(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop)); 109 | int dsc_image_enumerate_patches(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(unsigned v, void *patchable_location, bool *stop)); 110 | int dsc_image_enumerate_chained_fixups(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop)); 111 | 112 | uint64_t dsc_get_base_address(DyldSharedCache *sharedCache); 113 | 114 | void dsc_free(DyldSharedCache *sharedCache); 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /src/Entitlements.h: -------------------------------------------------------------------------------- 1 | #ifndef ENTITLEMENTS_H 2 | #define ENTITLEMENTS_H 3 | 4 | #include "CSBlob.h" 5 | #include "DER.h" 6 | CS_DecodedBlob *create_xml_entitlements_blob(const char *entitlementsFile); 7 | CS_DecodedBlob *create_der_entitlements_blob(const char *entitlementsFile); 8 | 9 | #endif // ENTITLEMENTS_H -------------------------------------------------------------------------------- /src/Fat.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Fat.h" 5 | #include "MachO.h" 6 | #include "MachOByteOrder.h" 7 | 8 | #include "FileStream.h" 9 | #include "MemoryStream.h" 10 | 11 | int fat_read_at_offset(Fat *fat, uint64_t offset, size_t size, void *outBuf) 12 | { 13 | return memory_stream_read(fat->stream, offset, size, outBuf); 14 | } 15 | 16 | MemoryStream *fat_get_stream(Fat *fat) 17 | { 18 | return fat->stream; 19 | } 20 | 21 | int fat_parse_slices(Fat *fat) 22 | { 23 | // Get size of file 24 | size_t fileSize = memory_stream_get_size(fat->stream); 25 | if (fileSize == MEMORY_STREAM_SIZE_INVALID) { 26 | printf("Error: Failed to parse fat slices, memory_stream_get_size returned MEMORY_STREAM_SIZE_INVALID\n"); 27 | return -1; 28 | } 29 | 30 | // Read the fat header 31 | struct fat_header fatHeader; 32 | fat_read_at_offset(fat, 0, sizeof(fatHeader), &fatHeader); 33 | FAT_HEADER_APPLY_BYTE_ORDER(&fatHeader, BIG_TO_HOST_APPLIER); 34 | 35 | // Check if the file is a fat file 36 | if (fatHeader.magic == FAT_MAGIC || fatHeader.magic == FAT_MAGIC_64) { 37 | //printf("Fat header found! Magic: 0x%x.\n", fatHeader.magic); 38 | bool is64 = fatHeader.magic == FAT_MAGIC_64; 39 | 40 | // Sanity check the number of MachOs 41 | if (fatHeader.nfat_arch > 5 || fatHeader.nfat_arch < 1) { 42 | printf("Error: invalid number of MachO slices (%d), this likely means you are not using an iOS MachO.\n", fatHeader.nfat_arch); 43 | return -1; 44 | } 45 | 46 | fat->slicesCount = fatHeader.nfat_arch; 47 | fat->slices = malloc(sizeof(MachO*) * fat->slicesCount); 48 | memset(fat->slices, 0, sizeof(MachO*) * fat->slicesCount); 49 | 50 | // Iterate over all machOs 51 | for (uint32_t i = 0; i < fatHeader.nfat_arch; i++) { 52 | struct fat_arch_64 arch64 = {0}; 53 | if (is64) { 54 | // Read the arch descriptor 55 | fat_read_at_offset(fat, sizeof(struct fat_header) + i * sizeof(arch64), sizeof(arch64), &arch64); 56 | FAT_ARCH_64_APPLY_BYTE_ORDER(&arch64, BIG_TO_HOST_APPLIER); 57 | } 58 | else { 59 | // Read the fat arch structure 60 | struct fat_arch arch = {0}; 61 | fat_read_at_offset(fat, sizeof(struct fat_header) + i * sizeof(arch), sizeof(arch), &arch); 62 | FAT_ARCH_APPLY_BYTE_ORDER(&arch, BIG_TO_HOST_APPLIER); 63 | 64 | // Convert fat_arch to fat_arch_64 65 | arch64 = (struct fat_arch_64){ 66 | .cputype = arch.cputype, 67 | .cpusubtype = arch.cpusubtype, 68 | .offset = (uint64_t)arch.offset, 69 | .size = (uint64_t)arch.size, 70 | .align = arch.align, 71 | .reserved = 0, 72 | }; 73 | } 74 | 75 | MemoryStream *machOStream = memory_stream_softclone(fat->stream); 76 | int r = memory_stream_trim(machOStream, arch64.offset, fileSize - (arch64.offset + arch64.size)); 77 | if (r == 0) { 78 | fat->slices[i] = macho_init(machOStream, arch64); 79 | if (!fat->slices[i]) return -1; 80 | } 81 | } 82 | } else { 83 | // Not Fat? Parse single slice 84 | 85 | fat->slicesCount = 1; 86 | fat->slices = malloc(sizeof(MachO) * fat->slicesCount); 87 | memset(fat->slices, 0, sizeof(MachO) * fat->slicesCount); 88 | 89 | MemoryStream *machOStream = memory_stream_softclone(fat->stream); 90 | 91 | struct mach_header machHeader; 92 | memory_stream_read(machOStream, 0, sizeof(machHeader), &machHeader); 93 | MACH_HEADER_APPLY_BYTE_ORDER(&machHeader, LITTLE_TO_HOST_APPLIER); 94 | 95 | struct fat_arch_64 singleArch = {0}; 96 | singleArch.cpusubtype = machHeader.cpusubtype; 97 | singleArch.cputype = machHeader.cputype; 98 | singleArch.offset = 0; 99 | singleArch.size = fileSize; 100 | singleArch.align = 0x4000; 101 | 102 | MachO *singleSlice = macho_init(machOStream, singleArch); 103 | if (!singleSlice) return -1; 104 | fat->slices[0] = singleSlice; 105 | } 106 | //printf("Found %u MachO slice%s\n", fat->slicesCount, fat->slicesCount > 1 ? "s." : "."); 107 | return 0; 108 | } 109 | 110 | void fat_enumerate_slices(Fat *fat, void (^enumBlock)(MachO *macho, bool *stop)) 111 | { 112 | for (uint32_t i = 0; i < fat->slicesCount; i++) { 113 | MachO *curMacho = fat->slices[i]; 114 | bool stop = false; 115 | enumBlock(curMacho, &stop); 116 | if (stop) break; 117 | } 118 | } 119 | 120 | MachO *fat_find_slice(Fat *fat, cpu_type_t cputype, cpu_subtype_t cpusubtype) 121 | { 122 | for (uint32_t i = 0; i < fat->slicesCount; i++) { 123 | MachO *curMacho = fat->slices[i]; 124 | if (curMacho) { 125 | if (curMacho->machHeader.cputype == cputype && curMacho->machHeader.cpusubtype == cpusubtype) { 126 | return curMacho; 127 | } 128 | } 129 | } 130 | return NULL; 131 | } 132 | 133 | MachO *fat_get_single_slice(Fat *fat) 134 | { 135 | if (fat->slicesCount == 1) { 136 | return fat->slices[0]; 137 | } 138 | return NULL; 139 | } 140 | 141 | void fat_free(Fat *fat) 142 | { 143 | if (fat->slices != NULL) { 144 | for (int i = 0; i < fat->slicesCount; i++) { 145 | if (fat->slices[i]) { 146 | macho_free(fat->slices[i]); 147 | } 148 | } 149 | free(fat->slices); 150 | } 151 | if (fat->stream) memory_stream_free(fat->stream); 152 | free(fat); 153 | } 154 | 155 | Fat *fat_init_from_memory_stream(MemoryStream *stream) 156 | { 157 | Fat *fat = malloc(sizeof(Fat)); 158 | if (!fat) return NULL; 159 | memset(fat, 0, sizeof(Fat)); 160 | 161 | fat->stream = stream; 162 | 163 | if (fat_parse_slices(fat) != 0) goto fail; 164 | 165 | //size_t size = memory_stream_get_size(fat->stream); 166 | //printf("File size 0x%zx bytes, MachO slice count %u.\n", size, fat->slicesCount); 167 | return fat; 168 | 169 | fail: 170 | fat->stream = NULL; // The Fat only "owns" the stream when this function does not fail 171 | fat_free(fat); 172 | return NULL; 173 | } 174 | 175 | Fat *fat_dsc_init_from_memory_stream(MemoryStream *stream, DyldSharedCache *containingCache, DyldSharedCacheImage *cacheImage) 176 | { 177 | Fat *fat = fat_init_from_memory_stream(stream); 178 | if (fat) { 179 | for (int i = 0; i < fat->slicesCount; i++) { 180 | fat->slices[i]->containingCache = containingCache; 181 | fat->slices[i]->cacheImage = cacheImage; 182 | } 183 | } 184 | return fat; 185 | } 186 | 187 | Fat *fat_init_from_path(const char *filePath) 188 | { 189 | MemoryStream *stream = file_stream_init_from_path(filePath, 0, FILE_STREAM_SIZE_AUTO, 0); 190 | if (stream) { 191 | Fat *fat = fat_init_from_memory_stream(stream); 192 | if (!fat) { 193 | memory_stream_free(stream); 194 | } 195 | return fat; 196 | } 197 | return NULL; 198 | } 199 | 200 | Fat *fat_create_for_macho_array(char *firstInputPath, MachO **machoArray, int machoArrayCount) { 201 | Fat *fat = fat_init_from_path(firstInputPath); 202 | for (int i = 1; i < machoArrayCount; i++) { 203 | if (fat_add_macho(fat, machoArray[i]) != 0) { 204 | printf("Error: failed to add MachO to Fat.\n"); 205 | fat_free(fat); 206 | return NULL; 207 | } 208 | } 209 | return fat; 210 | } 211 | 212 | int fat_add_macho(Fat *fat, MachO *macho) 213 | { 214 | fat->slicesCount++; 215 | fat->slices = realloc(fat->slices, sizeof(MachO*) * fat->slicesCount); 216 | if (!fat->slices) return -1; 217 | fat->slices[fat->slicesCount - 1] = macho; 218 | return 0; 219 | } -------------------------------------------------------------------------------- /src/Fat.h: -------------------------------------------------------------------------------- 1 | #ifndef MACHO_H 2 | #define MACHO_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "MemoryStream.h" 12 | typedef struct MachO MachO; 13 | typedef struct DyldSharedCache DyldSharedCache; 14 | typedef struct DyldSharedCacheImage DyldSharedCacheImage; 15 | 16 | // A Fat structure can either represent a fat file with multiple slices, in which the slices will be loaded into the slices attribute 17 | // Or a single slice MachO, in which case it serves as a compatibility layer and the single slice will also be loaded into the slices attribute 18 | typedef struct Fat 19 | { 20 | MemoryStream *stream; 21 | MachO **slices; 22 | uint32_t slicesCount; 23 | int fileDescriptor; 24 | } Fat; 25 | 26 | int fat_read_at_offset(Fat *fat, uint64_t offset, size_t size, void *outBuf); 27 | 28 | MemoryStream *fat_get_stream(Fat *fat); 29 | 30 | // Initialise a Fat structure from a memory stream 31 | Fat *fat_init_from_memory_stream(MemoryStream *stream); 32 | 33 | // Initialise a FAT structure from a memory stream of something that is inside a dyld shared cache 34 | Fat *fat_dsc_init_from_memory_stream(MemoryStream *stream, DyldSharedCache *containingCache, DyldSharedCacheImage *cacheImage); 35 | 36 | // Initialise a FAT structure using the path to the file 37 | Fat *fat_init_from_path(const char *filePath); 38 | 39 | // Find macho with cputype and cpusubtype in Fat, returns NULL if not found 40 | MachO *fat_find_slice(Fat *fat, cpu_type_t cputype, cpu_subtype_t cpusubtype); 41 | 42 | // Enumerate all slices contained in Fat 43 | void fat_enumerate_slices(Fat *fat, void (^enumBlock)(MachO *macho, bool *stop)); 44 | 45 | // If Fat only has a single slice, return it 46 | MachO *fat_get_single_slice(Fat *fat); 47 | 48 | // Create a Fat structure from an array of MachO structures 49 | Fat *fat_create_for_macho_array(char *firstInputPath, MachO **machoArray, int machoArrayCount); 50 | 51 | // Add a MachO to the Fat structure 52 | int fat_add_macho(Fat *fat, MachO *macho); 53 | 54 | // Free all elements of the Fat structure 55 | void fat_free(Fat *fat); 56 | 57 | #endif // MACHO_H -------------------------------------------------------------------------------- /src/FileStream.c: -------------------------------------------------------------------------------- 1 | #include "FileStream.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static int _file_stream_context_is_trimmed(FileStreamContext *context) 7 | { 8 | return (context->bufferStart != 0 || context->bufferSize != context->fileSize); 9 | } 10 | 11 | static int file_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf) 12 | { 13 | FileStreamContext *context = stream->context; 14 | lseek(context->fd, context->bufferStart + offset, SEEK_SET); 15 | return read(context->fd, outBuf, size); 16 | } 17 | 18 | static int file_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf) 19 | { 20 | FileStreamContext *context = stream->context; 21 | 22 | // we can't write to non mutable files 23 | if ((stream->flags & MEMORY_STREAM_FLAG_MUTABLE) == 0) return -1; 24 | 25 | // we can't write to files we don't own 26 | if ((stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) == 0) return -1; 27 | 28 | size_t sizeToExpand = 0; 29 | // only expand when possible 30 | if ((context->bufferStart + offset + size) > context->fileSize) { 31 | if (((stream->flags | MEMORY_STREAM_FLAG_AUTO_EXPAND) == 0) || _file_stream_context_is_trimmed(context)) { 32 | printf("Error: file_stream_write failed, file is not auto expandable.\n"); 33 | return -1; 34 | } 35 | sizeToExpand = (context->bufferStart + offset + size) - context->fileSize; 36 | } 37 | 38 | // this is not supported for now: TODO fill with 0's then append the rest 39 | if (context->bufferStart + offset > context->fileSize) return -1; 40 | 41 | context->fileSize += sizeToExpand; 42 | context->bufferSize += sizeToExpand; 43 | 44 | lseek(context->fd, context->bufferStart + offset, SEEK_SET); 45 | return write(context->fd, inBuf, size); 46 | } 47 | 48 | static int file_stream_get_size(MemoryStream *stream, size_t *sizeOut) 49 | { 50 | FileStreamContext *context = stream->context; 51 | *sizeOut = context->bufferSize; 52 | return 0; 53 | } 54 | 55 | static MemoryStream *file_stream_softclone(MemoryStream *stream) 56 | { 57 | FileStreamContext *context = stream->context; 58 | return file_stream_init_from_file_descriptor_nodup(context->fd, context->bufferStart, context->bufferSize, 0); 59 | } 60 | 61 | static MemoryStream *file_stream_hardclone(MemoryStream *stream) 62 | { 63 | FileStreamContext *context = stream->context; 64 | int thisFlags = 0; 65 | if (stream->flags & MEMORY_STREAM_FLAG_MUTABLE) { 66 | thisFlags |= FILE_STREAM_FLAG_WRITABLE; 67 | } 68 | if (stream->flags & MEMORY_STREAM_FLAG_AUTO_EXPAND) { 69 | thisFlags |= FILE_STREAM_FLAG_AUTO_EXPAND; 70 | } 71 | return file_stream_init_from_file_descriptor(context->fd, context->bufferStart, context->bufferSize, thisFlags); 72 | } 73 | 74 | static int file_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd) 75 | { 76 | FileStreamContext *context = stream->context; 77 | if ((int64_t)context->bufferSize - (trimAtStart + trimAtEnd) < 0) { 78 | return -1; 79 | } 80 | 81 | if ((stream->flags & MEMORY_STREAM_FLAG_MUTABLE) && !_file_stream_context_is_trimmed(context)) { 82 | // If this stream is mutable, we want to actually trim the file itself 83 | uint32_t newSize = context->bufferSize - trimAtStart - trimAtEnd; 84 | memory_stream_copy_data(stream, trimAtStart, stream, 0, newSize); 85 | ftruncate(context->fd, newSize); 86 | } 87 | else { 88 | // Else just trim the part of the file that this buffer represents 89 | context->bufferStart += trimAtStart; 90 | context->bufferSize -= (trimAtStart + trimAtEnd); 91 | } 92 | 93 | return 0; 94 | } 95 | 96 | static int file_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd) 97 | { 98 | FileStreamContext *context = stream->context; 99 | 100 | // Expanding at start is not supported (for now?) 101 | if (expandAtStart != 0) return -1; 102 | 103 | // If this buffer is trimmed, expanding is also not supported 104 | if (_file_stream_context_is_trimmed(context)) return -1; 105 | 106 | lseek(context->fd, 0, SEEK_END); 107 | for (size_t i = expandAtEnd; i < 0; i--) { 108 | char buf = 0; 109 | write(context->fd, &buf, 1); 110 | } 111 | return 0; 112 | } 113 | 114 | static void file_stream_free(MemoryStream *stream) 115 | { 116 | FileStreamContext *context = stream->context; 117 | if (context) { 118 | if (context->fd > 0) { 119 | if (stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) { 120 | if (close(context->fd) != 0) { 121 | perror("close"); 122 | } 123 | } 124 | } 125 | free(context); 126 | } 127 | } 128 | 129 | MemoryStream *file_stream_init_from_file_descriptor_nodup(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags) 130 | { 131 | MemoryStream *stream = malloc(sizeof(MemoryStream)); 132 | if (!stream) return NULL; 133 | memset(stream, 0, sizeof(MemoryStream)); 134 | 135 | struct stat s; 136 | int statRes = fstat(fd, &s); 137 | if (statRes != 0) { 138 | printf("Error: stat returned %d for %d.\n", statRes, fd); 139 | goto fail; 140 | } 141 | 142 | FileStreamContext *context = malloc(sizeof(FileStreamContext)); 143 | context->fd = fd; 144 | context->fileSize = s.st_size; 145 | 146 | context->bufferStart = bufferStart; 147 | if (bufferSize == FILE_STREAM_SIZE_AUTO) { 148 | context->bufferSize = context->fileSize; 149 | } 150 | else { 151 | context->bufferSize = bufferSize; 152 | } 153 | 154 | stream->context = context; 155 | stream->flags = 0; 156 | if (flags & FILE_STREAM_FLAG_WRITABLE) { 157 | stream->flags |= MEMORY_STREAM_FLAG_MUTABLE; 158 | } 159 | if (flags & FILE_STREAM_FLAG_AUTO_EXPAND) { 160 | stream->flags |= MEMORY_STREAM_FLAG_AUTO_EXPAND; 161 | } 162 | 163 | stream->read = file_stream_read; 164 | stream->write = file_stream_write; 165 | stream->getSize = file_stream_get_size; 166 | 167 | stream->trim = file_stream_trim; 168 | stream->expand = file_stream_expand; 169 | 170 | stream->softclone = file_stream_softclone; 171 | stream->hardclone = file_stream_hardclone; 172 | stream->free = file_stream_free; 173 | 174 | return stream; 175 | 176 | fail: 177 | file_stream_free(stream); 178 | return NULL; 179 | } 180 | 181 | MemoryStream *file_stream_init_from_file_descriptor(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags) 182 | { 183 | MemoryStream *stream = file_stream_init_from_file_descriptor_nodup(dup(fd), bufferStart, bufferSize, flags); 184 | if (stream) { 185 | stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; 186 | } 187 | return stream; 188 | } 189 | 190 | MemoryStream *file_stream_init_from_path(const char *path, uint32_t bufferStart, size_t bufferSize, uint32_t flags) 191 | { 192 | int openFlags = 0; 193 | if (flags & FILE_STREAM_FLAG_WRITABLE) { 194 | openFlags = O_RDWR | O_CREAT; 195 | } 196 | else { 197 | openFlags = O_RDONLY; 198 | } 199 | int fd = open(path, openFlags); 200 | if (fd < 0) { 201 | printf("Failed to open %s: %s\n", path, strerror(errno)); 202 | return NULL; 203 | } 204 | 205 | MemoryStream *stream = file_stream_init_from_file_descriptor_nodup(fd, bufferStart, bufferSize, flags); 206 | if (stream) { 207 | stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; 208 | } 209 | return stream; 210 | } -------------------------------------------------------------------------------- /src/FileStream.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_STREAM_H 2 | #define FILE_STREAM_H 3 | 4 | #include "MemoryStream.h" 5 | 6 | #define FILE_STREAM_SIZE_AUTO 0 7 | #define FILE_STREAM_FLAG_WRITABLE (1 << 0) 8 | #define FILE_STREAM_FLAG_AUTO_EXPAND (1 << 1) 9 | 10 | typedef struct FileStreamContext { 11 | int fd; 12 | size_t fileSize; 13 | uint32_t bufferStart; 14 | size_t bufferSize; 15 | } FileStreamContext; 16 | 17 | MemoryStream *file_stream_init_from_file_descriptor_nodup(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags); 18 | MemoryStream *file_stream_init_from_file_descriptor(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags); 19 | MemoryStream *file_stream_init_from_path(const char *path, uint32_t bufferStart, size_t bufferSize, uint32_t flags); 20 | 21 | #endif // FILE_STREAM_H -------------------------------------------------------------------------------- /src/Host.c: -------------------------------------------------------------------------------- 1 | #include "Host.h" 2 | 3 | #include "MachO.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int host_get_cpu_information(cpu_type_t *cputype, cpu_subtype_t *cpusubtype) 12 | { 13 | size_t len; 14 | 15 | // Query for cputype 16 | len = sizeof(cputype); 17 | if (sysctlbyname("hw.cputype", cputype, &len, NULL, 0) == -1) { printf("Error: no cputype.\n"); return -1; } 18 | 19 | // Query for cpusubtype 20 | len = sizeof(cpusubtype); 21 | if (sysctlbyname("hw.cpusubtype", cpusubtype, &len, NULL, 0) == -1) { printf("Error: no cpusubtype.\n"); return -1; } 22 | 23 | return 0; 24 | } 25 | 26 | int host_supported_arm64e_abi(void) 27 | { 28 | struct utsname name; 29 | if (uname(&name) != 0) return -1; 30 | if (strcmp(name.release, "20.0.0") >= 0) { 31 | // iOS 14+, macOS 11+ use new ABI 32 | return 2; 33 | } 34 | else { 35 | // iOS 12-13 use old ABI 36 | return 1; 37 | } 38 | } 39 | 40 | MachO *fat_find_preferred_slice(Fat *fat) 41 | { 42 | cpu_type_t cputype; 43 | cpu_subtype_t cpusubtype; 44 | if (host_get_cpu_information(&cputype, &cpusubtype) != 0) { return NULL; } 45 | 46 | MachO *preferredMacho = NULL; 47 | 48 | // If you intend on supporting non darwin, implement platform specific logic here using #ifdef's 49 | if (cputype == CPU_TYPE_ARM64) { 50 | if (cpusubtype == CPU_SUBTYPE_ARM64E) { 51 | // If this is an arm64e device, first try to find a new ABI arm64e slice 52 | int supportedArm64eABI = host_supported_arm64e_abi(); 53 | if (supportedArm64eABI != -1) { 54 | if (supportedArm64eABI == 2) { 55 | // Find new ABI slice if host supports it 56 | preferredMacho = fat_find_slice(fat, cputype, (CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_ARM64E_ABI_V2)); 57 | } 58 | if (!preferredMacho) { 59 | // If no new ABI slice is found or the host does not support it, try to find an old ABI arm64e slice 60 | preferredMacho = fat_find_slice(fat, cputype, CPU_SUBTYPE_ARM64E); 61 | if (preferredMacho) { 62 | if (macho_get_filetype(preferredMacho) == MH_EXECUTE && supportedArm64eABI == 2) { 63 | // If this is an old ABI binary trying to run on a new ABI system, discard it 64 | // If it's an old ABI *library* trying to be loaded on a new ABI system, use it 65 | preferredMacho = NULL; 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | if (!preferredMacho) { 73 | // If not arm64e device or no arm64e slice found, try to find regular arm64 slice 74 | 75 | // The Kernel prefers an arm64v8 slice to an arm64 slice, so check that first 76 | // On iOS <14, dyld does not support arm64v8 slices, but that doesn't matter as the Kernel will still try to spawn it 77 | preferredMacho = fat_find_slice(fat, cputype, CPU_SUBTYPE_ARM64_V8); 78 | if (!preferredMacho) { 79 | // If that's not found, finally check for a regular arm64 slice 80 | preferredMacho = fat_find_slice(fat, cputype, CPU_SUBTYPE_ARM64_ALL); 81 | } 82 | } 83 | } 84 | 85 | if (!preferredMacho) { 86 | printf("Error: failed to find a preferred MachO slice that matches the host architecture.\n"); 87 | } 88 | return preferredMacho; 89 | } -------------------------------------------------------------------------------- /src/Host.h: -------------------------------------------------------------------------------- 1 | #ifndef HOST_H 2 | #define HOST_H 3 | 4 | #include "Fat.h" 5 | 6 | #define CPU_SUBTYPE_ARM64E_ABI_V2 0x80000000 7 | 8 | int host_get_cpu_information(cpu_type_t *cputype, cpu_subtype_t *cpusubtype); 9 | 10 | // Retrieve the preferred MachO slice from a Fat 11 | // Preferred slice as in the slice that the kernel would use when loading the file 12 | MachO *fat_find_preferred_slice(Fat *fat); 13 | 14 | #endif // HOST_H -------------------------------------------------------------------------------- /src/MachO.h: -------------------------------------------------------------------------------- 1 | #ifndef MACHO_SLICE_H 2 | #define MACHO_SLICE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "MemoryStream.h" 8 | #include "Fat.h" 9 | #include "DyldSharedCache.h" 10 | 11 | typedef struct MachOSegment 12 | { 13 | struct segment_command_64 command; 14 | struct section_64 sections[]; 15 | } __attribute__((__packed__)) MachOSegment; 16 | 17 | typedef struct FilesetMachO { 18 | char *entry_id; 19 | uint64_t vmaddr; 20 | uint64_t fileoff; 21 | Fat *underlyingMachO; 22 | } FilesetMachO; 23 | 24 | typedef struct MachO { 25 | MemoryStream *stream; 26 | bool is32Bit; 27 | struct mach_header machHeader; 28 | struct fat_arch_64 archDescriptor; 29 | uint64_t cachedBase; 30 | 31 | uint32_t filesetCount; 32 | FilesetMachO *filesetMachos; 33 | 34 | uint32_t segmentCount; 35 | MachOSegment **segments; 36 | 37 | DyldSharedCache *containingCache; 38 | DyldSharedCacheImage *cacheImage; 39 | } MachO; 40 | 41 | // Read data from a MachO at a specified offset 42 | int macho_read_at_offset(MachO *macho, uint64_t offset, size_t size, void *outBuf); 43 | 44 | // Write data from a MachO at a specified offset, auto expands, only works if opened via macho_init_for_writing 45 | int macho_write_at_offset(MachO *macho, uint64_t offset, size_t size, const void *inBuf); 46 | 47 | int macho_read_string_at_offset(MachO *macho, uint64_t offset, char **string); 48 | int macho_read_uleb128_at_offset(MachO *macho, uint64_t offset, uint64_t maxOffset, uint64_t *endOffsetOut, uint64_t *valueOut); 49 | 50 | MemoryStream *macho_get_stream(MachO *macho); 51 | uint32_t macho_get_filetype(MachO *macho); 52 | struct mach_header *macho_get_mach_header(MachO *macho); 53 | size_t macho_get_mach_header_size(MachO *macho); 54 | DyldSharedCache *macho_get_containing_cache(MachO *macho); 55 | 56 | // Perform translation between file offsets and virtual addresses 57 | int macho_translate_fileoff_to_vmaddr(MachO *macho, uint64_t fileoff, uint64_t *vmaddrOut, MachOSegment **segmentOut); 58 | int macho_translate_vmaddr_to_fileoff(MachO *macho, uint64_t vmaddr, uint64_t *fileoffOut, MachOSegment **segmentOut); 59 | 60 | // Wrappers to deal with virtual addresses 61 | int macho_read_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, void *outBuf); 62 | int macho_write_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, const void *inBuf); 63 | int macho_read_string_at_vmaddr(MachO *macho, uint64_t vmaddr, char **outString); 64 | uint64_t macho_get_base_address(MachO *macho); 65 | 66 | int macho_enumerate_load_commands(MachO *macho, void (^enumeratorBlock)(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop)); 67 | int macho_enumerate_segments(MachO *macho, void (^enumeratorBlock)(struct segment_command_64 *segment, bool *stop)); 68 | int macho_enumerate_sections(MachO *macho, void (^enumeratorBlock)(struct section_64 *section, struct segment_command_64 *segment, bool *stop)); 69 | int macho_enumerate_symbols(MachO *macho, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop)); 70 | int macho_enumerate_dependencies(MachO *macho, void (^enumeratorBlock)(const char *dylibPath, uint32_t cmd, struct dylib* dylib, bool *stop)); 71 | int macho_enumerate_rpaths(MachO *macho, void (^enumeratorBlock)(const char *rpath, bool *stop)); 72 | int macho_enumerate_function_starts(MachO *macho, void (^enumeratorBlock)(uint64_t funcAddr, bool *stop)); 73 | 74 | int macho_lookup_segment_by_addr(MachO *macho, uint64_t vmaddr, struct segment_command_64 *segmentOut); 75 | int macho_lookup_section_by_addr(MachO *macho, uint64_t vmaddr, struct section_64 *sectionOut); 76 | 77 | // Initialise a MachO object from a MemoryStream and it's corresponding Fat arch descriptor 78 | MachO *macho_init(MemoryStream *stream, struct fat_arch_64 archDescriptor); 79 | 80 | // Initialize a single slice macho for writing to it 81 | MachO *macho_init_for_writing(const char *filePath); 82 | 83 | // Create an array of MachO objects from an array of paths 84 | MachO **macho_array_create_for_paths(char **inputPaths, int inputPathsCount); 85 | 86 | // Check if a MachO is encrypted 87 | bool macho_is_encrypted(MachO *macho); 88 | 89 | void macho_free(MachO *macho); 90 | 91 | #endif // MACHO_SLICE_H 92 | -------------------------------------------------------------------------------- /src/MachOByteOrder.h: -------------------------------------------------------------------------------- 1 | #ifndef MACHO_BYTE_ORDER_H 2 | #define MACHO_BYTE_ORDER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // 8-bit integers needed for CodeDirectory 9 | #define BIG_TO_HOST(n) _Generic((n), \ 10 | int8_t: n, \ 11 | uint8_t: n, \ 12 | int16_t: OSSwapBigToHostInt16(n), \ 13 | uint16_t: OSSwapBigToHostInt16(n), \ 14 | int32_t: OSSwapBigToHostInt32(n), \ 15 | uint32_t: OSSwapBigToHostInt32(n), \ 16 | int64_t: OSSwapBigToHostInt64(n), \ 17 | uint64_t: OSSwapBigToHostInt64(n) \ 18 | ) 19 | 20 | #define HOST_TO_BIG(n) _Generic((n), \ 21 | int8_t: n, \ 22 | uint8_t: n, \ 23 | uint16_t: OSSwapHostToBigInt16(n), \ 24 | int16_t: OSSwapHostToBigInt16(n), \ 25 | int32_t: OSSwapHostToBigInt32(n), \ 26 | uint32_t: OSSwapHostToBigInt32(n), \ 27 | int64_t: OSSwapHostToBigInt64(n), \ 28 | uint64_t: OSSwapHostToBigInt64(n) \ 29 | ) 30 | 31 | #define LITTLE_TO_HOST(n) _Generic((n), \ 32 | int8_t: n, \ 33 | uint8_t: n, \ 34 | int16_t: OSSwapLittleToHostInt16(n), \ 35 | uint16_t: OSSwapLittleToHostInt16(n), \ 36 | int32_t: OSSwapLittleToHostInt32(n), \ 37 | uint32_t: OSSwapLittleToHostInt32(n), \ 38 | int64_t: OSSwapLittleToHostInt64(n), \ 39 | uint64_t: OSSwapLittleToHostInt64(n) \ 40 | ) 41 | 42 | #define HOST_TO_LITTLE(n) _Generic((n), \ 43 | int8_t: n, \ 44 | uint8_t: n, \ 45 | int16_t: OSSwapHostToLittleInt16(n), \ 46 | uint16_t: OSSwapHostToLittleInt16(n), \ 47 | int32_t: OSSwapHostToLittleInt32(n), \ 48 | uint32_t: OSSwapHostToLittleInt32(n), \ 49 | int64_t: OSSwapHostToLittleInt64(n), \ 50 | uint64_t: OSSwapHostToLittleInt64(n) \ 51 | ) 52 | 53 | #define HOST_TO_LITTLE_APPLIER(instance, member) \ 54 | (instance)->member = HOST_TO_LITTLE((instance)->member) 55 | 56 | #define HOST_TO_BIG_APPLIER(instance, member) \ 57 | (instance)->member = HOST_TO_BIG((instance)->member) 58 | 59 | #define LITTLE_TO_HOST_APPLIER(instance, member) \ 60 | (instance)->member = LITTLE_TO_HOST((instance)->member) 61 | 62 | #define BIG_TO_HOST_APPLIER(instance, member) \ 63 | (instance)->member = BIG_TO_HOST((instance)->member) 64 | 65 | #define FAT_HEADER_APPLY_BYTE_ORDER(fh, applier) \ 66 | applier(fh, magic); \ 67 | applier(fh, nfat_arch); 68 | 69 | #define FAT_ARCH_APPLY_BYTE_ORDER(arch, applier) \ 70 | applier(arch, cputype); \ 71 | applier(arch, cpusubtype); \ 72 | applier(arch, offset); \ 73 | applier(arch, size); \ 74 | applier(arch, align); 75 | 76 | #define FAT_ARCH_64_APPLY_BYTE_ORDER(arch, applier) \ 77 | applier(arch, cputype); \ 78 | applier(arch, cpusubtype); \ 79 | applier(arch, offset); \ 80 | applier(arch, size); \ 81 | applier(arch, align); \ 82 | applier(arch, reserved); 83 | 84 | #define MACH_HEADER_APPLY_BYTE_ORDER(mh, applier) \ 85 | applier(mh, magic); \ 86 | applier(mh, cputype); \ 87 | applier(mh, cpusubtype); \ 88 | applier(mh, filetype); \ 89 | applier(mh, ncmds); \ 90 | applier(mh, sizeofcmds); 91 | 92 | #define LOAD_COMMAND_APPLY_BYTE_ORDER(lc, applier) \ 93 | applier(lc, cmd); \ 94 | applier(lc, cmdsize); 95 | 96 | #define LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(lc, applier) \ 97 | applier(lc, cmd); \ 98 | applier(lc, cmdsize); \ 99 | applier(lc, dataoff); \ 100 | applier(lc, datasize); 101 | 102 | #define ENCRYPTION_INFO_COMMAND_APPLY_BYTE_ORDER(eic, applier) \ 103 | applier(eic, cmd); \ 104 | applier(eic, cmdsize); \ 105 | applier(eic, cryptoff); \ 106 | applier(eic, cryptsize); \ 107 | applier(eic, cryptid); 108 | 109 | #define BLOB_INDEX_APPLY_BYTE_ORDER(bi, applier) \ 110 | applier(bi, type); \ 111 | applier(bi, offset); 112 | 113 | #define SUPERBLOB_APPLY_BYTE_ORDER(sb, applier) \ 114 | applier(sb, magic); \ 115 | applier(sb, length); \ 116 | applier(sb, count); 117 | 118 | #define GENERIC_BLOB_APPLY_BYTE_ORDER(gb, applier) \ 119 | applier(gb, magic); \ 120 | applier(gb, length); 121 | 122 | #define CODE_DIRECTORY_APPLY_BYTE_ORDER(cd, applier) \ 123 | applier(cd, magic); \ 124 | applier(cd, length); \ 125 | applier(cd, version); \ 126 | applier(cd, flags); \ 127 | applier(cd, hashOffset); \ 128 | applier(cd, identOffset); \ 129 | applier(cd, nSpecialSlots); \ 130 | applier(cd, nCodeSlots); \ 131 | applier(cd, codeLimit); \ 132 | applier(cd, hashSize); \ 133 | applier(cd, hashType); \ 134 | applier(cd, platform); \ 135 | applier(cd, pageSize); \ 136 | applier(cd, spare2); \ 137 | applier(cd, scatterOffset); \ 138 | applier(cd, teamOffset); 139 | 140 | #define SEGMENT_COMMAND_APPLY_BYTE_ORDER(sc, applier) \ 141 | applier(sc, cmd); \ 142 | applier(sc, cmdsize); \ 143 | applier(sc, fileoff); \ 144 | applier(sc, filesize); \ 145 | applier(sc, vmaddr); \ 146 | applier(sc, vmsize); \ 147 | applier(sc, flags); \ 148 | applier(sc, initprot); \ 149 | applier(sc, maxprot); \ 150 | applier(sc, nsects); 151 | 152 | #define SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(sc64, applier) \ 153 | applier(sc64, cmd); \ 154 | applier(sc64, cmdsize); \ 155 | applier(sc64, fileoff); \ 156 | applier(sc64, filesize); \ 157 | applier(sc64, vmaddr); \ 158 | applier(sc64, vmsize); \ 159 | applier(sc64, flags); \ 160 | applier(sc64, initprot); \ 161 | applier(sc64, maxprot); \ 162 | applier(sc64, nsects); 163 | 164 | #define SECTION_APPLY_BYTE_ORDER(sc, applier) \ 165 | applier(sc, addr); \ 166 | applier(sc, align); \ 167 | applier(sc, flags); \ 168 | applier(sc, nreloc); \ 169 | applier(sc, offset); \ 170 | applier(sc, reserved1); \ 171 | applier(sc, reserved2); \ 172 | applier(sc, size); 173 | 174 | #define SECTION_64_APPLY_BYTE_ORDER(sc64, applier) \ 175 | applier(sc64, addr); \ 176 | applier(sc64, align); \ 177 | applier(sc64, flags); \ 178 | applier(sc64, nreloc); \ 179 | applier(sc64, offset); \ 180 | applier(sc64, reserved1); \ 181 | applier(sc64, reserved2); \ 182 | applier(sc64, reserved3); \ 183 | applier(sc64, size); 184 | 185 | #define FILESET_ENTRY_COMMAND_APPLY_BYTE_ORDER(fse, applier) \ 186 | applier(fse, cmd); \ 187 | applier(fse, cmdsize); \ 188 | applier(fse, vmaddr); \ 189 | applier(fse, fileoff); \ 190 | applier(fse, entry_id.offset); \ 191 | applier(fse, reserved); 192 | 193 | #define SYMTAB_COMMAND_APPLY_BYTE_ORDER(symt, applier) \ 194 | applier(symt, cmd); \ 195 | applier(symt, cmdsize); \ 196 | applier(symt, nsyms); \ 197 | applier(symt, stroff); \ 198 | applier(symt, strsize); \ 199 | applier(symt, symoff); 200 | 201 | #define NLIST_APPLY_BYTE_ORDER(nl, applier) \ 202 | applier(nl, n_un.n_strx); \ 203 | applier(nl, n_type); \ 204 | applier(nl, n_sect); \ 205 | applier(nl, n_desc); \ 206 | applier(nl, n_value); 207 | 208 | #define NLIST_64_APPLY_BYTE_ORDER(nl, applier) \ 209 | applier(nl, n_un.n_strx); \ 210 | applier(nl, n_type); \ 211 | applier(nl, n_sect); \ 212 | applier(nl, n_desc); \ 213 | applier(nl, n_value); 214 | 215 | #define DYLIB_APPLY_BYTE_ORDER(dylib, applier) \ 216 | applier(dylib, name.offset); \ 217 | applier(dylib, timestamp); \ 218 | applier(dylib, current_version); \ 219 | applier(dylib, compatibility_version); 220 | 221 | #define DYLIB_COMMAND_APPLY_BYTE_ORDER(dycmd, applier) \ 222 | LOAD_COMMAND_APPLY_BYTE_ORDER(dycmd, applier); \ 223 | DYLIB_APPLY_BYTE_ORDER((&dycmd->dylib), applier); 224 | 225 | #define RPATH_COMMAND_APPLY_BYTE_ORDER(rpcmd, applier) \ 226 | LOAD_COMMAND_APPLY_BYTE_ORDER(rpcmd, applier); \ 227 | applier(rpcmd, path.offset); 228 | 229 | #endif // MACHO_BYTE_ORDER_H 230 | -------------------------------------------------------------------------------- /src/MachOLoadCommand.c: -------------------------------------------------------------------------------- 1 | #include "MachOLoadCommand.h" 2 | #include "Util.h" 3 | #include "CSBlob.h" 4 | 5 | char *load_command_to_string(int loadCommand) { 6 | switch (loadCommand) { 7 | case LC_SEGMENT: 8 | return "LC_SEGMENT"; 9 | case LC_SYMTAB: 10 | return "LC_SYMTAB"; 11 | case LC_SYMSEG: 12 | return "LC_SYMSEG"; 13 | case LC_THREAD: 14 | return "LC_THREAD"; 15 | case LC_UNIXTHREAD: 16 | return "LC_UNIXTHREAD"; 17 | case LC_LOADFVMLIB: 18 | return "LC_LOADFVMLIB"; 19 | case LC_IDFVMLIB: 20 | return "LC_IDFVMLIB"; 21 | case LC_IDENT: 22 | return "LC_IDENT"; 23 | case LC_FVMFILE: 24 | return "LC_FVMFILE"; 25 | case LC_PREPAGE: 26 | return "LC_PREPAGE"; 27 | case LC_DYSYMTAB: 28 | return "LC_DYSYMTAB"; 29 | case LC_LOAD_DYLIB: 30 | return "LC_LOAD_DYLIB"; 31 | case LC_ID_DYLIB: 32 | return "LC_ID_DYLIB"; 33 | case LC_LOAD_DYLINKER: 34 | return "LC_LOAD_DYLINKER"; 35 | case LC_ID_DYLINKER: 36 | return "LC_ID_DYLINKER"; 37 | case LC_PREBOUND_DYLIB: 38 | return "LC_PREBOUND_DYLIB"; 39 | case LC_ROUTINES: 40 | return "LC_ROUTINES"; 41 | case LC_SUB_FRAMEWORK: 42 | return "LC_SUB_FRAMEWORK"; 43 | case LC_SUB_UMBRELLA: 44 | return "LC_SUB_UMBRELLA"; 45 | case LC_SUB_CLIENT: 46 | return "LC_SUB_CLIENT"; 47 | case LC_SUB_LIBRARY: 48 | return "LC_SUB_LIBRARY"; 49 | case LC_TWOLEVEL_HINTS: 50 | return "LC_TWOLEVEL_HINTS"; 51 | case LC_PREBIND_CKSUM: 52 | return "LC_PREBIND_CKSUM"; 53 | case LC_LOAD_WEAK_DYLIB: 54 | return "LC_LOAD_WEAK_DYLIB"; 55 | case LC_SEGMENT_64: 56 | return "LC_SEGMENT_64"; 57 | case LC_ROUTINES_64: 58 | return "LC_ROUTINES_64"; 59 | case LC_UUID: 60 | return "LC_UUID"; 61 | case LC_RPATH: 62 | return "LC_RPATH"; 63 | case LC_CODE_SIGNATURE: 64 | return "LC_CODE_SIGNATURE"; 65 | case LC_SEGMENT_SPLIT_INFO: 66 | return "LC_SEGMENT_SPLIT_INFO"; 67 | case LC_REEXPORT_DYLIB: 68 | return "LC_REEXPORT_DYLIB"; 69 | case LC_LAZY_LOAD_DYLIB: 70 | return "LC_LAZY_LOAD_DYLIB"; 71 | case LC_ENCRYPTION_INFO: 72 | return "LC_ENCRYPTION_INFO"; 73 | case LC_DYLD_INFO: 74 | return "LC_DYLD_INFO"; 75 | case LC_DYLD_INFO_ONLY: 76 | return "LC_DYLD_INFO_ONLY"; 77 | case LC_LOAD_UPWARD_DYLIB: 78 | return "LC_LOAD_UPWARD_DYLIB"; 79 | case LC_VERSION_MIN_MACOSX: 80 | return "LC_VERSION_MIN_MACOSX"; 81 | case LC_VERSION_MIN_IPHONEOS: 82 | return "LC_VERSION_MIN_IPHONEOS"; 83 | case LC_FUNCTION_STARTS: 84 | return "LC_FUNCTION_STARTS"; 85 | case LC_DYLD_ENVIRONMENT: 86 | return "LC_DYLD_ENVIRONMENT"; 87 | case LC_MAIN: 88 | return "LC_MAIN"; 89 | case LC_DATA_IN_CODE: 90 | return "LC_DATA_IN_CODE"; 91 | case LC_SOURCE_VERSION: 92 | return "LC_SOURCE_VERSION"; 93 | case LC_DYLIB_CODE_SIGN_DRS: 94 | return "LC_DYLIB_CODE_SIGN_DRS"; 95 | case LC_ENCRYPTION_INFO_64: 96 | return "LC_ENCRYPTION_INFO_64"; 97 | case LC_LINKER_OPTION: 98 | return "LC_LINKER_OPTION"; 99 | case LC_LINKER_OPTIMIZATION_HINT: 100 | return "LC_LINKER_OPTIMIZATION_HINT"; 101 | case LC_VERSION_MIN_TVOS: 102 | return "LC_VERSION_MIN_TVOS"; 103 | case LC_VERSION_MIN_WATCHOS: 104 | return "LC_VERSION_MIN_WATCHOS"; 105 | case LC_NOTE: 106 | return "LC_NOTE"; 107 | case LC_BUILD_VERSION: 108 | return "LC_BUILD_VERSION"; 109 | case LC_DYLD_EXPORTS_TRIE: 110 | return "LC_DYLD_EXPORTS_TRIE"; 111 | case LC_DYLD_CHAINED_FIXUPS: 112 | return "LC_DYLD_CHAINED_FIXUPS"; 113 | case LC_FILESET_ENTRY: 114 | return "LC_FILESET_ENTRY"; 115 | default: 116 | return "LC_UNKNOWN"; 117 | } 118 | } 119 | 120 | void update_segment_command_64(MachO *macho, const char *segmentName, uint64_t vmaddr, uint64_t vmsize, uint64_t fileoff, uint64_t filesize) { 121 | macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { 122 | if (loadCommand.cmd == LC_SEGMENT_64) { 123 | struct segment_command_64 *segmentCommand = (struct segment_command_64 *)cmd; 124 | SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, LITTLE_TO_HOST_APPLIER); 125 | if (strcmp(segmentCommand->segname, segmentName) == 0) { 126 | segmentCommand->vmaddr = vmaddr; 127 | segmentCommand->vmsize = vmsize; 128 | segmentCommand->fileoff = fileoff; 129 | segmentCommand->filesize = filesize; 130 | SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, HOST_TO_LITTLE_APPLIER); 131 | memory_stream_write(macho->stream, offset, sizeof(struct segment_command_64), segmentCommand); 132 | *stop = true; 133 | } 134 | } 135 | }); 136 | } 137 | 138 | void update_lc_code_signature(MachO *macho, uint64_t size) { 139 | macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { 140 | if (loadCommand.cmd == LC_CODE_SIGNATURE) { 141 | struct linkedit_data_command *csLoadCommand = (struct linkedit_data_command *)cmd; 142 | LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(csLoadCommand, LITTLE_TO_HOST_APPLIER); 143 | csLoadCommand->datasize = size; 144 | LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(csLoadCommand, HOST_TO_LITTLE_APPLIER); 145 | memory_stream_write(macho->stream, offset, sizeof(struct linkedit_data_command), csLoadCommand); 146 | *stop = true; 147 | } 148 | }); 149 | } 150 | 151 | int update_load_commands_for_coretrust_bypass(MachO *macho, CS_SuperBlob *superblob, uint64_t originalCodeSignatureSize) { 152 | 153 | uint64_t sizeOfCodeSignature = BIG_TO_HOST(superblob->length); 154 | 155 | // Calculate how much padding we currently have 156 | __block uint64_t blockPaddingSize = 0; 157 | __block uint64_t vmAddress = 0; 158 | __block uint64_t fileOffset = 0; 159 | macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { 160 | if (loadCommand.cmd == LC_SEGMENT_64) { 161 | struct segment_command_64 *segmentCommand = (struct segment_command_64 *)cmd; 162 | SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, LITTLE_TO_HOST_APPLIER); 163 | if (strcmp(segmentCommand->segname, "__LINKEDIT") == 0) { 164 | blockPaddingSize = segmentCommand->filesize - originalCodeSignatureSize; 165 | vmAddress = segmentCommand->vmaddr; 166 | fileOffset = segmentCommand->fileoff; 167 | *stop = true; 168 | } 169 | } 170 | }); 171 | 172 | if (blockPaddingSize == 0 || vmAddress == 0 || fileOffset == 0) { 173 | printf("Error: failed to get existing values for __LINKEDIT segment.\n"); 174 | return -1; 175 | } 176 | 177 | uint64_t newSegmentSize = sizeOfCodeSignature + blockPaddingSize; 178 | uint64_t newVMSize = align_to_size(newSegmentSize, 0x4000); 179 | 180 | // Update the segment command 181 | printf("Updating __LINKEDIT segment...\n"); 182 | update_segment_command_64(macho, "__LINKEDIT", vmAddress, newVMSize, fileOffset, newSegmentSize); 183 | 184 | // Update the code signature load command 185 | printf("Updating LC_CODE_SIGNATURE load command...\n"); 186 | update_lc_code_signature(macho, sizeOfCodeSignature); 187 | 188 | return 0; 189 | } -------------------------------------------------------------------------------- /src/MachOLoadCommand.h: -------------------------------------------------------------------------------- 1 | #ifndef MACHO_LOAD_COMMAND_H 2 | #define MACHO_LOAD_COMMAND_H 3 | 4 | #include 5 | 6 | #ifndef LC_FILESET_ENTRY 7 | 8 | #define MH_FILESET 0xc 9 | #define LC_FILESET_ENTRY 0x80000035 10 | 11 | struct fileset_entry_command { 12 | uint32_t cmd; /* LC_FILESET_ENTRY */ 13 | uint32_t cmdsize; /* includes entry_id string */ 14 | uint64_t vmaddr; /* memory address of the entry */ 15 | uint64_t fileoff; /* file offset of the entry */ 16 | union lc_str entry_id; /* contained entry id */ 17 | uint32_t reserved; /* reserved */ 18 | }; 19 | 20 | #endif 21 | 22 | #include "MachO.h" 23 | #include "FileStream.h" 24 | #include "MachOByteOrder.h" 25 | #include "CSBlob.h" 26 | 27 | // Convert load command to load command name 28 | char *load_command_to_string(int loadCommand); 29 | void update_segment_command_64(MachO *macho, const char *segmentName, uint64_t vmaddr, uint64_t vmsize, uint64_t fileoff, uint64_t filesize); 30 | void update_lc_code_signature(MachO *macho, uint64_t size); 31 | int update_load_commands_for_coretrust_bypass(MachO *macho, CS_SuperBlob *superblob, uint64_t originalCodeSignatureSize); 32 | 33 | #endif // MACHO_LOAD_COMMAND_H -------------------------------------------------------------------------------- /src/MemoryStream.c: -------------------------------------------------------------------------------- 1 | #include "MemoryStream.h" 2 | #include "Util.h" 3 | 4 | int memory_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf) 5 | { 6 | if (stream->read) { 7 | int ret = stream->read(stream, offset, size, outBuf); 8 | if (ret != size) { return -1; } 9 | return 0; 10 | } 11 | return -1; 12 | } 13 | 14 | int memory_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf) 15 | { 16 | if (stream->write) { 17 | int ret = stream->write(stream, offset, size, inBuf); 18 | if (ret != size) { return -1; } 19 | return 0; 20 | } 21 | return -1; 22 | } 23 | 24 | int memory_stream_insert(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf) 25 | { 26 | if (!(stream->flags & MEMORY_STREAM_FLAG_MUTABLE)) goto fail; 27 | 28 | size_t streamSize = memory_stream_get_size(stream); 29 | if (memory_stream_expand(stream, 0, size) != 0) goto fail; 30 | if (memory_stream_copy_data(stream, offset, stream, offset + size, streamSize-offset) != 0) goto fail; 31 | if (memory_stream_write(stream, offset, size, inBuf) != 0) goto fail; 32 | return 0; 33 | 34 | fail: 35 | printf("Error: memory_stream_insert failed\n"); 36 | return -1; 37 | } 38 | 39 | int memory_stream_delete(MemoryStream *stream, uint64_t offset, size_t size) 40 | { 41 | if (size == 0) return 0; 42 | if (!(stream->flags & MEMORY_STREAM_FLAG_MUTABLE)) goto fail; 43 | 44 | size_t streamSize = memory_stream_get_size(stream); 45 | if (memory_stream_copy_data(stream, offset+size, stream, offset, streamSize-(offset+size)) != 0) goto fail; 46 | if (memory_stream_trim(stream, 0, size) != 0) goto fail; 47 | return 0; 48 | 49 | fail: 50 | printf("Error: memory_stream_delete failed\n"); 51 | return -1; 52 | } 53 | 54 | int memory_stream_read_string(MemoryStream *stream, uint64_t offset, char **outString) 55 | { 56 | size_t size = 0; 57 | char c = 1; 58 | while (c != 0) { 59 | int r = memory_stream_read(stream, offset+size, sizeof(c), &c); 60 | if (r != 0) return r; 61 | size++; 62 | } 63 | 64 | *outString = malloc(size+1); 65 | return memory_stream_read(stream, offset, size+1, *outString); 66 | } 67 | 68 | int memory_stream_write_string(MemoryStream *stream, uint64_t offset, const char *string) 69 | { 70 | size_t size = strlen(string) + 1; 71 | return memory_stream_write(stream, offset, size, string); 72 | } 73 | 74 | size_t memory_stream_get_size(MemoryStream *stream) 75 | { 76 | if (stream->getSize) { 77 | size_t sizeOut; 78 | if (stream->getSize(stream, &sizeOut) == 0) return sizeOut; 79 | } 80 | return MEMORY_STREAM_SIZE_INVALID; 81 | } 82 | 83 | uint8_t *memory_stream_get_raw_pointer(MemoryStream *stream) 84 | { 85 | if (stream->getRawPtr) { 86 | return stream->getRawPtr(stream); 87 | } 88 | return NULL; 89 | } 90 | 91 | uint32_t memory_stream_get_flags(MemoryStream *stream) 92 | { 93 | return stream->flags; 94 | } 95 | 96 | void _memory_stream_clone(MemoryStream *clone, MemoryStream *stream) 97 | { 98 | clone->read = stream->read; 99 | clone->write = stream->write; 100 | clone->getSize = stream->getSize; 101 | clone->getRawPtr = stream->getRawPtr; 102 | 103 | clone->trim = stream->trim; 104 | clone->expand = stream->expand; 105 | 106 | clone->softclone = stream->softclone; 107 | clone->hardclone = stream->hardclone; 108 | clone->free = stream->free; 109 | } 110 | 111 | MemoryStream *memory_stream_softclone(MemoryStream *stream) 112 | { 113 | if (stream->softclone) { 114 | MemoryStream *clone = stream->softclone(stream); 115 | if (clone) { 116 | _memory_stream_clone(clone, stream); 117 | return clone; 118 | } 119 | } 120 | return NULL; 121 | } 122 | 123 | MemoryStream *memory_stream_hardclone(MemoryStream *stream) 124 | { 125 | if (stream->hardclone) { 126 | MemoryStream *clone = stream->hardclone(stream); 127 | if (clone) { 128 | _memory_stream_clone(clone, stream); 129 | return clone; 130 | } 131 | } 132 | return NULL; 133 | } 134 | 135 | int memory_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd) 136 | { 137 | if (stream->trim) { 138 | return stream->trim(stream, trimAtStart, trimAtEnd); 139 | } 140 | return -1; 141 | } 142 | 143 | int memory_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd) 144 | { 145 | if (stream->expand) { 146 | return stream->expand(stream, expandAtStart, expandAtEnd); 147 | } 148 | return -1; 149 | } 150 | 151 | void memory_stream_free(MemoryStream *stream) 152 | { 153 | if (stream->free) { 154 | stream->free(stream); 155 | } 156 | free(stream); 157 | } 158 | 159 | #define COPY_DATA_BUFFER_SIZE 0x4000 160 | int memory_stream_copy_data(MemoryStream *originStream, uint64_t originOffset, MemoryStream *targetStream, uint64_t targetOffset, size_t size) 161 | { 162 | size_t originSize = memory_stream_get_size(originStream); 163 | size_t targetSize = memory_stream_get_size(targetStream); 164 | if (originSize == MEMORY_STREAM_SIZE_INVALID || targetSize == MEMORY_STREAM_SIZE_INVALID) { 165 | printf("Error: memory_stream_copy_data failed, invalid size detected\n"); 166 | return -1; 167 | } 168 | 169 | if (originOffset + size > originSize) { 170 | printf("Error: memory_stream_copy_data failed, originOffset OOB\n"); 171 | return -1; 172 | } 173 | if (targetOffset + size > targetSize && !(memory_stream_get_flags(targetStream) | MEMORY_STREAM_FLAG_AUTO_EXPAND)) { 174 | printf("Error: memory_stream_copy_data failed, targetOffset OOB\n"); 175 | return -1; 176 | } 177 | 178 | bool backwards = (originStream == targetStream) && (targetOffset > originOffset); 179 | 180 | uint8_t buffer[COPY_DATA_BUFFER_SIZE]; 181 | for (uint32_t copiedSize = 0; copiedSize < size; copiedSize += COPY_DATA_BUFFER_SIZE) { 182 | uint32_t remainingSize = size - copiedSize; 183 | uint32_t sizeToCopy = COPY_DATA_BUFFER_SIZE; 184 | if (remainingSize < sizeToCopy) { 185 | sizeToCopy = remainingSize; 186 | } 187 | 188 | uint64_t readOffset = backwards ? ((originOffset + (size - copiedSize)) - sizeToCopy) : (originOffset + copiedSize); 189 | uint64_t writeOffset = backwards ? ((targetOffset + (size - copiedSize)) - sizeToCopy) : (targetOffset + copiedSize); 190 | 191 | int rr = memory_stream_read(originStream, readOffset, sizeToCopy, buffer); 192 | if (rr != 0) { 193 | printf("Error: memory_stream_copy_data failed on memory_stream_read (%d)\n", rr); 194 | return rr; 195 | } 196 | int wr = memory_stream_write(targetStream, writeOffset, sizeToCopy, buffer); 197 | if (wr != 0) { 198 | printf("Error: memory_stream_copy_data failed on memory_stream_write (%d)\n", wr); 199 | return rr; 200 | } 201 | } 202 | 203 | return 0; 204 | } 205 | 206 | int memory_stream_find_memory(MemoryStream *stream, uint64_t searchStartOffset, uint64_t searchEndOffset, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundOffsetOut) 207 | { 208 | __block int r = -1; 209 | enumerate_range(searchStartOffset, searchEndOffset, alignment, nbytes, ^bool(uint64_t cur) { 210 | uint8_t buf[nbytes]; 211 | memory_stream_read(stream, cur, nbytes, buf); 212 | if (!memcmp_masked(buf, bytes, mask, nbytes)) { 213 | *foundOffsetOut = cur; 214 | r = 0; 215 | return false; 216 | } 217 | return true; 218 | }); 219 | return r; 220 | } -------------------------------------------------------------------------------- /src/MemoryStream.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_STREAM_H 2 | #define MEMORY_STREAM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define MEMORY_STREAM_FLAG_OWNS_DATA (1 << 0) 13 | #define MEMORY_STREAM_FLAG_MUTABLE (1 << 1) 14 | #define MEMORY_STREAM_FLAG_AUTO_EXPAND (1 << 2) 15 | 16 | #define MEMORY_STREAM_SIZE_INVALID (size_t)-1 17 | 18 | // A generic memory IO interface that is used throughout this project 19 | // Can be backed by anything, just the functions have to be implemented 20 | typedef struct s_MemoryStream { 21 | void *context; 22 | uint32_t flags; 23 | 24 | int (*read)(struct s_MemoryStream *stream, uint64_t offset, size_t size, void *outBuf); 25 | int (*write)(struct s_MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf); 26 | int (*getSize)(struct s_MemoryStream *stream, size_t *sizeOut); 27 | uint8_t *(*getRawPtr)(struct s_MemoryStream *stream); 28 | 29 | int (*trim)(struct s_MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd); 30 | int (*expand)(struct s_MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd); 31 | 32 | struct s_MemoryStream *(*hardclone)(struct s_MemoryStream *stream); 33 | struct s_MemoryStream *(*softclone)(struct s_MemoryStream *stream); 34 | void (*free)(struct s_MemoryStream *stream); 35 | } MemoryStream; 36 | 37 | int memory_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf); 38 | int memory_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf); 39 | 40 | int memory_stream_insert(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf); 41 | int memory_stream_delete(MemoryStream *stream, uint64_t offset, size_t size); 42 | 43 | int memory_stream_read_string(MemoryStream *stream, uint64_t offset, char **outString); 44 | int memory_stream_write_string(MemoryStream *stream, uint64_t offset, const char *string); 45 | 46 | size_t memory_stream_get_size(MemoryStream *stream); 47 | uint8_t *memory_stream_get_raw_pointer(MemoryStream *stream); 48 | uint32_t memory_stream_get_flags(MemoryStream *stream); 49 | 50 | MemoryStream *memory_stream_softclone(MemoryStream *stream); 51 | MemoryStream *memory_stream_hardclone(MemoryStream *stream); 52 | int memory_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd); 53 | int memory_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd); 54 | 55 | void memory_stream_free(MemoryStream *stream); 56 | 57 | int memory_stream_copy_data(MemoryStream *originStream, uint64_t originOffset, MemoryStream *targetStream, uint64_t targetOffset, size_t size); 58 | int memory_stream_find_memory(MemoryStream *stream, uint64_t searchStartOffset, uint64_t searchEndOffset, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundOffsetOut); 59 | 60 | #endif // MEMORY_STREAM_H -------------------------------------------------------------------------------- /src/PatchFinder.h: -------------------------------------------------------------------------------- 1 | #ifndef PATCHFINDER_H 2 | #define PATCHFINDER_H 3 | 4 | #include 5 | #include "MachO.h" 6 | 7 | enum { 8 | PFMETRIC_TYPE_PATTERN, 9 | PFMETRIC_TYPE_STRING, 10 | PFMETRIC_TYPE_XREF, 11 | }; 12 | 13 | typedef struct s_PFSectionInfo { 14 | char sectname[17]; 15 | char segname[17]; 16 | uint64_t fileoff; 17 | uint64_t vmaddr; 18 | uint64_t size; 19 | uint32_t initprot; 20 | uint32_t maxprot; 21 | } PFSectionInfo; 22 | 23 | void pfsec_info_populate_section64(PFSectionInfo *sectionInfo, struct section_64 *section64); 24 | void pfsec_info_populate_segment(PFSectionInfo *sectionInfo, MachOSegment *segment); 25 | void pfsec_info_populate_dsc_mapping(PFSectionInfo *sectionInfo, DyldSharedCacheMapping *dscMapping); 26 | 27 | typedef struct s_PFSection { 28 | MemoryStream *stream; 29 | MachO *macho; 30 | DyldSharedCache *sharedCache; 31 | PFSectionInfo info; 32 | uint8_t *cache; 33 | uint64_t (*pointerDecoder)(struct s_PFSection *section, uint64_t vmaddr, uint64_t value); 34 | } PFSection; 35 | 36 | PFSection *pfsec_init_from_macho(MachO *macho, const char *filesetEntryId, const char *segName, const char *sectName); 37 | PFSection *pfsec_init_from_dsc_mapping(DyldSharedCache *sharedCache, DyldSharedCacheMapping *mapping); 38 | MachO *pfsec_get_macho(PFSection *section); 39 | DyldSharedCache *pfsec_get_dsc(PFSection *section); 40 | void pfsec_set_pointer_decoder(PFSection *section, uint64_t (*pointerDecoder)(struct s_PFSection *section, uint64_t vmaddr, uint64_t value)); 41 | int pfsec_read_reloff(PFSection *section, uint64_t rel, size_t size, void *outBuf); 42 | uint32_t pfsec_read32_reloff(PFSection *section, uint64_t rel); 43 | int pfsec_read_at_address(PFSection *section, uint64_t vmaddr, void *outBuf, size_t size); 44 | uint32_t pfsec_read32(PFSection *section, uint64_t vmaddr); 45 | uint64_t pfsec_read64(PFSection *section, uint64_t vmaddr); 46 | uint64_t pfsec_read_pointer(PFSection *section, uint64_t vmaddr); 47 | int pfsec_read_string(PFSection *section, uint64_t vmaddr, char **outString); 48 | int pfsec_set_cached(PFSection *section, bool cached); 49 | void *pfsec_get_raw_pointer(PFSection *section); 50 | uint64_t pfsec_find_prev_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask); 51 | uint64_t pfsec_find_next_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask); 52 | uint64_t pfsec_find_function_start(PFSection *section, uint64_t midAddr); 53 | bool pfsec_contains_vmaddr(PFSection *section, uint64_t addr); 54 | void pfsec_free(PFSection *section); 55 | 56 | 57 | typedef struct s_MetricShared { 58 | uint32_t type; 59 | } MetricShared; 60 | 61 | typedef struct s_PFPatternMetric { 62 | MetricShared shared; 63 | 64 | void *bytes; 65 | void *mask; 66 | size_t nbytes; 67 | uint16_t alignment; 68 | } PFPatternMetric; 69 | 70 | typedef struct s_PFStringMetric { 71 | MetricShared shared; 72 | 73 | char *string; 74 | } PFStringMetric; 75 | 76 | typedef enum { 77 | XREF_TYPE_MASK_CALL = (1 << 0), 78 | XREF_TYPE_MASK_JUMP = (1 << 1), 79 | XREF_TYPE_MASK_REFERENCE = (1 << 2), 80 | XREF_TYPE_MASK_POINTER = (1 << 3), 81 | XREF_TYPE_MASK_ALL = (XREF_TYPE_MASK_CALL | XREF_TYPE_MASK_REFERENCE | XREF_TYPE_MASK_POINTER), 82 | } PFXrefTypeMask; 83 | 84 | typedef struct s_PFXrefMetric { 85 | MetricShared shared; 86 | 87 | // Normal xref 88 | uint64_t address; 89 | 90 | // Dynamic xref 91 | bool (*dynamicHandler)(PFSection *section, struct s_PFXrefMetric *metric, uint64_t location, uint64_t target); 92 | void *ctx; 93 | 94 | PFXrefTypeMask typeMask; 95 | } PFXrefMetric; 96 | 97 | PFPatternMetric *pfmetric_pattern_init(void *bytes, void *mask, size_t nbytes, uint16_t alignment); 98 | PFStringMetric *pfmetric_string_init(const char *string); 99 | PFXrefMetric *pfmetric_xref_init(uint64_t address, PFXrefTypeMask types); 100 | PFXrefMetric *pfmetric_dynamic_xref_init(bool (*dynamicHandler)(PFSection *section, PFXrefMetric *metric, uint64_t location, uint64_t target), void *ctx, PFXrefTypeMask types); 101 | void pfmetric_free(void *metric); 102 | 103 | void pfmetric_run_in_range(PFSection *section, uint64_t startAddr, uint64_t endAddr, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop)); 104 | void pfmetric_run(PFSection *section, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop)); 105 | #endif -------------------------------------------------------------------------------- /src/PatchFinder_arm64.c: -------------------------------------------------------------------------------- 1 | #include "PatchFinder_arm64.h" 2 | #include "PatchFinder.h" 3 | #include "arm64.h" 4 | #include 5 | 6 | // Unified check for whether anything writes to a register between firstAddr and secondAddr 7 | bool pfsec_arm64_scan_register_write(PFSection *section, arm64_register reg, uint64_t endAddr, uint64_t startAddr) 8 | { 9 | if (startAddr == endAddr) { 10 | return false; 11 | } 12 | else if (startAddr > endAddr) { 13 | printf("Fatal error pfsec_arm64_scan_register_write: startAddr > endAddr\n"); 14 | return false; 15 | } 16 | 17 | uint16_t regNum = ARM64_REG_GET_NUM(reg); 18 | 19 | for (uint64_t curAddr = (endAddr - sizeof(uint32_t)); curAddr > startAddr; curAddr -= sizeof(uint32_t)) { 20 | uint32_t inst = pfsec_read32(section, curAddr); 21 | arm64_register destinationReg, sourceReg; 22 | 23 | // Check for immediate add writing to reg 24 | if (arm64_dec_add_imm(inst, &destinationReg, &sourceReg, NULL) == 0) goto found_inst_match; 25 | // Check for immediate mov writing to reg 26 | else if (arm64_dec_mov_imm(inst, &destinationReg, NULL, NULL, NULL) == 0) goto found_inst_match; 27 | // Check for register mov writing to reg 28 | else if (arm64_dec_mov_reg(inst, &destinationReg, &sourceReg) == 0) goto found_inst_match; 29 | // Check for immediate ldr writing to reg 30 | else if (arm64_dec_ldr_imm(inst, &destinationReg, NULL, NULL, NULL, NULL) == 0) goto found_inst_match; 31 | // Check for literal ldr writing to reg 32 | else if (arm64_dec_ldr_lit(inst, curAddr, NULL, &destinationReg) == 0) goto found_inst_match; 33 | // Check for immediate ldrs writing to reg 34 | else if (arm64_dec_ldrs_imm(inst, &destinationReg, NULL, NULL, NULL, NULL) == 0) goto found_inst_match; 35 | // Nothing matched, continue 36 | else continue; 37 | 38 | found_inst_match: 39 | if (ARM64_REG_GET_NUM(destinationReg) == regNum) { 40 | goto found_write; 41 | } 42 | // Didn't write to our register, continue 43 | continue; 44 | } 45 | 46 | return false; 47 | 48 | found_write: 49 | return true; 50 | } 51 | 52 | uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference(PFSection *section, uint64_t adrpAddr, uint64_t ldrStrAddAddr) 53 | { 54 | uint32_t ldrStrAddInst = pfsec_read32(section, ldrStrAddAddr); 55 | 56 | uint64_t imm = 0; 57 | if (arm64_dec_ldr_imm(ldrStrAddInst, NULL, NULL, &imm, NULL, NULL) != 0) { 58 | if (arm64_dec_ldrs_imm(ldrStrAddInst, NULL, NULL, &imm, NULL, NULL) != 0) { 59 | if (arm64_dec_str_imm(ldrStrAddInst, NULL, NULL, &imm, NULL, NULL) != 0) { 60 | uint16_t addImm = 0; 61 | if (arm64_dec_add_imm(ldrStrAddInst, NULL, NULL, &addImm) == 0) { 62 | imm = (uint64_t)addImm; 63 | } 64 | else { 65 | return 0; 66 | } 67 | } 68 | } 69 | } 70 | 71 | uint64_t adrpTarget = 0; 72 | arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL); 73 | 74 | return adrpTarget + imm; 75 | } 76 | 77 | uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(PFSection *section, uint64_t ldrStrAddAddr) 78 | { 79 | uint32_t inst = pfsec_read32(section, ldrStrAddAddr); 80 | 81 | bool isAdd = false; 82 | arm64_register reg; 83 | if (arm64_dec_ldr_imm(inst, NULL, ®, NULL, NULL, NULL) != 0) { 84 | if (arm64_dec_str_imm(inst, NULL, ®, NULL, NULL, NULL) != 0) { 85 | if (arm64_dec_add_imm(inst, NULL, ®, NULL) != 0) { 86 | return 0; 87 | } 88 | else { 89 | isAdd = true; 90 | } 91 | } 92 | } 93 | 94 | uint32_t adrpInst = 0, adrpInstMask = 0; 95 | arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, reg, &adrpInst, &adrpInstMask); 96 | uint64_t adrpAddr = pfsec_find_prev_inst(section, ldrStrAddAddr, 100, adrpInst, adrpInstMask); 97 | if (!adrpAddr) return 0; 98 | 99 | if (!isAdd) { 100 | // For str and ldr, there can be the following sequence: 101 | 102 | // adrp xA, (adrpAddr) 103 | // (0..n) instructions 104 | // add xA, xA, (specialCaseAddAddr) 105 | // (0..n) instructions 106 | // ldr/str xB, [xA] (ldrStrAddAddr) 107 | 108 | // We search for the middle add instruction and if we find it, we need to handle this case 109 | 110 | uint32_t searchCount = ((ldrStrAddAddr - adrpAddr) / 4) - 1; 111 | if (searchCount) { 112 | uint32_t specialCaseAddInst = 0, specialCaseAddMask = 0; 113 | arm64_gen_add_imm(reg, reg, OPT_UINT64_NONE, &specialCaseAddInst, &specialCaseAddMask); 114 | 115 | uint64_t specialCaseAddAddr = pfsec_find_prev_inst(section, ldrStrAddAddr - sizeof(uint32_t), searchCount, specialCaseAddInst, specialCaseAddMask); 116 | if (specialCaseAddAddr) { 117 | // In this case, we need to make sure there are no writes in the two "(0..n) instructions" sections 118 | if (pfsec_arm64_scan_register_write(section, reg, ldrStrAddAddr, specialCaseAddAddr)) return 0; 119 | if (pfsec_arm64_scan_register_write(section, reg, specialCaseAddAddr, adrpAddr)) return 0; 120 | 121 | uint64_t baseAddr = pfsec_arm64_resolve_adrp_ldr_str_add_reference(section, adrpAddr, specialCaseAddAddr); 122 | if (!baseAddr) return 0; 123 | 124 | uint32_t ldrStrAddInst = pfsec_read32(section, ldrStrAddAddr); 125 | uint64_t imm64 = 0; 126 | uint16_t imm16 = 0; 127 | if (arm64_dec_add_imm(ldrStrAddInst, NULL, NULL, &imm16) != 0) { 128 | if (arm64_dec_ldr_imm(ldrStrAddInst, NULL, NULL, &imm64, NULL, NULL) != 0) { 129 | if (arm64_dec_str_imm(ldrStrAddInst, NULL, NULL, &imm64, NULL, NULL) != 0) { 130 | fprintf(stderr, "Warning: failed decoding ldrStrAddInst (%#x @ %#llx)\n", ldrStrAddInst, ldrStrAddAddr); 131 | } 132 | } 133 | } 134 | else { 135 | imm64 = imm16; 136 | } 137 | 138 | return baseAddr + imm64; 139 | } 140 | } 141 | } 142 | 143 | if (pfsec_arm64_scan_register_write(section, reg, ldrStrAddAddr, adrpAddr)) return 0; 144 | return pfsec_arm64_resolve_adrp_ldr_str_add_reference(section, adrpAddr, ldrStrAddAddr); 145 | } 146 | 147 | uint64_t pfsec_arm64_resolve_stub(PFSection *section, uint64_t stubAddr) 148 | { 149 | // A stub is usually: 150 | // adrp x16, ? 151 | // ldr x16, ? 152 | // br x16 153 | 154 | // First, check if what we have actually is a stub 155 | uint32_t inst[3]; 156 | pfsec_read_at_address(section, stubAddr, inst, sizeof(inst)); 157 | 158 | uint32_t stubInst[3], stubMask[3]; 159 | arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_X(16), &stubInst[0], &stubMask[0]); 160 | arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_X(16), ARM64_REG_X(16), OPT_UINT64_NONE, &stubInst[1], &stubMask[1]); 161 | stubInst[2] = 0xd61f0200; 162 | stubMask[2] = 0xffffffff; 163 | 164 | if ((inst[0] & stubMask[0]) == stubInst[0] || 165 | (inst[1] & stubMask[1]) == stubInst[1] || 166 | (inst[2] & stubMask[2]) == stubInst[2]) { 167 | // This is a stub, resolve it 168 | uint64_t ptrAddr = pfsec_arm64_resolve_adrp_ldr_str_add_reference(section, stubAddr, stubAddr + 4); 169 | if (ptrAddr) { 170 | uint64_t targetAddr = 0; 171 | 172 | MachO *macho = pfsec_get_macho(section); 173 | if (macho) { 174 | macho_read_at_vmaddr(macho, ptrAddr, sizeof(targetAddr), &targetAddr); 175 | } 176 | else { 177 | DyldSharedCache *sharedCache = pfsec_get_dsc(section); 178 | if (sharedCache) { 179 | dsc_read_from_vmaddr(sharedCache, ptrAddr, sizeof(targetAddr), &targetAddr); 180 | } 181 | } 182 | 183 | return targetAddr; 184 | } 185 | } 186 | 187 | // Not a stub, just return original address 188 | return stubAddr; 189 | } 190 | 191 | void pfsec_arm64_enumerate_xrefs(PFSection *section, Arm64XrefTypeMask types, void (^xrefBlock)(Arm64XrefType type, uint64_t source, uint64_t target, bool *stop)) 192 | { 193 | bool stop = false; 194 | if (section->info.initprot & PROT_EXEC) { 195 | for (uint64_t addr = section->info.vmaddr; addr < (section->info.vmaddr + section->info.size) && !stop; addr += sizeof(uint32_t)) { 196 | uint32_t inst = pfsec_read32(section, addr); 197 | if ((types & ARM64_XREF_TYPE_MASK_B) || (types & ARM64_XREF_TYPE_MASK_BL)) { 198 | uint64_t target = 0; 199 | bool isBl = 0; 200 | if (arm64_dec_b_l(inst, addr, &target, &isBl) == 0) { 201 | if (isBl && (types & ARM64_XREF_TYPE_MASK_BL)) { 202 | xrefBlock(ARM64_XREF_TYPE_BL, addr, target, &stop); 203 | } 204 | else if (!isBl && (types & ARM64_XREF_TYPE_MASK_B)) { 205 | xrefBlock(ARM64_XREF_TYPE_B, addr, target, &stop); 206 | } 207 | continue; 208 | } 209 | } 210 | if (types & ARM64_XREF_TYPE_MASK_ADR) { 211 | uint64_t target = 0; 212 | bool isAdrp = false; 213 | arm64_register reg; 214 | if (arm64_dec_adr_p(inst, addr, &target, ®, &isAdrp) == 0) { 215 | if (!isAdrp) { 216 | xrefBlock(ARM64_XREF_TYPE_ADR, addr, target, &stop); 217 | } 218 | continue; 219 | } 220 | } 221 | if ((types & ARM64_XREF_TYPE_MASK_B_COND) || (types & ARM64_XREF_TYPE_MASK_BC_COND)) { 222 | uint64_t target = 0; 223 | bool isBc = false; 224 | if (arm64_dec_b_c_cond(inst, addr, &target, NULL, &isBc) == 0) { 225 | if (!isBc && (types & ARM64_XREF_TYPE_MASK_B_COND)) { 226 | xrefBlock(ARM64_XREF_TYPE_B_COND, addr, target, &stop); 227 | } 228 | if (isBc && (types & ARM64_XREF_TYPE_MASK_BC_COND)) { 229 | xrefBlock(ARM64_XREF_TYPE_BC_COND, addr, target, &stop); 230 | } 231 | } 232 | } 233 | if ((types & ARM64_XREF_TYPE_MASK_CBZ) || (types & ARM64_XREF_TYPE_MASK_CBNZ)) { 234 | uint64_t target = 0; 235 | bool isCbnz = false; 236 | if (arm64_dec_cb_n_z(inst, addr, &isCbnz, NULL, &target) == 0) { 237 | if (!isCbnz && (types & ARM64_XREF_TYPE_MASK_CBZ)) { 238 | xrefBlock(ARM64_XREF_TYPE_CBZ, addr, target, &stop); 239 | } 240 | if (isCbnz && (types & ARM64_XREF_TYPE_MASK_CBNZ)) { 241 | xrefBlock(ARM64_XREF_TYPE_CBNZ, addr, target, &stop); 242 | } 243 | } 244 | } 245 | if ((types & ARM64_XREF_TYPE_MASK_TBZ) || (types & ARM64_XREF_TYPE_MASK_TBNZ)) { 246 | uint64_t target = 0; 247 | bool isTbnz = false; 248 | if (arm64_dec_tb_n_z(inst, addr, &isTbnz, NULL, &target, NULL) == 0) { 249 | if (!isTbnz && (types & ARM64_XREF_TYPE_MASK_TBZ)) { 250 | xrefBlock(ARM64_XREF_TYPE_CBZ, addr, target, &stop); 251 | } 252 | if (isTbnz && (types & ARM64_XREF_TYPE_MASK_TBNZ)) { 253 | xrefBlock(ARM64_XREF_TYPE_CBNZ, addr, target, &stop); 254 | } 255 | } 256 | } 257 | #define ADRP_SEEK_BACK 8 258 | if (types & ARM64_XREF_TYPE_MASK_ADRP_ADD) { 259 | uint16_t addImm = 0; 260 | arm64_register addDestinationReg; 261 | arm64_register addSourceReg; 262 | if (arm64_dec_add_imm(inst, &addDestinationReg, &addSourceReg, &addImm) == 0) { 263 | uint32_t adrpInst = 0; 264 | uint32_t adrpMask = 0; 265 | if (arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, addSourceReg, &adrpInst, &adrpMask) == 0) { 266 | uint64_t adrpAddr = pfsec_find_prev_inst(section, addr, ADRP_SEEK_BACK, adrpInst, adrpMask); 267 | if (adrpAddr != 0) { 268 | if (!pfsec_arm64_scan_register_write(section, addSourceReg, addr, adrpAddr)) { 269 | uint64_t adrpTarget = 0; 270 | arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL); 271 | xrefBlock(ARM64_XREF_TYPE_ADRP_ADD, addr, adrpTarget + addImm, &stop); 272 | } 273 | } 274 | } 275 | } 276 | } 277 | if (types & ARM64_XREF_TYPE_MASK_ADRP_LDR) { 278 | arm64_register ldrDestinationReg; 279 | arm64_register ldrSourceReg; 280 | uint64_t ldrImm = 0; 281 | char ldrType = -1; 282 | arm64_ldr_str_type instType = 0; 283 | if (arm64_dec_ldr_imm(inst, &ldrDestinationReg, &ldrSourceReg, &ldrImm, &ldrType, &instType) == 0) { 284 | uint32_t adrpInst = 0; 285 | uint32_t adrpMask = 0; 286 | if (arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ldrSourceReg, &adrpInst, &adrpMask) == 0) { 287 | uint64_t adrpAddr = pfsec_find_prev_inst(section, addr, ADRP_SEEK_BACK, adrpInst, adrpMask); 288 | if (adrpAddr != 0) { 289 | if (!pfsec_arm64_scan_register_write(section, ldrSourceReg, addr, adrpAddr)) { 290 | uint64_t adrpTarget = 0; 291 | arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL); 292 | xrefBlock(ARM64_XREF_TYPE_ADRP_LDR, addr, adrpTarget + ldrImm, &stop); 293 | } 294 | } 295 | } 296 | } 297 | } 298 | if (types & ARM64_XREF_TYPE_MASK_ADRP_STR) { 299 | arm64_register strDestinationReg; 300 | arm64_register strSourceReg; 301 | uint64_t strImm = 0; 302 | char strType = -1; 303 | arm64_ldr_str_type instType = 0; 304 | if (arm64_dec_str_imm(inst, &strDestinationReg, &strSourceReg, &strImm, &strType, &instType) == 0) { 305 | uint32_t adrpInst = 0; 306 | uint32_t adrpMask = 0; 307 | if (arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, strSourceReg, &adrpInst, &adrpMask) == 0) { 308 | uint64_t adrpAddr = pfsec_find_prev_inst(section, addr, ADRP_SEEK_BACK, adrpInst, adrpMask); 309 | if (adrpAddr != 0) { 310 | if (!pfsec_arm64_scan_register_write(section, strSourceReg, addr, adrpAddr)) { 311 | uint64_t adrpTarget = 0; 312 | arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL); 313 | xrefBlock(ARM64_XREF_TYPE_ADRP_STR, addr, adrpTarget + strImm, &stop); 314 | } 315 | } 316 | } 317 | } 318 | } 319 | } 320 | } 321 | 322 | if ((types & ARM64_XREF_TYPE_MASK_POINTER)) { 323 | if (!strncmp(section->info.sectname, "__cfstring", sizeof(section->info.sectname))) { 324 | for (uint64_t addr = section->info.vmaddr; addr < (section->info.vmaddr + section->info.size) && !stop; addr += 0x20) { 325 | uint32_t fileoff = pfsec_read32(section, addr + 0x10); 326 | uint64_t vmaddr = 0; 327 | MachO *macho = pfsec_get_macho(section); 328 | if (macho) { 329 | macho_translate_fileoff_to_vmaddr(macho, fileoff, &vmaddr, NULL); 330 | xrefBlock(ARM64_XREF_TYPE_POINTER, addr, vmaddr, &stop); 331 | } 332 | } 333 | } 334 | else { 335 | for (uint64_t addr = section->info.vmaddr; addr < (section->info.vmaddr + section->info.size) && !stop; addr += sizeof(uint64_t)) { 336 | uint64_t ptr = pfsec_read_pointer(section, addr); 337 | xrefBlock(ARM64_XREF_TYPE_POINTER, addr, ptr, &stop); 338 | } 339 | } 340 | } 341 | } -------------------------------------------------------------------------------- /src/PatchFinder_arm64.h: -------------------------------------------------------------------------------- 1 | #ifndef PATCHFINDER_ARM64_H 2 | #define PATCHFINDER_ARM64_H 3 | 4 | #include "PatchFinder.h" 5 | #include "arm64.h" 6 | 7 | typedef enum { 8 | ARM64_XREF_TYPE_BL = 0, 9 | ARM64_XREF_TYPE_B, 10 | ARM64_XREF_TYPE_B_COND, 11 | ARM64_XREF_TYPE_BC_COND, 12 | ARM64_XREF_TYPE_CBZ, 13 | ARM64_XREF_TYPE_CBNZ, 14 | ARM64_XREF_TYPE_TBZ, 15 | ARM64_XREF_TYPE_TBNZ, 16 | ARM64_XREF_TYPE_ADR, 17 | ARM64_XREF_TYPE_ADRP_ADD, 18 | ARM64_XREF_TYPE_ADRP_LDR, 19 | ARM64_XREF_TYPE_ADRP_STR, 20 | ARM64_XREF_TYPE_POINTER, 21 | } Arm64XrefType; 22 | 23 | typedef enum { 24 | ARM64_XREF_TYPE_MASK_BL = (1 << ARM64_XREF_TYPE_BL), 25 | ARM64_XREF_TYPE_MASK_CALL = (ARM64_XREF_TYPE_MASK_BL), 26 | 27 | ARM64_XREF_TYPE_MASK_B = (1 << ARM64_XREF_TYPE_B), 28 | ARM64_XREF_TYPE_MASK_B_COND = (1 << ARM64_XREF_TYPE_B_COND), 29 | ARM64_XREF_TYPE_MASK_BC_COND = (1 << ARM64_XREF_TYPE_BC_COND), 30 | ARM64_XREF_TYPE_MASK_CBZ = (1 << ARM64_XREF_TYPE_CBZ), 31 | ARM64_XREF_TYPE_MASK_CBNZ = (1 << ARM64_XREF_TYPE_CBNZ), 32 | ARM64_XREF_TYPE_MASK_TBZ = (1 << ARM64_XREF_TYPE_TBZ), 33 | ARM64_XREF_TYPE_MASK_TBNZ = (1 << ARM64_XREF_TYPE_TBNZ), 34 | ARM64_XREF_TYPE_MASK_JUMP = (ARM64_XREF_TYPE_B | ARM64_XREF_TYPE_MASK_B_COND | ARM64_XREF_TYPE_MASK_BC_COND | ARM64_XREF_TYPE_MASK_CBZ | ARM64_XREF_TYPE_MASK_CBNZ | ARM64_XREF_TYPE_MASK_TBZ | ARM64_XREF_TYPE_MASK_TBNZ), 35 | 36 | ARM64_XREF_TYPE_MASK_ADR = (1 << ARM64_XREF_TYPE_ADR), 37 | ARM64_XREF_TYPE_MASK_ADRP_ADD = (1 << ARM64_XREF_TYPE_ADRP_ADD), 38 | ARM64_XREF_TYPE_MASK_ADRP_LDR = (1 << ARM64_XREF_TYPE_ADRP_LDR), 39 | ARM64_XREF_TYPE_MASK_ADRP_STR = (1 << ARM64_XREF_TYPE_ADRP_STR), 40 | ARM64_XREF_TYPE_MASK_REFERENCE = (ARM64_XREF_TYPE_MASK_ADR | ARM64_XREF_TYPE_MASK_ADRP_ADD | ARM64_XREF_TYPE_MASK_ADRP_LDR | ARM64_XREF_TYPE_MASK_ADRP_STR), 41 | 42 | ARM64_XREF_TYPE_MASK_POINTER = (1 << ARM64_XREF_TYPE_POINTER), 43 | 44 | ARM64_XREF_TYPE_ALL = (ARM64_XREF_TYPE_MASK_CALL | ARM64_XREF_TYPE_MASK_JUMP | ARM64_XREF_TYPE_MASK_REFERENCE | ARM64_XREF_TYPE_MASK_POINTER), 45 | } Arm64XrefTypeMask; 46 | 47 | bool pfsec_arm64_scan_register_write(PFSection *section, arm64_register reg, uint64_t endAddr, uint64_t startAddr); 48 | uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference(PFSection *section, uint64_t adrpAddr, uint64_t ldrStrAddAddr); 49 | uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(PFSection *section, uint64_t ldrStrAddAddr); 50 | uint64_t pfsec_arm64_resolve_stub(PFSection *section, uint64_t stubAddr); 51 | void pfsec_arm64_enumerate_xrefs(PFSection *section, Arm64XrefTypeMask types, void (^xrefBlock)(Arm64XrefType type, uint64_t source, uint64_t target, bool *stop)); 52 | #endif -------------------------------------------------------------------------------- /src/Util.c: -------------------------------------------------------------------------------- 1 | #include "Util.h" 2 | #include 3 | #include 4 | #include 5 | 6 | int64_t sxt64(int64_t value, uint8_t bits) 7 | { 8 | value = ((uint64_t)value) << (64 - bits); 9 | value >>= (64 - bits); 10 | return value; 11 | } 12 | 13 | int memcmp_masked(const void *str1, const void *str2, unsigned char* mask, size_t n) 14 | { 15 | const unsigned char* p = (const unsigned char*)str1; 16 | const unsigned char* q = (const unsigned char*)str2; 17 | 18 | if (p == q) return 0; 19 | for (int i = 0; i < n; i++) { 20 | unsigned char cMask = 0xFF; 21 | if (mask) { 22 | cMask = mask[i]; 23 | } 24 | if((p[i] & cMask) != (q[i] & cMask)) { 25 | // we do not care about 1 / -1 26 | return -1; 27 | } 28 | } 29 | 30 | return 0; 31 | } 32 | 33 | uint64_t align_to_size(int size, int alignment) 34 | { 35 | return (size + alignment - 1) & ~(alignment - 1); 36 | } 37 | 38 | void print_hash(uint8_t *hash, size_t size) 39 | { 40 | for (int j = 0; j < size; j++) { 41 | printf("%02x", hash[j]); 42 | } 43 | } 44 | 45 | void enumerate_range(uint64_t start, uint64_t end, uint16_t alignment, size_t nbytes, bool (^enumerator)(uint64_t)) 46 | { 47 | if (start == end) return; 48 | if (alignment == 0) return; 49 | if (nbytes == 0) return; 50 | if (nbytes % alignment) return; 51 | if (nbytes > (end - start)) return; 52 | 53 | int dir = start < end ? 1 : -1; 54 | 55 | if (dir == 1) { 56 | end -= nbytes; 57 | if (start >= end) return; 58 | } 59 | else { 60 | start -= nbytes; 61 | if (start <= end) return; 62 | } 63 | 64 | for (uint64_t cur = start; dir == 1 ? (cur + (alignment * dir)) <= end : (cur + (alignment * dir)) >= end; cur += (dir * alignment)) { 65 | if (!enumerator(cur)) break; 66 | 67 | // Extra condition to prevent underflow when we hit 0 and the direction is backwards 68 | if (dir == -1 && cur == 0) break; 69 | } 70 | } 71 | 72 | int read_string(int fd, char **strOut) 73 | { 74 | uint32_t sz = 0; 75 | off_t pos = lseek(fd, 0, SEEK_CUR); 76 | char c = 0; 77 | do { 78 | if (read(fd, &c, sizeof(c)) != sizeof(c)) return -1; 79 | sz++; 80 | } while(c != 0); 81 | 82 | lseek(fd, pos, SEEK_SET); 83 | *strOut = malloc(sz); 84 | read(fd, *strOut, sz); 85 | return 0; 86 | } 87 | 88 | bool string_has_prefix(const char *str, const char *prefix) 89 | { 90 | if (!str || !prefix) { 91 | return false; 92 | } 93 | 94 | size_t str_len = strlen(str); 95 | size_t prefix_len = strlen(prefix); 96 | 97 | if (str_len < prefix_len) { 98 | return false; 99 | } 100 | 101 | return !strncmp(str, prefix, prefix_len); 102 | } 103 | 104 | bool string_has_suffix(const char *str, const char *suffix) 105 | { 106 | if (!str || !suffix) { 107 | return false; 108 | } 109 | 110 | size_t str_len = strlen(str); 111 | size_t suffix_len = strlen(suffix); 112 | 113 | if (str_len < suffix_len) { 114 | return false; 115 | } 116 | 117 | return !strcmp(str + str_len - suffix_len, suffix); 118 | } 119 | -------------------------------------------------------------------------------- /src/Util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct s_optional_uint64 { 9 | bool isSet; 10 | uint64_t value; 11 | } optional_uint64_t; 12 | #define OPT_UINT64_IS_SET(x) (x.isSet) 13 | #define OPT_UINT64_GET_VAL(x) (x.value) 14 | #define OPT_UINT64_NONE (optional_uint64_t){.isSet = false, .value = 0} 15 | #define OPT_UINT64(x) (optional_uint64_t){.isSet = true, .value = x} 16 | 17 | 18 | typedef struct s_optional_bool { 19 | bool isSet; 20 | bool value; 21 | } optional_bool; 22 | #define OPT_BOOL_IS_SET(x) (x.isSet) 23 | #define OPT_BOOL_GET_VAL(x) (x.value) 24 | #define OPT_BOOL_NONE (optional_bool){.isSet = false, .value = false} 25 | #define OPT_BOOL(x) (optional_bool){.isSet = true, .value = x} 26 | 27 | int64_t sxt64(int64_t value, uint8_t bits); 28 | int memcmp_masked(const void *str1, const void *str2, unsigned char* mask, size_t n); 29 | uint64_t align_to_size(int size, int alignment); 30 | void print_hash(uint8_t *hash, size_t size); 31 | void enumerate_range(uint64_t start, uint64_t end, uint16_t alignment, size_t nbytes, bool (^enumerator)(uint64_t cur)); 32 | int read_string(int fd, char **strOut); 33 | bool string_has_prefix(const char *str, const char *prefix); 34 | bool string_has_suffix(const char *str, const char *suffix); 35 | #endif 36 | -------------------------------------------------------------------------------- /src/arm64.h: -------------------------------------------------------------------------------- 1 | #ifndef ARM64_H 2 | #define ARM64_H 3 | 4 | #include "Util.h" 5 | 6 | typedef enum { 7 | // registers 8 | ARM64_REG_TYPE_X, 9 | ARM64_REG_TYPE_W, 10 | 11 | // vector shit 12 | ARM64_REG_TYPE_Q, 13 | ARM64_REG_TYPE_D, 14 | ARM64_REG_TYPE_S, 15 | ARM64_REG_TYPE_H, 16 | ARM64_REG_TYPE_B, 17 | } arm64_register_type; 18 | 19 | enum { 20 | ARM64_REG_MASK_ANY_FLAG = (1 << 0), 21 | ARM64_REG_MASK_X_W = (1 << 1), 22 | ARM64_REG_MASK_VECTOR = (1 << 2), 23 | ARM64_REG_MASK_ALL = (ARM64_REG_MASK_X_W | ARM64_REG_MASK_VECTOR), 24 | 25 | ARM64_REG_MASK_ANY_X_W = (ARM64_REG_MASK_X_W | ARM64_REG_MASK_ANY_FLAG), 26 | ARM64_REG_MASK_ANY_VECTOR = (ARM64_REG_MASK_VECTOR | ARM64_REG_MASK_ANY_FLAG), 27 | ARM64_REG_MASK_ANY_ALL = (ARM64_REG_MASK_ALL | ARM64_REG_MASK_ANY_FLAG), 28 | }; 29 | 30 | typedef enum { 31 | LDR_STR_TYPE_ANY, // NOTE: "ANY" will inevitably also match STUR and LDUR instructions 32 | LDR_STR_TYPE_POST_INDEX, 33 | LDR_STR_TYPE_PRE_INDEX, 34 | LDR_STR_TYPE_UNSIGNED, 35 | } arm64_ldr_str_type; 36 | 37 | typedef enum { 38 | ARM64_LDP_STP_TYPE_ANY, 39 | ARM64_LDP_STP_TYPE_POST_INDEX, 40 | ARM64_LDP_STP_TYPE_PRE_INDEX, 41 | ARM64_LDP_STP_TYPE_SIGNED, 42 | } arm64_ldp_stp_type; 43 | 44 | typedef struct s_arm64_register { 45 | uint8_t mask; 46 | arm64_register_type type; 47 | uint8_t num; 48 | } arm64_register; 49 | 50 | #define ARM64_REG(type_, num_) (arm64_register){.mask = ARM64_REG_MASK_ALL, .type = type_, .num = num_} 51 | #define ARM64_REG_X(x) ARM64_REG(ARM64_REG_TYPE_X, x) 52 | #define ARM64_REG_W(x) ARM64_REG(ARM64_REG_TYPE_W, x) 53 | #define ARM64_REG_Q(x) ARM64_REG(ARM64_REG_TYPE_Q, x) 54 | #define ARM64_REG_S(x) ARM64_REG(ARM64_REG_TYPE_S, x) 55 | #define ARM64_REG_H(x) ARM64_REG(ARM64_REG_TYPE_H, x) 56 | #define ARM64_REG_B(x) ARM64_REG(ARM64_REG_TYPE_B, x) 57 | #define ARM64_REG_ANY (arm64_register){.mask = ARM64_REG_MASK_ANY_ALL, .type = 0, .num = 0} 58 | #define ARM64_REG_ANY_X_W (arm64_register){.mask = ARM64_REG_MASK_ANY_X_W, .type = 0, .num = 0} 59 | #define ARM64_REG_ANY_VECTOR (arm64_register){.mask = ARM64_REG_MASK_ANY_VECTOR, .type = 0, .num = 0} 60 | #define ARM64_REG_GET_TYPE(x) (x.type) 61 | #define ARM64_REG_IS_X(x) (x.type == ARM64_REG_TYPE_X) 62 | #define ARM64_REG_IS_W(x) (x.type == ARM64_REG_TYPE_W) 63 | #define ARM64_REG_IS_VECTOR(x) (x.type == ARM64_REG_TYPE_Q || x.type == ARM64_REG_TYPE_D || x.type == ARM64_REG_TYPE_S || x.type == ARM64_REG_TYPE_H || x.type == ARM64_REG_TYPE_B) 64 | #define ARM64_REG_GET_NUM(x) (x.num & 0x1f) 65 | #define ARM64_REG_IS_ANY(x) (x.mask == ARM64_REG_MASK_ANY_ALL) 66 | #define ARM64_REG_IS_ANY_X_W(x) (x.mask == ARM64_REG_MASK_ANY_X_W) 67 | #define ARM64_REG_IS_ANY_VECTOR(x) (x.mask == ARM64_REG_MASK_ANY_VECTOR) 68 | uint8_t arm64_reg_type_get_width(arm64_register_type type); 69 | const char *arm64_reg_type_get_string(arm64_register_type type); 70 | const char *arm64_reg_get_type_string(arm64_register reg); 71 | 72 | #define ARM64_REG_NUM_SP 31 73 | 74 | typedef struct s_arm64_cond { 75 | bool isSet; 76 | uint8_t value; 77 | } arm64_cond; 78 | #define ARM64_COND(x) (arm64_cond){.isSet = true, .value = x} 79 | #define ARM64_COND_ANY (arm64_cond){.isSet = false, .value = 0} 80 | #define ARM64_COND_GET_VAL(x) (x.value & 0xf) 81 | #define ARM64_COND_IS_SET(x) x.isSet 82 | 83 | int arm64_gen_b_l(optional_bool optIsBl, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut); 84 | int arm64_dec_b_l(uint32_t inst, uint64_t origin, uint64_t *targetOut, bool *isBlOut); 85 | int arm64_gen_b_c_cond(optional_bool optIsBc, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_cond optCond, uint32_t *bytesOut, uint32_t *maskOut); 86 | int arm64_dec_b_c_cond(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_cond *condOut, bool *isBcOut); 87 | int arm64_gen_adr_p(optional_bool optIsAdrp, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_register reg, uint32_t *bytesOut, uint32_t *maskOut); 88 | int arm64_dec_adr_p(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *registerOut, bool *isAdrpOut); 89 | int arm64_gen_mov_imm(char type, arm64_register destinationReg, optional_uint64_t optImm, optional_uint64_t optShift, uint32_t *bytesOut, uint32_t *maskOut); 90 | int arm64_dec_mov_imm(uint32_t inst, arm64_register *destinationRegOut, uint64_t *immOut, uint64_t *shiftOut, char *typeOut); 91 | int arm64_gen_mov_reg(arm64_register destinationReg, arm64_register sourceReg, uint32_t *bytesOut, uint32_t *maskOut); 92 | int arm64_dec_mov_reg(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut); 93 | int arm64_gen_add_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); 94 | int arm64_dec_add_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut); 95 | int arm64_gen_sub_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, optional_bool optS, uint32_t *bytesOut, uint32_t *maskOut); 96 | int arm64_dec_sub_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut, bool *sOut); 97 | int arm64_gen_ldr_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); 98 | int arm64_dec_ldr_imm(uint32_t inst, arm64_register *destinationReg, arm64_register *addrReg, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut); 99 | int arm64_gen_ldrs_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); 100 | int arm64_dec_ldrs_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut); 101 | int arm64_gen_str_imm(char type, arm64_ldr_str_type instType, arm64_register sourceReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); 102 | int arm64_dec_str_imm(uint32_t inst, arm64_register *sourceRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut); 103 | int arm64_gen_ldr_lit(arm64_register destinationReg, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut); 104 | int arm64_dec_ldr_lit(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *destinationReg); 105 | int arm64_gen_ldp(arm64_ldp_stp_type instType, arm64_register destinationReg1, arm64_register destinationReg2, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); 106 | int arm64_gen_cb_n_z(optional_bool isCbnz, arm64_register reg, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut); 107 | int arm64_dec_cb_n_z(uint32_t inst, uint64_t origin, bool *isCbnzOut, arm64_register *regOut, uint64_t *targetOut); 108 | int arm64_gen_tb_n_z(optional_bool isTbnz, arm64_register reg, optional_uint64_t optTarget, optional_uint64_t optBit, uint32_t *bytesOut, uint32_t *maskOut); 109 | int arm64_dec_tb_n_z(uint32_t inst, uint64_t origin, bool *isTbnzOut, arm64_register *regOut, uint64_t *targetOut, uint64_t *bitOut); 110 | #endif -------------------------------------------------------------------------------- /src/fixup-chains.h: -------------------------------------------------------------------------------- 1 | /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- 2 | * 3 | * Copyright (c) 2018 Apple Inc. All rights reserved. 4 | * 5 | * @APPLE_LICENSE_HEADER_START@ 6 | * 7 | * This file contains Original Code and/or Modifications of Original Code 8 | * as defined in and that are subject to the Apple Public Source License 9 | * Version 2.0 (the 'License'). You may not use this file except in 10 | * compliance with the License. Please obtain a copy of the License at 11 | * http://www.opensource.apple.com/apsl/ and read it before using this 12 | * file. 13 | * 14 | * The Original Code and all software distributed under the License are 15 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 16 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 17 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 19 | * Please see the License for the specific language governing rights and 20 | * limitations under the License. 21 | * 22 | * @APPLE_LICENSE_HEADER_END@ 23 | */ 24 | 25 | #ifndef __MACH_O_FIXUP_CHAINS__ 26 | #define __MACH_O_FIXUP_CHAINS__ 6 27 | 28 | 29 | #include 30 | 31 | 32 | //#define LC_DYLD_EXPORTS_TRIE 0x80000033 // used with linkedit_data_command 33 | //#define LC_DYLD_CHAINED_FIXUPS 0x80000034 // used with linkedit_data_command, payload is dyld_chained_fixups_header 34 | 35 | 36 | // header of the LC_DYLD_CHAINED_FIXUPS payload 37 | struct dyld_chained_fixups_header 38 | { 39 | uint32_t fixups_version; // 0 40 | uint32_t starts_offset; // offset of dyld_chained_starts_in_image in chain_data 41 | uint32_t imports_offset; // offset of imports table in chain_data 42 | uint32_t symbols_offset; // offset of symbol strings in chain_data 43 | uint32_t imports_count; // number of imported symbol names 44 | uint32_t imports_format; // DYLD_CHAINED_IMPORT* 45 | uint32_t symbols_format; // 0 => uncompressed, 1 => zlib compressed 46 | }; 47 | 48 | // This struct is embedded in LC_DYLD_CHAINED_FIXUPS payload 49 | struct dyld_chained_starts_in_image 50 | { 51 | uint32_t seg_count; 52 | uint32_t seg_info_offset[1]; // each entry is offset into this struct for that segment 53 | // followed by pool of dyld_chain_starts_in_segment data 54 | }; 55 | 56 | // This struct is embedded in dyld_chain_starts_in_image 57 | // and passed down to the kernel for page-in linking 58 | struct dyld_chained_starts_in_segment 59 | { 60 | uint32_t size; // size of this (amount kernel needs to copy) 61 | uint16_t page_size; // 0x1000 or 0x4000 62 | uint16_t pointer_format; // DYLD_CHAINED_PTR_* 63 | uint64_t segment_offset; // offset in memory to start of segment 64 | uint32_t max_valid_pointer; // for 32-bit OS, any value beyond this is not a pointer 65 | uint16_t page_count; // how many pages are in array 66 | uint16_t page_start[1]; // each entry is offset in each page of first element in chain 67 | // or DYLD_CHAINED_PTR_START_NONE if no fixups on page 68 | // uint16_t chain_starts[1]; // some 32-bit formats may require multiple starts per page. 69 | // for those, if high bit is set in page_starts[], then it 70 | // is index into chain_starts[] which is a list of starts 71 | // the last of which has the high bit set 72 | }; 73 | 74 | enum { 75 | DYLD_CHAINED_PTR_START_NONE = 0xFFFF, // used in page_start[] to denote a page with no fixups 76 | DYLD_CHAINED_PTR_START_MULTI = 0x8000, // used in page_start[] to denote a page which has multiple starts 77 | DYLD_CHAINED_PTR_START_LAST = 0x8000, // used in chain_starts[] to denote last start in list for page 78 | }; 79 | 80 | // This struct is embedded in __TEXT,__chain_starts section in firmware 81 | struct dyld_chained_starts_offsets 82 | { 83 | uint32_t pointer_format; // DYLD_CHAINED_PTR_32_FIRMWARE 84 | uint32_t starts_count; // number of starts in array 85 | uint32_t chain_starts[1]; // array chain start offsets 86 | }; 87 | 88 | 89 | // values for dyld_chained_starts_in_segment.pointer_format 90 | enum { 91 | DYLD_CHAINED_PTR_ARM64E = 1, // stride 8, unauth target is vmaddr 92 | DYLD_CHAINED_PTR_64 = 2, // target is vmaddr 93 | DYLD_CHAINED_PTR_32 = 3, 94 | DYLD_CHAINED_PTR_32_CACHE = 4, 95 | DYLD_CHAINED_PTR_32_FIRMWARE = 5, 96 | DYLD_CHAINED_PTR_64_OFFSET = 6, // target is vm offset 97 | DYLD_CHAINED_PTR_ARM64E_OFFSET = 7, // old name 98 | DYLD_CHAINED_PTR_ARM64E_KERNEL = 7, // stride 4, unauth target is vm offset 99 | DYLD_CHAINED_PTR_64_KERNEL_CACHE = 8, 100 | DYLD_CHAINED_PTR_ARM64E_USERLAND = 9, // stride 8, unauth target is vm offset 101 | DYLD_CHAINED_PTR_ARM64E_FIRMWARE = 10, // stride 4, unauth target is vmaddr 102 | DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE = 11, // stride 1, x86_64 kernel caches 103 | DYLD_CHAINED_PTR_ARM64E_USERLAND24 = 12, // stride 8, unauth target is vm offset, 24-bit bind 104 | DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE = 13, // stride 8, regular/auth targets both vm offsets. Only A keys supported 105 | }; 106 | 107 | 108 | // DYLD_CHAINED_PTR_ARM64E 109 | struct dyld_chained_ptr_arm64e_rebase 110 | { 111 | uint64_t target : 43, 112 | high8 : 8, 113 | next : 11, // 4 or 8-byte stide 114 | bind : 1, // == 0 115 | auth : 1; // == 0 116 | }; 117 | 118 | // DYLD_CHAINED_PTR_ARM64E 119 | struct dyld_chained_ptr_arm64e_bind 120 | { 121 | uint64_t ordinal : 16, 122 | zero : 16, 123 | addend : 19, // +/-256K 124 | next : 11, // 4 or 8-byte stide 125 | bind : 1, // == 1 126 | auth : 1; // == 0 127 | }; 128 | 129 | // DYLD_CHAINED_PTR_ARM64E 130 | struct dyld_chained_ptr_arm64e_auth_rebase 131 | { 132 | uint64_t target : 32, // runtimeOffset 133 | diversity : 16, 134 | addrDiv : 1, 135 | key : 2, 136 | next : 11, // 4 or 8-byte stide 137 | bind : 1, // == 0 138 | auth : 1; // == 1 139 | }; 140 | 141 | // DYLD_CHAINED_PTR_ARM64E 142 | struct dyld_chained_ptr_arm64e_auth_bind 143 | { 144 | uint64_t ordinal : 16, 145 | zero : 16, 146 | diversity : 16, 147 | addrDiv : 1, 148 | key : 2, 149 | next : 11, // 4 or 8-byte stide 150 | bind : 1, // == 1 151 | auth : 1; // == 1 152 | }; 153 | 154 | // DYLD_CHAINED_PTR_64/DYLD_CHAINED_PTR_64_OFFSET 155 | struct dyld_chained_ptr_64_rebase 156 | { 157 | uint64_t target : 36, // 64GB max image size (DYLD_CHAINED_PTR_64 => vmAddr, DYLD_CHAINED_PTR_64_OFFSET => runtimeOffset) 158 | high8 : 8, // top 8 bits set to this (DYLD_CHAINED_PTR_64 => after slide added, DYLD_CHAINED_PTR_64_OFFSET => before slide added) 159 | reserved : 7, // all zeros 160 | next : 12, // 4-byte stride 161 | bind : 1; // == 0 162 | }; 163 | 164 | 165 | // DYLD_CHAINED_PTR_ARM64E_USERLAND24 166 | struct dyld_chained_ptr_arm64e_bind24 167 | { 168 | uint64_t ordinal : 24, 169 | zero : 8, 170 | addend : 19, // +/-256K 171 | next : 11, // 8-byte stide 172 | bind : 1, // == 1 173 | auth : 1; // == 0 174 | }; 175 | 176 | // DYLD_CHAINED_PTR_ARM64E_USERLAND24 177 | struct dyld_chained_ptr_arm64e_auth_bind24 178 | { 179 | uint64_t ordinal : 24, 180 | zero : 8, 181 | diversity : 16, 182 | addrDiv : 1, 183 | key : 2, 184 | next : 11, // 8-byte stide 185 | bind : 1, // == 1 186 | auth : 1; // == 1 187 | }; 188 | 189 | 190 | // DYLD_CHAINED_PTR_64 191 | struct dyld_chained_ptr_64_bind 192 | { 193 | uint64_t ordinal : 24, 194 | addend : 8, // 0 thru 255 195 | reserved : 19, // all zeros 196 | next : 12, // 4-byte stride 197 | bind : 1; // == 1 198 | }; 199 | 200 | // DYLD_CHAINED_PTR_64_KERNEL_CACHE, DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE 201 | struct dyld_chained_ptr_64_kernel_cache_rebase 202 | { 203 | uint64_t target : 30, // basePointers[cacheLevel] + target 204 | cacheLevel : 2, // what level of cache to bind to (indexes a mach_header array) 205 | diversity : 16, 206 | addrDiv : 1, 207 | key : 2, 208 | next : 12, // 1 or 4-byte stide 209 | isAuth : 1; // 0 -> not authenticated. 1 -> authenticated 210 | }; 211 | 212 | // DYLD_CHAINED_PTR_32 213 | // Note: for DYLD_CHAINED_PTR_32 some non-pointer values are co-opted into the chain 214 | // as out of range rebases. If an entry in the chain is > max_valid_pointer, then it 215 | // is not a pointer. To restore the value, subtract off the bias, which is 216 | // (64MB+max_valid_pointer)/2. 217 | struct dyld_chained_ptr_32_rebase 218 | { 219 | uint32_t target : 26, // vmaddr, 64MB max image size 220 | next : 5, // 4-byte stride 221 | bind : 1; // == 0 222 | }; 223 | 224 | // DYLD_CHAINED_PTR_32 225 | struct dyld_chained_ptr_32_bind 226 | { 227 | uint32_t ordinal : 20, 228 | addend : 6, // 0 thru 63 229 | next : 5, // 4-byte stride 230 | bind : 1; // == 1 231 | }; 232 | 233 | // DYLD_CHAINED_PTR_32_CACHE 234 | struct dyld_chained_ptr_32_cache_rebase 235 | { 236 | uint32_t target : 30, // 1GB max dyld cache TEXT and DATA 237 | next : 2; // 4-byte stride 238 | }; 239 | 240 | 241 | // DYLD_CHAINED_PTR_32_FIRMWARE 242 | struct dyld_chained_ptr_32_firmware_rebase 243 | { 244 | uint32_t target : 26, // 64MB max firmware TEXT and DATA 245 | next : 6; // 4-byte stride 246 | }; 247 | 248 | // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE 249 | struct dyld_chained_ptr_arm64e_shared_cache_rebase 250 | { 251 | uint64_t runtimeOffset : 34, // offset from the start of the shared cache 252 | high8 : 8, 253 | unused : 10, 254 | next : 11, // 8-byte stide 255 | auth : 1; // == 0 256 | }; 257 | 258 | // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE 259 | struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase 260 | { 261 | uint64_t runtimeOffset : 34, // offset from the start of the shared cache 262 | diversity : 16, 263 | addrDiv : 1, 264 | keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA 265 | next : 11, // 8-byte stide 266 | auth : 1; // == 1 267 | }; 268 | 269 | 270 | 271 | // values for dyld_chained_fixups_header.imports_format 272 | enum { 273 | DYLD_CHAINED_IMPORT = 1, 274 | DYLD_CHAINED_IMPORT_ADDEND = 2, 275 | DYLD_CHAINED_IMPORT_ADDEND64 = 3, 276 | }; 277 | 278 | // DYLD_CHAINED_IMPORT 279 | struct dyld_chained_import 280 | { 281 | uint32_t lib_ordinal : 8, // -15 .. 240 (0xF1 .. 0xF0) 282 | weak_import : 1, 283 | name_offset : 23; 284 | }; 285 | 286 | // DYLD_CHAINED_IMPORT_ADDEND 287 | struct dyld_chained_import_addend 288 | { 289 | uint32_t lib_ordinal : 8, // -15 .. 240 (0xF1 .. 0xF0) 290 | weak_import : 1, 291 | name_offset : 23; 292 | int32_t addend; 293 | }; 294 | 295 | // DYLD_CHAINED_IMPORT_ADDEND64 296 | struct dyld_chained_import_addend64 297 | { 298 | uint64_t lib_ordinal : 16, // -15 .. 65520 (0xFFF1 .. 0xFFF0) 299 | weak_import : 1, 300 | reserved : 15, 301 | name_offset : 32; 302 | uint64_t addend; 303 | }; 304 | 305 | #endif // __MACH_O_FIXUP_CHAINS__ 306 | 307 | -------------------------------------------------------------------------------- /tests/choma_cli/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | char *get_argument_value(int argc, char *argv[], const char *flag) 9 | { 10 | for (int i = 0; i < argc; i++) { 11 | if (!strcmp(argv[i], flag)) { 12 | if (i+1 < argc) { 13 | return argv[i+1]; 14 | } 15 | } 16 | } 17 | return NULL; 18 | } 19 | 20 | bool argument_exists(int argc, char *argv[], const char *flag) 21 | { 22 | for (int i = 0; i < argc; i++) { 23 | if (!strcmp(argv[i], flag)) { 24 | return true; 25 | } 26 | } 27 | return false; 28 | } 29 | 30 | void print_usage(char *executablePath) { 31 | printf("Options:\n"); 32 | printf("\t-i: Path to input file\n"); 33 | printf("\t-c: Parse the CMS superblob blob of a MachO\n"); 34 | printf("\t-e: Extract the Code Signature from a MachO\n"); 35 | printf("\t-s: Print all page hash code slots in a CodeDirectory blob\n"); 36 | printf("\t-v: Verify that the CodeDirectory hashes are correct\n"); 37 | printf("\t-f: Parse an MH_FILESET MachO and output it's sub-files\n"); 38 | printf("\t-y: Parse symbol table\n"); 39 | printf("\t-L: Parse dependency dylibs\n"); 40 | printf("\t-d: Parse code signature data (use with -c)\n"); 41 | printf("\t-h: Print this message\n"); 42 | printf("Examples:\n"); 43 | printf("\t%s -i -c\n", executablePath); 44 | printf("\t%s -i -c -s -v\n", executablePath); 45 | printf("\t%s -i -f\n", executablePath); 46 | exit(-1); 47 | } 48 | 49 | int main(int argc, char *argv[]) { 50 | 51 | if (argument_exists(argc, argv, "-h")) { 52 | print_usage(argv[0]); 53 | return 0; 54 | } 55 | 56 | if (argc < 2) { 57 | print_usage(argv[0]); 58 | return -1; 59 | } 60 | 61 | char *inputPath = get_argument_value(argc, argv, "-i"); 62 | if (!inputPath) { 63 | printf("Error: no input file specified.\n"); 64 | print_usage(argv[0]); 65 | return -1; 66 | } 67 | 68 | if (!argument_exists(argc, argv, "-c") && !argument_exists(argc, argv, "-f") && !argument_exists(argc, argv, "-y") && !argument_exists(argc, argv, "-L")) { 69 | printf("Error: no action specified.\n"); 70 | print_usage(argv[0]); 71 | return -1; 72 | } 73 | 74 | if (argument_exists(argc, argv, "-d")) { 75 | printf("Parsing code signature data.\n"); 76 | MemoryStream *stream = file_stream_init_from_path(get_argument_value(argc, argv, "-i"), 0, FILE_STREAM_SIZE_AUTO, 0); 77 | if (!stream) { 78 | printf("Error: could not open file %s.\n", get_argument_value(argc, argv, "-i")); 79 | return -1; 80 | } 81 | CS_SuperBlob *superblob = malloc(memory_stream_get_size(stream)); 82 | if (!superblob) { 83 | printf("Error: could not allocate memory for superblob.\n"); 84 | return -1; 85 | } 86 | memory_stream_read(stream, 0, memory_stream_get_size(stream), superblob); 87 | CS_DecodedSuperBlob *decodedSuperBlob = csd_superblob_decode(superblob); 88 | csd_superblob_print_content(decodedSuperBlob, NULL, argument_exists(argc, argv, "-s"), false); 89 | free(superblob); 90 | memory_stream_free(stream); 91 | return 0; 92 | } 93 | 94 | // Initialise the Fat structure 95 | printf("Initialising Fat structure from %s.\n", inputPath); 96 | Fat *fat = fat_init_from_path(inputPath); 97 | if (!fat) return -1; 98 | 99 | for (int i = 0; i < fat->slicesCount; i++) { 100 | MachO *slice = fat->slices[i]; 101 | printf("Slice %d (arch %x/%x, macho %x/%x):\n", i, slice->archDescriptor.cputype, slice->archDescriptor.cpusubtype, slice->machHeader.cputype, slice->machHeader.cpusubtype); 102 | if (argument_exists(argc, argv, "-c")) { 103 | CS_SuperBlob *superblob = macho_read_code_signature(slice); 104 | if (!superblob) { 105 | printf("Slice %d is not signed at all.\n", i); 106 | continue; 107 | } 108 | CS_DecodedSuperBlob *decodedSuperBlob = csd_superblob_decode(superblob); 109 | csd_superblob_print_content(decodedSuperBlob, slice, argument_exists(argc, argv, "-s"), argument_exists(argc, argv, "-v")); 110 | if (argument_exists(argc, argv, "-e")) { 111 | macho_extract_cs_to_file(slice, superblob); 112 | } 113 | } 114 | if (argument_exists(argc, argv, "-f")) { 115 | for (uint32_t i = 0; i < slice->segmentCount; i++) { 116 | MachOSegment *segment = slice->segments[i]; 117 | printf("(0x%08llx-0x%08llx)->(0x%09llx-0x%09llx) | %s\n", segment->command.fileoff, segment->command.fileoff + segment->command.filesize, segment->command.vmaddr, segment->command.vmaddr + segment->command.vmsize, segment->command.segname); 118 | for (int j = 0; j < segment->command.nsects; j++) { 119 | struct section_64 *section = &segment->sections[j]; 120 | printf("(0x%08x-0x%08llx)->(0x%09llx-0x%09llx) | %s.%s\n", section->offset, section->offset + section->size, section->addr, section->addr + section->size, section->segname, section->sectname); 121 | } 122 | } 123 | for (uint32_t i = 0; i < slice->filesetCount; i++) { 124 | MachO *filesetMachoSlice = slice->filesetMachos[i].underlyingMachO->slices[0]; 125 | char *entry_id = slice->filesetMachos[i].entry_id; 126 | for (int j = 0; j < filesetMachoSlice->segmentCount; j++) { 127 | MachOSegment *segment = filesetMachoSlice->segments[j]; 128 | printf("(0x%08llx-0x%08llx)->(0x%09llx-0x%09llx) | %s.%s\n", segment->command.fileoff, segment->command.fileoff + segment->command.filesize, segment->command.vmaddr, segment->command.vmaddr + segment->command.vmsize, entry_id, segment->command.segname); 129 | for (int k = 0; k < segment->command.nsects; k++) { 130 | struct section_64 *section = &segment->sections[k]; 131 | printf("(0x%08x-0x%08llx)->(0x%09llx-0x%09llx) | %s.%s.%s\n", section->offset, section->offset + section->size, section->addr, section->addr + section->size, entry_id, section->segname, section->sectname); 132 | } 133 | } 134 | } 135 | } 136 | if (argument_exists(argc, argv, "-y")) { 137 | printf("Symbols:\n"); 138 | macho_enumerate_symbols(slice, ^(const char *name, uint8_t type, uint64_t vmaddr, bool *stop) { 139 | const char *typeStr = NULL; 140 | switch(type & N_TYPE) { 141 | case N_UNDF: typeStr = "N_UNDF"; break; 142 | case N_ABS: typeStr = "N_ABS"; break; 143 | case N_SECT: typeStr = "N_SECT"; break; 144 | case N_PBUD: typeStr = "N_PBUD"; break; 145 | case N_INDR: typeStr = "N_INDR"; break; 146 | } 147 | uint64_t fileoff = 0; 148 | macho_translate_vmaddr_to_fileoff(slice, vmaddr, &fileoff, NULL); 149 | printf("%s (%s): 0x%llx / 0x%llx\n", name, typeStr, fileoff, vmaddr); 150 | }); 151 | } 152 | if (argument_exists(argc, argv, "-L")) { 153 | __block bool firstDependency = true; 154 | macho_enumerate_dependencies(slice, ^(const char *dylibPath, uint32_t cmd, struct dylib* dylib, bool *stop){ 155 | if (firstDependency) { 156 | printf("Dependencies:\n"); 157 | firstDependency = false; 158 | } 159 | printf("| %s (%s, compatibility version: %u, current version: %u)\n", dylibPath, load_command_to_string(cmd), dylib->current_version, dylib->compatibility_version); 160 | }); 161 | 162 | __block bool firstRpath = true; 163 | macho_enumerate_rpaths(slice, ^(const char *rpath, bool *stop){ 164 | if (firstRpath) { 165 | printf("Rpaths:\n"); 166 | firstRpath = false; 167 | } 168 | printf("| %s\n", rpath); 169 | }); 170 | } 171 | } 172 | 173 | fat_free(fat); 174 | 175 | return 0; 176 | 177 | } 178 | -------------------------------------------------------------------------------- /tests/choma_sign/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef DISABLE_SIGNING 10 | 11 | char *get_argument_value(int argc, char *argv[], const char *flag) 12 | { 13 | for (int i = 0; i < argc; i++) { 14 | if (!strcmp(argv[i], flag)) { 15 | if (i+1 < argc) { 16 | return argv[i+1]; 17 | } 18 | } 19 | } 20 | return NULL; 21 | } 22 | 23 | bool argument_exists(int argc, char *argv[], const char *flag) 24 | { 25 | for (int i = 0; i < argc; i++) { 26 | if (!strcmp(argv[i], flag)) { 27 | return true; 28 | } 29 | } 30 | return false; 31 | } 32 | 33 | void print_usage(const char *self) 34 | { 35 | printf("Options: \n"); 36 | printf("\t-i: input file\n"); 37 | printf("\t-o: output file\n"); 38 | printf("\t-r: replace input file / replace output file if it already exists\n"); 39 | printf("\t-a: input is an .app bundle\n"); 40 | printf("\t-t: optional team ID to use\n"); 41 | printf("\t-I: optional identifier to use\n"); 42 | printf("\t-h: print this help message\n"); 43 | printf("\t-e: path to entitlements file (XML or property list)\n"); 44 | printf("Examples:\n"); 45 | printf("\t%s -i (-r) (-o ) -e entitlements.plist\n", self); 46 | exit(-1); 47 | } 48 | 49 | int sign_macho(MachO *macho, const char *entitlements, char *teamID, char *identifier) { 50 | CS_DecodedBlob *xmlEntitlements = NULL, *derEntitlements = NULL; 51 | if (entitlements) { 52 | // Create entitlements blobs 53 | xmlEntitlements = create_xml_entitlements_blob(entitlements); 54 | derEntitlements = create_der_entitlements_blob(entitlements); 55 | if (!xmlEntitlements || !derEntitlements) { 56 | printf("Error: failed to generate entitlements blobs\n"); 57 | return 1; 58 | } 59 | } 60 | 61 | CS_DecodedBlob *codeDir = csd_code_directory_init(macho, CS_HASHTYPE_SHA256_256, false); 62 | if (!codeDir) { 63 | printf("Error: failed to generate new code directory\n"); 64 | } 65 | 66 | if (identifier) { 67 | if (csd_code_directory_set_identifier(codeDir, identifier) != 0) { 68 | printf("Error: failed to set identifier\n"); 69 | } 70 | } 71 | if (teamID) { 72 | if (csd_code_directory_set_team_id(codeDir, teamID) != 0) { 73 | printf("Error: failed to set team ID\n"); 74 | } 75 | } 76 | 77 | CS_DecodedSuperBlob *decodedSuperblob = csd_superblob_init(); 78 | csd_superblob_append_blob(decodedSuperblob, codeDir); 79 | if (xmlEntitlements) csd_superblob_append_blob(decodedSuperblob, xmlEntitlements); 80 | if (derEntitlements) csd_superblob_append_blob(decodedSuperblob, derEntitlements); 81 | 82 | csd_code_directory_update(codeDir, macho); 83 | csd_code_directory_update_special_slots(codeDir, xmlEntitlements, derEntitlements, NULL); 84 | csd_code_directory_print_content(codeDir, macho, true, false); 85 | 86 | if (xmlEntitlements) csd_blob_free(xmlEntitlements); 87 | if (derEntitlements) csd_blob_free(derEntitlements); 88 | csd_blob_free(codeDir); 89 | 90 | return 0; 91 | } 92 | 93 | int main(int argc, char *argv[]) { 94 | const char *inputPath = get_argument_value(argc, argv, "-i"); 95 | const char *outputPath = get_argument_value(argc, argv, "-o"); 96 | const char *entitlementsPath = get_argument_value(argc, argv, "-e"); 97 | char *teamID = get_argument_value(argc, argv, "-t"); 98 | char *identifier = get_argument_value(argc, argv, "-I"); 99 | 100 | bool replaceFile = argument_exists(argc, argv, "-r") || argument_exists(argc, argv, "-a"); 101 | bool printHelpMessage = argument_exists(argc, argv, "-h"); 102 | 103 | if (!inputPath || (!outputPath && !replaceFile) || printHelpMessage) { 104 | print_usage(argv[0]); 105 | } 106 | 107 | if (access(inputPath, F_OK) != 0) { 108 | printf("Error: cannot access %s.\n", inputPath); 109 | return 1; 110 | } 111 | 112 | if (entitlementsPath && access(entitlementsPath, F_OK) != 0) { 113 | printf("Error: cannot access %s.\n", entitlementsPath); 114 | return 1; 115 | } 116 | 117 | Fat *fat = fat_init_from_path(inputPath); 118 | if (!fat) { 119 | printf("Error: failed to initialise fat structure.\n"); 120 | return 1; 121 | } 122 | 123 | sign_macho(fat_find_preferred_slice(fat), entitlementsPath, teamID, identifier); 124 | 125 | fat_free(fat); 126 | } 127 | 128 | #else 129 | 130 | int main(int argc, char *argv[]) { 131 | return 0; 132 | } 133 | 134 | #endif // DISABLE_SIGNING -------------------------------------------------------------------------------- /tests/ct_bypass/DERTemplate.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | SEQUENCE (2 bytes) 5 | OBJECT IDENTIFIER (2+5 bytes) 1.3.14.3.2.26 (sha1) 6 | OCTET STRING (2+20 bytes) 7 | SEQUENCE (2 bytes) 8 | OBJECT IDENTIFIER (2+9 bytes) 2.16.840.1.101.3.4.2.1 (sha256) 9 | OCTET STRING (2+32 bytes) 10 | */ 11 | uint8_t CDHashesDERTemplate[] = { 12 | /* SEQUENCE (2 bytes) */ 13 | 0x30, 0x1D, 14 | /* OBJECT IDENTIFIER (2+5 bytes) */ 15 | 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 16 | /* OCTET STRING (2+20 bytes) */ 17 | 0x04, 0x14, 18 | /* SHA1 CDHash goes here */ 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | /* SEQUENCE (2 bytes) */ 22 | 0x30, 0x2D, 23 | /* OBJECT IDENTIFIER (2+9 bytes) */ 24 | 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 25 | /* OCTET STRING (2+32 bytes) */ 26 | 0x04, 0x20, 27 | /* SHA256 CDHash goes here */ 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 31 | }; 32 | 33 | #define CDHASHES_DER_SIZE 78 34 | #define CDHASHES_DER_SHA1_OFFSET 11 35 | #define CDHASHES_DER_SHA256_OFFSET 46 36 | -------------------------------------------------------------------------------- /tests/dsc_extract/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | char *get_argument_value(int argc, char *argv[], const char *flag) 12 | { 13 | for (int i = 0; i < argc; i++) { 14 | if (!strcmp(argv[i], flag)) { 15 | if (i+1 < argc) { 16 | return argv[i+1]; 17 | } 18 | } 19 | } 20 | return NULL; 21 | } 22 | 23 | bool argument_exists(int argc, char *argv[], const char *flag) 24 | { 25 | for (int i = 0; i < argc; i++) { 26 | if (!strcmp(argv[i], flag)) { 27 | return true; 28 | } 29 | } 30 | return false; 31 | } 32 | 33 | void print_usage(char *executablePath) { 34 | printf("Options:\n"); 35 | printf("\t-i: Path to input DSC file (e.g. dyld_shared_cache_arm64e)\n"); 36 | printf("\t-o: Path output file if required\n"); 37 | printf("\t-e: Path of image to extract\n"); 38 | printf("\t-l: List images contained in shared cache\n"); 39 | printf("\t-d: Print dependencies of image\n"); 40 | printf("\t-h: Print this message\n"); 41 | printf("Examples:\n"); 42 | printf("\t%s -i -l\n", executablePath); 43 | printf("\t%s -i -e -o \n", executablePath); 44 | exit(-1); 45 | } 46 | 47 | int main(int argc, char *argv[]) { 48 | if (argument_exists(argc, argv, "-h")) { 49 | print_usage(argv[0]); 50 | } 51 | 52 | char *inputPath = get_argument_value(argc, argv, "-i"); 53 | char *outputPath = get_argument_value(argc, argv, "-o"); 54 | char *imageToExtract = get_argument_value(argc, argv, "-e"); 55 | bool shouldListImages = argument_exists(argc, argv, "-l"); 56 | bool shouldPrintDependencies = argument_exists(argc, argv, "-d"); 57 | 58 | 59 | if (!inputPath) { 60 | printf("Error: input file required\n"); 61 | print_usage(argv[0]); 62 | } 63 | 64 | if (!shouldListImages && !outputPath && !shouldPrintDependencies) { 65 | printf("Error: output file required\n"); 66 | print_usage(argv[0]); 67 | } 68 | 69 | DyldSharedCache *dsc = dsc_init_from_path(inputPath); 70 | if (!dsc) { 71 | printf("Error: failed to parse dyld shared cache\n"); 72 | return -2; 73 | } 74 | 75 | __block MachO *machoToExtract = NULL; 76 | dsc_enumerate_images(dsc, ^(const char *path, DyldSharedCacheImage *imageHandle, MachO *imageMachO, bool *stop){ 77 | if (shouldListImages) printf("%s\n", path); 78 | if (imageToExtract && !strcmp(path, imageToExtract)) { 79 | machoToExtract = imageMachO; 80 | } 81 | }); 82 | 83 | if (machoToExtract && shouldPrintDependencies) { 84 | printf("Dependencies of %s:\n", imageToExtract); 85 | macho_enumerate_dependencies(machoToExtract, ^(const char *dylibPath, uint32_t cmd, struct dylib *dylib, bool *stop) { 86 | printf("| %s\n", dylibPath); 87 | }); 88 | } else if (!machoToExtract && imageToExtract) { 89 | printf("Error: failed to locate %s in shared cache\n", imageToExtract); 90 | } else if (machoToExtract) { 91 | FILE *f = fopen(outputPath, "wb+"); 92 | if (f) { 93 | MemoryStream *memoryStream = macho_get_stream(machoToExtract); 94 | fwrite(memory_stream_get_raw_pointer(memoryStream), memory_stream_get_size(memoryStream), 1, f); 95 | fclose(f); 96 | } else { 97 | printf("Error: failed to open %s for writing\n", outputPath); 98 | } 99 | } 100 | 101 | dsc_free(dsc); 102 | 103 | return 0; 104 | } 105 | -------------------------------------------------------------------------------- /tests/dsc_tester/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int main(int argc, char *argv[]) { 14 | if (argc < 2) return -1; 15 | 16 | uint64_t premapSlide = 0; 17 | 18 | if (argc >= 3) { 19 | if (!strcmp(argv[2], "--use-premap")) { 20 | task_dyld_info_data_t dyldInfo; 21 | uint32_t count = TASK_DYLD_INFO_COUNT; 22 | task_info(mach_task_self_, TASK_DYLD_INFO, (task_info_t)&dyldInfo, &count); 23 | struct dyld_all_image_infos *allImageInfos = (void *)dyldInfo.all_image_info_addr; 24 | premapSlide = allImageInfos->sharedCacheSlide; 25 | } 26 | } 27 | 28 | printf("Loading shared cache... "); 29 | 30 | DyldSharedCache *dsc = dsc_init_from_path_premapped(argv[1], premapSlide); 31 | if (!dsc) { 32 | printf("❌\n"); 33 | return -2; 34 | } 35 | printf("✅\n"); 36 | 37 | printf("Finding libdispatch... "); 38 | 39 | MachO *libDispatchMachO = dsc_lookup_macho_by_path(dsc, "/usr/lib/system/libdispatch.dylib", NULL); 40 | if (!libDispatchMachO) { 41 | printf("❌\n"); 42 | return -2; 43 | } 44 | printf("✅\n"); 45 | 46 | 47 | printf("Finding symbols... "); 48 | 49 | __block bool foundPrivateSymbol = false, foundPublicSymbol = false; 50 | macho_enumerate_symbols(libDispatchMachO, ^(const char *name, uint8_t type, uint64_t vmaddr, bool *stop) { 51 | if (!strcmp(name, "__dispatch_Block_copy")) { 52 | foundPrivateSymbol = true; 53 | } 54 | else if (!strcmp(name, "_dispatch_sync")) { 55 | foundPublicSymbol = true; 56 | } 57 | if (foundPrivateSymbol && foundPublicSymbol) *stop = true; 58 | }); 59 | 60 | printf("(public: %s, private %s)\n", foundPublicSymbol ? "✅" : "❌", foundPrivateSymbol ? "✅" : "❌"); 61 | 62 | printf("Parsing chained fixups... "); 63 | 64 | MachO *libsystemBlocksMachO = dsc_lookup_macho_by_path(dsc, "/usr/lib/system/libsystem_blocks.dylib", NULL); 65 | 66 | __block uint64_t blockCopySym = 0; 67 | if (libsystemBlocksMachO) { 68 | macho_enumerate_symbols(libsystemBlocksMachO, ^(const char *name, uint8_t type, uint64_t vmaddr, bool *stop){ 69 | if (!strcmp(name, "__Block_copy")) { 70 | blockCopySym = vmaddr; 71 | *stop = true; 72 | } 73 | }); 74 | } 75 | 76 | __block bool foundBlockCopyFixup = false; 77 | 78 | if (blockCopySym) { 79 | dsc_enumerate_chained_fixups(dsc, ^(DyldSharedCachePointer *pointer, bool *stop) { 80 | if (pointer->target == blockCopySym) { 81 | foundBlockCopyFixup = true; 82 | *stop = true; 83 | } 84 | }); 85 | } 86 | 87 | printf("%s\n", foundBlockCopyFixup ? "✅" : "❌"); 88 | 89 | dsc_free(dsc); 90 | 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /tests/dyld_patch/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | char gDopamineUUID[] = (char[]){'D', 'O', 'P', 'A', 'M', 'I', 'N', 'E', 'D', 'O', 'P', 'A', 'M', 'I', 'N', 'E' }; 6 | 7 | int apply_dyld_patch(const char *dyldPath) 8 | { 9 | MachO *dyldMacho = macho_init_for_writing(dyldPath); 10 | if (!dyldMacho) return -1; 11 | 12 | // Make AMFI flags always be `0xdf`, allows DYLD variables to always work 13 | __block uint64_t getAMFIAddr = 0; 14 | macho_enumerate_symbols(dyldMacho, ^(const char *name, uint8_t type, uint64_t vmaddr, bool *stop){ 15 | if (!strcmp(name, "__ZN5dyld413ProcessConfig8Security7getAMFIERKNS0_7ProcessERNS_15SyscallDelegateE")) { 16 | getAMFIAddr = vmaddr; 17 | } 18 | }); 19 | uint32_t getAMFIPatch[] = { 20 | 0xd2801be0, // mov x0, 0xdf 21 | 0xd65f03c0 // ret 22 | }; 23 | 24 | if (getAMFIAddr == 0) { 25 | printf("Error: Failed patchfinding getAMFI\n"); 26 | return -1; 27 | } 28 | 29 | macho_write_at_vmaddr(dyldMacho, getAMFIAddr, sizeof(getAMFIPatch), getAMFIPatch); 30 | 31 | // iOS 16+: Change LC_UUID to prevent the kernel from using the in-cache dyld 32 | macho_enumerate_load_commands(dyldMacho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { 33 | if (loadCommand.cmd == LC_UUID) { 34 | struct uuid_command *uuidCommand = (struct uuid_command *)cmd; 35 | memcpy(&uuidCommand->uuid, gDopamineUUID, sizeof(gDopamineUUID)); 36 | macho_write_at_offset(dyldMacho, offset, loadCommand.cmdsize, uuidCommand); 37 | *stop = true; 38 | } 39 | }); 40 | 41 | macho_free(dyldMacho); 42 | return 0; 43 | } 44 | 45 | void print_usage(char *executablePath) { 46 | printf("%s \n", executablePath); 47 | exit(-1); 48 | } 49 | 50 | int main(int argc, char *argv[]) { 51 | if (argc < 2) { 52 | print_usage(argv[0]); 53 | return -1; 54 | } 55 | 56 | const char *inputPath = argv[1]; 57 | apply_dyld_patch(inputPath); 58 | return 0; 59 | } -------------------------------------------------------------------------------- /tests/dylib_insert/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define CPU_SUBTYPE_ARM64E_ABI_V2 0x80000000 13 | 14 | char *extract_preferred_slice(const char *fatPath) 15 | { 16 | Fat *fat = fat_init_from_path(fatPath); 17 | if (!fat) return NULL; 18 | MachO *macho = fat_find_preferred_slice(fat); 19 | 20 | #if TARGET_OS_MAC && !TARGET_OS_IPHONE 21 | if (!macho) { 22 | // Check for arm64v8 first 23 | macho = fat_find_slice(fat, CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_V8); 24 | if (!macho) { 25 | // If that fails, check for regular arm64 26 | macho = fat_find_slice(fat, CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL); 27 | if (!macho) { 28 | // If that fails, check for arm64e with ABI v2 29 | macho = fat_find_slice(fat, CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_ARM64E_ABI_V2); 30 | if (!macho) { 31 | // If that fails, check for arm64e 32 | macho = fat_find_slice(fat, CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64E); 33 | if (!macho) { 34 | fat_free(fat); 35 | return NULL; 36 | } 37 | } 38 | } 39 | } 40 | } 41 | #else 42 | if (!macho) { 43 | fat_free(fat); 44 | return NULL; 45 | } 46 | #endif // TARGET_OS_MAC && !TARGET_OS_IPHONE 47 | 48 | // Only re-sign MH_EXECUTE, MH_DYLIB, and MH_BUNDLE 49 | if (macho->machHeader.filetype != MH_EXECUTE) { 50 | printf("Error: MachO is not an executable! This is an unsupported MachO type for inserting a dylib.\n"); 51 | fat_free(fat); 52 | return NULL; 53 | } 54 | 55 | char *temp = strdup("/tmp/XXXXXX"); 56 | int fd = mkstemp(temp); 57 | 58 | MemoryStream *outStream = file_stream_init_from_path(temp, 0, 0, FILE_STREAM_FLAG_WRITABLE | FILE_STREAM_FLAG_AUTO_EXPAND); 59 | MemoryStream *machoStream = macho_get_stream(macho); 60 | memory_stream_copy_data(machoStream, 0, outStream, 0, memory_stream_get_size(machoStream)); 61 | 62 | fat_free(fat); 63 | memory_stream_free(outStream); 64 | close(fd); 65 | return temp; 66 | } 67 | 68 | int extract_blobs(CS_SuperBlob *superBlob, const char *dir) 69 | { 70 | CS_DecodedSuperBlob *decodedSuperblob = csd_superblob_decode(superBlob); 71 | 72 | CS_DecodedBlob *blob = decodedSuperblob->firstBlob; 73 | while (blob) { 74 | char outPath[PATH_MAX]; 75 | CS_GenericBlob genericBlob; 76 | csd_blob_read(blob, 0, sizeof(genericBlob), &genericBlob); 77 | GENERIC_BLOB_APPLY_BYTE_ORDER(&genericBlob, BIG_TO_HOST_APPLIER); 78 | 79 | snprintf(outPath, PATH_MAX, "%s/%x_%x.bin", dir, blob->type, genericBlob.magic); 80 | 81 | uint64_t len = csd_blob_get_size(blob); 82 | uint8_t blobData[len]; 83 | csd_blob_read(blob, 0, len, blobData); 84 | 85 | FILE *f = fopen(outPath, "wb"); 86 | fwrite(blobData, len, 1, f); 87 | fclose(f); 88 | 89 | blob = blob->next; 90 | } 91 | return 0; 92 | } 93 | 94 | char *get_argument_value(int argc, char *argv[], const char *flag) 95 | { 96 | for (int i = 0; i < argc; i++) { 97 | if (!strcmp(argv[i], flag)) { 98 | if (i+1 < argc) { 99 | return argv[i+1]; 100 | } 101 | } 102 | } 103 | return NULL; 104 | } 105 | 106 | bool argument_exists(int argc, char *argv[], const char *flag) 107 | { 108 | for (int i = 0; i < argc; i++) { 109 | if (!strcmp(argv[i], flag)) { 110 | return true; 111 | } 112 | } 113 | return false; 114 | } 115 | 116 | void print_usage(const char *self) 117 | { 118 | printf("Options: \n"); 119 | printf("\t-i: input file\n"); 120 | printf("\t-o: output file\n"); 121 | printf("\t-r: replace input file\n"); 122 | printf("\t-d: path to dynamic library to insert (path to where it will be when loaded)\n"); 123 | printf("\t-w: load as a weak dynamic library (will continue execution if the library cannot be found)\n"); 124 | printf("\t-h: print this help message\n"); 125 | printf("Examples:\n"); 126 | printf("\t%s -i -d hook.dylib -o \n", self); 127 | printf("\t%s -i -a\n", self); 128 | exit(-1); 129 | } 130 | 131 | // Check if dylib is already inserted, check for a code signature and check for enough free space 132 | int check_load_commands(MachO *macho, bool *isInserted, bool *isSigned, bool *enoughFreeSpace, const char *dylibToInsert) { 133 | macho_enumerate_load_commands(macho, ^(struct load_command lc, uint64_t offset, void *cmd, bool *stop) { 134 | if (lc.cmd == LC_CODE_SIGNATURE) { 135 | if (isSigned) *isSigned = true; 136 | } 137 | if (lc.cmd == LC_LOAD_DYLIB || lc.cmd == LC_LOAD_WEAK_DYLIB) { 138 | struct dylib_command *command = (struct dylib_command *)cmd; 139 | char *dylibName = NULL; 140 | macho_read_string_at_offset(macho, offset + command->dylib.name.offset, &dylibName); 141 | if (dylibName && !strcmp(dylibName, dylibToInsert)) { 142 | printf("Info: found existing load command for %s, not going to insert a new one.\n", dylibToInsert); 143 | if (isInserted) *isInserted = true; 144 | } 145 | } 146 | }); 147 | 148 | int insertionSize = sizeof(struct dylib_command) + strlen(dylibToInsert); 149 | struct dylib_command *emptySpace = malloc(insertionSize); 150 | memset(emptySpace, 0, insertionSize); 151 | 152 | struct dylib_command *endOfLoadCommands = malloc(insertionSize); 153 | macho_read_at_offset(macho, macho_get_mach_header_size(macho) + macho->machHeader.sizeofcmds, insertionSize, endOfLoadCommands); 154 | 155 | if (!memcmp(emptySpace, endOfLoadCommands, insertionSize)) { 156 | if (enoughFreeSpace) *enoughFreeSpace = true; 157 | } 158 | 159 | free(emptySpace); 160 | free(endOfLoadCommands); 161 | 162 | return 0; 163 | } 164 | 165 | int insert_load_command(MachO *macho, const char *dylibPath, bool weakDylib) { 166 | struct dylib_command command = { 0 }; 167 | int insertionSize = sizeof(command) + strlen(dylibPath); 168 | 169 | // Main load command 170 | command.cmd = weakDylib ? LC_LOAD_WEAK_DYLIB : LC_LOAD_DYLIB; 171 | command.cmdsize = (HOST_TO_LITTLE(insertionSize) + 3) & ~3; 172 | 173 | // dylib structure 174 | command.dylib.name.offset = HOST_TO_LITTLE((int)sizeof(command)); 175 | command.dylib.timestamp = 0; 176 | command.dylib.current_version = 0; 177 | command.dylib.compatibility_version = 0; 178 | 179 | void *insertion = malloc(insertionSize); 180 | memcpy(insertion, &command, sizeof(command)); 181 | memcpy(insertion + sizeof(command), dylibPath, strlen(dylibPath)); 182 | 183 | macho_write_at_offset(macho, macho_get_mach_header_size(macho) + macho->machHeader.sizeofcmds, insertionSize, insertion); 184 | free(insertion); 185 | 186 | struct mach_header *header = malloc(sizeof(struct mach_header)); 187 | memcpy(header, &macho->machHeader, sizeof(struct mach_header)); 188 | MACH_HEADER_APPLY_BYTE_ORDER(header, LITTLE_TO_HOST_APPLIER); 189 | 190 | header->ncmds++; 191 | header->sizeofcmds += (insertionSize + 3) & ~3; 192 | MACH_HEADER_APPLY_BYTE_ORDER(header, HOST_TO_LITTLE_APPLIER); 193 | 194 | macho_write_at_offset(macho, 0, sizeof(struct mach_header), header); 195 | memcpy(&macho->machHeader, header, sizeof(struct mach_header)); 196 | 197 | free(header); 198 | 199 | return 0; 200 | } 201 | 202 | int main(int argc, char *argv[]) { 203 | const char *inputFile = get_argument_value(argc, argv, "-i"); 204 | const char *outputFile = get_argument_value(argc, argv, "-o"); 205 | const char *dylibPath = get_argument_value(argc, argv, "-d"); 206 | 207 | bool replace = argument_exists(argc, argv, "-r"); 208 | bool weakDylib = argument_exists(argc, argv, "-w"); 209 | bool showHelp = argument_exists(argc, argv, "-h"); 210 | 211 | if (!inputFile || (!outputFile && !replace) || !dylibPath || showHelp) { 212 | print_usage(argv[0]); 213 | } 214 | 215 | if (access(inputFile, F_OK) != 0) { 216 | printf("Error: cannot access %s.\n", inputFile); 217 | return 1; 218 | } 219 | 220 | char *slicePath = extract_preferred_slice(inputFile); 221 | if (!slicePath) { 222 | printf("Error: failed to extract preferred slice.\n"); 223 | return 1; 224 | } 225 | 226 | MachO *macho = macho_init_for_writing(slicePath); 227 | if (!macho) { 228 | printf("Error: failed to initialise MachO structure.\n"); 229 | return 1; 230 | } 231 | 232 | int r = 0; 233 | 234 | bool isInserted = false, isSigned = false, enoughFreeSpace = false; 235 | r = check_load_commands(macho, &isInserted, &isSigned, &enoughFreeSpace, dylibPath); 236 | 237 | if (r || isInserted) goto out; 238 | if (isSigned) { 239 | printf("Warning: binary is signed, so you will need to re-sign after inserting the library.\n"); 240 | } 241 | if (!enoughFreeSpace) { 242 | printf("Error: no free space available for new load command.\n"); 243 | r = 1; 244 | goto out; 245 | } 246 | 247 | r = insert_load_command(macho, dylibPath, weakDylib); 248 | if (r) goto out; 249 | r = copyfile(slicePath, replace ? inputFile : outputFile, 0, COPYFILE_ALL | COPYFILE_MOVE | COPYFILE_UNLINK); 250 | if (r == 0) { 251 | chmod(outputFile, 0755); 252 | printf("Successfully inserted new load command.\n"); 253 | } 254 | else { 255 | perror("copyfile"); 256 | } 257 | 258 | out: 259 | macho_free(macho); 260 | 261 | return r; 262 | } -------------------------------------------------------------------------------- /tests/fat_create/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define ARM64_ALIGNMENT 0xE 15 | 16 | char *get_argument_value(int argc, char *argv[], const char *flag) 17 | { 18 | for (int i = 0; i < argc; i++) { 19 | if (!strcmp(argv[i], flag)) { 20 | if (i+1 < argc) { 21 | return argv[i+1]; 22 | } 23 | } 24 | } 25 | return NULL; 26 | } 27 | 28 | bool argument_exists(int argc, char *argv[], const char *flag) 29 | { 30 | for (int i = 0; i < argc; i++) { 31 | if (!strcmp(argv[i], flag)) { 32 | return true; 33 | } 34 | } 35 | return false; 36 | } 37 | 38 | void print_usage(char *executablePath) { 39 | printf("Options:\n"); 40 | printf("\t-o: Path to output file\n"); 41 | printf("\t-r: Replace output file if it already exists\n"); 42 | printf("\t-h: Print this message\n"); 43 | printf("Examples:\n"); 44 | printf("\t%s -o ...\n", executablePath); 45 | exit(-1); 46 | } 47 | 48 | char **get_input_paths(int argc, char *argv[], int *inputPathsCount) { 49 | char **inputPaths = malloc(sizeof(char *) * argc); 50 | int count = 0; 51 | for (int i = 1; i < argc; i++) { 52 | // Make sure this isn't a flag or the output path 53 | if (argv[i][0] != '-' && strcmp(argv[i], get_argument_value(argc, argv, "-o"))) { 54 | inputPaths[count] = argv[i]; 55 | count++; 56 | } 57 | } 58 | if (count == 0) { 59 | free(inputPaths); 60 | return NULL; 61 | } 62 | *inputPathsCount = count; 63 | return inputPaths; 64 | } 65 | 66 | int main(int argc, char *argv[]) { 67 | if (argument_exists(argc, argv, "-h")) { 68 | print_usage(argv[0]); 69 | return -1; 70 | } 71 | 72 | char *outputPath = get_argument_value(argc, argv, "-o"); 73 | if (!outputPath) { 74 | printf("Error: no output file specified.\n"); 75 | print_usage(argv[0]); 76 | return -1; 77 | } 78 | 79 | // Get the input paths 80 | int inputPathsCount = 0; 81 | char **inputPaths = get_input_paths(argc, argv, &inputPathsCount); 82 | if (!inputPaths) { 83 | printf("Error: no input files specified.\n"); 84 | print_usage(argv[0]); 85 | return -1; 86 | } 87 | 88 | // Create the output Fat 89 | struct stat st; 90 | if (stat(outputPath, &st) == 0) { 91 | if (argument_exists(argc, argv, "-r")) { 92 | if (remove(outputPath) != 0) { 93 | printf("Error: failed to remove output file.\n"); 94 | return -1; 95 | } 96 | } else { 97 | printf("Error: output file already exists.\n"); 98 | return -1; 99 | } 100 | } 101 | 102 | FILE *outputFile = fopen(outputPath, "w"); 103 | if (!outputFile) { 104 | printf("Error: failed to create output file.\n"); 105 | return -1; 106 | } 107 | fclose(outputFile); 108 | 109 | // Create an array of MachO objects 110 | MachO **machoArray = macho_array_create_for_paths(inputPaths, inputPathsCount); 111 | if (!machoArray) { 112 | printf("Error: failed to create Fat array.\n"); 113 | return -1; 114 | } 115 | 116 | // Create the Fat object 117 | Fat *fat = fat_create_for_macho_array(inputPaths[0], machoArray, inputPathsCount); 118 | printf("Created Fat with %u slices.\n", fat->slicesCount); 119 | 120 | // Write the Fat to the output file 121 | struct fat_header fatHeader; 122 | fatHeader.magic = FAT_MAGIC; 123 | fatHeader.nfat_arch = fat->slicesCount; 124 | FAT_HEADER_APPLY_BYTE_ORDER(&fatHeader, HOST_TO_BIG_APPLIER); 125 | uint64_t alignment = pow(2, ARM64_ALIGNMENT); 126 | uint64_t paddingSize = alignment - sizeof(struct fat_header) - (sizeof(struct fat_arch) * fat->slicesCount); 127 | MemoryStream *stream = file_stream_init_from_path(outputPath, 0, FILE_STREAM_SIZE_AUTO, FILE_STREAM_FLAG_WRITABLE | FILE_STREAM_FLAG_AUTO_EXPAND); 128 | memory_stream_write(stream, 0, sizeof(struct fat_header), &fatHeader); 129 | 130 | uint64_t lastSliceEnd = alignment; 131 | for (int i = 0; i < fat->slicesCount; i++) { 132 | struct fat_arch archDescriptor; 133 | archDescriptor.cpusubtype = fat->slices[i]->archDescriptor.cpusubtype; 134 | archDescriptor.cputype = fat->slices[i]->archDescriptor.cputype; 135 | archDescriptor.size = fat->slices[i]->archDescriptor.size; 136 | archDescriptor.offset = align_to_size(lastSliceEnd, alignment); 137 | archDescriptor.align = ARM64_ALIGNMENT; 138 | FAT_ARCH_APPLY_BYTE_ORDER(&archDescriptor, HOST_TO_BIG_APPLIER); 139 | printf("Writing to offset 0x%lx\n", sizeof(struct fat_header) + (sizeof(struct fat_arch) * i)); 140 | memory_stream_write(stream, sizeof(struct fat_header) + (sizeof(struct fat_arch) * i), sizeof(struct fat_arch), &archDescriptor); 141 | lastSliceEnd += align_to_size(memory_stream_get_size(fat->slices[i]->stream), alignment); 142 | } 143 | uint8_t *padding = malloc(paddingSize); 144 | memset(padding, 0, paddingSize); 145 | memory_stream_write(stream, sizeof(struct fat_header) + (sizeof(struct fat_arch) * fat->slicesCount), paddingSize, padding); 146 | free(padding); 147 | 148 | uint64_t offset = alignment; 149 | for (int i = 0; i < fat->slicesCount; i++) { 150 | MachO *macho = fat->slices[i]; 151 | int size = memory_stream_get_size(macho->stream); 152 | void *data = malloc(size); 153 | memory_stream_read(macho->stream, 0, size, data); 154 | memory_stream_write(stream, offset, size, data); 155 | free(data); 156 | uint64_t alignedSize = i == fat->slicesCount - 1 ? size : align_to_size(size, alignment);; 157 | printf("Slice %d: 0x%x bytes, aligned to 0x%llx bytes.\n", i, size, alignedSize); 158 | padding = malloc(alignedSize - size); 159 | memset(padding, 0, alignedSize - size); 160 | memory_stream_write(stream, offset + size, alignedSize - size, padding); 161 | free(padding); 162 | offset += alignedSize; 163 | } 164 | 165 | if (fat) fat_free(fat); 166 | if (machoArray) free(machoArray); 167 | if (stream) memory_stream_free(stream); 168 | if (inputPaths) free(inputPaths); 169 | 170 | return 0; 171 | } -------------------------------------------------------------------------------- /tests/kpf/main.c: -------------------------------------------------------------------------------- 1 | #include "choma/Fat.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) { 13 | if (argc != 2) return -1; 14 | 15 | int fd = open(argv[1], O_RDONLY); 16 | if (fd < 0) return -1; 17 | 18 | struct stat stat_buf; 19 | fstat(fd, &stat_buf); 20 | 21 | void *mapping = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 22 | if (mapping == MAP_FAILED) return -1; 23 | 24 | MemoryStream *stream = buffered_stream_init_from_buffer(mapping, stat_buf.st_size, 0); 25 | if (!stream) return -1; 26 | 27 | Fat *fat = fat_init_from_memory_stream(stream); 28 | if (!fat) return -1; 29 | 30 | //Fat *fat = fat_init_from_path(argv[1]); 31 | //printf("fat: %p\n", fat); 32 | //if (!fat) return -1; 33 | 34 | MachO *macho = fat_find_preferred_slice(fat); 35 | if (macho) { 36 | clock_t t; 37 | t = clock(); 38 | 39 | PFSection *kernelTextSection = pfsec_init_from_macho(macho, "com.apple.kernel", "__TEXT_EXEC", "__text"); 40 | pfsec_set_cached(kernelTextSection, true); 41 | 42 | PFSection *kernelStringSection = pfsec_init_from_macho(macho, "com.apple.kernel", "__TEXT", "__cstring"); 43 | pfsec_set_cached(kernelStringSection, true); 44 | 45 | uint32_t pacibspBytes = 0xD503237F; 46 | uint32_t pacibspMask = 0xFFFFFFFF; 47 | PFPatternMetric *pacibspMetric = pfmetric_pattern_init(&pacibspBytes, &pacibspMask, sizeof(pacibspBytes), sizeof(uint32_t)); 48 | pfmetric_run(kernelTextSection, pacibspMetric, ^(uint64_t vmaddr, bool *stop) { 49 | printf("PACIBSP: 0x%llx (%x)\n", vmaddr, pfsec_read32(kernelTextSection, vmaddr+4)); 50 | }); 51 | pfmetric_free(pacibspMetric); 52 | 53 | uint32_t bBytes = 0; 54 | uint32_t bMask = 0; 55 | arm64_gen_b_l(OPT_BOOL_NONE, OPT_UINT64_NONE, OPT_UINT64_NONE, &bBytes, &bMask); 56 | PFPatternMetric *bMetric = pfmetric_pattern_init(&bBytes, &bMask, sizeof(bBytes), sizeof(uint32_t)); 57 | pfmetric_run(kernelTextSection, bMetric, ^(uint64_t vmaddr, bool *stop) { 58 | uint64_t target = 0; 59 | bool isBl = false; 60 | if (arm64_dec_b_l(pfsec_read32(kernelTextSection, vmaddr), vmaddr, &target, &isBl) == 0) { 61 | if (isBl) { 62 | printf("%llx: \"bl %llx\"\n", vmaddr, target); 63 | } 64 | else { 65 | printf("%llx: \"b %llx\"\n", vmaddr, target); 66 | } 67 | } 68 | }); 69 | pfmetric_free(bMetric); 70 | 71 | uint32_t adrBytes = 0; 72 | uint32_t adrMask = 0; 73 | arm64_gen_adr_p(OPT_BOOL_NONE, OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrBytes, &adrMask); 74 | PFPatternMetric *adrMetric = pfmetric_pattern_init(&adrBytes, &adrMask, sizeof(adrBytes), sizeof(uint32_t)); 75 | pfmetric_run(kernelTextSection, adrMetric, ^(uint64_t vmaddr, bool *stop) { 76 | uint64_t target = 0; 77 | bool isAdrp = false; 78 | arm64_register reg; 79 | if (arm64_dec_adr_p(pfsec_read32(kernelTextSection, vmaddr), vmaddr, &target, ®, &isAdrp) == 0) { 80 | if (isAdrp) { 81 | printf("%llx: \"adrp x%u, %llx\"\n", vmaddr, ARM64_REG_GET_NUM(reg), target); 82 | } 83 | else { 84 | printf("%llx: \"adr x%u, %llx\"\n", vmaddr, ARM64_REG_GET_NUM(reg), target); 85 | } 86 | } 87 | }); 88 | 89 | uint32_t addBytes = 0; 90 | uint32_t addMask = 0; 91 | arm64_gen_add_imm(ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &addBytes, &addMask); 92 | PFPatternMetric *addMetric = pfmetric_pattern_init(&addBytes, &addMask, sizeof(addBytes), sizeof(uint32_t)); 93 | pfmetric_run(kernelTextSection, addMetric, ^(uint64_t vmaddr, bool *stop) { 94 | arm64_register destinationReg; 95 | arm64_register sourceReg; 96 | uint16_t imm = 0; 97 | uint32_t inst = pfsec_read32(kernelTextSection, vmaddr); 98 | if (arm64_dec_add_imm(inst, &destinationReg, &sourceReg, &imm) == 0) { 99 | printf("%llx: \"add %s%u, %s%u, 0x%x\"\n", vmaddr, arm64_reg_get_type_string(destinationReg), ARM64_REG_GET_NUM(destinationReg), arm64_reg_get_type_string(sourceReg), ARM64_REG_GET_NUM(sourceReg), imm); 100 | } 101 | }); 102 | 103 | 104 | uint32_t ldrBytes = 0; 105 | uint32_t ldrMask = 0; 106 | arm64_gen_ldr_imm(-1, LDR_STR_TYPE_ANY, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrBytes, &ldrMask); 107 | PFPatternMetric *ldrMetric = pfmetric_pattern_init(&ldrBytes, &ldrMask, sizeof(ldrBytes), sizeof(uint32_t)); 108 | pfmetric_run(kernelTextSection, ldrMetric, ^(uint64_t vmaddr, bool *stop) { 109 | arm64_register destinationReg; 110 | arm64_register sourceReg; 111 | uint64_t imm = 0; 112 | uint32_t inst = pfsec_read32(kernelTextSection, vmaddr); 113 | char type = 0; 114 | arm64_ldr_str_type instType; 115 | if (arm64_dec_ldr_imm(inst, &destinationReg, &sourceReg, &imm, &type, &instType) == 0) { 116 | if (type == 0) { 117 | printf("%llx: \"ldr %s%u, [%s%u, 0x%llx]%s\"\n", vmaddr, arm64_reg_get_type_string(destinationReg), ARM64_REG_GET_NUM(destinationReg), arm64_reg_get_type_string(sourceReg), ARM64_REG_GET_NUM(sourceReg), imm, instType == LDR_STR_TYPE_PRE_INDEX ? "!" : ""); 118 | } 119 | else { 120 | printf("%llx: \"ldr%c %s%u, [%s%u, 0x%llx]%s\"\n", vmaddr, type, arm64_reg_get_type_string(destinationReg), ARM64_REG_GET_NUM(destinationReg), arm64_reg_get_type_string(sourceReg), ARM64_REG_GET_NUM(sourceReg), imm, instType == LDR_STR_TYPE_PRE_INDEX ? "!" : ""); 121 | } 122 | } 123 | }); 124 | 125 | uint32_t strBytes = 0; 126 | uint32_t strMask = 0; 127 | arm64_gen_str_imm(-1, LDR_STR_TYPE_ANY, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &strBytes, &strMask); 128 | PFPatternMetric *strMetric = pfmetric_pattern_init(&strBytes, &strMask, sizeof(strBytes), sizeof(uint32_t)); 129 | pfmetric_run(kernelTextSection, strMetric, ^(uint64_t vmaddr, bool *stop) { 130 | arm64_register destinationReg; 131 | arm64_register sourceReg; 132 | uint64_t imm = 0; 133 | uint32_t inst = pfsec_read32(kernelTextSection, vmaddr); 134 | char type = 0; 135 | arm64_ldr_str_type instType; 136 | if (arm64_dec_str_imm(inst, &destinationReg, &sourceReg, &imm, &type, &instType) == 0) { 137 | if (type == 0) { 138 | printf("%llx: \"str %s%u, [%s%u, 0x%llx]%s\"\n", vmaddr, arm64_reg_get_type_string(destinationReg), ARM64_REG_GET_NUM(destinationReg), arm64_reg_get_type_string(sourceReg), ARM64_REG_GET_NUM(sourceReg), imm, instType == LDR_STR_TYPE_PRE_INDEX ? "!" : ""); 139 | } 140 | else { 141 | printf("%llx: \"str%c %s%u, [%s%u, 0x%llx]%s\"\n", vmaddr, type, arm64_reg_get_type_string(destinationReg), ARM64_REG_GET_NUM(destinationReg), arm64_reg_get_type_string(sourceReg), ARM64_REG_GET_NUM(sourceReg), imm, instType == LDR_STR_TYPE_PRE_INDEX ? "!" : ""); 142 | } 143 | } 144 | }); 145 | 146 | pfsec_arm64_enumerate_xrefs(kernelTextSection, ARM64_XREF_TYPE_ALL, ^(Arm64XrefType type, uint64_t source, uint64_t target, bool *stop) { 147 | if (type == ARM64_XREF_TYPE_ADRP_ADD) { 148 | printf("ADRL %llx -> %llx\n", source, target); 149 | } 150 | else if (type == ARM64_XREF_TYPE_ADRP_LDR) { 151 | printf("ADRP+LDR %llx -> %llx\n", source, target); 152 | } 153 | else if (type == ARM64_XREF_TYPE_ADRP_STR) { 154 | printf("ADRP+STR %llx -> %llx\n", source, target); 155 | } 156 | }); 157 | 158 | PFStringMetric *stringMetric = pfmetric_string_init("trust_cache_init"); 159 | pfmetric_run(kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop1) { 160 | printf("\"trust_cache_init\": 0x%llx\n", vmaddr); 161 | PFXrefMetric *refMetric = pfmetric_xref_init(vmaddr, XREF_TYPE_MASK_REFERENCE); 162 | pfmetric_run(kernelTextSection, refMetric, ^(uint64_t xrefaddr, bool *stop2) { 163 | printf("\"trust_cache_init\" xref from %llx\n", xrefaddr); 164 | printf("kernel_bootstrap_thread: %llx\n", pfsec_find_function_start(kernelTextSection, xrefaddr)); 165 | *stop1 = true; 166 | *stop2 = true; 167 | }); 168 | pfmetric_free(refMetric); 169 | }); 170 | pfmetric_free(stringMetric); 171 | 172 | t = clock() - t; 173 | double time_taken = ((double)t)/CLOCKS_PER_SEC; 174 | printf("KPF finished in %lf seconds\n", time_taken); 175 | 176 | pfsec_free(kernelTextSection); 177 | } 178 | 179 | fat_free(fat); 180 | return 0; 181 | } --------------------------------------------------------------------------------