├── .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 $@ $*.c $(OUTPUT_DIR)/lib/libchoma.a
90 | @ldid -Sexternal/ios/entitlements.plist $@
91 | else
92 | $(TESTS_OUTPUT_DIR)/%: $(TESTS_SRC_DIR)/%
93 | @mkdir -p $(dir $@)
94 | @rm -rf $@
95 | $(CC) $(CFLAGS) $(LDFLAGS) -I$(OUTPUT_DIR)/include -o $@ $*.c $(OUTPUT_DIR)/lib/libchoma.a
96 | endif
97 | endif
98 |
99 | copy-choma-headers: $(CHOMA_HEADERS)
100 | @rm -rf $(CHOMA_HEADERS_DST_DIR)
101 | @mkdir -p $(CHOMA_HEADERS_DST_DIR)
102 | @cp $^ $(CHOMA_HEADERS_DST_DIR)
103 |
104 | clean-all: clean clean-output
105 |
106 | clean: clean-test
107 | @rm -rf $(BUILD_DIR)/*
108 |
109 | clean-test:
110 | @rm -rf $(OUTPUT_DIR)/tests/*
111 |
112 | clean-output:
113 | @rm -rf $(OUTPUT_DIR)/*
114 |
115 | install: all
116 | @mkdir -p $(INSTALL_PATH)/include
117 | @rm -rf $(INSTALL_PATH)/include/choma
118 | @cp -r $(OUTPUT_DIR)/include/choma $(INSTALL_PATH)/include
119 | @rm -rf $(INSTALL_PATH)/lib/libchoma.*
120 | @cp -r $(OUTPUT_DIR)/lib $(INSTALL_PATH)
121 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ChOma
2 |
3 | ChOma is a simple library for parsing and manipulating MachO files and their CMS blobs. Written for exploitation of [CVE-2023-41991](https://support.apple.com/en-gb/HT213926), a vulnerability in the CoreTrust kernel extension, and for use in [TrollStore](https://github.com/opa334/TrollStore) and [XPF](https://github.com/opa334/XPF) (which is used by [Dopamine](https://github.com/opa334/Dopamine) as the kernel patchfinder)
4 |
5 | ## Compilation
6 |
7 | ### Building for macOS
8 | `make`
9 |
10 | ### Building for iOS
11 | `make TARGET=ios`
12 |
13 | ### Additional Options
14 | `DEBUG=1`: Build with address sanitizer
15 |
16 | `DISABLE_SIGNING=1`: Disable all features that depend on OpenSSL
17 |
18 | `DISABLE_TESTS=1`: Don't build tests
19 |
20 | `INSTALL_PATH=/some/path`: Path where ChOma gets installed to when using `make install`
21 |
22 | ## Usage
23 |
24 | To use the library, you can compile with `make all`. This will produce `libchoma.a` and `libchoma.dylib`, which can be linked to your own project, as well as multiple test binaries:
25 | * `choma_cli` - a binary to demonstrate the features of ChOma
26 | * `ct_bypass` - a binary that use CVE-2023-41991 to apply a CoreTrust bypass to an iOS binary
27 | * `dyld_patch` - a binary that will patch an iOS 15+ dyld binary to ignore AMFI flags (used by Dopamine)
28 | * `fat_create` - a binary that will create a fat MachO out of a selection of MachOs, ignoring all CPU types and subtypes
29 | * `kpf` - a binary that was used to begin writing a kernel patchfinder used in Dopamine 2.0, superseded by [XPF](https://github.com/opa334/XPF)
30 |
31 | ## CoreTrust bypass
32 |
33 | ChOma was written primarily for the purpose of exploiting CVE-2023-41991, which allows a binary to bypass CoreTrust during code-signing and appear as an App Store-signed binary. As a result, binaries can be permanently signed on device and have arbitrary entitlements, apart from a few restricted ones that are only allowed to be used by trustcached binaries.
34 |
35 | The vulnerability is caused by CoreTrust incorrectly handling multiple signers in a CMS signature blob. The signature blob will have two signers: the App Store certificate chain (which has a valid signature for a different code signature) and a custom certificate chain (which has a valid signature for our code signature). Due to it incorrectly validating both signers, CoreTrust will return the CD hashes from our signer but set the policy flags using the App Store signer.
36 |
37 | The exploit is implemented in `ct_bypass`, and works by:
38 | 1. Updating the load commands by calculating the new sizes of the __LINKEDIT segment and the code signature.
39 | 2. Updating the page hashes in the SHA256 CodeDirectory to match the new load command data.
40 | 3. Replacing the SHA1 CodeDirectory with one from a valid App Store binary.
41 | 4. Creating a new signature blob that has two signers, the App Store and our custom certificate chain.
42 | 5. Updating the necessary fields in the signature blob to match the CD hashes.
43 | 6. Signing the signature blob for the custom identity (the App Store identity will already have an intact signature).
44 | 7. Inserting the new code signature into the binary.
45 |
46 | ## Terminology
47 |
48 | Inside ChOma, there are a few terms that are used to describe various parts of the MachO file. These are:
49 | - **Fat** - represents a Fat MachO file (a MachO file that contains multiple slices, which are each a MachO file for a different architecture).
50 | - **MachO** - represents either a single-architecture MachO file, or a slice of a Fat MachO file.
51 |
52 | ## Underlying mechanisms
53 | ChOma uses the `MemoryBuffer` structure to provide a unified way to read, write, shrink and expand data buffers, that works across both files and memory. Each `MemoryBuffer` has a `context` field that determines whether the functions interpret it as a `BufferedStream` object (for regular memory buffers) or as a `FileStream` object (for files).
54 |
55 | Each `MemoryBuffer` object contains function pointers for reading, writing, retrieving the size, expanding, shrinking and then soft or hard cloning. You can inspect these inside [`src/MemoryBuffer.h`](src/MemoryStream.h), and can see how they are used by looking at how we manipulate MachO files across the library.
56 |
--------------------------------------------------------------------------------
/cert.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opa334/ChOma/7e1313c20a346b65864d44c973abafb86bdc4dd5/cert.p12
--------------------------------------------------------------------------------
/external/ios/entitlements.plist:
--------------------------------------------------------------------------------
1 |
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 | }
--------------------------------------------------------------------------------