Filza 27.2 worse_query: public and developer builds

This commit is contained in:
admin committed 2026-11-11 03:40:37 +08:00
commit 91a6adf723
267 files changed
+42015

No files matched your search

+1
View File
@@ -0,0 +1 @@
../src
+42
View File
@@ -0,0 +1,42 @@
#include "Base64.h"
// https://stackoverflow.com/a/6782480
static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'};
static int mod_table[] = {0, 2, 1};
char *base64_encode(const unsigned char *data,
size_t input_length,
size_t *output_length) {
*output_length = 4 * ((input_length + 2) / 3);
char *encoded_data = malloc(*output_length);
if (encoded_data == NULL) return NULL;
for (int i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
}
for (int i = 0; i < mod_table[input_length % 3]; i++)
encoded_data[*output_length - 1 - i] = '=';
return encoded_data;
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef BASE64_H
#define BASE64_H
#include <stdint.h>
#include <stdlib.h>
char *base64_encode(const unsigned char *data,
size_t input_length,
size_t *output_length);
#endif // BASE64_H
+194
View File
@@ -0,0 +1,194 @@
#include "BufferedStream.h"
#include "MemoryStream.h"
#include <stdlib.h>
static int buffered_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd);
static int _buffered_stream_make_own_data(MemoryStream *stream)
{
BufferedStreamContext *context = stream->context;
if ((stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) == 0) {
void *newBuffer = malloc(context->subBufferSize);
memcpy(newBuffer, context->buffer + context->subBufferStart, context->subBufferSize);
context->buffer = newBuffer;
stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA;
}
return 0;
}
static int buffered_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf)
{
BufferedStreamContext *context = stream->context;
if ((offset + size) > context->subBufferSize) {
printf("Error: cannot read %zx bytes at %llx, maximum is %zx.\n", size, offset, context->subBufferSize);
return -1;
}
memcpy(outBuf, context->buffer + context->subBufferStart + offset, size);
return size;
}
static int buffered_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf)
{
BufferedStreamContext *context = stream->context;
bool expandAllowed = (stream->flags & MEMORY_STREAM_FLAG_AUTO_EXPAND);
bool needsExpand = (offset + size) > context->subBufferSize;
if (needsExpand && !expandAllowed) {
printf("Error: cannot write %zx bytes at %llx, maximum is %zx.\n", size, offset, context->subBufferSize);
return -1;
}
if ((stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) == 0) {
int r = _buffered_stream_make_own_data(stream);
if (r != 0) return r;
}
if (needsExpand) {
buffered_stream_expand(stream, 0, (offset + size) - context->subBufferSize);
}
memcpy(context->buffer + context->subBufferStart + offset, inBuf, size);
return size;
}
static int buffered_stream_get_size(MemoryStream *stream, size_t *sizeOut)
{
BufferedStreamContext *context = stream->context;
*sizeOut = context->subBufferSize;
return 0;
}
static uint8_t *buffered_stream_get_raw_pointer(MemoryStream *stream)
{
BufferedStreamContext *context = stream->context;
return &context->buffer[context->subBufferStart];
}
static int buffered_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd)
{
BufferedStreamContext *context = stream->context;
// TODO: bound checks
context->subBufferStart += trimAtStart;
context->subBufferSize -= (trimAtEnd + trimAtStart);
return 0;
}
static int buffered_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd)
{
BufferedStreamContext *context = stream->context;
size_t newSize = context->subBufferSize + expandAtStart + expandAtEnd;
uint8_t *newBuffer = malloc(newSize);
memset(newBuffer, 0, newSize);
memcpy(&newBuffer[expandAtStart], &context->buffer[context->subBufferStart], context->subBufferSize);
if (stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) {
free(context->buffer);
}
context->buffer = newBuffer;
context->bufferSize = newSize;
context->subBufferStart = 0;
context->subBufferSize = newSize;
stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA;
return 0;
}
static MemoryStream *buffered_stream_softclone(MemoryStream *stream)
{
MemoryStream* clone = malloc(sizeof(MemoryStream));
if (!clone) return NULL;
memset(clone, 0, sizeof(MemoryStream));
BufferedStreamContext *context = stream->context;
BufferedStreamContext *contextCopy = malloc(sizeof(BufferedStreamContext));
contextCopy->buffer = context->buffer;
contextCopy->bufferSize = context->bufferSize;
contextCopy->subBufferStart = context->subBufferStart;
contextCopy->subBufferSize = context->subBufferSize;
clone->flags = stream->flags & ~(MEMORY_STREAM_FLAG_OWNS_DATA);
clone->context = contextCopy;
return clone;
}
static MemoryStream *buffered_stream_hardclone(MemoryStream *stream)
{
MemoryStream* clone = buffered_stream_softclone(stream);
if (clone) {
_buffered_stream_make_own_data(clone);
}
return clone;
}
static void buffered_stream_free(MemoryStream *stream)
{
BufferedStreamContext *context = stream->context;
if (context->buffer) {
if (stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) {
free(context->buffer);
}
}
free(context);
}
static int _buffered_stream_init(MemoryStream *stream)
{
stream->read = buffered_stream_read;
stream->write = buffered_stream_write;
stream->getSize = buffered_stream_get_size;
stream->getRawPtr = buffered_stream_get_raw_pointer;
stream->trim = buffered_stream_trim;
stream->expand = buffered_stream_expand;
stream->softclone = buffered_stream_softclone;
stream->hardclone = buffered_stream_hardclone;
stream->free = buffered_stream_free;
stream->flags = MEMORY_STREAM_FLAG_MUTABLE;
return 0;
}
MemoryStream *buffered_stream_init_from_buffer_nocopy(void *buffer, size_t bufferSize, uint32_t flags)
{
MemoryStream *stream = malloc(sizeof(MemoryStream));
if (!stream) return NULL;
memset(stream, 0, sizeof(MemoryStream));
BufferedStreamContext *context = malloc(sizeof(BufferedStreamContext));
context->buffer = buffer;
context->bufferSize = bufferSize;
context->subBufferStart = 0;
context->subBufferSize = bufferSize;
stream->context = context;
if (_buffered_stream_init(stream) != 0) goto fail;
if (flags & BUFFERED_STREAM_FLAG_AUTO_EXPAND) {
stream->flags |= MEMORY_STREAM_FLAG_AUTO_EXPAND;
}
return stream;
fail:
buffered_stream_free(stream);
return NULL;
}
MemoryStream *buffered_stream_init_from_buffer(void *buffer, size_t bufferSize, uint32_t flags)
{
void *copy = malloc(bufferSize);
if (!copy) return NULL;
memcpy(copy, buffer, bufferSize);
MemoryStream *stream = buffered_stream_init_from_buffer_nocopy(copy, bufferSize, flags);
if (stream) {
stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA;
}
return stream;
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef BUFFERED_STREAM_H
#define BUFFERED_STREAM_H
#include "MemoryStream.h"
#include <stdbool.h>
#define BUFFERED_STREAM_FLAG_AUTO_EXPAND (1 << 0)
typedef struct BufferedStreamContext {
uint8_t *buffer;
size_t bufferSize;
uint32_t subBufferStart;
size_t subBufferSize;
} BufferedStreamContext;
MemoryStream *buffered_stream_init_from_buffer_nocopy(void *buffer, size_t bufferSize, uint32_t flags);
MemoryStream *buffered_stream_init_from_buffer(void *buffer, size_t bufferSize, uint32_t flags);
#endif // BUFFERED_STREAM_H
+523
View File
@@ -0,0 +1,523 @@
#include "CSBlob.h"
#include "CodeDirectory.h"
#include "MachO.h"
#include "MachOByteOrder.h"
#include "BufferedStream.h"
#include "MemoryStream.h"
#include "Util.h"
#include <mach-o/loader.h>
#include <stddef.h>
#include <stdint.h>
const char *cs_blob_magic_to_string(uint32_t magic)
{
switch (magic) {
case CSMAGIC_REQUIREMENT:
return "Requirement blob";
case CSMAGIC_REQUIREMENTS:
return "Requirements blob";
case CSMAGIC_CODEDIRECTORY:
return "Code directory blob";
case CSMAGIC_EMBEDDED_SIGNATURE:
return "Embedded signature blob";
case CSMAGIC_EMBEDDED_SIGNATURE_OLD:
return "Embedded signature blob (old)";
case CSMAGIC_EMBEDDED_ENTITLEMENTS:
return "Entitlements blob";
case CSMAGIC_EMBEDDED_DER_ENTITLEMENTS:
return "DER entitlements blob";
case CSMAGIC_DETACHED_SIGNATURE:
return "Detached signature blob";
case CSMAGIC_BLOBWRAPPER:
return "Signature blob";
case CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT:
return "Launchd contraint blob";
default:
return "Unknown blob type";
}
}
const char *cs_slot_type_to_string(uint32_t slotType)
{
if (slotType & CSSLOT_ALTERNATE_CODEDIRECTORIES) {
int num = slotType & 0xfff;
switch (num) {
case 0:
return "Alternate code directory slot (1)";
case 1:
return "Alternate code directory slot (2)";
case 2:
return "Alternate code directory slot (3)";
case 3:
return "Alternate code directory slot (4)";
case 4:
return "Alternate code directory slot (5)";
default:
return "Alternate code directory slot (invalid)";
}
}
switch (slotType) {
case CSSLOT_CODEDIRECTORY:
return "Code directory slot";
case CSSLOT_INFOSLOT:
return "Info slot";
case CSSLOT_REQUIREMENTS:
return "Requirements slot";
case CSSLOT_RESOURCEDIR:
return "Resource Dir slot";
case CSSLOT_APPLICATION:
return "Application slot";
case CSSLOT_ENTITLEMENTS:
return "Entitlements slot";
case CSSLOT_DER_ENTITLEMENTS:
return "DER entitlements slot";
case CSSLOT_LAUNCH_CONSTRAINT_SELF:
return "Launch constraint slot (self)";
case CSSLOT_LAUNCH_CONSTRAINT_PARENT:
return "Launch constraint slot (parent)";
case CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE:
return "Launch constraint slot (responsible)";
case CSSLOT_LIBRARY_CONSTRAINT:
return "Library constraint slot";
case CSSLOT_SIGNATURESLOT:
return "Signature slot";
case CSSLOT_IDENTIFICATIONSLOT:
return "Identification slot";
case CSSLOT_TICKETSLOT:
return "Ticket slot";
default:
return "Unknown blob type";
}
}
int macho_parse_signature_blob_to_der_encoded_data(MachO *macho, uint32_t signatureBlobOffset, uint32_t signatureBlobLength, void *outputDER)
{
return macho_read_at_offset(macho, signatureBlobOffset + 8, signatureBlobLength - 8, outputDER);
}
int macho_find_code_signature_bounds(MachO *macho, uint32_t *offsetOut, uint32_t *sizeOut)
{
__block int r = -1;
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (loadCommand.cmd == LC_CODE_SIGNATURE) {
struct linkedit_data_command *csLoadCommand = ((struct linkedit_data_command *)cmd);
LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(csLoadCommand, LITTLE_TO_HOST_APPLIER);
if (offsetOut) *offsetOut = csLoadCommand->dataoff;
if (sizeOut) *sizeOut = csLoadCommand->datasize;
*stop = true;
r = 0;
}
});
return r;
}
CS_SuperBlob *macho_read_code_signature(MachO *macho)
{
uint32_t offset = 0, size = 0;
if (macho_find_code_signature_bounds(macho, &offset, &size) == 0) {
CS_SuperBlob *dataOut = malloc(size);
if (macho_read_at_offset(macho, offset, size, dataOut) == 0) {
return dataOut;
}
else {
free(dataOut);
}
}
return NULL;
}
int macho_replace_code_signature(MachO *macho, CS_SuperBlob *superblob)
{
uint32_t csSegmentOffset = 0, csSegmentSize = 0;
macho_find_code_signature_bounds(macho, &csSegmentOffset, &csSegmentSize);
uint32_t sizeOfCodeSignature = 0;
memory_stream_read(macho->stream, csSegmentOffset + offsetof(CS_SuperBlob, length), sizeof(sizeOfCodeSignature), &sizeOfCodeSignature);
sizeOfCodeSignature = BIG_TO_HOST(sizeOfCodeSignature);
uint64_t newCodeSignatureSize = BIG_TO_HOST(superblob->length);
// See how much space we have to write the new code signature
uint64_t entireFileSize = memory_stream_get_size(macho->stream);
uint64_t freeSpace = entireFileSize - csSegmentOffset;
uint64_t paddingSize = freeSpace - sizeOfCodeSignature;
if (newCodeSignatureSize >= freeSpace) {
macho_write_at_offset(macho, csSegmentOffset, newCodeSignatureSize, superblob);
uint8_t padding[paddingSize];
memset(padding, 0, paddingSize);
macho_write_at_offset(macho, csSegmentOffset + newCodeSignatureSize, paddingSize, padding);
} else if (newCodeSignatureSize < freeSpace) {
memory_stream_trim(macho_get_stream(macho), 0, entireFileSize - csSegmentOffset);
macho_write_at_offset(macho, csSegmentOffset, newCodeSignatureSize, superblob);
uint8_t padding[paddingSize];
memset(padding, 0, paddingSize);
macho_write_at_offset(macho, csSegmentOffset + newCodeSignatureSize, paddingSize, padding);
}
return 0;
}
int macho_extract_cs_to_file(MachO *macho, CS_SuperBlob *superblob)
{
FILE *csDataFile = fopen("Code_Signature-Data", "wb+");
fwrite(superblob, BIG_TO_HOST(superblob->length), 1, csDataFile);
fclose(csDataFile);
return 0;
}
CS_DecodedBlob *csd_blob_init(uint32_t type, CS_GenericBlob *blobData)
{
CS_DecodedBlob *blob = malloc(sizeof(CS_DecodedBlob));
if (!blob) return NULL;
memset(blob, 0, sizeof(CS_DecodedBlob));
blob->type = type;
blob->stream = buffered_stream_init_from_buffer(blobData, BIG_TO_HOST(blobData->length), BUFFERED_STREAM_FLAG_AUTO_EXPAND);
return blob;
}
int csd_blob_read(CS_DecodedBlob *blob, uint64_t offset, size_t size, void *outBuf)
{
return memory_stream_read(blob->stream, offset, size, outBuf);
}
static void _csd_blob_fix_length(CS_DecodedBlob *blob)
{
uint32_t curSize = HOST_TO_BIG((uint32_t)memory_stream_get_size(blob->stream));
memory_stream_write(blob->stream, offsetof(CS_GenericBlob, length), sizeof(curSize), &curSize);
}
int csd_blob_write(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf)
{
int r = memory_stream_write(blob->stream, offset, size, inBuf);
if (r == 0) _csd_blob_fix_length(blob);
return r;
}
int csd_blob_insert(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf)
{
int r = memory_stream_insert(blob->stream, offset, size, inBuf);
if (r == 0) _csd_blob_fix_length(blob);
return r;
}
int csd_blob_delete(CS_DecodedBlob *blob, uint64_t offset, size_t size)
{
int r = memory_stream_delete(blob->stream, offset, size);
if (r == 0) _csd_blob_fix_length(blob);
return r;
}
int csd_blob_read_string(CS_DecodedBlob *blob, uint64_t offset, char **outString)
{
return memory_stream_read_string(blob->stream, offset, outString);
}
int csd_blob_write_string(CS_DecodedBlob *blob, uint64_t offset, const char *string)
{
int r = memory_stream_write_string(blob->stream, offset, string);
if (r == 0) _csd_blob_fix_length(blob);
return r;
}
size_t csd_blob_get_size(CS_DecodedBlob *blob)
{
return memory_stream_get_size(blob->stream);
}
uint32_t csd_blob_get_type(CS_DecodedBlob *blob)
{
return blob->type;
}
void csd_blob_set_type(CS_DecodedBlob *blob, uint32_t type)
{
blob->type = type;
}
void csd_blob_free(CS_DecodedBlob *blob)
{
if (blob->stream) {
memory_stream_free(blob->stream);
}
free(blob);
}
CS_DecodedSuperBlob *csd_superblob_init(void)
{
CS_DecodedSuperBlob *decodedSuperblob = malloc(sizeof(CS_DecodedSuperBlob));
if (!decodedSuperblob) return NULL;
memset(decodedSuperblob, 0, sizeof(CS_DecodedSuperBlob));
return decodedSuperblob;
}
CS_DecodedSuperBlob *csd_superblob_decode(CS_SuperBlob *superblob)
{
uint32_t magic = BIG_TO_HOST(superblob->magic);
if (magic != CSMAGIC_EMBEDDED_SIGNATURE && magic != CSMAGIC_DETACHED_SIGNATURE) return NULL;
CS_DecodedSuperBlob *decodedSuperblob = csd_superblob_init();
if (!decodedSuperblob) return NULL;
CS_DecodedBlob **nextBlob = &decodedSuperblob->firstBlob;
decodedSuperblob->magic = magic;
for (uint32_t i = 0; i < BIG_TO_HOST(superblob->count); i++) {
CS_BlobIndex curIndex = superblob->index[i];
BLOB_INDEX_APPLY_BYTE_ORDER(&curIndex, BIG_TO_HOST_APPLIER);
//printf("decoding %u (type: %x, offset: 0x%x)\n", i, curIndex.type, curIndex.offset);
CS_GenericBlob *curBlobData = (CS_GenericBlob *)(((uint8_t*)superblob) + curIndex.offset);
*nextBlob = csd_blob_init(curIndex.type, curBlobData);
nextBlob = &(*nextBlob)->next;
}
return decodedSuperblob;
}
CS_SuperBlob *csd_superblob_encode(CS_DecodedSuperBlob *decodedSuperblob)
{
uint32_t blobCount = 0, blobSize = 0;
// Determine amount and size of contained blobs
CS_DecodedBlob *nextBlob = decodedSuperblob->firstBlob;
while (nextBlob) {
blobCount++;
blobSize += csd_blob_get_size(nextBlob);
nextBlob = nextBlob->next;
}
uint32_t superblobLength = sizeof(CS_SuperBlob) + (sizeof(CS_BlobIndex) * blobCount) + blobSize;
CS_SuperBlob *superblob = malloc(superblobLength);
// Populate superblob fields
superblob->count = blobCount;
superblob->length = superblobLength;
superblob->magic = decodedSuperblob->magic;
SUPERBLOB_APPLY_BYTE_ORDER(superblob, HOST_TO_BIG_APPLIER)
// Populate indexes and write actual backing data
uint32_t idx = 0;
uint32_t dataStartOffset = sizeof(CS_SuperBlob) + (sizeof(CS_BlobIndex) * blobCount);
uint8_t *superblobData = ((uint8_t*)superblob) + dataStartOffset;
uint8_t *superblobDataCur = superblobData;
nextBlob = decodedSuperblob->firstBlob;
while (nextBlob) {
// Populate blob data
uint32_t curSize = csd_blob_get_size(nextBlob);
csd_blob_read(nextBlob, 0, curSize, superblobDataCur);
// Populate index
CS_BlobIndex *curIndex = &superblob->index[idx];
curIndex->offset = dataStartOffset + (superblobDataCur - superblobData);
curIndex->type = nextBlob->type;
BLOB_INDEX_APPLY_BYTE_ORDER(curIndex, HOST_TO_BIG_APPLIER);
superblobDataCur += curSize;
idx++;
nextBlob = nextBlob->next;
}
return superblob;
}
CS_DecodedBlob *csd_superblob_find_blob(CS_DecodedSuperBlob *superblob, uint32_t type, uint32_t *indexOut)
{
CS_DecodedBlob *blob = superblob->firstBlob;
uint32_t i = 0;
while (blob) {
if (blob->type == type) {
if (indexOut) *indexOut = i;
return blob;
}
blob = blob->next;
i++;
}
return NULL;
}
CS_DecodedBlob *csd_superblob_find_blob_by_magic(CS_DecodedSuperBlob *superblob, uint32_t magic, uint32_t *indexOut)
{
CS_DecodedBlob *blob = superblob->firstBlob;
uint32_t i = 0;
while (blob) {
uint32_t blobMagic = 0;
memory_stream_read(blob->stream, 0, sizeof(uint32_t), &blobMagic);
if (BIG_TO_HOST(blobMagic) == magic) {
if (indexOut) *indexOut = i;
return blob;
}
blob = blob->next;
i++;
}
return NULL;
}
int csd_superblob_insert_blob_after_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, CS_DecodedBlob *afterBlob)
{
blobToInsert->next = afterBlob->next;
afterBlob->next = blobToInsert;
return 0;
}
int csd_superblob_insert_blob_at_index(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, uint32_t atIndex)
{
if (atIndex == 0) {
blobToInsert->next = superblob->firstBlob;
superblob->firstBlob = blobToInsert;
return 0;
}
else {
uint32_t i = 0;
CS_DecodedBlob *blobAtIndex = superblob->firstBlob;
while (blobAtIndex && i < atIndex) {
blobAtIndex = blobAtIndex->next;
i++;
}
if (blobAtIndex) {
return csd_superblob_insert_blob_after_blob(superblob, blobToInsert, blobAtIndex);
}
return -1;
}
}
int csd_superblob_append_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToAppend)
{
if (!superblob->firstBlob) {
superblob->firstBlob = blobToAppend;
return 0;
}
CS_DecodedBlob *lastBlob = superblob->firstBlob;
while (lastBlob->next) {
lastBlob = lastBlob->next;
}
return csd_superblob_insert_blob_after_blob(superblob, blobToAppend, lastBlob);
}
int csd_superblob_replace_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *newBlob)
{
uint32_t index = 0;
CS_DecodedBlob *blob = csd_superblob_find_blob(superblob, newBlob->type, &index);
if (blob) {
csd_superblob_remove_blob_at_index(superblob, index);
csd_superblob_insert_blob_at_index(superblob, newBlob, index);
} else {
csd_superblob_append_blob(superblob, newBlob);
}
return 0;
}
int csd_superblob_remove_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToRemove)
{
CS_DecodedBlob *blob = superblob->firstBlob;
if (blob == blobToRemove) {
superblob->firstBlob = blobToRemove->next;
return 0;
}
while (blob) {
if (blob->next == blobToRemove) {
blob->next = blobToRemove->next;
return 0;
}
blob = blob->next;
}
return -1;
}
int csd_superblob_remove_blob_at_index(CS_DecodedSuperBlob *superblob, uint32_t atIndex)
{
if (atIndex == 0) {
return csd_superblob_remove_blob(superblob, superblob->firstBlob);
}
else {
uint32_t i = 0;
CS_DecodedBlob *blobAtIndex = superblob->firstBlob;
while (blobAtIndex && i < atIndex) {
blobAtIndex = blobAtIndex->next;
i++;
}
if (blobAtIndex) {
int r = csd_superblob_remove_blob(superblob, blobAtIndex);
if (r == 0) csd_blob_free(blobAtIndex);
return r;
}
return -1;
}
return 0;
}
CS_DecodedBlob *csd_superblob_find_best_code_directory(CS_DecodedSuperBlob *decodedSuperblob)
{
CS_DecodedBlob *bestCDBlob = NULL;
unsigned bestCDBlobRank = 0;
CS_DecodedBlob *blob = decodedSuperblob->firstBlob;
while (blob) {
if (blob->type == CSSLOT_CODEDIRECTORY || ((CSSLOT_ALTERNATE_CODEDIRECTORIES <= blob->type && blob->type < CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT))) {
unsigned CDBlobRank = csd_code_directory_calculate_rank(blob);
if (CDBlobRank > bestCDBlobRank) {
bestCDBlob = blob;
bestCDBlobRank = CDBlobRank;
}
}
blob = blob->next;
}
return bestCDBlob;
}
int csd_superblob_calculate_best_cdhash(CS_DecodedSuperBlob *decodedSuperblob, void *cdhashOut, int *cdhashTypeOut)
{
if (!cdhashOut) return -1;
CS_DecodedBlob *bestCDBlob = csd_superblob_find_best_code_directory(decodedSuperblob);
if (cdhashTypeOut) *cdhashTypeOut = csd_code_directory_get_hash_type(bestCDBlob);
return csd_code_directory_calculate_hash(bestCDBlob, cdhashOut);
}
int csd_superblob_print_content(CS_DecodedSuperBlob *decodedSuperblob, MachO *macho, bool printAllSlots, bool verifySlots)
{
CS_DecodedBlob *currentBlob = decodedSuperblob->firstBlob;
int count = 0;
uint32_t offset = 0;
while (currentBlob) {
uint32_t blobType = currentBlob->type;
printf("Slot %d: %s (offset 0x%x, type: 0x%x).\n", count++, cs_slot_type_to_string(blobType), offset, blobType);
if (blobType == CSSLOT_CODEDIRECTORY || blobType == CSSLOT_ALTERNATE_CODEDIRECTORIES) {
csd_code_directory_print_content(currentBlob, macho, printAllSlots, verifySlots);
}
else if (blobType == CSSLOT_SIGNATURESLOT) {
CS_GenericBlob *cms_blob = malloc(sizeof(CS_GenericBlob));
memset(cms_blob, 0, sizeof(CS_GenericBlob));
memory_stream_read(currentBlob->stream, 0, sizeof(CS_GenericBlob), cms_blob);
GENERIC_BLOB_APPLY_BYTE_ORDER(cms_blob, BIG_TO_HOST_APPLIER);
printf("This is the %s, magic %#x.\n", cs_blob_magic_to_string(cms_blob->magic), cms_blob->magic);
}
else {
CS_GenericBlob *generic_blob = malloc(sizeof(CS_GenericBlob));
memset(generic_blob, 0, sizeof(CS_GenericBlob));
memory_stream_read(currentBlob->stream, 0, sizeof(CS_GenericBlob), generic_blob);
GENERIC_BLOB_APPLY_BYTE_ORDER(generic_blob, BIG_TO_HOST_APPLIER);
printf("This is the %s, magic %#x.\n", cs_blob_magic_to_string(generic_blob->magic), generic_blob->magic);
}
offset += csd_blob_get_size(currentBlob);
currentBlob = currentBlob->next;
}
return 0;
}
void csd_superblob_free(CS_DecodedSuperBlob *decodedSuperblob)
{
CS_DecodedBlob *nextBlob = decodedSuperblob->firstBlob;
while (nextBlob) {
CS_DecodedBlob *prevBlob = nextBlob;
nextBlob = nextBlob->next;
csd_blob_free(prevBlob);
}
free(decodedSuperblob);
}
+120
View File
@@ -0,0 +1,120 @@
#ifndef CS_BLOB_H
#define CS_BLOB_H
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include "Fat.h"
#include "MachO.h"
#include "MemoryStream.h"
// Blob index
typedef struct __BlobIndex {
uint32_t type;
uint32_t offset;
} CS_BlobIndex;
// CMS superblob
typedef struct __SuperBlob {
uint32_t magic;
uint32_t length;
uint32_t count;
CS_BlobIndex index[];
} CS_SuperBlob;
typedef struct __GenericBlob {
uint32_t magic; /* magic number */
uint32_t length; /* total length of blob */
char data[];
} CS_GenericBlob;
// CMS blob magic types
typedef enum {
CSMAGIC_REQUIREMENT = 0xfade0c00,
CSMAGIC_REQUIREMENTS = 0xfade0c01,
CSMAGIC_CODEDIRECTORY = 0xfade0c02,
CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0,
CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02,
CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171,
CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172,
CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1,
CSMAGIC_BLOBWRAPPER = 0xfade0b01,
CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT = 0xfade8181,
} CS_BlobMagic;
typedef enum {
CSSLOT_CODEDIRECTORY = 0,
CSSLOT_INFOSLOT = 1,
CSSLOT_REQUIREMENTS = 2,
CSSLOT_RESOURCEDIR = 3,
CSSLOT_APPLICATION = 4,
CSSLOT_ENTITLEMENTS = 5,
CSSLOT_DER_ENTITLEMENTS = 7,
CSSLOT_LAUNCH_CONSTRAINT_SELF = 8,
CSSLOT_LAUNCH_CONSTRAINT_PARENT = 9,
CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE = 10,
CSSLOT_LIBRARY_CONSTRAINT = 11,
CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */
CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */
CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */
CSSLOT_SIGNATURESLOT = 0x10000,
CSSLOT_IDENTIFICATIONSLOT = 0x10001,
CSSLOT_TICKETSLOT = 0x10002,
} CS_SlotType;
typedef struct s_CS_DecodedBlob {
struct s_CS_DecodedBlob *next;
uint32_t type;
MemoryStream *stream;
} CS_DecodedBlob;
typedef struct s_CS_DecodedSuperBlob {
uint32_t magic;
struct s_CS_DecodedBlob *firstBlob;
} CS_DecodedSuperBlob;
// Convert blob magic to readable blob type string
const char *cs_blob_magic_to_string(uint32_t magic);
const char *cs_slot_type_to_string(uint32_t slotType);
// Extract Code Signature to file
int macho_extract_cs_to_file(MachO *macho, CS_SuperBlob *superblob);
int macho_find_code_signature_bounds(MachO *macho, uint32_t *offsetOut, uint32_t *sizeOut);
CS_SuperBlob *macho_read_code_signature(MachO *macho);
int macho_replace_code_signature(MachO *macho, CS_SuperBlob *superblob);
CS_DecodedBlob *csd_blob_init(uint32_t type, CS_GenericBlob *blobData);
int csd_blob_read(CS_DecodedBlob *blob, uint64_t offset, size_t size, void *outBuf);
int csd_blob_write(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf);
int csd_blob_insert(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf);
int csd_blob_delete(CS_DecodedBlob *blob, uint64_t offset, size_t size);
int csd_blob_read_string(CS_DecodedBlob *blob, uint64_t offset, char **outString);
int csd_blob_write_string(CS_DecodedBlob *blob, uint64_t offset, const char *string);
size_t csd_blob_get_size(CS_DecodedBlob *blob);
uint32_t csd_blob_get_type(CS_DecodedBlob *blob);
void csd_blob_set_type(CS_DecodedBlob *blob, uint32_t type);
void csd_blob_free(CS_DecodedBlob *blob);
CS_DecodedSuperBlob *csd_superblob_init(void);
CS_DecodedSuperBlob *csd_superblob_decode(CS_SuperBlob *superblob);
CS_SuperBlob *csd_superblob_encode(CS_DecodedSuperBlob *decodedSuperblob);
CS_DecodedBlob *csd_superblob_find_blob(CS_DecodedSuperBlob *superblob, uint32_t type, uint32_t *indexOut);
CS_DecodedBlob *csd_superblob_find_blob_by_magic(CS_DecodedSuperBlob *superblob, uint32_t magic, uint32_t *indexOut);
int csd_superblob_insert_blob_after_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, CS_DecodedBlob *afterBlob);
int csd_superblob_insert_blob_at_index(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, uint32_t atIndex);
int csd_superblob_append_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToAppend);
int csd_superblob_replace_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *newBlob);
int csd_superblob_remove_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToRemove); // <- Important: When calling this, caller is responsible for freeing blobToRemove
int csd_superblob_remove_blob_at_index(CS_DecodedSuperBlob *superblob, uint32_t atIndex);
int csd_superblob_calculate_best_cdhash(CS_DecodedSuperBlob *decodedSuperblob, void *cdhashOut, int *cdhashType);
int csd_superblob_print_content(CS_DecodedSuperBlob *decodedSuperblob, MachO *macho, bool printAllSlots, bool verifySlots);
void csd_superblob_free(CS_DecodedSuperBlob *decodedSuperblob);
#endif // CS_BLOB_H
+195
View File
@@ -0,0 +1,195 @@
/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
*
* Copyright (c) 2003-2010 Apple Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#ifndef _CACHE_PATCHING_H_
#define _CACHE_PATCHING_H_
#include <stdint.h>
//
// MARK: --- V1 patching. This is for old caches, before Large/Split caches ---
//
struct dyld_cache_patch_info_v1
{
uint64_t patchTableArrayAddr; // (unslid) address of array for dyld_cache_image_patches for each image
uint64_t patchTableArrayCount; // count of patch table entries
uint64_t patchExportArrayAddr; // (unslid) address of array for patch exports for each image
uint64_t patchExportArrayCount; // count of patch exports entries
uint64_t patchLocationArrayAddr; // (unslid) address of array for patch locations for each patch
uint64_t patchLocationArrayCount;// count of patch location entries
uint64_t patchExportNamesAddr; // blob of strings of export names for patches
uint64_t patchExportNamesSize; // size of string blob of export names for patches
};
struct dyld_cache_image_patches_v1
{
uint32_t patchExportsStartIndex;
uint32_t patchExportsCount;
};
struct dyld_cache_patchable_export_v1
{
uint32_t cacheOffsetOfImpl;
uint32_t patchLocationsStartIndex;
uint32_t patchLocationsCount;
uint32_t exportNameOffset;
};
struct dyld_cache_patchable_location_v1
{
uint32_t cacheOffset;
uint64_t high7 : 7,
addend : 5, // 0..31
authenticated : 1,
usesAddressDiversity : 1,
key : 2,
discriminator : 16;
};
//
// MARK: --- V2 patching. This is for Large/Split caches and newer ---
//
struct dyld_cache_patch_info_v2
{
uint32_t patchTableVersion; // == 2
uint32_t patchLocationVersion; // == 0 for now
uint64_t patchTableArrayAddr; // (unslid) address of array for dyld_cache_image_patches_v2 for each image
uint64_t patchTableArrayCount; // count of patch table entries
uint64_t patchImageExportsArrayAddr; // (unslid) address of array for dyld_cache_image_export_v2 for each image
uint64_t patchImageExportsArrayCount; // count of patch table entries
uint64_t patchClientsArrayAddr; // (unslid) address of array for dyld_cache_image_clients_v2 for each image
uint64_t patchClientsArrayCount; // count of patch clients entries
uint64_t patchClientExportsArrayAddr; // (unslid) address of array for patch exports for each client image
uint64_t patchClientExportsArrayCount; // count of patch exports entries
uint64_t patchLocationArrayAddr; // (unslid) address of array for patch locations for each patch
uint64_t patchLocationArrayCount; // count of patch location entries
uint64_t patchExportNamesAddr; // blob of strings of export names for patches
uint64_t patchExportNamesSize; // size of string blob of export names for patches
};
struct dyld_cache_image_patches_v2
{
uint32_t patchClientsStartIndex;
uint32_t patchClientsCount;
uint32_t patchExportsStartIndex; // Points to dyld_cache_image_export_v2[]
uint32_t patchExportsCount;
};
struct dyld_cache_image_export_v2
{
uint32_t dylibOffsetOfImpl; // Offset from the dylib we used to find a dyld_cache_image_patches_v2
uint32_t exportNameOffset : 28;
uint32_t patchKind : 4; // One of DyldSharedCache::patchKind
};
struct dyld_cache_image_clients_v2
{
uint32_t clientDylibIndex;
uint32_t patchExportsStartIndex; // Points to dyld_cache_patchable_export_v2[]
uint32_t patchExportsCount;
};
struct dyld_cache_patchable_export_v2
{
uint32_t imageExportIndex; // Points to dyld_cache_image_export_v2
uint32_t patchLocationsStartIndex; // Points to dyld_cache_patchable_location_v2[]
uint32_t patchLocationsCount;
};
struct dyld_cache_patchable_location_v2
{
uint32_t dylibOffsetOfUse; // Offset from the dylib we used to get a dyld_cache_image_clients_v2
uint32_t high7 : 7,
addend : 5, // 0..31
authenticated : 1,
usesAddressDiversity : 1,
key : 2,
discriminator : 16;
};
//
// MARK: --- V3 patching. This is V2 plus support for GOT combining ---
//
struct dyld_cache_patch_info_v3
{
struct dyld_cache_patch_info_v2 infoV2;
// uint32_t patchTableVersion; // == 3
// ... other fields from dyld_cache_patch_info_v2
uint64_t gotClientsArrayAddr; // (unslid) address of array for dyld_cache_image_got_clients_v3 for each image
uint64_t gotClientsArrayCount; // count of got clients entries. Should always match the patchTableArrayCount
uint64_t gotClientExportsArrayAddr; // (unslid) address of array for patch exports for each GOT image
uint64_t gotClientExportsArrayCount; // count of patch exports entries
uint64_t gotLocationArrayAddr; // (unslid) address of array for patch locations for each GOT patch
uint64_t gotLocationArrayCount; // count of patch location entries
};
struct dyld_cache_image_got_clients_v3
{
uint32_t patchExportsStartIndex; // Points to dyld_cache_patchable_export_v3[]
uint32_t patchExportsCount;
};
struct dyld_cache_patchable_export_v3
{
uint32_t imageExportIndex; // Points to dyld_cache_image_export_v2
uint32_t patchLocationsStartIndex; // Points to dyld_cache_patchable_location_v3[]
uint32_t patchLocationsCount;
};
struct dyld_cache_patchable_location_v3
{
uint64_t cacheOffsetOfUse; // Offset from the cache header
uint32_t high7 : 7,
addend : 5, // 0..31
authenticated : 1,
usesAddressDiversity : 1,
key : 2,
discriminator : 16;
};
struct dyld_cache_patchable_location_v4
{
uint64_t dylibOffsetOfUse; // Offset from the cache header
union {
struct {
uint32_t authenticated : 1, // == 1
high7 : 7,
isWeakImport : 1,
addend : 5, // 0..31
usesAddressDiversity : 1,
keyIsD : 1, // B keys are not permitted. So this is just whether the A key is I or D (0 => I, 1 => D)
discriminator : 16;
} auth;
struct {
uint32_t authenticated : 1, // == 0
high7 : 7,
isWeakImport : 1,
addend : 23;
} regular;
};
};
#endif /* _CACHE_PATCHING_H_ */
+611
View File
@@ -0,0 +1,611 @@
#include "CodeDirectory.h"
#include "CSBlob.h"
#include "Util.h"
#include "BufferedStream.h"
#include "MemoryStream.h"
#include <CommonCrypto/CommonDigest.h>
#include <stddef.h>
void print_cdhash(cdhash_t cdhash)
{
for (int i = 0; i < sizeof(cdhash_t); i++) {
printf("%02x", cdhash[i]);
}
}
void csd_code_directory_read_slot_hash(CS_DecodedBlob *codeDirBlob, MachO *macho, int slot, uint8_t *slotHashOut)
{
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER);
csd_blob_read(codeDirBlob, codeDir.hashOffset + (slot * codeDir.hashSize), codeDir.hashSize, slotHashOut);
}
bool code_directory_calculate_page_hash(CS_CodeDirectory *codeDir, MachO *macho, int slot, uint8_t *pageHashOut)
{
uint32_t pageToReadSize = (uint32_t)(pow(2.0, (double)(codeDir->pageSize)));
uint32_t pageToReadOffset = slot * pageToReadSize;
// Special case for reading the code signature itself
if (slot == codeDir->nCodeSlots - 1) {
uint32_t csOffset = 0, csSize = 0;
macho_find_code_signature_bounds(macho, &csOffset, &csSize);
if (pageToReadOffset > csOffset) return false;
if (!csOffset || !csSize) pageToReadSize = memory_stream_get_size(macho->stream) - pageToReadOffset;
else pageToReadSize = csOffset - pageToReadOffset;
}
// Bail out when past EOF
if ((pageToReadOffset + pageToReadSize) > memory_stream_get_size(macho_get_stream(macho))) return false;
uint8_t page[pageToReadSize];
if (macho_read_at_offset(macho, pageToReadOffset, pageToReadSize, page) != 0) return false;
switch (codeDir->hashType) {
case CS_HASHTYPE_SHA160_160: {
CC_SHA1(page, (CC_LONG)pageToReadSize, pageHashOut);
break;
}
case CS_HASHTYPE_SHA256_256:
case CS_HASHTYPE_SHA256_160: {
uint8_t fullHash[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(page, (CC_LONG)pageToReadSize, fullHash);
memcpy(pageHashOut, fullHash, codeDir->hashSize);
break;
}
case CS_HASHTYPE_SHA384_384: {
uint8_t fullHash[CC_SHA384_DIGEST_LENGTH];
CC_SHA256(page, (CC_LONG)pageToReadSize, fullHash);
memcpy(pageHashOut, fullHash, codeDir->hashSize);
break;
}
default: {
return false;
}
}
return true;
}
bool csd_code_directory_verify_code_slot(CS_DecodedBlob *codeDirBlob, MachO *macho, int slot)
{
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER);
uint8_t slotHash[codeDir.hashSize];
csd_code_directory_read_slot_hash(codeDirBlob, macho, slot, slotHash);
uint8_t pageHash[codeDir.hashSize];
if (!code_directory_calculate_page_hash(&codeDir, macho, slot, slotHash)) return false;
return (memcmp(slotHash, pageHash, codeDir.hashSize) == 0);
}
bool csd_code_directory_verify_code_slots(CS_DecodedBlob *codeDirBlob, MachO *macho, int slot)
{
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, HOST_TO_BIG_APPLIER);
for (int i = 0; i < codeDir.nCodeSlots; i++) {
if (!csd_code_directory_verify_code_slot(codeDirBlob, macho, i)) {
return false;
}
}
return true;
}
const char *cs_hash_type_to_string(int hashType)
{
switch (hashType) {
case CS_HASHTYPE_SHA160_160:
return "SHA-1 160";
case CS_HASHTYPE_SHA256_256:
return "SHA-2 256";
case CS_HASHTYPE_SHA256_160:
return "SHA-2 160";
case CS_HASHTYPE_SHA384_384:
return "SHA-3 384";
default:
return "Unknown blob type";
}
}
int cs_hash_type_to_length(int hashType)
{
switch (hashType) {
case CS_HASHTYPE_SHA160_160:
return 0x14;
case CS_HASHTYPE_SHA256_256:
case CS_HASHTYPE_SHA256_160:
return 0x20;
case CS_HASHTYPE_SHA384_384:
return 0x30;
default:
return 0;
}
}
int cs_code_directory_get_size(int version) {
switch (version) {
case 0x20600: return 0x70;
case 0x20500: return 0x60;
case 0x20400: return 0x58;
case 0x20300: return 0x40;
// 0x20200 matches ChOma's CS_CodeDirectory structure
default: return 0x34;
}
}
const char* cs_slot_to_string(int slot)
{
switch (slot) {
case -11:
return "Loaded library launch constraints hash";
case -10:
return "Responsible process launch constraints hash";
case -9:
return "Parent process launch constraints hash";
case -8:
return "Process launch constraints hash";
case -7:
return "DER entitlements hash";
case -6:
return "DMG signature hash";
case -5:
return "Entitlements hash";
case -4:
return "App-specific hash";
case -3:
return "CodeResources hash";
case -2:
return "Requirements blob hash";
case -1:
return "Info.plist hash";
default:
return "Page hash";
}
}
char *csd_code_directory_copy_identifier(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut)
{
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER);
if (codeDir.identOffset == 0) return NULL;
char *identity = NULL;
csd_blob_read_string(codeDirBlob, codeDir.identOffset, &identity);
if (offsetOut) *offsetOut = codeDir.identOffset;
return identity;
}
char *csd_code_directory_copy_team_id(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut)
{
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER);
if (codeDir.version < 0x20200) return NULL;
if (codeDir.teamOffset == 0) return NULL;
char *teamId = NULL;
csd_blob_read_string(codeDirBlob, codeDir.teamOffset, &teamId);
if (offsetOut) *offsetOut = codeDir.teamOffset;
return teamId;
}
int csd_code_directory_set_team_id(CS_DecodedBlob *codeDirBlob, char *newTeamID)
{
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER);
size_t newTeamIDSize = strlen(newTeamID)+1;
int32_t shift = 0;
uint32_t initalTeamOffset = 0;
char *previousTeamID = csd_code_directory_copy_team_id(codeDirBlob, &initalTeamOffset);
if (previousTeamID) {
// If there is already a TeamID, delete it
uint32_t previousTeamIDSize = strlen(previousTeamID)+1;
csd_blob_delete(codeDirBlob, initalTeamOffset, previousTeamIDSize);
shift -= previousTeamIDSize;
free(previousTeamID);
}
if (initalTeamOffset) {
codeDir.teamOffset = initalTeamOffset;
}
else {
uint32_t identityOffset = 0;
char *identity = csd_code_directory_copy_identifier(codeDirBlob, &identityOffset);
if (!identity) {
codeDir.teamOffset = cs_code_directory_get_size(codeDir.version);
} else {
codeDir.teamOffset = identityOffset + strlen(identity) + 1;
free(identity);
}
}
// Insert new team ID
csd_blob_insert(codeDirBlob, codeDir.teamOffset, newTeamIDSize, newTeamID);
shift += newTeamIDSize;
// Shift other offsets as needed (Since we inserted data in the middle)
if (codeDir.hashOffset != 0 && codeDir.hashOffset > initalTeamOffset) {
codeDir.hashOffset += shift;
}
if (codeDir.scatterOffset != 0 && codeDir.scatterOffset > initalTeamOffset) {
codeDir.scatterOffset += shift;
}
// Write changes to codeDir struct
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, HOST_TO_BIG_APPLIER);
csd_blob_write(codeDirBlob, 0, sizeof(codeDir), &codeDir);
return 0;
}
int csd_code_directory_set_identifier(CS_DecodedBlob *codeDirBlob, char *newIdentifier)
{
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER);
size_t newIdentifierSize = strlen(newIdentifier)+1;
int32_t shift = 0;
uint32_t initialIDOffset = 0;
char *previousIdentifier = csd_code_directory_copy_identifier(codeDirBlob, &initialIDOffset);
if (previousIdentifier) {
uint32_t previousIdentifierSize = strlen(previousIdentifier)+1;
csd_blob_delete(codeDirBlob, initialIDOffset, previousIdentifierSize);
shift -= previousIdentifierSize;
free(previousIdentifier);
}
if (initialIDOffset) {
codeDir.identOffset = initialIDOffset;
}
else {
uint32_t teamOffset = 0;
char *team = csd_code_directory_copy_team_id(codeDirBlob, &teamOffset);
if (!team) {
codeDir.identOffset = cs_code_directory_get_size(codeDir.version);
} else {
codeDir.identOffset = teamOffset - strlen(team) + 1;
free(team);
}
}
// Insert new identifier
csd_blob_insert(codeDirBlob, codeDir.identOffset, newIdentifierSize, newIdentifier);
shift += newIdentifierSize;
// Shift other offsets as needed (Since we inserted data in the middle)
if (codeDir.teamOffset != 0 && codeDir.teamOffset > initialIDOffset) {
codeDir.teamOffset += shift;
}
if (codeDir.hashOffset != 0 && codeDir.hashOffset > initialIDOffset) {
codeDir.hashOffset += shift;
}
if (codeDir.scatterOffset != 0 && codeDir.scatterOffset > initialIDOffset) {
codeDir.scatterOffset += shift;
}
// Write changes to codeDir struct
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, HOST_TO_BIG_APPLIER);
csd_blob_write(codeDirBlob, 0, sizeof(codeDir), &codeDir);
return 0;
}
uint32_t csd_code_directory_get_flags(CS_DecodedBlob *codeDirBlob)
{
uint32_t flags = 0;
csd_blob_read(codeDirBlob, offsetof(CS_CodeDirectory, flags), sizeof(flags), &flags);
return BIG_TO_HOST(flags);
}
void csd_code_directory_set_flags(CS_DecodedBlob *codeDirBlob, uint32_t flags)
{
flags = HOST_TO_BIG(flags);
csd_blob_write(codeDirBlob, offsetof(CS_CodeDirectory, flags), sizeof(flags), &flags);
}
uint8_t csd_code_directory_get_hash_type(CS_DecodedBlob *codeDirBlob)
{
uint8_t hashType = 0;
csd_blob_read(codeDirBlob, offsetof(CS_CodeDirectory, hashType), sizeof(hashType), &hashType);
return hashType;
}
void csd_code_directory_set_hash_type(CS_DecodedBlob *codeDirBlob, uint8_t hashType)
{
csd_blob_write(codeDirBlob, offsetof(CS_CodeDirectory, hashType), sizeof(hashType), &hashType);
}
unsigned csd_code_directory_calculate_rank(CS_DecodedBlob *codeDirBlob)
{
// The supported hash types, ranked from least to most preferred. From XNU's
// bsd/kern/ubc_subr.c.
static uint32_t rankedHashTypes[] = {
CS_HASHTYPE_SHA160_160,
CS_HASHTYPE_SHA256_160,
CS_HASHTYPE_SHA256_256,
CS_HASHTYPE_SHA384_384,
};
// Define the rank of the code directory as its index in the array plus one.
uint8_t type = csd_code_directory_get_hash_type(codeDirBlob);
for (unsigned i = 0; i < sizeof(rankedHashTypes) / sizeof(rankedHashTypes[0]); i++) {
if (rankedHashTypes[i] == type) {
return (i + 1);
}
}
return 0;
}
int csd_code_directory_calculate_hash(CS_DecodedBlob *codeDirBlob, void *cdhashOut)
{
if (!codeDirBlob || !cdhashOut) return -1;
// Longest possible buffer, will cut it off at the end as cdhash size is fixed
uint8_t cdhash[CC_SHA384_DIGEST_LENGTH];
size_t cdBlobSize = csd_blob_get_size(codeDirBlob);
uint8_t *cdBlob = memory_stream_get_raw_pointer(codeDirBlob->stream);
switch (csd_code_directory_get_hash_type(codeDirBlob)) {
case CS_HASHTYPE_SHA160_160: {
CC_SHA1(cdBlob, (CC_LONG)cdBlobSize, cdhash);
break;
}
case CS_HASHTYPE_SHA256_256:
case CS_HASHTYPE_SHA256_160: {
CC_SHA256(cdBlob, (CC_LONG)cdBlobSize, cdhash);
break;
}
case CS_HASHTYPE_SHA384_384: {
CC_SHA384(cdBlob, (CC_LONG)cdBlobSize, cdhash);
break;
}
default:
return -1;
}
memcpy(cdhashOut, cdhash, CS_CDHASH_LEN);
return 0;
}
int csd_code_directory_print_content(CS_DecodedBlob *codeDirBlob, MachO *macho, bool printSlots, bool verifySlots)
{
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER);
// Version 0x20000
printf("Code directory:\n");
printf("\tMagic: 0x%X\n", codeDir.magic);
printf("\tLength: 0x%x\n", codeDir.length);
printf("\tVersion: 0x%x\n", codeDir.version);
printf("\tFlags: 0x%x\n", codeDir.flags);
printf("\tHash offset: 0x%x\n", codeDir.hashOffset);
uint32_t identifierOffset = 0;
char *identifier = csd_code_directory_copy_identifier(codeDirBlob, &identifierOffset);
if (identifier) {
printf("\tIdentifier: \"%s\" (@ 0x%x)\n", identifier, identifierOffset);
free(identifier);
}
printf("\tNumber of special slots: %u\n", codeDir.nSpecialSlots);
printf("\tNumber of code slots: %u\n", codeDir.nCodeSlots);
printf("\tCode limit: 0x%x\n", codeDir.codeLimit);
printf("\tHash size: 0x%x\n", codeDir.hashSize);
printf("\tHash type: %s\n", cs_hash_type_to_string(codeDir.hashType));
printf("\tPlatform: %d\n", codeDir.platform);
printf("\tPage size: 0x%x\n", codeDir.pageSize);
// Version 0x20100
if (codeDir.version >= 0x20100) {
printf("\tScatter offset: 0x%x\n", codeDir.scatterOffset);
uint32_t teamOffset = 0;
char *teamId = csd_code_directory_copy_team_id(codeDirBlob, &teamOffset);
if (teamId) {
printf("\tTeam ID: \"%s\" (@ 0x%x)\n", teamId, teamOffset);
free(teamId);
}
}
printf("\n");
bool codeSlotsCorrect = true;
bool needsNewline = false;
for (int64_t i = -((int64_t)codeDir.nSpecialSlots); i < (int64_t)codeDir.nCodeSlots; i++) {
// Read slot
uint8_t slotHash[codeDir.hashSize];
csd_code_directory_read_slot_hash(codeDirBlob, macho, i, slotHash);
if (printSlots || verifySlots) {
// Print the slot number
needsNewline = true;
printf("%s%lld: ", i < 0 ? "" : " ", i);
print_hash(slotHash, codeDir.hashSize);
// Check if hash is just zeroes
bool isZero = true;
for (int j = 0; j < codeDir.hashSize; j++) {
if (slotHash[j] != 0) {
isZero = false;
break;
}
}
// TrollStore TODO: Validate that hashes are correct
// validateHashes(macho, specialSlots, codeDir.nSpecialSlots * codeDir.hashSize);
// Don't print the slot name if the hash is just zeroes
if (!isZero) {
// Print the special slot name (if applicable)
printf(" (%s)", cs_slot_to_string(i));
}
}
if (verifySlots && i >= 0) {
uint8_t pageHash[codeDir.hashSize];
needsNewline = true;
bool correct = false;
bool calcWorked = code_directory_calculate_page_hash(&codeDir, macho, i, pageHash);
if (calcWorked) {
correct = (memcmp(slotHash, pageHash, codeDir.hashSize) == 0);
}
if (correct) {
printf(" ✅");
}
else {
codeSlotsCorrect = false;
if (!calcWorked) {
printf(" ❌ (unable to calculate, probably EOF?)");
}
else {
printf(" ❌ (should be: ");
print_hash(pageHash, codeDir.hashSize);
printf(")");
}
}
}
if (needsNewline) printf("\n");
needsNewline = false;
}
if (verifySlots) {
if (codeSlotsCorrect) {
printf("All page hashes are valid!\n");
}
else {
printf("Some page hashes are invalid!\n");
}
}
char cdhash[CS_CDHASH_LEN];
if (csd_code_directory_calculate_hash(codeDirBlob, cdhash) == 0) {
printf(" CDHash: ");
print_hash((uint8_t *)cdhash, sizeof(cdhash));
printf("\n");
}
return 0;
}
int csd_code_directory_update_special_slots(CS_DecodedBlob *codeDirBlob, CS_DecodedBlob *xmlEntitlements, CS_DecodedBlob *derEntitlements, CS_DecodedBlob *requirements) {
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(CS_CodeDirectory), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER);
int hashLen = 0;
unsigned char *(*hashFunc)(const void *data, CC_LONG len, unsigned char *md) = NULL;
switch (codeDir.hashType) {
case CS_HASHTYPE_SHA160_160:
hashLen = CC_SHA1_DIGEST_LENGTH;
hashFunc = CC_SHA1;
break;
case CS_HASHTYPE_SHA256_160:
case CS_HASHTYPE_SHA256_256:
hashLen = CC_SHA256_DIGEST_LENGTH;
hashFunc = CC_SHA256;
break;
case CS_HASHTYPE_SHA384_384:
hashLen = CC_SHA384_DIGEST_LENGTH;
hashFunc = CC_SHA384;
break;
default:
break;
}
if (!hashLen) {
printf("Error: unknown hash type (%d)\n", codeDir.hashType);
}
for (int i = 1; i <= codeDir.nSpecialSlots; i++) {
uint8_t newHash[hashLen];
uint32_t hashOffset = codeDir.hashOffset - (i * codeDir.hashSize);
if (xmlEntitlements && i == CSSLOT_ENTITLEMENTS) {
// XXX: memory_stream_get_raw_pointer only works for buffered streams
hashFunc(memory_stream_get_raw_pointer(xmlEntitlements->stream), memory_stream_get_size(xmlEntitlements->stream), newHash);
csd_blob_write(codeDirBlob, hashOffset, codeDir.hashSize, newHash);
} else if (derEntitlements && i == CSSLOT_DER_ENTITLEMENTS) {
hashFunc(memory_stream_get_raw_pointer(derEntitlements->stream), memory_stream_get_size(derEntitlements->stream), newHash);
csd_blob_write(codeDirBlob, hashOffset, codeDir.hashSize, newHash);
} else if (requirements && i == CSSLOT_REQUIREMENTS) {
hashFunc(memory_stream_get_raw_pointer(requirements->stream), memory_stream_get_size(requirements->stream), newHash);
csd_blob_write(codeDirBlob, hashOffset, codeDir.hashSize, newHash);
}
}
return 0;
}
int csd_code_directory_update_code_slots(CS_DecodedBlob *codeDirBlob, MachO *macho)
{
CS_CodeDirectory codeDir;
csd_blob_read(codeDirBlob, 0, sizeof(CS_CodeDirectory), &codeDir);
CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER);
// since this function is only for updating the hashes
// we make the assumption here that the nPages of the existing code directory is correct
// TODO: maybe check that nCodeSlots lines up with what we expect
for (uint32_t slotIdx = 0; slotIdx < codeDir.nCodeSlots; slotIdx++) {
// Calculate hash
uint8_t pageHash[codeDir.hashSize];
if (!code_directory_calculate_page_hash(&codeDir, macho, slotIdx, pageHash)) {
return -1;
}
// Write hash to CodeDirectory
uint32_t offsetOfBlobToReplace = codeDir.hashOffset + (slotIdx * codeDir.hashSize);
csd_blob_write(codeDirBlob, offsetOfBlobToReplace, codeDir.hashSize, pageHash);
}
return 0;
}
CS_DecodedBlob *csd_code_directory_init(MachO *macho, int hashType, bool alternate) {
CS_CodeDirectory newCodeDir = { 0 };
memset(&newCodeDir, 0, sizeof(CS_CodeDirectory));
newCodeDir.magic = CSMAGIC_CODEDIRECTORY;
newCodeDir.version = 0x20200;
// Default values
newCodeDir.nSpecialSlots = 7; // Only go down to DER entitlements hash
newCodeDir.hashType = hashType;
newCodeDir.hashSize = cs_hash_type_to_length(hashType);
newCodeDir.pageSize = 0xC; // 0x4000
newCodeDir.hashOffset = sizeof(CS_CodeDirectory) + (newCodeDir.nSpecialSlots * newCodeDir.hashSize);
newCodeDir.nCodeSlots = (int)(align_to_size(memory_stream_get_size(macho->stream), 0x1000) / 0x1000);
// Code limit
// This is everything up to the FADE0CC0 magic
CS_DecodedBlob *blob = malloc(sizeof(CS_DecodedBlob));
blob->type = alternate ? CSSLOT_ALTERNATE_CODEDIRECTORIES : CSSLOT_CODEDIRECTORY;
int finalLength = sizeof(CS_CodeDirectory) + (newCodeDir.nSpecialSlots * newCodeDir.hashSize) + (newCodeDir.nCodeSlots * newCodeDir.hashSize);
newCodeDir.length = finalLength;
CODE_DIRECTORY_APPLY_BYTE_ORDER(&newCodeDir, HOST_TO_BIG_APPLIER);
void *buffer = malloc(finalLength);
memset(buffer, 0, finalLength);
memcpy(buffer, &newCodeDir, sizeof(CS_CodeDirectory));
blob->stream = buffered_stream_init_from_buffer(buffer, finalLength, BUFFERED_STREAM_FLAG_AUTO_EXPAND);
return blob;
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef CODE_DIRECTORY_H
#define CODE_DIRECTORY_H
#include <stdint.h>
#include <math.h>
#include <CommonCrypto/CommonDigest.h>
#include "MachO.h"
#include "CSBlob.h"
#include "Fat.h"
#include "MachOByteOrder.h"
#include "MachOLoadCommand.h"
#include "MemoryStream.h"
// Code directory blob header
typedef struct __CodeDirectory {
uint32_t magic;
uint32_t length;
uint32_t version;
uint32_t flags;
uint32_t hashOffset;
uint32_t identOffset;
uint32_t nSpecialSlots;
uint32_t nCodeSlots;
uint32_t codeLimit;
uint8_t hashSize;
uint8_t hashType;
uint8_t platform;
uint8_t pageSize;
uint32_t spare2;
/* Version 0x20100 */
uint32_t scatterOffset;
/* Version 0x20200 */
uint32_t teamOffset;
} CS_CodeDirectory
__attribute__ ((aligned(1)));
#define CS_CDHASH_LEN 20
typedef uint8_t cdhash_t[CS_CDHASH_LEN];
void print_cdhash(cdhash_t cdhash);
enum CS_HashType {
CS_HASHTYPE_SHA160_160 = 1,
CS_HASHTYPE_SHA256_256 = 2,
CS_HASHTYPE_SHA256_160 = 3,
CS_HASHTYPE_SHA384_384 = 4,
};
char *csd_code_directory_copy_identifier(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut);
char *csd_code_directory_copy_team_id(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut);
int csd_code_directory_set_team_id(CS_DecodedBlob *codeDirBlob, char *newTeamID);
int csd_code_directory_set_identifier(CS_DecodedBlob *codeDirBlob, char *newIdentifier);
uint32_t csd_code_directory_get_flags(CS_DecodedBlob *codeDirBlob);
void csd_code_directory_set_flags(CS_DecodedBlob *codeDirBlob, uint32_t flags);
uint8_t csd_code_directory_get_hash_type(CS_DecodedBlob *codeDirBlob);
void csd_code_directory_set_hash_type(CS_DecodedBlob *codeDirBlob, uint8_t hashType);
unsigned csd_code_directory_calculate_rank(CS_DecodedBlob *codeDirBlob);
int csd_code_directory_calculate_hash(CS_DecodedBlob *codeDirBlob, void *cdhashOut);
int csd_code_directory_print_content(CS_DecodedBlob *codeDirBlob, MachO *macho, bool printSlots, bool verifySlots);
int csd_code_directory_update_special_slots(CS_DecodedBlob *codeDirBlob, CS_DecodedBlob *xmlEntitlements, CS_DecodedBlob *derEntitlements, CS_DecodedBlob *requirements);
int csd_code_directory_update_code_slots(CS_DecodedBlob *codeDirBlob, MachO *macho);
CS_DecodedBlob *csd_code_directory_init(MachO *macho, int hashType, bool alternate);
#endif // CODE_DIRECTORY_H
+232
View File
@@ -0,0 +1,232 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <CoreFoundation/CoreFoundation.h>
#include "MachOByteOrder.h"
#include "DER.h"
#define ASN1_CONSTRUCTED 0x20
#define ASN1_SEQUENCE 0x10
#define ASN1_SET 0x11
#define ASN1_BOOLEAN 0x01
#define ASN1_INTEGER 0x02
#define ASN1_UTF8_STRING 0x0C
// DER entitlements are a SET of SEQUENCE serialisations, each sequence being an entitlement
void der_free_encoded_item(DEREncodedItem *item) {
free(item->data);
free(item);
}
DEREncodedItem *der_encode_length(uint32_t length) {
if (length <= 0x7f) {
uint8_t *data = malloc(1);
if (data == NULL) {
return NULL;
}
data[0] = length;
DEREncodedItem *item = malloc(sizeof(DEREncodedItem));
if (item == NULL) {
return NULL;
}
item->data = data;
item->length = 1;
return item;
} else {
uint32_t len = length;
uint32_t lenBytes = 0;
while (len) {
len >>= 8;
lenBytes++;
}
uint8_t *data = malloc(lenBytes + 1);
if (data == NULL) {
return NULL;
}
data[0] = 0x80 | lenBytes;
for (uint32_t i = 1; i <= lenBytes; i++) {
data[i] = (length >> (8 * (lenBytes - i))) & 0xff;
}
DEREncodedItem *item = malloc(sizeof(DEREncodedItem));
if (item == NULL) {
free(data);
return NULL;
}
item->data = data;
item->length = lenBytes + 1;
return item;
}
}
DEREncodedItem *der_encode_item(DERItem *item) {
DEREncodedItem *length = der_encode_length(item->length);
if (length->data == NULL) {
return NULL;
}
uint32_t totalLength = 1 + length->length + item->length;
uint8_t *data = malloc(totalLength);
if (data == NULL) {
der_free_encoded_item(length);
return NULL;
}
data[0] = item->tag;
memcpy(&data[1], length->data, length->length);
memcpy(&data[1 + length->length], item->data, item->length);
DEREncodedItem *encodedItem = malloc(sizeof(DEREncodedItem));
if (encodedItem == NULL) {
der_free_encoded_item(length);
free(data);
return NULL;
}
encodedItem->data = data;
encodedItem->length = totalLength;
der_free_encoded_item(length);
return encodedItem;
}
DEREncodedItem *der_encode_boolean(bool value) {
DERItem item;
item.tag = ASN1_BOOLEAN;
item.data = malloc(1);
if (item.data == NULL) {
return NULL;
}
item.data[0] = value;
item.length = 1;
return der_encode_item(&item);
}
DEREncodedItem *der_encode_integer(uint32_t value) {
DERItem item;
item.tag = ASN1_INTEGER;
item.data = malloc(sizeof(uint32_t));
if (item.data == NULL) {
return NULL;
}
// Convert to big-endian
value = HOST_TO_BIG(value);
memcpy(item.data, &value, sizeof(uint32_t));
item.length = 4;
return der_encode_item(&item);
}
DEREncodedItem *der_encode_utf8_string(const char *string) {
DERItem item;
item.tag = ASN1_UTF8_STRING;
item.data = (uint8_t *)string;
item.length = strlen(string);
return der_encode_item(&item);
}
DEREncodedItem *der_encode_sequence(DEREncodedItem *items[], uint32_t nItems) {
uint8_t tag = ASN1_SEQUENCE | ASN1_CONSTRUCTED;
uint32_t totalLength = 0;
for (uint32_t i = 0; i < nItems; i++) {
totalLength += items[i]->length;
}
DEREncodedItem *length = der_encode_length(totalLength);
if (length == NULL) {
return NULL;
}
uint32_t totalDataLength = 1 + length->length + totalLength;
uint8_t *data = malloc(totalDataLength);
if (data == NULL) {
der_free_encoded_item(length);
return NULL;
}
data[0] = tag;
memcpy(&data[1], length->data, length->length);
uint32_t offset = 1 + length->length;
for (uint32_t i = 0; i < nItems; i++) {
memcpy(&data[offset], items[i]->data, items[i]->length);
offset += items[i]->length;
}
DEREncodedItem *encodedItem = malloc(sizeof(DEREncodedItem));
if (encodedItem == NULL) {
der_free_encoded_item(length);
free(data);
return NULL;
}
encodedItem->data = data;
encodedItem->length = totalDataLength;
der_free_encoded_item(length);
return encodedItem;
}
DEREncodedItem *der_encode_set(DEREncodedItem *items[], uint32_t nItems) {
uint8_t tag = ASN1_SET | ASN1_CONSTRUCTED;
uint32_t totalLength = 0;
for (uint32_t i = 0; i < nItems; i++) {
totalLength += items[i]->length;
}
DEREncodedItem *length = der_encode_length(totalLength);
if (length == NULL) {
return NULL;
}
uint32_t totalDataLength = 1 + length->length + totalLength;
uint8_t *data = malloc(totalDataLength);
if (data == NULL) {
der_free_encoded_item(length);
return NULL;
}
data[0] = tag;
memcpy(&data[1], length->data, length->length);
uint32_t offset = 1 + length->length;
for (uint32_t i = 0; i < nItems; i++) {
memcpy(&data[offset], items[i]->data, items[i]->length);
offset += items[i]->length;
}
DEREncodedItem *encodedItem = malloc(sizeof(DEREncodedItem));
if (encodedItem == NULL) {
der_free_encoded_item(length);
free(data);
return NULL;
}
encodedItem->data = data;
encodedItem->length = totalDataLength;
der_free_encoded_item(length);
return encodedItem;
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef DER_H
#define DER_H
#include <stdint.h>
typedef struct {
uint8_t tag;
uint32_t length;
uint8_t *data;
} DERItem;
typedef struct {
uint8_t *data;
uint32_t length;
} DEREncodedItem;
void der_free_encoded_item(DEREncodedItem *item);
DEREncodedItem *der_encode_boolean(bool value);
DEREncodedItem *der_encode_integer(uint32_t value);
DEREncodedItem *der_encode_utf8_string(const char *string);
DEREncodedItem *der_encode_sequence(DEREncodedItem *items[], uint32_t nItems);
DEREncodedItem *der_encode_set(DEREncodedItem *items[], uint32_t nItems);
#endif // DER_H
+998
View File
@@ -0,0 +1,998 @@
#include "DyldSharedCache.h"
#include <libgen.h>
#include <sys/syslimits.h>
#include <sys/types.h>
#include <mach-o/nlist.h>
#include <dirent.h>
#include <sys/mman.h>
#include "BufferedStream.h"
#include "Util.h"
#include "MachO.h"
#include "Fat.h"
int string_comparator(void const *a, void const *b) {
char const *aa = *(char const **)a;
char const *bb = *(char const **)b;
int r = strcmp(aa, bb);
return r;
}
void dsc_enumerate_mappings(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCacheMapping *mapping, DyldSharedCacheFile *sourceFile, bool *stop))
{
for (unsigned i = 0; i < sharedCache->mappingCount; i++) {
DyldSharedCacheMapping *mapping = &sharedCache->mappings[i];
bool stop = false;
enumeratorBlock(mapping, mapping->file, &stop);
if (stop) break;
}
}
DyldSharedCacheMapping *dsc_lookup_mapping(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size)
{
__block DyldSharedCacheMapping *mappingOut = NULL;
dsc_enumerate_mappings(sharedCache, ^(DyldSharedCacheMapping *mapping, DyldSharedCacheFile *sourceFile, bool *stop) {
uint64_t mappingEndAddr = mapping->vmaddr + mapping->size;
uint64_t searchEndAddr = vmaddr + size;
if (size != 0) searchEndAddr--;
if (vmaddr >= mapping->vmaddr && (searchEndAddr < mappingEndAddr)) {
mappingOut = mapping;
*stop = true;
}
});
return mappingOut;
}
void *dsc_find_buffer(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size)
{
DyldSharedCacheMapping *mapping = dsc_lookup_mapping(sharedCache, vmaddr, size);
if (mapping) {
return (void *)((uintptr_t)mapping->ptr + (vmaddr - mapping->vmaddr));
}
return NULL;
}
int dsc_read_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, size_t size, void *outBuf)
{
uint64_t startAddr = vmaddr;
uint64_t endAddr = startAddr + size;
uint64_t curAddr = startAddr;
while (curAddr < endAddr) {
DyldSharedCacheMapping *mapping = dsc_lookup_mapping(sharedCache, curAddr, 0);
if (!mapping) return -1;
uint64_t startOffset = curAddr - mapping->vmaddr;
uint64_t mappingRemaining = mapping->size - startOffset;
uint64_t copySize = endAddr - curAddr;
if (copySize > mappingRemaining) copySize = mappingRemaining;
memcpy((void *)((intptr_t)outBuf + (curAddr - startAddr)), (void *)((intptr_t)mapping->ptr + startOffset), copySize);
curAddr += copySize;
}
return 0;
}
int dsc_read_string_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, char **outString)
{
uint64_t len = 0;
char c = 0;
do {
if (dsc_read_from_vmaddr(sharedCache, vmaddr + (len++), sizeof(c), &c) != 0) return -1;
} while (c != 0);
*outString = malloc(len);
return dsc_read_from_vmaddr(sharedCache, vmaddr, len, *outString);
}
uint64_t dsc_fileoff_to_vmaddr(DyldSharedCache *sharedCache, DyldSharedCacheFile *file, uint64_t fileoff)
{
for (unsigned i = 0; i < sharedCache->mappingCount; i++) {
DyldSharedCacheMapping *mapping = &sharedCache->mappings[i];
if (mapping->file == file) {
if (fileoff >= mapping->fileoff && fileoff < (mapping->fileoff + mapping->size)) {
return mapping->vmaddr + (fileoff - mapping->fileoff);
}
}
}
return 0;
}
uint64_t dsc_vmaddr_to_fileoff(DyldSharedCache *sharedCache, uint64_t vmaddr, DyldSharedCacheFile **fileOut)
{
DyldSharedCacheMapping *mapping = dsc_lookup_mapping(sharedCache, vmaddr, 0);
if (mapping) {
if (fileOut) *fileOut = mapping->file;
return (vmaddr - mapping->vmaddr) + mapping->fileoff;
}
return 0;
}
DyldSharedCacheFile *_dsc_load_file(const char *dscPath, const char suffix[32])
{
int fd = -1;
DyldSharedCacheFile *file = NULL;
char filepath[strlen(dscPath) + strnlen(suffix, 32) + 1];
strcpy(filepath, dscPath);
strncat(filepath, suffix, 32);
fd = open(filepath, O_RDONLY);
if (fd < 0) goto fail;
struct stat sb;
if (fstat(fd, &sb) != 0) goto fail;
if (sb.st_size < sizeof(struct dyld_cache_header)) goto fail;
file = malloc(sizeof(DyldSharedCacheFile));
if (!file) goto fail;
lseek(fd, 0, SEEK_SET);
read(fd, &file->header, sizeof(file->header));
if (strncmp(file->header.magic, "dyld_v", 6) != 0) goto fail;
// A lot of version detection works through the mappingOffset attribute
// This attribute typically points to the end of the header, since the mappings directly follow the header
// To make certain version detection easier, we zero out any fields after it, since these are unused on the version the DSC is from
// This reduces the amount of version (mappingOffset) checks neccessary, but does not fully eliminate them
if (file->header.mappingOffset < sizeof(file->header)) {
memset((void *)((uintptr_t)&file->header + file->header.mappingOffset), 0, sizeof(file->header) - file->header.mappingOffset);
}
else if (file->header.mappingOffset > sizeof(file->header)) {
static bool versionWarningPrinted = false;
if (!versionWarningPrinted) {
fprintf(stderr, "Warning: DSC version is newer than what ChOma supports, your mileage may vary.\n");
versionWarningPrinted = true;
}
}
file->fd = fd;
file->filepath = strdup(filepath);
file->filesize = sb.st_size;
return file;
fail:
if (file) free(file);
if (fd >= 0) close(fd);
return NULL;
}
int dsc_file_read_at_offset(DyldSharedCacheFile *dscFile, uint64_t offset, size_t size, void *outBuf)
{
lseek(dscFile->fd, offset, SEEK_SET);
return !(read(dscFile->fd, outBuf, size) == size);
}
int dsc_file_read_string_at_offset(DyldSharedCacheFile *dscFile, uint64_t offset, char **outBuf)
{
lseek(dscFile->fd, offset, SEEK_SET);
return read_string(dscFile->fd, outBuf);
}
DyldSharedCache *dsc_init_from_path_premapped(const char *path, uint32_t premapSlide)
{
if (!path) return NULL;
DyldSharedCache *sharedCache = malloc(sizeof(DyldSharedCache));
sharedCache->mappings = NULL;
sharedCache->mappingCount = 0;
sharedCache->symbolFile.index = 0;
sharedCache->premapSlide = premapSlide;
// Load main DSC file
DyldSharedCacheFile *mainFile = _dsc_load_file(path, "");
if (!mainFile) {
fprintf(stderr, "Error: Failed to load main cache file\n");
dsc_free(sharedCache);
return NULL;
}
struct dyld_cache_header *mainHeader = &mainFile->header;
if (!strncmp(mainHeader->magic, "dyld_v1 armv7", 14)) {
sharedCache->cputype = CPU_TYPE_ARM;
sharedCache->cpusubtype = CPU_SUBTYPE_ARM_V7;
}
else if (!strncmp(mainHeader->magic, "dyld_v1 arm64", 14)) {
sharedCache->cputype = CPU_TYPE_ARM64;
sharedCache->cpusubtype = CPU_SUBTYPE_ARM64_ALL;
}
else if (!strncmp(mainHeader->magic, "dyld_v1 arm64e", 15)) {
sharedCache->cputype = CPU_TYPE_ARM64;
sharedCache->cpusubtype = CPU_SUBTYPE_ARM64E;
}
else {
// Only arm supported for now
printf("Error: DSC has unsupported architecture\n");
return NULL;
}
bool symbolFileExists = !!memcmp(mainHeader->symbolFileUUID, UUID_NULL, sizeof(UUID_NULL));
uint32_t subCacheArrayCount = mainHeader->subCacheArrayCount;
sharedCache->fileCount = 1 + subCacheArrayCount + symbolFileExists;
sharedCache->files = malloc(sizeof(struct DyldSharedCacheFile *) * sharedCache->fileCount);
sharedCache->files[0] = mainFile;
if (subCacheArrayCount > 0) {
// If there are sub caches, load them aswell
int subCacheStructVersion = mainHeader->mappingOffset <= offsetof(struct dyld_cache_header, cacheSubType) ? 1 : 2;
for (uint32_t i = 0; i < subCacheArrayCount; i++) {
DyldSharedCacheFile **file = &sharedCache->files[1+i];
struct dyld_subcache_entry subcacheEntry;
if (subCacheStructVersion == 1) {
struct dyld_subcache_entry_v1 v1Entry;
dsc_file_read_at_offset(mainFile, mainHeader->subCacheArrayOffset + sizeof(v1Entry) * i, sizeof(v1Entry), &v1Entry);
// Old format (iOS <=15) had no suffix string, here the suffix is derived from the index
memcpy(subcacheEntry.uuid, v1Entry.uuid, sizeof(uuid_t));
subcacheEntry.cacheVMOffset = v1Entry.cacheVMOffset;
snprintf(subcacheEntry.fileSuffix, sizeof(subcacheEntry.fileSuffix), ".%u", i+1);
}
else {
dsc_file_read_at_offset(mainFile, mainHeader->subCacheArrayOffset + sizeof(subcacheEntry) * i, sizeof(subcacheEntry), &subcacheEntry);
}
*file = _dsc_load_file(path, subcacheEntry.fileSuffix);
if (!*file) {
fprintf(stderr, "Error: Failed to map subcache with suffix %s\n", subcacheEntry.fileSuffix);
dsc_free(sharedCache);
return NULL;
}
struct dyld_cache_header *header = &(*file)->header;
if (memcmp(header->uuid, subcacheEntry.uuid, sizeof(header->uuid)) != 0) {
fprintf(stderr, "Error: UUID mismatch on subcache with suffix %s\n", subcacheEntry.fileSuffix);
dsc_free(sharedCache);
return NULL;
}
}
}
if (symbolFileExists) {
// If there is a .symbols file, load that aswell and use it for getting symbols
sharedCache->symbolFile.index = sharedCache->fileCount - 1;
sharedCache->files[sharedCache->symbolFile.index] = _dsc_load_file(path, ".symbols");
if (!sharedCache->files[sharedCache->symbolFile.index]) {
fprintf(stderr, "Error: Failed to map symbols subcache\n");
dsc_free(sharedCache);
return NULL;
}
struct dyld_cache_header *header = &sharedCache->files[sharedCache->symbolFile.index]->header;
if (memcmp(header->uuid, mainHeader->symbolFileUUID, sizeof(header->uuid)) != 0) {
fprintf(stderr, "Error: UUID mismatch on symbols subcache\n");
dsc_free(sharedCache);
return NULL;
}
}
sharedCache->baseAddress = mainHeader->sharedRegionStart ?: UINT64_MAX;
for (unsigned i = 0; i < sharedCache->fileCount; i++) {
DyldSharedCacheFile *file = sharedCache->files[i];
if (!file) continue;
if (i != 0 && i == sharedCache->symbolFile.index) {
// If there is a separate .symbols file (and we don't use the main shared cache for symbols)
// then skip any mappings inside it, since this usually only contains a bogus mapping
continue;
}
struct dyld_cache_header *header = &file->header;
//printf("Parsing DSC %s\n", file->filepath);
if (file->filesize < (header->mappingOffset + header->mappingCount * sizeof(struct dyld_cache_mapping_info))) {
fprintf(stderr, "Warning: Failed to parse DSC %s.\n", file->filepath);
}
bool slideInfoExists = (bool)header->mappingWithSlideOffset;
uint64_t mappingOffset = (slideInfoExists ? header->mappingWithSlideOffset : header->mappingOffset);
unsigned prevMappingCount = sharedCache->mappingCount;
sharedCache->mappingCount += header->mappingCount;
sharedCache->mappings = realloc(sharedCache->mappings, sharedCache->mappingCount * sizeof(DyldSharedCacheMapping));
for (int k = 0; k < header->mappingCount; k++) {
DyldSharedCacheMapping *thisMapping = &sharedCache->mappings[prevMappingCount + k];
struct dyld_cache_mapping_and_slide_info fullInfo = {};
if (slideInfoExists) {
dsc_file_read_at_offset(file, mappingOffset + (k * sizeof(fullInfo)), sizeof(fullInfo), &fullInfo);
}
else {
struct dyld_cache_mapping_info mappingInfo;
dsc_file_read_at_offset(file, mappingOffset + k * sizeof(mappingInfo), sizeof(mappingInfo), &mappingInfo);
fullInfo.address = mappingInfo.address;
fullInfo.size = mappingInfo.size;
fullInfo.fileOffset = mappingInfo.fileOffset;
fullInfo.maxProt = mappingInfo.maxProt;
fullInfo.initProt = mappingInfo.initProt;
fullInfo.slideInfoFileOffset = 0;
fullInfo.slideInfoFileSize = 0;
}
thisMapping->file = file;
thisMapping->size = fullInfo.size;
thisMapping->fileoff = fullInfo.fileOffset;
thisMapping->vmaddr = fullInfo.address;
thisMapping->flags = fullInfo.flags;
if (sharedCache->premapSlide) {
thisMapping->ptr = (void *)(thisMapping->vmaddr + sharedCache->premapSlide);
}
else {
thisMapping->ptr = mmap(NULL, thisMapping->size, PROT_READ, MAP_FILE | MAP_SHARED, file->fd, thisMapping->fileoff);
}
// Find base address on shared caches that don't have sharedRegionStart
if (!mainHeader->sharedRegionStart) {
if (thisMapping->vmaddr < sharedCache->baseAddress) {
sharedCache->baseAddress = thisMapping->vmaddr;
}
}
thisMapping->initProt = fullInfo.initProt;
thisMapping->maxProt = fullInfo.maxProt;
if (fullInfo.slideInfoFileOffset) {
thisMapping->slideInfoSize = fullInfo.slideInfoFileSize;
thisMapping->slideInfoPtr = malloc(thisMapping->slideInfoSize);
dsc_file_read_at_offset(file, fullInfo.slideInfoFileOffset, thisMapping->slideInfoSize, thisMapping->slideInfoPtr);
}
else {
thisMapping->slideInfoPtr = NULL;
thisMapping->slideInfoSize = 0;
}
}
}
if (mainHeader->imagesTextCount) {
struct dyld_cache_image_text_info imageTexts[mainHeader->imagesTextCount];
dsc_file_read_at_offset(mainFile, mainHeader->imagesTextOffset, sizeof(imageTexts), imageTexts);
sharedCache->containedImageCount = mainHeader->imagesTextCount;
sharedCache->containedImages = malloc(mainHeader->imagesTextCount * sizeof(DyldSharedCacheImage));
for (uint64_t i = 0; i < mainHeader->imagesTextCount; i++) {
struct dyld_cache_image_text_info *imageTextInfo = &imageTexts[i];
DyldSharedCacheImage *image = &sharedCache->containedImages[i];
image->address = imageTextInfo->loadAddress;
image->size = imageTextInfo->textSegmentSize;
void *buffer = dsc_find_buffer(sharedCache, image->address, image->size);
if (!buffer) {
continue;
}
image->index = i;
memcpy(&image->uuid, &imageTextInfo->uuid, sizeof(uuid_t));
dsc_file_read_string_at_offset(mainFile, imageTextInfo->pathOffset, &image->path);
MemoryStream *stream = buffered_stream_init_from_buffer_nocopy(buffer, image->size, 0);
image->fat = fat_dsc_init_from_memory_stream(stream, sharedCache, image);
}
}
else {
uint64_t imagesOffset = mainHeader->imagesOffsetOld ?: mainHeader->imagesOffset;
uint64_t imagesCount = mainHeader->imagesCountOld ?: mainHeader->imagesCount;
struct dyld_cache_image_info imageInfos[imagesCount];
dsc_file_read_at_offset(mainFile, imagesOffset, sizeof(imageInfos), imageInfos);
sharedCache->containedImageCount = imagesCount;
sharedCache->containedImages = malloc(imagesCount * sizeof(DyldSharedCacheImage));
for (uint64_t i = 0; i < imagesCount; i++) {
DyldSharedCacheImage *image = &sharedCache->containedImages[i];
// There is no size in this format, so we need to calculate it
// Either based on the image after it or based on the end of the mapping
DyldSharedCacheMapping *mappingForThisImage = dsc_lookup_mapping(sharedCache, imageInfos[i].address, 0);
if (!mappingForThisImage) {
continue;
}
uint64_t mappingEndAddr = mappingForThisImage->vmaddr + mappingForThisImage->size;
// Some images have the same address and also the list is not sorted
// So we need to traverse it to find the next image after this one
uint64_t endAddr = UINT64_MAX;
for (int k = 0; k < imagesCount; k++) {
if (imageInfos[k].address > imageInfos[i].address) {
if (endAddr > imageInfos[k].address) {
endAddr = imageInfos[k].address;
break;
}
}
}
// If there was no image after it or the image after it is in a different mapping
// Use the end of the mapping as the end address
if (endAddr > mappingEndAddr) {
endAddr = mappingEndAddr;
}
image->address = imageInfos[i].address;
image->size = endAddr - imageInfos[i].address;
void *buffer = dsc_find_buffer(sharedCache, image->address, image->size);
if (!buffer) {
continue;
}
image->index = i;
dsc_file_read_string_at_offset(mainFile, imageInfos[i].pathFileOffset, &image->path);
MemoryStream *stream = buffered_stream_init_from_buffer_nocopy(buffer, image->size, 0);
image->fat = fat_dsc_init_from_memory_stream(stream, sharedCache, &sharedCache->containedImages[i]);
}
}
return sharedCache;
}
int _dsc_load_symbols(DyldSharedCache *sharedCache)
{
if (!sharedCache->symbolFile.loaded) {
if (sharedCache->symbolFile.index != -1) {
DyldSharedCacheFile *symbolCacheFile = sharedCache->files[sharedCache->symbolFile.index];
struct dyld_cache_header *symbolCacheHeader = &symbolCacheFile->header;
if (symbolCacheHeader->localSymbolsOffset) {
struct dyld_cache_local_symbols_info symbolsInfo;
dsc_file_read_at_offset(symbolCacheFile, symbolCacheHeader->localSymbolsOffset, sizeof(symbolsInfo), &symbolsInfo);
for (uint64_t i = 0; i < symbolsInfo.entriesCount; i++) {
uint64_t dylibOffset = 0;
uint32_t nlistStartIndex = 0;
uint32_t nlistCount = 0;
int r = 0;
#define _GENERIC_READ_SYMBOL_ENTRY(entryType) do { \
struct entryType symbolEntry; \
if ((r = dsc_file_read_at_offset(symbolCacheFile, symbolCacheHeader->localSymbolsOffset + symbolsInfo.entriesOffset + i * sizeof(symbolEntry), sizeof(symbolEntry), &symbolEntry)) != 0) break; \
dylibOffset = symbolEntry.dylibOffset; \
nlistStartIndex = symbolEntry.nlistStartIndex; \
nlistCount = symbolEntry.nlistCount; \
} while (0)
if (symbolCacheHeader->mappingOffset >= offsetof(struct dyld_cache_header, symbolFileUUID)) {
_GENERIC_READ_SYMBOL_ENTRY(dyld_cache_local_symbols_entry_64);
}
else {
_GENERIC_READ_SYMBOL_ENTRY(dyld_cache_local_symbols_entry);
}
if (r != 0) continue;
#undef _GENERIC_READ_SYMBOL_ENTRY
DyldSharedCacheImage *image = dsc_lookup_image_by_address(sharedCache, sharedCache->baseAddress + dylibOffset);
if (image) {
image->nlistCount = nlistCount;
image->nlistStartIndex = nlistStartIndex;
}
}
sharedCache->symbolFile.nlistCount = symbolsInfo.nlistCount;
uint64_t nlistSize = (dsc_is32bit(sharedCache) ? sizeof(struct nlist) : sizeof(struct nlist_64)) * sharedCache->symbolFile.nlistCount;
sharedCache->symbolFile.nlist = malloc(nlistSize);
dsc_file_read_at_offset(symbolCacheFile, symbolCacheHeader->localSymbolsOffset + symbolsInfo.nlistOffset, nlistSize, sharedCache->symbolFile.nlist);
uint64_t stringsOffsetPage = (symbolCacheHeader->localSymbolsOffset + symbolsInfo.stringsOffset) & ~PAGE_MASK;
uint64_t stringsOffsetPageOff = (symbolCacheHeader->localSymbolsOffset + symbolsInfo.stringsOffset) & PAGE_MASK;
sharedCache->symbolFile.stringsSize = symbolsInfo.stringsSize;
char *mappedStrings = mmap(NULL, sharedCache->symbolFile.stringsSize + stringsOffsetPageOff, PROT_READ, MAP_FILE | MAP_PRIVATE, symbolCacheFile->fd, stringsOffsetPage);
if (mappedStrings != MAP_FAILED) {
sharedCache->symbolFile.strings = mappedStrings + stringsOffsetPageOff;
}
else {
perror("mmap");
}
}
}
sharedCache->symbolFile.loaded = true;
}
return sharedCache->symbolFile.nlist ? 0 : -1;
}
DyldSharedCache *dsc_init_from_path(const char *path)
{
return dsc_init_from_path_premapped(path, 0);
}
bool dsc_is32bit(DyldSharedCache *sharedCache)
{
if (sharedCache->cputype == CPU_TYPE_ARM) {
return true;
}
else {
return false;
}
}
void dsc_enumerate_files(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *filepath, size_t filesize, struct dyld_cache_header *header))
{
for (int i = 0; i < sharedCache->fileCount; i++) {
enumeratorBlock(sharedCache->files[i]->filepath, sharedCache->files[i]->filesize, &sharedCache->files[i]->header);
}
}
void dsc_enumerate_images(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *path, DyldSharedCacheImage *imageHandle, MachO *imageMachO, bool *stop))
{
for (unsigned i = 0; i < sharedCache->containedImageCount; i++) {
bool stop = false;
DyldSharedCacheImage *imageHandle = &sharedCache->containedImages[i];
MachO *macho = fat_get_single_slice(imageHandle->fat);
if (imageHandle && macho) {
enumeratorBlock(sharedCache->containedImages[i].path, imageHandle, macho, &stop);
}
if (stop) break;
}
}
DyldSharedCacheImage *dsc_find_image_for_section_address(DyldSharedCache *sharedCache, uint64_t address)
{
__block DyldSharedCacheImage *image = NULL;
for (unsigned i = 0; i < sharedCache->containedImageCount; i++) {
if (sharedCache->containedImages[i].fat->slicesCount == 1) {
MachO *macho = sharedCache->containedImages[i].fat->slices[0];
if (macho) {
macho_enumerate_sections(macho, ^(struct section_64 *section, struct segment_command_64 *segment, bool *stop) {
if (address >= section->addr && address < (section->addr + section->size)) {
image = &sharedCache->containedImages[i];
}
});
}
}
}
return image;
}
MachO *dsc_image_get_macho(DyldSharedCacheImage *image)
{
return fat_get_single_slice(image->fat);
}
DyldSharedCacheImage *dsc_lookup_image_by_address(DyldSharedCache *sharedCache, uint64_t address)
{
__block DyldSharedCacheImage *image = NULL;
for (unsigned i = 0; i < sharedCache->containedImageCount; i++) {
MachO *macho = dsc_image_get_macho(&sharedCache->containedImages[i]);
struct segment_command_64 segment;
if (macho_lookup_segment_by_addr(macho, address, &segment) == 0) {
image = &sharedCache->containedImages[i];
}
if (image) break;
}
return image;
}
MachO *dsc_lookup_macho_by_address(DyldSharedCache *sharedCache, uint64_t address, DyldSharedCacheImage **imageHandleOut)
{
DyldSharedCacheImage *image = dsc_lookup_image_by_address(sharedCache, address);
if (image) {
if (imageHandleOut) *imageHandleOut = image;
return dsc_image_get_macho(image);
}
return NULL;
}
DyldSharedCacheImage *dsc_lookup_image_by_path(DyldSharedCache *sharedCache, const char *path)
{
for (unsigned i = 0; i < sharedCache->containedImageCount; i++) {
if (!strcmp(sharedCache->containedImages[i].path, path)) {
return &sharedCache->containedImages[i];
}
}
return NULL;
}
MachO *dsc_lookup_macho_by_path(DyldSharedCache *sharedCache, const char *path, DyldSharedCacheImage **imageHandleOut)
{
DyldSharedCacheImage *image = dsc_lookup_image_by_path(sharedCache, path);
if (image) {
if (imageHandleOut) *imageHandleOut = image;
return dsc_image_get_macho(image);
}
return NULL;
}
int dsc_image_enumerate_symbols(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop))
{
if (_dsc_load_symbols(sharedCache) != 0) return -1;
struct dyld_cache_header *symbolCacheHeader = &sharedCache->files[sharedCache->symbolFile.index]->header;
if (!symbolCacheHeader->localSymbolsOffset) return -1;
char *stringTable = sharedCache->symbolFile.strings;
uint32_t firstSymIdx = image->nlistStartIndex;
uint32_t lastSymIdx = image->nlistStartIndex + image->nlistCount - 1;
if (lastSymIdx > sharedCache->symbolFile.nlistCount) return -1;
for (uint32_t symIdx = firstSymIdx; symIdx <= lastSymIdx; symIdx++) {
uint64_t n_strx = 0;
uint64_t n_value = 0;
uint8_t n_type = 0;
#define _GENERIC_READ_NLIST(nlistType) do { \
struct nlistType *entry = &((struct nlistType *)sharedCache->symbolFile.nlist)[symIdx]; \
n_strx = entry->n_un.n_strx; \
n_value = entry->n_value; \
n_type = entry->n_type; \
} while (0)
if (dsc_is32bit(sharedCache)) {
_GENERIC_READ_NLIST(nlist);
}
else {
_GENERIC_READ_NLIST(nlist_64);
}
#undef _GENERIC_READ_NLIST
if (n_strx > sharedCache->symbolFile.stringsSize) return -1;
bool stop = false;
enumeratorBlock(&stringTable[n_strx], n_type, n_value, &stop);
if (stop) break;
}
return 0;
}
int dsc_image_enumerate_patches(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(unsigned v, void *patchable_location, bool *stop))
{
struct dyld_cache_header *mainHeader = &sharedCache->files[0]->header;
struct dyld_cache_patch_info_v3 *patchInfo = dsc_find_buffer(sharedCache, mainHeader->patchInfoAddr, mainHeader->patchInfoSize);
if (patchInfo->infoV2.patchTableVersion == 4) {
struct dyld_cache_image_got_clients_v3 *gotClients = (void *)((uintptr_t)patchInfo + (patchInfo->gotClientsArrayAddr - mainHeader->patchInfoAddr));
struct dyld_cache_patchable_export_v3 *clientExports = (void *)((uintptr_t)patchInfo + (patchInfo->gotClientExportsArrayAddr - mainHeader->patchInfoAddr));
struct dyld_cache_patchable_location_v4 *locationArray = (void *)((uintptr_t)patchInfo + (patchInfo->gotLocationArrayAddr - mainHeader->patchInfoAddr));
uint32_t patchExportsStartIndex = gotClients[image->index].patchExportsStartIndex;
uint32_t patchExportsEndIndex = patchExportsStartIndex + gotClients[image->index].patchExportsCount;
for (uint32_t i = patchExportsStartIndex; i < patchExportsEndIndex; i++) {
uint32_t patchLocationsStartIndex = clientExports[i].patchLocationsStartIndex;
uint32_t patchLocationsEndIndex = patchLocationsStartIndex + clientExports[i].patchLocationsCount;
for (uint32_t k = clientExports[i].patchLocationsStartIndex; k < patchLocationsEndIndex; k++) {
bool stop = false;
enumeratorBlock(4, &locationArray[k], &stop);
if (stop) return 0;
}
}
}
return 0;
}
int dsc_mapping_enumerate_chained_fixups_on_range(DyldSharedCache *sharedCache, DyldSharedCacheMapping *mapping, uint64_t rangeStart, uint64_t rangeEnd, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop))
{
if (!mapping->slideInfoPtr) return -1;
if (sharedCache->premapSlide != 0) return -1; // XXX: Not supported on premapped dscs
uint32_t version = *(uint32_t *)mapping->slideInfoPtr;
switch (version) {
case 5: {
struct dyld_cache_slide_info5 *info = mapping->slideInfoPtr;
uint64_t startAddr = mapping->vmaddr;
uint64_t endAddr = startAddr + (info->page_starts_count * info->page_size);
uint64_t pageMask = info->page_size - 1;
bool hasRange = false;
if (rangeStart != 0 && rangeEnd != 0) {
if (rangeStart < startAddr || rangeStart >= endAddr) return -1;
startAddr = rangeStart & ~pageMask;
if (rangeEnd <= startAddr || rangeEnd > endAddr) return -1;
endAddr = (rangeEnd + pageMask) & ~pageMask;
hasRange = true;
}
for (uint64_t pageAddr = startAddr; pageAddr < endAddr; pageAddr += info->page_size) {
uint64_t pageIdx = (pageAddr - mapping->vmaddr) / info->page_size;
uint32_t delta = info->page_starts[pageIdx];
union dyld_cache_slide_pointer5* loc = (mapping->ptr + (pageAddr - mapping->vmaddr));
if (delta != DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE) {
delta /= 8; // The first delta is a direct offset, all further ones are (offset / 8)
do {
loc += delta;
delta = loc->auth.next;
uint64_t value = loc->regular.runtimeOffset + info->value_add;
if (!loc->auth.auth) {
value |= ((uint64_t)loc->regular.high8 << 56);
}
DyldSharedCachePointer pointer = (DyldSharedCachePointer){
.location = ((uint64_t)loc - (uint64_t)mapping->ptr) + mapping->vmaddr,
.target = value,
.authenticated = loc->auth.auth,
.key = loc->auth.keyIsData ? PAC_KEY_DA : PAC_KEY_IA,
.diversifier = loc->auth.diversity,
.hasAddressDiversity = loc->auth.addrDiv,
};
if (hasRange) {
if (pointer.location < rangeStart) continue;
if (pointer.location >= rangeEnd) break;
}
bool stop = false;
enumeratorBlock(&pointer, &stop);
if (stop) return 0;
} while (delta != 0);
}
}
break;
}
// XXX: What actually uses version 4???
case 3: {
struct dyld_cache_slide_info3 *info = mapping->slideInfoPtr;
uint64_t startAddr = mapping->vmaddr;
uint64_t endAddr = startAddr + (info->page_starts_count * info->page_size);
uint64_t pageMask = info->page_size - 1;
bool hasRange = false;
if (rangeStart != 0 && rangeEnd != 0) {
if (rangeStart < startAddr || rangeStart >= endAddr) return -1;
startAddr = rangeStart & ~pageMask;
if (rangeEnd <= startAddr || rangeEnd > endAddr) return -1;
endAddr = (rangeEnd + pageMask) & ~pageMask;
hasRange = true;
}
for (uint64_t pageAddr = startAddr; pageAddr < endAddr; pageAddr += info->page_size) {
uint64_t pageIdx = (pageAddr - mapping->vmaddr) / info->page_size;
uint32_t delta = info->page_starts[pageIdx];
union dyld_cache_slide_pointer3* loc = (mapping->ptr + (pageAddr - mapping->vmaddr));
if (delta != DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE) {
delta /= 8; // The first delta is a direct offset, all further ones are (offset / 8)
do {
loc += delta;
delta = loc->auth.offsetToNextPointer;
uint64_t value = 0;
if (loc->auth.authenticated) {
value = loc->auth.offsetFromSharedCacheBase + info->auth_value_add;
}
else {
uint64_t value51 = loc->plain.pointerValue;
uint64_t top8Bits = value51 & 0x0007F80000000000ULL;
uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFFULL;
value = (top8Bits << 13) | bottom43Bits;
}
DyldSharedCachePointer pointer = (DyldSharedCachePointer){
.location = ((uint64_t)loc - (uint64_t)mapping->ptr) + mapping->vmaddr,
.target = value,
.authenticated = loc->auth.authenticated,
.key = loc->auth.key,
.diversifier = loc->auth.diversityData,
.hasAddressDiversity = loc->auth.hasAddressDiversity,
};
if (hasRange) {
if (pointer.location < rangeStart) continue;
if (pointer.location >= rangeEnd) break;
}
bool stop = false;
enumeratorBlock(&pointer, &stop);
if (stop) return 0;
} while (delta != 0);
}
}
break;
}
case 2: {
struct dyld_cache_slide_info2 *info = mapping->slideInfoPtr;
uint16_t *pageStarts = (uint16_t *)((uintptr_t)info + info->page_starts_offset);
uint16_t *pageExtras = (uint16_t *)((uintptr_t)info + info->page_extras_offset);
uint64_t deltaMask = info->delta_mask;
uint64_t valueMask = ~deltaMask;
uint64_t deltaShift = __builtin_ctzll(deltaMask) - 2;
uint64_t startAddr = mapping->vmaddr;
uint64_t endAddr = startAddr + (info->page_starts_count * info->page_size);
uint64_t pageMask = info->page_size - 1;
bool hasRange = false;
if (rangeStart != 0 && rangeEnd != 0) {
if (rangeStart < startAddr || rangeStart >= endAddr) return -1;
startAddr = rangeStart & ~pageMask;
if (rangeEnd <= startAddr || rangeEnd > endAddr) return -1;
endAddr = (rangeEnd + pageMask) & ~pageMask;
hasRange = true;
}
for (uint64_t pageAddr = startAddr; pageAddr < endAddr; pageAddr += info->page_size) {
uint64_t pageIdx = (pageAddr - mapping->vmaddr) / info->page_size;
uint16_t pageEntry = pageStarts[pageIdx];
if (pageEntry == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE) continue;
int (^processChain)(uint32_t pageOffset) = ^(uint32_t pageOffset) {
uint32_t delta = 1;
while (delta != 0) {
uint64_t locAddr = pageAddr + pageOffset;
uint64_t rawValue = 0;
dsc_read_from_vmaddr(sharedCache, locAddr, dsc_is32bit(sharedCache) ? sizeof(uint32_t) : sizeof(uint64_t), &rawValue);
delta = (uint32_t)((rawValue & deltaMask) >> deltaShift);
uint64_t target = (rawValue & valueMask);
if (target != 0) {
target += info->value_add;
}
pageOffset += delta;
DyldSharedCachePointer pointer = (DyldSharedCachePointer){
.location = locAddr,
.target = target,
.authenticated = false,
.key = 0,
.diversifier = 0,
.hasAddressDiversity = false,
};
if (hasRange) {
if (pointer.location < rangeStart) continue;
if (pointer.location >= rangeEnd) break;
}
bool stop = false;
enumeratorBlock(&pointer, &stop);
if (stop) return 0;
}
return 1;
};
if (pageEntry == DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) {
uint16_t chainIdx = (pageEntry & 0x3fff);
bool done = false;
while (!done) {
uint16_t pInfo = pageExtras[chainIdx];
uint32_t pageStartOffset = (pInfo & 0x3fff) * 4;
int r = processChain(pageStartOffset);
if (r == 0) return 0;
done = (pInfo & DYLD_CACHE_SLIDE_PAGE_ATTR_END);
chainIdx++;
}
}
else {
uint32_t pageStartOffset = pageEntry * 4;
int r = processChain(pageStartOffset);
if (r == 0) return 0;
}
}
break;
}
default: {
static bool didShowUnsupportedWarning = false;
if (!didShowUnsupportedWarning) {
fprintf(stderr, "Warning: Unsupported chained fixup version (%u)\n", version);
didShowUnsupportedWarning = true;
}
}
break;
}
return 0;
}
int dsc_mapping_enumerate_chained_fixups(DyldSharedCache *sharedCache, DyldSharedCacheMapping *mapping, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop))
{
return dsc_mapping_enumerate_chained_fixups_on_range(sharedCache, mapping, 0, 0, enumeratorBlock);
}
int dsc_enumerate_chained_fixups(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop))
{
for (uint64_t i = 0; i < sharedCache->mappingCount; i++) {
__block bool stopAll = false;
DyldSharedCacheMapping *mapping = &sharedCache->mappings[i];
dsc_mapping_enumerate_chained_fixups(sharedCache, mapping, ^(DyldSharedCachePointer *pointer, bool *stop) {
enumeratorBlock(pointer, &stopAll);
*stop = stopAll;
});
if (stopAll) break;
}
return 0;
}
int dsc_image_enumerate_chained_fixups(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop))
{
/*
Please note that in iOS 18 and macOS Sonoma, GOTs were unified and they are no longer associated to the image machos
This means multiple images use the same GOTs, which intern means this function will not enumerate over them
This might be fixable by parsing the __auth_stub functions, but that is non trivial
*/
MachO *macho = fat_get_single_slice(image->fat);
if (macho) {
__block bool stopAll = false;
macho_enumerate_segments(macho, ^(struct segment_command_64 *segment, bool *stop) {
//printf("%.*s %#llx %#llx\n", (int)sizeof(segment->segname), segment->segname, segment->vmaddr, segment->vmsize);
if (!strncmp(segment->segname, "__DATA", 6)) /*__DATA, __DATA_DIRTY, __DATA_CONST */ {
DyldSharedCacheMapping* mapping = dsc_lookup_mapping(sharedCache, segment->vmaddr, segment->vmsize);
if (mapping) {
dsc_mapping_enumerate_chained_fixups_on_range(sharedCache, mapping, segment->vmaddr, segment->vmaddr + segment->vmsize, ^(DyldSharedCachePointer *pointer, bool *stop2){
enumeratorBlock(pointer, &stopAll);
*stop2 = stopAll;
});
}
}
*stop = stopAll;
});
}
return 0;
}
uint64_t dsc_get_base_address(DyldSharedCache *sharedCache)
{
return sharedCache->baseAddress;
}
void dsc_free(DyldSharedCache *sharedCache)
{
if (sharedCache->fileCount > 0) {
for (unsigned i = 0; i < sharedCache->fileCount; i++) {
close(sharedCache->files[i]->fd);
free(sharedCache->files[i]->filepath);
free(sharedCache->files[i]);
}
free(sharedCache->files);
}
if (sharedCache->mappings) {
for (unsigned i = 0; i < sharedCache->mappingCount; i++) {
if (!sharedCache->premapSlide && sharedCache->mappings[i].ptr) {
munmap(sharedCache->mappings[i].ptr, sharedCache->mappings[i].size);
}
if (sharedCache->mappings[i].slideInfoPtr) {
free(sharedCache->mappings[i].slideInfoPtr);
}
}
free(sharedCache->mappings);
}
if (sharedCache->containedImages) {
for (unsigned i = 0; i < sharedCache->containedImageCount; i++) {
if (sharedCache->containedImages[i].path) {
free(sharedCache->containedImages[i].path);
}
if (sharedCache->containedImages[i].fat) {
fat_free(sharedCache->containedImages[i].fat);
}
}
free(sharedCache->containedImages);
}
if (sharedCache->symbolFile.strings) {
uintptr_t stringsPage = (uintptr_t)sharedCache->symbolFile.strings & ~PAGE_MASK;
uintptr_t stringsPageOff = (uintptr_t)sharedCache->symbolFile.strings & PAGE_MASK;
munmap((void *)stringsPage, sharedCache->symbolFile.stringsSize + stringsPageOff);
}
if (sharedCache->symbolFile.nlist) {
free(sharedCache->symbolFile.nlist);
}
free(sharedCache);
}
+116
View File
@@ -0,0 +1,116 @@
#ifndef DYLD_SHARED_CACHE_H
#define DYLD_SHARED_CACHE_H
#include "dyld_cache_format.h"
#include <uuid/uuid.h>
#include "CachePatching.h"
#include <stddef.h>
#include <stdbool.h>
typedef struct MachO MachO;
typedef struct Fat Fat;
typedef struct DyldSharedCacheFile {
char *filepath;
size_t filesize;
int fd;
struct dyld_cache_header header;
} DyldSharedCacheFile;
typedef struct DyldSharedCacheMapping {
uint64_t vmaddr;
uint64_t fileoff;
void *ptr;
uint64_t size;
uint32_t maxProt;
uint32_t initProt;
uint64_t flags;
// ABI stable until here
void *slideInfoPtr;
uint64_t slideInfoSize;
struct DyldSharedCacheFile *file;
} DyldSharedCacheMapping;
typedef struct DyldSharedCacheImage {
uint64_t address;
uint64_t size;
uint64_t index;
uuid_t uuid;
char *path;
uint32_t nlistStartIndex;
uint32_t nlistCount;
Fat *fat;
} DyldSharedCacheImage;
typedef struct DyldSharedCache {
unsigned fileCount;
DyldSharedCacheFile **files;
struct {
bool loaded;
unsigned index;
void *nlist;
uint32_t nlistCount;
char *strings;
uint32_t stringsSize;
} symbolFile;
unsigned mappingCount;
DyldSharedCacheMapping *mappings;
uint64_t baseAddress;
uint32_t premapSlide;
uint32_t cputype;
uint32_t cpusubtype;
uint64_t containedImageCount;
DyldSharedCacheImage *containedImages;
} DyldSharedCache;
typedef struct DyldSharedCachePointer {
uint64_t location;
uint64_t target;
bool authenticated;
uint8_t key;
uint16_t diversifier;
bool hasAddressDiversity;
} DyldSharedCachePointer;
enum PAC_KEY {
PAC_KEY_IA = 0,
PAC_KEY_IB = 1,
PAC_KEY_DA = 2,
PAC_KEY_DB = 3,
};
DyldSharedCache *dsc_init_from_path_premapped(const char *path, uint32_t premapSlide);
DyldSharedCache *dsc_init_from_path(const char *path);
bool dsc_is32bit(DyldSharedCache *sharedCache);
void dsc_enumerate_files(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *filepath, size_t filesize, struct dyld_cache_header *header));
void dsc_enumerate_mappings(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCacheMapping *mapping, DyldSharedCacheFile *sourceFile, bool *stop));
DyldSharedCacheMapping *dsc_lookup_mapping(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size);
void *dsc_find_buffer(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size);
int dsc_read_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, size_t size, void *outBuf);
int dsc_read_string_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, char **outString);
uint64_t dsc_fileoff_to_vmaddr(DyldSharedCache *sharedCache, DyldSharedCacheFile *file, uint64_t fileoff);
uint64_t dsc_vmaddr_to_fileoff(DyldSharedCache *sharedCache, uint64_t vmaddr, DyldSharedCacheFile **fileOut);
void dsc_enumerate_images(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *path, DyldSharedCacheImage *imageHandle, MachO *imageMachO, bool *stop));
DyldSharedCacheImage *dsc_find_image_for_section_address(DyldSharedCache *sharedCache, uint64_t address);
MachO *dsc_image_get_macho(DyldSharedCacheImage *image);
DyldSharedCacheImage *dsc_lookup_image_by_address(DyldSharedCache *sharedCache, uint64_t address);
MachO *dsc_lookup_macho_by_address(DyldSharedCache *sharedCache, uint64_t address, DyldSharedCacheImage **imageHandleOut);
DyldSharedCacheImage *dsc_lookup_image_by_path(DyldSharedCache *sharedCache, const char *path);
MachO *dsc_lookup_macho_by_path(DyldSharedCache *sharedCache, const char *path, DyldSharedCacheImage **imageHandleOut);
int dsc_enumerate_chained_fixups(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop));
int dsc_image_enumerate_symbols(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop));
int dsc_image_enumerate_patches(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(unsigned v, void *patchable_location, bool *stop));
int dsc_image_enumerate_chained_fixups(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop));
uint64_t dsc_get_base_address(DyldSharedCache *sharedCache);
void dsc_free(DyldSharedCache *sharedCache);
#endif
+508
View File
@@ -0,0 +1,508 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <CoreFoundation/CoreFoundation.h>
#include "CSBlob.h"
#include "MachO.h"
#include "MemoryStream.h"
#include "FileStream.h"
#include "MachOByteOrder.h"
#include "Entitlements.h"
CFDataRef get_property_list_data(CFPropertyListRef plist) {
CFDataRef data = NULL;
CFErrorRef error = NULL;
data = CFPropertyListCreateData(
kCFAllocatorDefault,
plist,
kCFPropertyListXMLFormat_v1_0,
0,
&error
);
if (data == NULL) {
if (error != NULL) {
CFShow(error);
CFRelease(error);
}
}
return data;
}
CFPropertyListRef create_property_list_from_file(const char *xmlFile) {
FILE *file = fopen(xmlFile, "rb");
if (file == NULL) {
fprintf(stderr, "Error: failed to open entitlements file\n");
return NULL;
}
fseek(file, 0, SEEK_END);
size_t entitlementsSize = ftell(file);
fseek(file, 0, SEEK_SET);
void *entitlements = malloc(entitlementsSize);
if (entitlements == NULL) {
fprintf(stderr, "Error: failed to allocate memory for entitlements file\n");
fclose(file);
return NULL;
}
if (fread(entitlements, 1, entitlementsSize, file) != entitlementsSize) {
fprintf(stderr, "Error: failed to read entitlements file\n");
free(entitlements);
fclose(file);
return NULL;
}
fclose(file);
CFDataRef entitlementsData = CFDataCreate(NULL, entitlements, entitlementsSize);
if (entitlementsData == NULL) {
fprintf(stderr, "Error: failed to create CFData from entitlements file\n");
free(entitlements);
return NULL;
}
CFPropertyListFormat format;
CFErrorRef error;
CFPropertyListRef entitlementsPlist = CFPropertyListCreateWithData(NULL, entitlementsData, kCFPropertyListMutableContainersAndLeaves, &format, &error);
if (entitlementsPlist == NULL) {
fprintf(stderr, "Error: failed to parse entitlements file\n");
CFRelease(entitlementsData);
CFRelease(error);
free(entitlements);
return NULL;
}
CFRelease(entitlementsData);
free(entitlements);
return entitlementsPlist;
}
CS_DecodedBlob *create_xml_entitlements_blob(const char *entitlementsFile) {
CFPropertyListRef entitlementsPlist = create_property_list_from_file(entitlementsFile);
if (entitlementsPlist == NULL) {
fprintf(stderr, "Error: failed to create property list from entitlements file\n");
return NULL;
}
CFDataRef entitlementsPlistData = get_property_list_data(entitlementsPlist);
if (entitlementsPlistData == NULL) {
fprintf(stderr, "Error: failed to convert entitlements property list to data\n");
CFRelease(entitlementsPlist);
return NULL;
}
uint32_t *genericBlob = malloc(sizeof(CSMAGIC_EMBEDDED_ENTITLEMENTS) + sizeof(uint32_t) + CFDataGetLength(entitlementsPlistData));
if (genericBlob == NULL) {
fprintf(stderr, "Error: failed to allocate memory for entitlements blob\n");
CFRelease(entitlementsPlist);
CFRelease(entitlementsPlistData);
return NULL;
}
// Messy, but it works
*genericBlob = HOST_TO_BIG(CSMAGIC_EMBEDDED_ENTITLEMENTS);
uint32_t totalSize = sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t) + CFDataGetLength(entitlementsPlistData);
*(genericBlob + 1) = HOST_TO_BIG(totalSize);
memcpy((char *)genericBlob + sizeof(CSMAGIC_EMBEDDED_ENTITLEMENTS) + sizeof(uint32_t), CFDataGetBytePtr(entitlementsPlistData), CFDataGetLength(entitlementsPlistData));
CS_DecodedBlob *entitlementsBlob = csd_blob_init(CSSLOT_ENTITLEMENTS, (CS_GenericBlob *)genericBlob);
if (entitlementsBlob == NULL) {
fprintf(stderr, "Error: failed to create entitlements blob\n");
free(genericBlob);
}
CFRelease(entitlementsPlist);
CFRelease(entitlementsPlistData);
return entitlementsBlob;
}
enum EntitlementType {
ENTITLEMENT_TYPE_BOOL,
ENTITLEMENT_TYPE_INT,
ENTITLEMENT_TYPE_STRING,
ENTITLEMENT_TYPE_ARRAY,
};
enum ArrayType {
ARRAY_TYPE_STRING,
ARRAY_TYPE_INT,
};
typedef struct {
uint32_t nItems;
enum ArrayType type;
void *items;
} EntitlementArray;
typedef struct {
enum EntitlementType type;
union {
bool boolValue;
int intValue;
char *stringValue;
EntitlementArray arrayValue;
};
} EntitlementValue;
typedef struct {
char *key;
EntitlementValue value;
} Entitlement;
typedef struct {
uint32_t nEntitlements;
Entitlement *entitlements;
} Entitlements;
void print_entitlement_information(Entitlement *entitlement) {
printf("Key: %s\n", entitlement->key);
switch (entitlement->value.type) {
case ENTITLEMENT_TYPE_BOOL:
printf("Value: %s\n", entitlement->value.boolValue ? "true" : "false");
break;
case ENTITLEMENT_TYPE_INT:
printf("Value: %d\n", entitlement->value.intValue);
break;
case ENTITLEMENT_TYPE_STRING:
printf("Value: %s\n", entitlement->value.stringValue);
break;
case ENTITLEMENT_TYPE_ARRAY:
printf("Values:\n");
for (int i = 0; i < entitlement->value.arrayValue.nItems; i++) {
printf("%d: %s\n", i + 1, ((char **)entitlement->value.arrayValue.items)[i]);
}
break;
}
}
DEREncodedItem *der_encode_array(EntitlementArray array) {
// Encode each item in the array, then create a sequence of the items
DEREncodedItem **items = malloc(sizeof(DEREncodedItem *) * array.nItems);
for (int i = 0; i < array.nItems; i++) {
// Encode the item
if (array.type == ARRAY_TYPE_STRING) {
items[i] = der_encode_utf8_string(((char **)array.items)[i]);
} else {
items[i] = der_encode_integer(((int *)array.items)[i]);
}
}
// Create a sequence of the items
DEREncodedItem *sequence = der_encode_sequence(items, array.nItems);
if (sequence == NULL) {
fprintf(stderr, "Error: failed to encode sequence\n");
return NULL;
}
// Free the encoded items
for (int i = 0; i < array.nItems; i++) {
der_free_encoded_item(items[i]);
}
free(items);
return sequence;
}
DEREncodedItem *der_encode_entitlement(Entitlement entitlement) {
if (!entitlement.key) {
fprintf(stderr, "Error: missing entitlement key\n");
return NULL;
}
DEREncodedItem *key = der_encode_utf8_string(entitlement.key);
if (key == NULL) {
fprintf(stderr, "Error: failed to encode entitlement key\n");
return NULL;
}
DEREncodedItem *value = NULL;
switch (entitlement.value.type) {
case ENTITLEMENT_TYPE_BOOL:
value = der_encode_boolean(entitlement.value.boolValue);
break;
case ENTITLEMENT_TYPE_INT:
value = der_encode_integer(entitlement.value.intValue);
break;
case ENTITLEMENT_TYPE_STRING:
value = der_encode_utf8_string(entitlement.value.stringValue);
break;
case ENTITLEMENT_TYPE_ARRAY:
value = der_encode_array(entitlement.value.arrayValue);
break;
}
if (value == NULL) {
fprintf(stderr, "Error: failed to encode entitlement value\n");
der_free_encoded_item(key);
return NULL;
}
DEREncodedItem *items[] = {key, value};
DEREncodedItem *sequence = der_encode_sequence(items, 2);
if (sequence == NULL) {
fprintf(stderr, "Error: failed to encode sequence\n");
der_free_encoded_item(key);
der_free_encoded_item(value);
return NULL;
}
der_free_encoded_item(key);
der_free_encoded_item(value);
return sequence;
}
DEREncodedItem *der_encode_entitlements(DEREncodedItem *entitlements[], uint32_t nEntitlements) {
DEREncodedItem *entitlementsSet = der_encode_set(entitlements, nEntitlements);
if (entitlementsSet == NULL) {
fprintf(stderr, "Error: failed to encode set of entitlements\n");
return NULL;
}
for(int i = 0; i < nEntitlements; i++) {
der_free_encoded_item(entitlements[i]);
}
return entitlementsSet;
}
static void append_to_entitlements(const void* key, const void* value, void* context) {
Entitlements *entitlements = (Entitlements *)context;
CFStringRef keyString = (CFStringRef)key;
CFIndex keyLength = CFStringGetLength(keyString);
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(keyLength, kCFStringEncodingUTF8);
char *keyCString = malloc(maxSize);
if (keyCString == NULL) {
fprintf(stderr, "Error: failed to allocate memory for entitlement key\n");
return;
}
if (!CFStringGetCString(keyString, keyCString, maxSize, kCFStringEncodingUTF8)) {
fprintf(stderr, "Error: failed to convert entitlement key to C string\n");
free(keyCString);
return;
}
EntitlementValue entitlementValue;
CFTypeID valueType = CFGetTypeID(value);
if (valueType == CFBooleanGetTypeID()) {
entitlementValue.type = ENTITLEMENT_TYPE_BOOL;
entitlementValue.boolValue = CFBooleanGetValue((CFBooleanRef)value);
} else if (valueType == CFNumberGetTypeID()) {
entitlementValue.type = ENTITLEMENT_TYPE_INT;
CFNumberGetValue((CFNumberRef)value, kCFNumberSInt32Type, &entitlementValue.intValue);
} else if (valueType == CFStringGetTypeID()) {
entitlementValue.type = ENTITLEMENT_TYPE_STRING;
CFIndex valueLength = CFStringGetLength((CFStringRef)value);
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8);
char *valueCString = malloc(maxSize);
if (valueCString == NULL) {
fprintf(stderr, "Error: failed to allocate memory for entitlement value\n");
free(keyCString);
return;
}
if (!CFStringGetCString((CFStringRef)value, valueCString, maxSize, kCFStringEncodingUTF8)) {
fprintf(stderr, "Error: failed to convert entitlement value to C string\n");
free(keyCString);
free(valueCString);
return;
}
entitlementValue.stringValue = valueCString;
} else if (valueType == CFArrayGetTypeID()) {
entitlementValue.type = ENTITLEMENT_TYPE_ARRAY;
CFArrayRef array = (CFArrayRef)value;
CFIndex nItems = CFArrayGetCount(array);
char **items = malloc(sizeof(char *) * nItems);
if (items == NULL) {
fprintf(stderr, "Error: failed to allocate memory for entitlement array\n");
free(keyCString);
return;
}
for (int i = 0; i < nItems; i++) {
CFStringRef item = (CFStringRef)CFArrayGetValueAtIndex(array, i);
CFIndex itemLength = CFStringGetLength(item);
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(itemLength, kCFStringEncodingUTF8);
char *itemCString = malloc(maxSize);
if (itemCString == NULL) {
fprintf(stderr, "Error: failed to allocate memory for entitlement\n");
for (int j = 0; j < i; j++) {
free(items[j]);
}
free(items);
free(keyCString);
return;
}
if (!CFStringGetCString(item, itemCString, maxSize, kCFStringEncodingUTF8)) {
fprintf(stderr, "Error: failed to convert entitlement to C string\n");
for (int j = 0; j < i; j++) {
free(items[j]);
}
free(items);
free(keyCString);
free(itemCString);
return;
}
items[i] = itemCString;
}
EntitlementArray arrayValue = {
.nItems = nItems,
.type = ARRAY_TYPE_STRING,
.items = items,
};
entitlementValue.arrayValue = arrayValue;
} else {
fprintf(stderr, "Error: unknown entitlement value type\n");
free(keyCString);
return;
}
Entitlement newEntitlement = {
.key = keyCString,
.value = entitlementValue,
};
// print_entitlement_information(&newEntitlement);
entitlements->entitlements[entitlements->nEntitlements] = newEntitlement;
entitlements->nEntitlements++;
return;
}
Entitlements *parse_entitlements_from_property_list(const char *entitlementsFile) {
CFPropertyListRef entitlementsPlist = create_property_list_from_file(entitlementsFile);
if (entitlementsPlist == NULL) {
fprintf(stderr, "Error: failed to create property list from entitlements file\n");
return NULL;
}
if (CFGetTypeID(entitlementsPlist) != CFDictionaryGetTypeID()) {
fprintf(stderr, "Error: entitlements file is not a dictionary\n");
CFRelease(entitlementsPlist);
return NULL;
}
CFDictionaryRef entitlementsDict = (CFDictionaryRef)entitlementsPlist;
CFIndex nEntitlements = CFDictionaryGetCount(entitlementsDict);
Entitlements *entitlements = malloc(sizeof(Entitlements));
if (entitlements == NULL) {
fprintf(stderr, "Error: failed to allocate memory for entitlements\n");
CFRelease(entitlementsPlist);
return NULL;
}
entitlements->nEntitlements = 0;
entitlements->entitlements = malloc(sizeof(Entitlement) * nEntitlements);
if (entitlements->entitlements == NULL) {
fprintf(stderr, "Error: failed to allocate memory for entitlements\n");
CFRelease(entitlementsPlist);
free(entitlements);
return NULL;
}
CFDictionaryApplyFunction(entitlementsDict, append_to_entitlements, entitlements);
CFRelease(entitlementsPlist);
return entitlements;
}
DEREncodedItem *encode_entitlements_property_list(const char *entitlementsFile) {
Entitlements *entitlements = parse_entitlements_from_property_list(entitlementsFile);
if (entitlements == NULL) {
fprintf(stderr, "Error: failed to parse entitlements\n");
return NULL;
}
printf("Parsed %d entitlements from entitlements file.\n", entitlements->nEntitlements);
DEREncodedItem **encodedEntitlements = malloc(sizeof(DEREncodedItem *) * entitlements->nEntitlements);
if (encodedEntitlements == NULL) {
fprintf(stderr, "Error: failed to allocate memory for encoded entitlements\n");
free(entitlements->entitlements);
free(entitlements);
return NULL;
}
for (int i = 0; i < entitlements->nEntitlements; i++) {
encodedEntitlements[i] = der_encode_entitlement(entitlements->entitlements[i]);
if (encodedEntitlements[i] == NULL) {
fprintf(stderr, "Error: failed to encode entitlement\n");
for (int j = 0; j < i; j++) {
der_free_encoded_item(encodedEntitlements[j]);
}
free(encodedEntitlements);
free(entitlements->entitlements);
free(entitlements);
return NULL;
}
}
DEREncodedItem *entitlementsSet = der_encode_entitlements(encodedEntitlements, entitlements->nEntitlements);
if (entitlementsSet == NULL) {
fprintf(stderr, "Error: failed to encode entitlements\n");
for (int i = 0; i < entitlements->nEntitlements; i++) {
der_free_encoded_item(encodedEntitlements[i]);
}
free(encodedEntitlements);
free(entitlements->entitlements);
free(entitlements);
return NULL;
}
free(encodedEntitlements);
free(entitlements->entitlements);
free(entitlements);
return entitlementsSet;
}
CS_DecodedBlob *create_der_entitlements_blob(const char *entitlementsFile) {
DEREncodedItem *entitlements = encode_entitlements_property_list(entitlementsFile);
if (entitlements == NULL) {
fprintf(stderr, "Error: failed to encode entitlements\n");
return NULL;
}
uint32_t *genericBlob = malloc(sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t) + entitlements->length);
if (genericBlob == NULL) {
fprintf(stderr, "Error: failed to allocate memory for entitlements blob\n");
der_free_encoded_item(entitlements);
return NULL;
}
*genericBlob = HOST_TO_BIG(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS);
uint32_t totalSize = sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t) + entitlements->length;
*(genericBlob + 1) = HOST_TO_BIG(totalSize);
memcpy((char *)genericBlob + sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t), entitlements->data, entitlements->length);
CS_DecodedBlob *entitlementsBlob = csd_blob_init(CSSLOT_ENTITLEMENTS, (CS_GenericBlob *)genericBlob);
if (entitlementsBlob == NULL) {
fprintf(stderr, "Error: failed to create entitlements blob\n");
free(genericBlob);
}
der_free_encoded_item(entitlements);
return entitlementsBlob;
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef ENTITLEMENTS_H
#define ENTITLEMENTS_H
#include "CSBlob.h"
#include "DER.h"
CS_DecodedBlob *create_xml_entitlements_blob(const char *entitlementsFile);
CS_DecodedBlob *create_der_entitlements_blob(const char *entitlementsFile);
#endif // ENTITLEMENTS_H
+219
View File
@@ -0,0 +1,219 @@
#include <mach-o/fat.h>
#include <stdbool.h>
#include "Fat.h"
#include "MachO.h"
#include "MachOByteOrder.h"
#include "FileStream.h"
#include "MemoryStream.h"
int fat_read_at_offset(Fat *fat, uint64_t offset, size_t size, void *outBuf)
{
return memory_stream_read(fat->stream, offset, size, outBuf);
}
MemoryStream *fat_get_stream(Fat *fat)
{
return fat->stream;
}
int fat_parse_slices(Fat *fat)
{
// Get size of file
size_t fileSize = memory_stream_get_size(fat->stream);
if (fileSize == MEMORY_STREAM_SIZE_INVALID) {
printf("Error: Failed to parse fat slices, memory_stream_get_size returned MEMORY_STREAM_SIZE_INVALID\n");
return -1;
}
// Read the fat header
struct fat_header fatHeader;
fat_read_at_offset(fat, 0, sizeof(fatHeader), &fatHeader);
FAT_HEADER_APPLY_BYTE_ORDER(&fatHeader, BIG_TO_HOST_APPLIER);
// Check if the file is a fat file
if (fatHeader.magic == FAT_MAGIC || fatHeader.magic == FAT_MAGIC_64) {
//printf("Fat header found! Magic: 0x%x.\n", fatHeader.magic);
bool is64 = fatHeader.magic == FAT_MAGIC_64;
// Sanity check the number of MachOs
if (fatHeader.nfat_arch > 5 || fatHeader.nfat_arch < 1) {
printf("Error: invalid number of MachO slices (%d), this likely means you are not using an iOS MachO.\n", fatHeader.nfat_arch);
return -1;
}
fat->slicesCount = fatHeader.nfat_arch;
fat->slices = malloc(sizeof(MachO*) * fat->slicesCount);
memset(fat->slices, 0, sizeof(MachO*) * fat->slicesCount);
// Iterate over all machOs
for (uint32_t i = 0; i < fatHeader.nfat_arch; i++) {
struct fat_arch_64 arch64 = {0};
if (is64) {
// Read the arch descriptor
fat_read_at_offset(fat, sizeof(struct fat_header) + i * sizeof(arch64), sizeof(arch64), &arch64);
FAT_ARCH_64_APPLY_BYTE_ORDER(&arch64, BIG_TO_HOST_APPLIER);
}
else {
// Read the fat arch structure
struct fat_arch arch = {0};
fat_read_at_offset(fat, sizeof(struct fat_header) + i * sizeof(arch), sizeof(arch), &arch);
FAT_ARCH_APPLY_BYTE_ORDER(&arch, BIG_TO_HOST_APPLIER);
// Convert fat_arch to fat_arch_64
arch64 = (struct fat_arch_64){
.cputype = arch.cputype,
.cpusubtype = arch.cpusubtype,
.offset = (uint64_t)arch.offset,
.size = (uint64_t)arch.size,
.align = arch.align,
.reserved = 0,
};
}
MemoryStream *machOStream = memory_stream_softclone(fat->stream);
int r = memory_stream_trim(machOStream, arch64.offset, fileSize - (arch64.offset + arch64.size));
if (r == 0) {
fat->slices[i] = macho_init(machOStream, arch64);
if (!fat->slices[i]) return -1;
}
}
} else {
// Not Fat? Parse single slice
fat->slicesCount = 1;
fat->slices = malloc(sizeof(MachO) * fat->slicesCount);
memset(fat->slices, 0, sizeof(MachO) * fat->slicesCount);
MemoryStream *machOStream = memory_stream_softclone(fat->stream);
struct mach_header machHeader;
memory_stream_read(machOStream, 0, sizeof(machHeader), &machHeader);
MACH_HEADER_APPLY_BYTE_ORDER(&machHeader, LITTLE_TO_HOST_APPLIER);
struct fat_arch_64 singleArch = {0};
singleArch.cpusubtype = machHeader.cpusubtype;
singleArch.cputype = machHeader.cputype;
singleArch.offset = 0;
singleArch.size = fileSize;
singleArch.align = 0x4000;
MachO *singleSlice = macho_init(machOStream, singleArch);
if (!singleSlice) return -1;
fat->slices[0] = singleSlice;
}
//printf("Found %u MachO slice%s\n", fat->slicesCount, fat->slicesCount > 1 ? "s." : ".");
return 0;
}
void fat_enumerate_slices(Fat *fat, void (^enumBlock)(MachO *macho, bool *stop))
{
for (uint32_t i = 0; i < fat->slicesCount; i++) {
MachO *curMacho = fat->slices[i];
bool stop = false;
enumBlock(curMacho, &stop);
if (stop) break;
}
}
MachO *fat_find_slice(Fat *fat, cpu_type_t cputype, cpu_subtype_t cpusubtype)
{
for (uint32_t i = 0; i < fat->slicesCount; i++) {
MachO *curMacho = fat->slices[i];
if (curMacho) {
if (curMacho->machHeader.cputype == cputype && curMacho->machHeader.cpusubtype == cpusubtype) {
return curMacho;
}
}
}
return NULL;
}
MachO *fat_get_single_slice(Fat *fat)
{
if (fat->slicesCount == 1) {
return fat->slices[0];
}
return NULL;
}
void fat_free(Fat *fat)
{
if (fat->slices != NULL) {
for (int i = 0; i < fat->slicesCount; i++) {
if (fat->slices[i]) {
macho_free(fat->slices[i]);
}
}
free(fat->slices);
}
if (fat->stream) memory_stream_free(fat->stream);
free(fat);
}
Fat *fat_init_from_memory_stream(MemoryStream *stream)
{
Fat *fat = malloc(sizeof(Fat));
if (!fat) return NULL;
memset(fat, 0, sizeof(Fat));
fat->stream = stream;
if (fat_parse_slices(fat) != 0) goto fail;
//size_t size = memory_stream_get_size(fat->stream);
//printf("File size 0x%zx bytes, MachO slice count %u.\n", size, fat->slicesCount);
return fat;
fail:
fat->stream = NULL; // The Fat only "owns" the stream when this function does not fail
fat_free(fat);
return NULL;
}
Fat *fat_dsc_init_from_memory_stream(MemoryStream *stream, DyldSharedCache *containingCache, DyldSharedCacheImage *cacheImage)
{
Fat *fat = fat_init_from_memory_stream(stream);
if (fat) {
for (int i = 0; i < fat->slicesCount; i++) {
fat->slices[i]->containingCache = containingCache;
fat->slices[i]->cacheImage = cacheImage;
}
}
return fat;
}
Fat *fat_init_from_path(const char *filePath)
{
MemoryStream *stream = file_stream_init_from_path(filePath, 0, FILE_STREAM_SIZE_AUTO, 0);
if (stream) {
Fat *fat = fat_init_from_memory_stream(stream);
if (!fat) {
memory_stream_free(stream);
}
return fat;
}
return NULL;
}
Fat *fat_create_for_macho_array(char *firstInputPath, MachO **machoArray, int machoArrayCount) {
Fat *fat = fat_init_from_path(firstInputPath);
for (int i = 1; i < machoArrayCount; i++) {
if (fat_add_macho(fat, machoArray[i]) != 0) {
printf("Error: failed to add MachO to Fat.\n");
fat_free(fat);
return NULL;
}
}
return fat;
}
int fat_add_macho(Fat *fat, MachO *macho)
{
fat->slicesCount++;
fat->slices = realloc(fat->slices, sizeof(MachO*) * fat->slicesCount);
if (!fat->slices) return -1;
fat->slices[fat->slicesCount - 1] = macho;
return 0;
}
+57
View File
@@ -0,0 +1,57 @@
#ifndef MACHO_H
#define MACHO_H
#include <stdio.h>
#include <libkern/OSByteOrder.h>
#include <mach/mach.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <sys/stat.h>
#include "MemoryStream.h"
typedef struct MachO MachO;
typedef struct DyldSharedCache DyldSharedCache;
typedef struct DyldSharedCacheImage DyldSharedCacheImage;
// A Fat structure can either represent a fat file with multiple slices, in which the slices will be loaded into the slices attribute
// 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
typedef struct Fat
{
MemoryStream *stream;
MachO **slices;
uint32_t slicesCount;
int fileDescriptor;
} Fat;
int fat_read_at_offset(Fat *fat, uint64_t offset, size_t size, void *outBuf);
MemoryStream *fat_get_stream(Fat *fat);
// Initialise a Fat structure from a memory stream
Fat *fat_init_from_memory_stream(MemoryStream *stream);
// Initialise a FAT structure from a memory stream of something that is inside a dyld shared cache
Fat *fat_dsc_init_from_memory_stream(MemoryStream *stream, DyldSharedCache *containingCache, DyldSharedCacheImage *cacheImage);
// Initialise a FAT structure using the path to the file
Fat *fat_init_from_path(const char *filePath);
// Find macho with cputype and cpusubtype in Fat, returns NULL if not found
MachO *fat_find_slice(Fat *fat, cpu_type_t cputype, cpu_subtype_t cpusubtype);
// Enumerate all slices contained in Fat
void fat_enumerate_slices(Fat *fat, void (^enumBlock)(MachO *macho, bool *stop));
// If Fat only has a single slice, return it
MachO *fat_get_single_slice(Fat *fat);
// Create a Fat structure from an array of MachO structures
Fat *fat_create_for_macho_array(char *firstInputPath, MachO **machoArray, int machoArrayCount);
// Add a MachO to the Fat structure
int fat_add_macho(Fat *fat, MachO *macho);
// Free all elements of the Fat structure
void fat_free(Fat *fat);
#endif // MACHO_H
+210
View File
@@ -0,0 +1,210 @@
#include "FileStream.h"
#include <sys/fcntl.h>
#include <errno.h>
#include <os/log.h>
static int _file_stream_context_is_trimmed(FileStreamContext *context)
{
return (context->bufferStart != 0 || context->bufferSize != context->fileSize);
}
static int file_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf)
{
FileStreamContext *context = stream->context;
lseek(context->fd, context->bufferStart + offset, SEEK_SET);
return read(context->fd, outBuf, size);
}
static int file_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf)
{
FileStreamContext *context = stream->context;
// we can't write to non mutable files
if ((stream->flags & MEMORY_STREAM_FLAG_MUTABLE) == 0) return -1;
// we can't write to files we don't own
if ((stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) == 0) return -1;
size_t sizeToExpand = 0;
// only expand when possible
if ((context->bufferStart + offset + size) > context->fileSize) {
if (((stream->flags | MEMORY_STREAM_FLAG_AUTO_EXPAND) == 0) || _file_stream_context_is_trimmed(context)) {
printf("Error: file_stream_write failed, file is not auto expandable.\n");
return -1;
}
sizeToExpand = (context->bufferStart + offset + size) - context->fileSize;
}
// this is not supported for now: TODO fill with 0's then append the rest
if (context->bufferStart + offset > context->fileSize) return -1;
context->fileSize += sizeToExpand;
context->bufferSize += sizeToExpand;
lseek(context->fd, context->bufferStart + offset, SEEK_SET);
return write(context->fd, inBuf, size);
}
static int file_stream_get_size(MemoryStream *stream, size_t *sizeOut)
{
FileStreamContext *context = stream->context;
*sizeOut = context->bufferSize;
return 0;
}
static MemoryStream *file_stream_softclone(MemoryStream *stream)
{
FileStreamContext *context = stream->context;
return file_stream_init_from_file_descriptor_nodup(context->fd, context->bufferStart, context->bufferSize, 0);
}
static MemoryStream *file_stream_hardclone(MemoryStream *stream)
{
FileStreamContext *context = stream->context;
int thisFlags = 0;
if (stream->flags & MEMORY_STREAM_FLAG_MUTABLE) {
thisFlags |= FILE_STREAM_FLAG_WRITABLE;
}
if (stream->flags & MEMORY_STREAM_FLAG_AUTO_EXPAND) {
thisFlags |= FILE_STREAM_FLAG_AUTO_EXPAND;
}
return file_stream_init_from_file_descriptor(context->fd, context->bufferStart, context->bufferSize, thisFlags);
}
static int file_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd)
{
FileStreamContext *context = stream->context;
if ((int64_t)context->bufferSize - (trimAtStart + trimAtEnd) < 0) {
return -1;
}
if ((stream->flags & MEMORY_STREAM_FLAG_MUTABLE) && !_file_stream_context_is_trimmed(context)) {
// If this stream is mutable, we want to actually trim the file itself
uint32_t newSize = context->bufferSize - trimAtStart - trimAtEnd;
memory_stream_copy_data(stream, trimAtStart, stream, 0, newSize);
ftruncate(context->fd, newSize);
}
else {
// Else just trim the part of the file that this buffer represents
context->bufferStart += trimAtStart;
context->bufferSize -= (trimAtStart + trimAtEnd);
}
return 0;
}
static int file_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd)
{
FileStreamContext *context = stream->context;
// Expanding at start is not supported (for now?)
if (expandAtStart != 0) return -1;
// If this buffer is trimmed, expanding is also not supported
if (_file_stream_context_is_trimmed(context)) return -1;
lseek(context->fd, 0, SEEK_END);
for (size_t i = expandAtEnd; i < 0; i--) {
char buf = 0;
write(context->fd, &buf, 1);
}
return 0;
}
static void file_stream_free(MemoryStream *stream)
{
FileStreamContext *context = stream->context;
if (context) {
if (context->fd > 0) {
if (stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) {
if (close(context->fd) != 0) {
perror("close");
}
}
}
free(context);
}
}
MemoryStream *file_stream_init_from_file_descriptor_nodup(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags)
{
MemoryStream *stream = malloc(sizeof(MemoryStream));
if (!stream) return NULL;
memset(stream, 0, sizeof(MemoryStream));
struct stat s;
int statRes = fstat(fd, &s);
if (statRes != 0) {
printf("Error: stat returned %d for %d.\n", statRes, fd);
goto fail;
}
FileStreamContext *context = malloc(sizeof(FileStreamContext));
context->fd = fd;
context->fileSize = s.st_size;
context->bufferStart = bufferStart;
if (bufferSize == FILE_STREAM_SIZE_AUTO) {
context->bufferSize = context->fileSize;
}
else {
context->bufferSize = bufferSize;
}
stream->context = context;
stream->flags = 0;
if (flags & FILE_STREAM_FLAG_WRITABLE) {
stream->flags |= MEMORY_STREAM_FLAG_MUTABLE;
}
if (flags & FILE_STREAM_FLAG_AUTO_EXPAND) {
stream->flags |= MEMORY_STREAM_FLAG_AUTO_EXPAND;
}
stream->read = file_stream_read;
stream->write = file_stream_write;
stream->getSize = file_stream_get_size;
stream->trim = file_stream_trim;
stream->expand = file_stream_expand;
stream->softclone = file_stream_softclone;
stream->hardclone = file_stream_hardclone;
stream->free = file_stream_free;
return stream;
fail:
file_stream_free(stream);
return NULL;
}
MemoryStream *file_stream_init_from_file_descriptor(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags)
{
MemoryStream *stream = file_stream_init_from_file_descriptor_nodup(dup(fd), bufferStart, bufferSize, flags);
if (stream) {
stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA;
}
return stream;
}
MemoryStream *file_stream_init_from_path(const char *path, uint32_t bufferStart, size_t bufferSize, uint32_t flags)
{
int openFlags = 0;
if (flags & FILE_STREAM_FLAG_WRITABLE) {
openFlags = O_RDWR | O_CREAT;
}
else {
openFlags = O_RDONLY;
}
int fd = open(path, openFlags);
if (fd < 0) {
printf("Failed to open %s: %s\n", path, strerror(errno));
return NULL;
}
MemoryStream *stream = file_stream_init_from_file_descriptor_nodup(fd, bufferStart, bufferSize, flags);
if (stream) {
stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA;
}
return stream;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef FILE_STREAM_H
#define FILE_STREAM_H
#include "MemoryStream.h"
#define FILE_STREAM_SIZE_AUTO 0
#define FILE_STREAM_FLAG_WRITABLE (1 << 0)
#define FILE_STREAM_FLAG_AUTO_EXPAND (1 << 1)
typedef struct FileStreamContext {
int fd;
size_t fileSize;
uint32_t bufferStart;
size_t bufferSize;
} FileStreamContext;
MemoryStream *file_stream_init_from_file_descriptor_nodup(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags);
MemoryStream *file_stream_init_from_file_descriptor(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags);
MemoryStream *file_stream_init_from_path(const char *path, uint32_t bufferStart, size_t bufferSize, uint32_t flags);
#endif // FILE_STREAM_H
+89
View File
@@ -0,0 +1,89 @@
#include "Host.h"
#include "MachO.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/machine.h>
#include <sys/utsname.h>
int host_get_cpu_information(cpu_type_t *cputype, cpu_subtype_t *cpusubtype)
{
size_t len;
// Query for cputype
len = sizeof(cputype);
if (sysctlbyname("hw.cputype", cputype, &len, NULL, 0) == -1) { printf("Error: no cputype.\n"); return -1; }
// Query for cpusubtype
len = sizeof(cpusubtype);
if (sysctlbyname("hw.cpusubtype", cpusubtype, &len, NULL, 0) == -1) { printf("Error: no cpusubtype.\n"); return -1; }
return 0;
}
int host_supported_arm64e_abi(void)
{
struct utsname name;
if (uname(&name) != 0) return -1;
if (strcmp(name.release, "20.0.0") >= 0) {
// iOS 14+, macOS 11+ use new ABI
return 2;
}
else {
// iOS 12-13 use old ABI
return 1;
}
}
MachO *fat_find_preferred_slice(Fat *fat)
{
cpu_type_t cputype;
cpu_subtype_t cpusubtype;
if (host_get_cpu_information(&cputype, &cpusubtype) != 0) { return NULL; }
MachO *preferredMacho = NULL;
// If you intend on supporting non darwin, implement platform specific logic here using #ifdef's
if (cputype == CPU_TYPE_ARM64) {
if (cpusubtype == CPU_SUBTYPE_ARM64E) {
// If this is an arm64e device, first try to find a new ABI arm64e slice
int supportedArm64eABI = host_supported_arm64e_abi();
if (supportedArm64eABI != -1) {
if (supportedArm64eABI == 2) {
// Find new ABI slice if host supports it
preferredMacho = fat_find_slice(fat, cputype, (CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_ARM64E_ABI_V2));
}
if (!preferredMacho) {
// If no new ABI slice is found or the host does not support it, try to find an old ABI arm64e slice
preferredMacho = fat_find_slice(fat, cputype, CPU_SUBTYPE_ARM64E);
if (preferredMacho) {
if (macho_get_filetype(preferredMacho) == MH_EXECUTE && supportedArm64eABI == 2) {
// If this is an old ABI binary trying to run on a new ABI system, discard it
// If it's an old ABI *library* trying to be loaded on a new ABI system, use it
preferredMacho = NULL;
}
}
}
}
}
if (!preferredMacho) {
// If not arm64e device or no arm64e slice found, try to find regular arm64 slice
// The Kernel prefers an arm64v8 slice to an arm64 slice, so check that first
// On iOS <14, dyld does not support arm64v8 slices, but that doesn't matter as the Kernel will still try to spawn it
preferredMacho = fat_find_slice(fat, cputype, CPU_SUBTYPE_ARM64_V8);
if (!preferredMacho) {
// If that's not found, finally check for a regular arm64 slice
preferredMacho = fat_find_slice(fat, cputype, CPU_SUBTYPE_ARM64_ALL);
}
}
}
if (!preferredMacho) {
printf("Error: failed to find a preferred MachO slice that matches the host architecture.\n");
}
return preferredMacho;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef HOST_H
#define HOST_H
#include "Fat.h"
#define CPU_SUBTYPE_ARM64E_ABI_V2 0x80000000
int host_get_cpu_information(cpu_type_t *cputype, cpu_subtype_t *cpusubtype);
// Retrieve the preferred MachO slice from a Fat
// Preferred slice as in the slice that the kernel would use when loading the file
MachO *fat_find_preferred_slice(Fat *fat);
#endif // HOST_H
+812
View File
@@ -0,0 +1,812 @@
#include "Fat.h"
#include "FileStream.h"
#include "MachO.h"
#include "MachOByteOrder.h"
#include "MachOLoadCommand.h"
#include "MemoryStream.h"
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach/machine.h>
#include <stdlib.h>
#include <dlfcn.h>
int macho_read_at_offset(MachO *macho, uint64_t offset, size_t size, void *outBuf)
{
if (macho->containingCache) {
// When this MachO is inside the DSC, it will have segments that point to "out of macho" memory
// So, attempt to translate every offset we get
// Problem: Translation doesn't work before the segments have been loaded and loading the segments requires this function
// Solution: Gracefully fall back to memory_stream_read in the case where translation fails
uint64_t vmaddr = 0;
if (macho_translate_fileoff_to_vmaddr(macho, offset, &vmaddr, NULL) == 0) {
return macho_read_at_vmaddr(macho, vmaddr, size, outBuf);
}
}
return memory_stream_read(macho->stream, offset, size, outBuf);
}
int macho_read_string_at_offset(MachO *macho, uint64_t offset, char **outString)
{
if (macho->containingCache) {
// Same as above
uint64_t vmaddr = 0;
if (macho_translate_fileoff_to_vmaddr(macho, offset, &vmaddr, NULL) == 0) {
return macho_read_string_at_vmaddr(macho, vmaddr, outString);
}
}
return memory_stream_read_string(macho->stream, offset, outString);
}
int macho_read_uleb128_at_offset(MachO *macho, uint64_t offset, uint64_t maxOffset, uint64_t *endOffsetOut, uint64_t *valueOut)
{
uint64_t result = 0;
int bit = 0;
uint64_t curOffset = offset;
int r = 0;
uint8_t v = 0;
do {
macho_read_at_offset(macho, curOffset, sizeof(v), &v);
if (curOffset == maxOffset) {
r = -1;
break;
}
uint64_t slice = v & 0x7f;
if (bit > 63) {
r = -1;
break;
}
else {
result |= (slice << bit);
bit += 7;
}
curOffset++;
}
while (v & 0x80);
if (r == 0) {
if (endOffsetOut) *endOffsetOut = curOffset;
if (valueOut) *valueOut = result;
}
return r;
}
int macho_write_at_offset(MachO *macho, uint64_t offset, size_t size, const void *inBuf)
{
return memory_stream_write(macho->stream, offset, size, inBuf);
}
MemoryStream *macho_get_stream(MachO *macho)
{
return macho->stream;
}
uint32_t macho_get_filetype(MachO *macho)
{
return macho->machHeader.filetype;
}
struct mach_header *macho_get_mach_header(MachO *macho)
{
return &macho->machHeader;
}
size_t macho_get_mach_header_size(MachO *macho)
{
return macho->is32Bit ? sizeof(struct mach_header) : sizeof(struct mach_header_64);
}
DyldSharedCache *macho_get_containing_cache(MachO *macho)
{
return macho->containingCache;
}
int macho_translate_fileoff_to_vmaddr(MachO *macho, uint64_t fileoff, uint64_t *vmaddrOut, MachOSegment **segmentOut)
{
for (uint32_t i = 0; i < macho->segmentCount; i++) {
MachOSegment *segment = macho->segments[i];
uint64_t segmentStartOff = segment->command.fileoff;
uint64_t segmentEndOff = segment->command.fileoff + segment->command.filesize;
if (fileoff >= segmentStartOff && fileoff < segmentEndOff) {
uint64_t relativeFileoff = fileoff - segmentStartOff;
*vmaddrOut = segment->command.vmaddr + relativeFileoff;
return 0;
}
}
return -1;
}
int macho_translate_vmaddr_to_fileoff(MachO *macho, uint64_t vmaddr, uint64_t *fileoffOut, MachOSegment **segmentOut)
{
for (uint32_t i = 0; i < macho->segmentCount; i++) {
MachOSegment *segment = macho->segments[i];
uint64_t segmentVmAddr = segment->command.vmaddr;
uint64_t segmentVmEnd = segment->command.vmaddr + segment->command.vmsize;
if (vmaddr >= segmentVmAddr && vmaddr < segmentVmEnd) {
uint64_t relativeVmAddr = vmaddr - segmentVmAddr;
*fileoffOut = segment->command.fileoff + relativeVmAddr;
if (segmentOut) *segmentOut = segment;
return 0;
}
}
return -1;
}
int macho_read_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, void *outBuf)
{
if (macho->containingCache) {
return dsc_read_from_vmaddr(macho->containingCache, vmaddr, size, outBuf);
}
else {
MachOSegment *segment;
uint64_t fileoff = 0;
int r = macho_translate_vmaddr_to_fileoff(macho, vmaddr, &fileoff, &segment);
if (r != 0) return r;
uint64_t readEnd = vmaddr + size;
if (readEnd >= (segment->command.vmaddr + segment->command.vmsize)) {
// prevent OOB
return -1;
}
return macho_read_at_offset(macho, fileoff, size, outBuf);
}
}
int macho_read_string_at_vmaddr(MachO *macho, uint64_t vmaddr, char **outString)
{
if (macho->containingCache) {
return dsc_read_string_from_vmaddr(macho->containingCache, vmaddr, outString);
}
else {
MachOSegment *segment;
uint64_t fileoff = 0;
int r = macho_translate_vmaddr_to_fileoff(macho, vmaddr, &fileoff, &segment);
if (r != 0) return r;
if (vmaddr >= (segment->command.vmaddr + segment->command.vmsize)) {
// prevent OOB
return -1;
}
return macho_read_string_at_offset(macho, fileoff, outString);
}
}
int macho_write_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, const void *inBuf)
{
MachOSegment *segment;
uint64_t fileoff = 0;
int r = macho_translate_vmaddr_to_fileoff(macho, vmaddr, &fileoff, &segment);
if (r != 0) return r;
uint64_t readEnd = vmaddr + size;
if (readEnd >= (segment->command.vmaddr + segment->command.vmsize)) {
// prevent OOB
return -1;
}
return macho_write_at_offset(macho, fileoff, size, inBuf);
}
int macho_enumerate_load_commands(MachO *macho, void (^enumeratorBlock)(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop))
{
if (macho->machHeader.ncmds < 1 || macho->machHeader.ncmds > 1000) {
printf("Error: invalid number of load commands (%d).\n", macho->machHeader.ncmds);
return -1;
}
// First load command starts after mach header
uint64_t offset = macho_get_mach_header_size(macho);
for (int j = 0; j < macho->machHeader.ncmds; j++) {
struct load_command loadCommand;
if (macho_read_at_offset(macho, offset, sizeof(loadCommand), &loadCommand) != 0) continue;
LOAD_COMMAND_APPLY_BYTE_ORDER(&loadCommand, LITTLE_TO_HOST_APPLIER);
if (strcmp(load_command_to_string(loadCommand.cmd), "LC_UNKNOWN") == 0)
{
printf("Ignoring unknown command: 0x%x.\n", loadCommand.cmd);
}
else {
// TODO: Check if cmdsize matches expected size for cmd
uint8_t cmd[loadCommand.cmdsize];
if (macho_read_at_offset(macho, offset, loadCommand.cmdsize, cmd) != 0) continue;
bool stop = false;
enumeratorBlock(loadCommand, offset, (void *)cmd, &stop);
if (stop) break;
}
offset += loadCommand.cmdsize;
}
return 0;
}
int macho_enumerate_segments(MachO *macho, void (^enumeratorBlock)(struct segment_command_64 *segment, bool *stop))
{
for (uint32_t i = 0; i < macho->segmentCount; i++) {
bool stop = false;
enumeratorBlock(&macho->segments[i]->command, &stop);
if (stop) return 0;
}
return 0;
}
int macho_enumerate_sections(MachO *macho, void (^enumeratorBlock)(struct section_64 *section, struct segment_command_64 *segment, bool *stop))
{
for (uint32_t i = 0; i < macho->segmentCount; i++) {
for (uint32_t k = 0; k < macho->segments[i]->command.nsects; k++) {
bool stop = false;
enumeratorBlock(&macho->segments[i]->sections[k], &macho->segments[i]->command, &stop);
if (stop) return 0;
}
}
return 0;
}
struct trie_node {
char *string;
uint64_t value;
};
int macho_read_trie_node_at_offset(MachO *macho, uint64_t offset, uint64_t maxOffset, uint64_t *endOffsetOut, struct trie_node **trieNodesOut, unsigned *trieNodesCountOut)
{
if (!trieNodesOut || !trieNodesCountOut) return -1;
uint8_t trieValue = 0;
macho_read_at_offset(macho, offset, sizeof(trieValue), &trieValue);
offset += sizeof(trieValue);
if (trieValue != 0) {
*trieNodesOut = NULL;
*trieNodesCountOut = 0;
goto done;
}
uint8_t numberOfBranches = 0;
macho_read_at_offset(macho, offset, sizeof(numberOfBranches), &numberOfBranches);
offset += sizeof(numberOfBranches);
*trieNodesCountOut = numberOfBranches;
*trieNodesOut = malloc(sizeof(struct trie_node) * *trieNodesCountOut);
for (uint8_t i = 0; i < numberOfBranches; i++) {
struct trie_node *node = &(*trieNodesOut)[i];
macho_read_string_at_offset(macho, offset, &node->string);
offset += strlen(node->string)+1;
macho_read_uleb128_at_offset(macho, offset, maxOffset, &offset, &node->value);
}
done:
if (endOffsetOut) *endOffsetOut = offset;
return 0;
}
int macho_enumerate_symbols(MachO *macho, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop))
{
bool didScanDSC = false;
if (macho->containingCache && macho->cacheImage) {
// For stuff inside the DSC we need to use the cache image to also be able to fetch private symbols
// Private symbols are normally replaced with <redacted> in the LC_SYMTAB of the MachO
didScanDSC = (dsc_image_enumerate_symbols(macho->containingCache, macho->cacheImage, enumeratorBlock) == 0);
}
__block bool hasSymtab = false;
__block struct symtab_command symtabCommand;
__block bool hasExportsTrie = false;
__block struct linkedit_data_command trieCommand;
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (loadCommand.cmd == LC_SYMTAB) {
hasSymtab = true;
memcpy(&symtabCommand, cmd, sizeof(symtabCommand));
SYMTAB_COMMAND_APPLY_BYTE_ORDER(&symtabCommand, LITTLE_TO_HOST_APPLIER);
}
else if (loadCommand.cmd == LC_DYLD_EXPORTS_TRIE) {
hasExportsTrie = true;
memcpy(&trieCommand, cmd, sizeof(trieCommand));
LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(&trieCommand, LITTLE_TO_HOST_APPLIER);
}
if (hasSymtab && hasExportsTrie) *stop = true;
});
if (hasSymtab) {
char *strtbl = malloc(symtabCommand.strsize);
if (macho_read_at_offset(macho, symtabCommand.stroff, symtabCommand.strsize, strtbl) != 0) {
free(strtbl);
return -1;
}
for (int i = 0; i < symtabCommand.nsyms; i++) {
uint64_t n_strx = 0;
uint64_t n_value = 0;
uint8_t n_type = 0;
int r = 0;
#define _GENERIC_READ_NLIST(nlistType, APPLIER) do { \
struct nlistType entry; \
if ((r = macho_read_at_offset(macho, symtabCommand.symoff + (i * sizeof(entry)), sizeof(entry), &entry)) != 0) break; \
APPLIER(&entry, LITTLE_TO_HOST_APPLIER); \
n_strx = entry.n_un.n_strx; \
n_value = entry.n_value; \
n_type = entry.n_type; \
} while (0)
if (macho->is32Bit) {
_GENERIC_READ_NLIST(nlist, NLIST_APPLY_BYTE_ORDER);
}
else {
_GENERIC_READ_NLIST(nlist_64, NLIST_64_APPLY_BYTE_ORDER);
}
if (r != 0) continue;
#undef _GENERIC_READ_NLIST
if (n_strx >= symtabCommand.strsize || n_strx == 0) continue;
const char *symbolName = &strtbl[n_strx];
if (symbolName[0] == 0) continue;
if (didScanDSC) {
/* If we already got the real private symbols from the DSC, omit any censored ones */
if (!strcmp(symbolName, "<redacted>")) continue;
}
bool stopSym = false;
enumeratorBlock(symbolName, n_type, n_value, &stopSym);
if (stopSym) {
break;
}
}
free(strtbl);
}
else if (hasExportsTrie) {
uint64_t trieBegin = trieCommand.dataoff;
uint64_t trieSize = trieCommand.datasize;
uint64_t trieEnd = trieBegin + trieSize;
uint64_t trieBeginVm = 0;
uint64_t trieEndVm = 0;
macho_translate_fileoff_to_vmaddr(macho, trieBegin, &trieBeginVm, NULL);
macho_translate_fileoff_to_vmaddr(macho, trieEnd, &trieEndVm, NULL);
uint64_t trieCur = trieBegin;
struct trie_node *frontier = NULL;
unsigned frontierCount = 0;
macho_read_trie_node_at_offset(macho, trieCur, trieEnd, &trieCur, &frontier, &frontierCount);
for (unsigned i = 0; i < frontierCount; i++) {
struct trie_node *node = &frontier[i];
uint64_t offset = trieBegin + node->value;
struct trie_node *children = NULL;
unsigned childrenCount = 0;
macho_read_trie_node_at_offset(macho, offset, trieEnd, &offset, &children, &childrenCount);
if (!childrenCount) {
uint64_t ulebOffset = offset - sizeof(uint8_t);
uint64_t flags = 0;
macho_read_uleb128_at_offset(macho, ulebOffset, trieEnd, &ulebOffset, &flags);
ulebOffset += sizeof(uint8_t);
uint64_t addrOff = 0;
macho_read_uleb128_at_offset(macho, ulebOffset, trieEnd, &ulebOffset, &addrOff);
bool stop = false;
enumeratorBlock(node->string, 0, macho_get_base_address(macho) + addrOff, &stop);
if (stop) break;
}
else {
unsigned prevEnd = frontierCount;
frontierCount += childrenCount;
frontier = realloc(frontier, sizeof(struct trie_node) * frontierCount);
node = &frontier[i];
for (unsigned k = 0; k < childrenCount; k++) {
frontier[prevEnd + k].string = malloc(strlen(node->string) + strlen(children[k].string) + 1);
strcpy(frontier[prevEnd + k].string, node->string);
strcat(frontier[prevEnd + k].string, children[k].string);
frontier[prevEnd + k].value = children[k].value;
}
for (unsigned k = 0; k < childrenCount; k++) {
free(children[k].string);
}
free(children);
}
}
for (unsigned i = 0; i < frontierCount; i++) {
free(frontier[i].string);
}
free(frontier);
}
return 0;
}
int macho_enumerate_dependencies(MachO *macho, void (^enumeratorBlock)(const char *dylibPath, uint32_t cmd, struct dylib* dylib, bool *stop))
{
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop){
if (loadCommand.cmd == LC_LOAD_DYLIB ||
loadCommand.cmd == LC_LOAD_WEAK_DYLIB ||
loadCommand.cmd == LC_REEXPORT_DYLIB ||
loadCommand.cmd == LC_LAZY_LOAD_DYLIB ||
loadCommand.cmd == LC_LOAD_UPWARD_DYLIB) {
struct dylib_command *dylibCommand = (struct dylib_command *)cmd;
DYLIB_COMMAND_APPLY_BYTE_ORDER(dylibCommand, LITTLE_TO_HOST_APPLIER);
if (dylibCommand->dylib.name.offset >= loadCommand.cmdsize || dylibCommand->dylib.name.offset < sizeof(struct dylib_command)) {
printf("WARNING: Malformed dependency at 0x%llx (Name offset out of bounds)\n", offset);
return;
}
char *dependencyPath = ((char *)cmd + dylibCommand->dylib.name.offset);
size_t dependencyLength = strnlen(dependencyPath, loadCommand.cmdsize - dylibCommand->dylib.name.offset);
if (!dependencyLength) {
printf("WARNING: Malformed dependency at 0x%llx (Name has zero length)\n", offset);
return;
}
if (dependencyPath[dependencyLength] != 0) {
printf("WARNING: Malformed dependency at 0x%llx (Name has non NULL end byte)\n", offset);
return;
}
bool stopDepdendency = false;
enumeratorBlock(dependencyPath, loadCommand.cmd, &dylibCommand->dylib, &stopDepdendency);
if (stopDepdendency) {
*stop = true;
return;
}
}
});
return 0;
}
int macho_enumerate_rpaths(MachO *macho, void (^enumeratorBlock)(const char *rpath, bool *stop))
{
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (loadCommand.cmd == LC_RPATH) {
struct rpath_command *rpathCommand = (struct rpath_command *)cmd;
RPATH_COMMAND_APPLY_BYTE_ORDER(rpathCommand, LITTLE_TO_HOST_APPLIER);
if (rpathCommand->path.offset >= loadCommand.cmdsize || rpathCommand->path.offset < sizeof(struct rpath_command)) {
printf("WARNING: Malformed rpath at 0x%llx (Path offset out of bounds)\n", offset);
return;
}
char *rpath = ((char *)cmd) + rpathCommand->path.offset;
size_t rpathLength = strnlen(rpath, rpathCommand->cmdsize - rpathCommand->path.offset);
if (!rpathLength) {
printf("WARNING: Malformed rpath at 0x%llx (Path has zero length)\n", offset);
return;
}
if (rpath[rpathLength] != 0) {
printf("WARNING: Malformed rpath at 0x%llx (Name has non NULL end byte)\n", offset);
return;
}
bool stopRpath = false;
enumeratorBlock(rpath, &stopRpath);
if (stopRpath) {
*stop = true;
}
}
});
return 0;
}
uint64_t macho_get_base_address(MachO *macho)
{
if (macho->cachedBase) return macho->cachedBase;
__block int64_t base = UINT64_MAX;
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (loadCommand.cmd == LC_SEGMENT_64) {
struct segment_command_64 *segmentCommand = (struct segment_command_64 *)cmd;
SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, LITTLE_TO_HOST_APPLIER);
if (strncmp(segmentCommand->segname, "__PRELINK", 9) != 0
&& strncmp(segmentCommand->segname, "__PLK", 5) != 0
&& strncmp(segmentCommand->segname, "__PAGEZERO", 10) != 0) {
// PRELINK and PAGEZERO are before the actual base, so we ignore them
if (segmentCommand->vmaddr < base) {
base = segmentCommand->vmaddr;
}
}
}
});
macho->cachedBase = base;
return macho->cachedBase;
}
int macho_enumerate_function_starts(MachO *macho, void (^enumeratorBlock)(uint64_t funcAddr, bool *stop))
{
__block uint64_t functionStartsDataOff = 0, functionStartsDataSize = 0;
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stopLC) {
if (loadCommand.cmd == LC_FUNCTION_STARTS) {
struct linkedit_data_command *functionStartsCommand = (struct linkedit_data_command *)cmd;
LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(functionStartsCommand, LITTLE_TO_HOST_APPLIER);
functionStartsDataOff = functionStartsCommand->dataoff;
functionStartsDataSize = functionStartsCommand->datasize;
*stopLC = true;
}
});
if (!functionStartsDataOff || !functionStartsDataSize) return -1;
uint8_t *info = malloc(functionStartsDataSize);
if (macho_read_at_offset(macho, functionStartsDataOff, functionStartsDataSize, info) == 0) {
uint8_t *infoEnd = &info[functionStartsDataSize];
uint64_t address = macho_get_base_address(macho);
for (uint8_t *p = info; (*p != 0) && (p < infoEnd); ) {
bool stop = false;
uint64_t delta = 0;
uint32_t shift = 0;
bool more = true;
do {
uint8_t byte = *p++;
delta |= ((byte & 0x7F) << shift);
shift += 7;
if (byte < 0x80) {
address += delta;
enumeratorBlock(address, &stop);
more = false;
}
} while (more);
if (stop) break;
}
}
free(info);
return 0;
}
int macho_lookup_segment_by_addr(MachO *macho, uint64_t vmaddr, struct segment_command_64 *segmentOut)
{
__block int r = -1;
macho_enumerate_segments(macho, ^(struct segment_command_64 *segment, bool *stop){
if (vmaddr >= segment->vmaddr && vmaddr < (segment->vmaddr + segment->vmsize)) {
r = 0;
memcpy(segmentOut, segment, sizeof(struct segment_command_64));
*stop = true;
}
});
return r;
}
int macho_lookup_section_by_addr(MachO *macho, uint64_t vmaddr, struct section_64 *sectionOut)
{
__block int r = -1;
macho_enumerate_sections(macho, ^(struct section_64 *section, struct segment_command_64 *segment, bool *stop) {
if (vmaddr >= section->addr && vmaddr < (section->addr + section->size)) {
r = 0;
memcpy(sectionOut, section, sizeof(struct section_64));
*stop = true;
}
});
return r;
}
int macho_parse_segments(MachO *macho)
{
return macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (macho->is32Bit && loadCommand.cmd == LC_SEGMENT) {
macho->segmentCount++;
if (macho->segments == NULL) { macho->segments = malloc(macho->segmentCount * sizeof(MachOSegment*)); }
else { macho->segments = realloc(macho->segments, macho->segmentCount * sizeof(MachOSegment*)); }
// For simplicity, we convert segment_command's to segment_command_64's
// This allowes all logic to unifily operate on segment_command_64
struct segment_command segmentCommand;
memcpy(&segmentCommand, cmd, sizeof(segmentCommand));
SEGMENT_COMMAND_APPLY_BYTE_ORDER(&segmentCommand, LITTLE_TO_HOST_APPLIER);
MachOSegment **segment = &macho->segments[macho->segmentCount-1];
*segment = malloc(sizeof(MachOSegment) + segmentCommand.nsects * sizeof(struct section_64));
(*segment)->command = (struct segment_command_64) {
.cmd = segmentCommand.cmd,
.cmdsize = segmentCommand.cmdsize,
.fileoff = segmentCommand.fileoff,
.filesize = segmentCommand.filesize,
.vmaddr = segmentCommand.vmaddr,
.vmsize = segmentCommand.vmsize,
.flags = segmentCommand.flags,
.initprot = segmentCommand.initprot,
.maxprot = segmentCommand.maxprot,
.nsects = segmentCommand.nsects,
};
memcpy((*segment)->command.segname, segmentCommand.segname, sizeof(segmentCommand.segname));
for (uint32_t i = 0; i < macho->segments[macho->segmentCount-1]->command.nsects; i++) {
struct section section;
memcpy(&section, cmd + sizeof(struct segment_command) + (i * sizeof(struct section)), sizeof(section));
SECTION_APPLY_BYTE_ORDER(&section, LITTLE_TO_HOST_APPLIER);
(*segment)->sections[i] = (struct section_64) {
.addr = section.addr,
.align = section.align,
.flags = section.flags,
.nreloc = section.nreloc,
.offset = section.offset,
.reserved1 = section.reserved1,
.reserved2 = section.reserved2,
.reserved3 = 0,
.size = section.size,
};
memcpy((*segment)->sections[i].sectname, section.sectname, sizeof(section.sectname));
memcpy((*segment)->sections[i].segname, section.segname, sizeof(section.segname));
}
}
else if (!macho->is32Bit && loadCommand.cmd == LC_SEGMENT_64) {
macho->segmentCount++;
if (macho->segments == NULL) { macho->segments = malloc(macho->segmentCount * sizeof(MachOSegment*)); }
else { macho->segments = realloc(macho->segments, macho->segmentCount * sizeof(MachOSegment*)); }
macho->segments[macho->segmentCount-1] = malloc(loadCommand.cmdsize);
memcpy(macho->segments[macho->segmentCount-1], cmd, loadCommand.cmdsize);
SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(&macho->segments[macho->segmentCount-1]->command, LITTLE_TO_HOST_APPLIER);
for (uint32_t i = 0; i < macho->segments[macho->segmentCount-1]->command.nsects; i++) {
SECTION_64_APPLY_BYTE_ORDER(&macho->segments[macho->segmentCount-1]->sections[i], LITTLE_TO_HOST_APPLIER);
}
}
});
}
int macho_parse_fileset_machos(MachO *macho)
{
if (macho_get_filetype(macho) != MH_FILESET) return -1;
return macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (loadCommand.cmd == LC_FILESET_ENTRY) {
uint32_t i = macho->filesetCount;
macho->filesetCount++;
struct fileset_entry_command *filesetCommand = cmd;
FILESET_ENTRY_COMMAND_APPLY_BYTE_ORDER(filesetCommand, LITTLE_TO_HOST_APPLIER);
if (macho->filesetMachos == NULL) { macho->filesetMachos = malloc(macho->filesetCount * sizeof(FilesetMachO)); }
else { macho->filesetMachos = realloc(macho->filesetMachos, macho->filesetCount * sizeof(FilesetMachO)); }
FilesetMachO *filesetMacho = &macho->filesetMachos[i];
filesetMacho->entry_id = strdup((char *)cmd + filesetCommand->entry_id.offset);
filesetMacho->vmaddr = filesetCommand->vmaddr;
filesetMacho->fileoff = filesetCommand->fileoff;
MemoryStream *subStream = memory_stream_softclone(macho->stream);
// TODO: Also cut trim to the end of the macho, but for that we would need to determine it's size
memory_stream_trim(subStream, filesetCommand->fileoff, 0);
filesetMacho->underlyingMachO = fat_init_from_memory_stream(subStream);
}
});
}
int _macho_parse(MachO *macho)
{
macho->is32Bit = (macho->archDescriptor.cpusubtype == CPU_SUBTYPE_ARM_V6 || macho->archDescriptor.cpusubtype == CPU_SUBTYPE_ARM_V7 || macho->archDescriptor.cpusubtype == CPU_SUBTYPE_ARM_V7S);
if (macho->machHeader.sizeofcmds % (macho->is32Bit ? sizeof(uint32_t) : sizeof(uint64_t)) != 0) {
printf("Error: sizeofcmds is not a multiple of %lu (%d).\n", macho->is32Bit ? sizeof(uint32_t) : sizeof(uint64_t), macho->machHeader.sizeofcmds);
return -1;
}
macho_parse_segments(macho);
macho_parse_fileset_machos(macho);
return 0;
}
MachO *macho_init(MemoryStream *stream, struct fat_arch_64 archDescriptor)
{
MachO *macho = malloc(sizeof(MachO));
if (!macho) return NULL;
memset(macho, 0, sizeof(MachO));
macho->stream = stream;
macho->archDescriptor = archDescriptor;
if (macho_read_at_offset(macho, 0, sizeof(macho->machHeader), &macho->machHeader) != 0) goto fail;
MACH_HEADER_APPLY_BYTE_ORDER(&macho->machHeader, LITTLE_TO_HOST_APPLIER);
// Check the magic against the expected values
if (macho->machHeader.magic != MH_MAGIC_64 && macho->machHeader.magic != MH_MAGIC) {
goto fail;
}
if (_macho_parse(macho) != 0) goto fail;
return macho;
fail:
macho_free(macho);
return NULL;
}
MachO *macho_init_for_writing(const char *filePath)
{
MachO *macho = malloc(sizeof(MachO));
if (!macho) return NULL;
memset(macho, 0, sizeof(MachO));
macho->stream = file_stream_init_from_path(filePath, 0, FILE_STREAM_SIZE_AUTO, FILE_STREAM_FLAG_WRITABLE | FILE_STREAM_FLAG_AUTO_EXPAND);
if (!macho->stream) goto fail;
size_t fileSize = memory_stream_get_size(macho->stream);
memory_stream_read(macho->stream, 0, sizeof(struct mach_header), &macho->machHeader);
MACH_HEADER_APPLY_BYTE_ORDER(&macho->machHeader, HOST_TO_LITTLE_APPLIER);
if (macho->machHeader.magic != MH_MAGIC_64) goto fail;
macho->archDescriptor.cpusubtype = macho->machHeader.cpusubtype;
macho->archDescriptor.cputype = macho->machHeader.cputype;
macho->archDescriptor.offset = 0;
macho->archDescriptor.size = fileSize;
macho->archDescriptor.align = 0x4000;
if (_macho_parse(macho) != 0) goto fail;
return macho;
fail:
macho_free(macho);
return NULL;
}
MachO **macho_array_create_for_paths(char **inputPaths, int inputPathsCount) {
Fat **fatArray = malloc(sizeof(Fat *) * inputPathsCount);
MachO **machoArray;
int sliceCount = 0;
for (int i = 0; i < inputPathsCount; i++) {
Fat *fat = fat_init_from_path(inputPaths[i]);
if (!fat) {
printf("Error: failed to create Fat from file: %s\n", inputPaths[i]);
return NULL;
}
sliceCount += fat->slicesCount;
fatArray[i] = fat;
}
machoArray = malloc(sizeof(MachO *) * sliceCount);
for (int i = 0; i < inputPathsCount; i++) {
for (int j = 0; j < fatArray[i]->slicesCount; j++) {
machoArray[i] = fatArray[i]->slices[j];
}
}
return machoArray;
}
bool macho_is_encrypted(MachO *macho)
{
__block bool isEncrypted = false;
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (loadCommand.cmd == LC_ENCRYPTION_INFO_64 || loadCommand.cmd == LC_ENCRYPTION_INFO) {
struct encryption_info_command *encryptionInfoCommand = cmd;
ENCRYPTION_INFO_COMMAND_APPLY_BYTE_ORDER(encryptionInfoCommand, LITTLE_TO_HOST_APPLIER);
if (encryptionInfoCommand->cryptid == 1) {
*stop = true;
isEncrypted = true;
}
}
});
return isEncrypted;
}
void macho_free(MachO *macho)
{
if (macho->filesetCount != 0 && macho->filesetMachos) {
for (uint32_t i = 0; i < macho->filesetCount; i++) {
fat_free(macho->filesetMachos[i].underlyingMachO);
free(macho->filesetMachos[i].entry_id);
}
free(macho->filesetMachos);
}
if (macho->segmentCount != 0 && macho->segments) {
for (uint32_t i = 0; i < macho->segmentCount; i++) {
free(macho->segments[i]);
}
free(macho->segments);
}
if (macho->stream) {
memory_stream_free(macho->stream);
}
free(macho);
}
+91
View File
@@ -0,0 +1,91 @@
#ifndef MACHO_SLICE_H
#define MACHO_SLICE_H
#include <stdbool.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include "MemoryStream.h"
#include "Fat.h"
#include "DyldSharedCache.h"
typedef struct MachOSegment
{
struct segment_command_64 command;
struct section_64 sections[];
} __attribute__((__packed__)) MachOSegment;
typedef struct FilesetMachO {
char *entry_id;
uint64_t vmaddr;
uint64_t fileoff;
Fat *underlyingMachO;
} FilesetMachO;
typedef struct MachO {
MemoryStream *stream;
bool is32Bit;
struct mach_header machHeader;
struct fat_arch_64 archDescriptor;
uint64_t cachedBase;
uint32_t filesetCount;
FilesetMachO *filesetMachos;
uint32_t segmentCount;
MachOSegment **segments;
DyldSharedCache *containingCache;
DyldSharedCacheImage *cacheImage;
} MachO;
// Read data from a MachO at a specified offset
int macho_read_at_offset(MachO *macho, uint64_t offset, size_t size, void *outBuf);
// Write data from a MachO at a specified offset, auto expands, only works if opened via macho_init_for_writing
int macho_write_at_offset(MachO *macho, uint64_t offset, size_t size, const void *inBuf);
int macho_read_string_at_offset(MachO *macho, uint64_t offset, char **string);
int macho_read_uleb128_at_offset(MachO *macho, uint64_t offset, uint64_t maxOffset, uint64_t *endOffsetOut, uint64_t *valueOut);
MemoryStream *macho_get_stream(MachO *macho);
uint32_t macho_get_filetype(MachO *macho);
struct mach_header *macho_get_mach_header(MachO *macho);
size_t macho_get_mach_header_size(MachO *macho);
DyldSharedCache *macho_get_containing_cache(MachO *macho);
// Perform translation between file offsets and virtual addresses
int macho_translate_fileoff_to_vmaddr(MachO *macho, uint64_t fileoff, uint64_t *vmaddrOut, MachOSegment **segmentOut);
int macho_translate_vmaddr_to_fileoff(MachO *macho, uint64_t vmaddr, uint64_t *fileoffOut, MachOSegment **segmentOut);
// Wrappers to deal with virtual addresses
int macho_read_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, void *outBuf);
int macho_write_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, const void *inBuf);
int macho_read_string_at_vmaddr(MachO *macho, uint64_t vmaddr, char **outString);
uint64_t macho_get_base_address(MachO *macho);
int macho_enumerate_load_commands(MachO *macho, void (^enumeratorBlock)(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop));
int macho_enumerate_segments(MachO *macho, void (^enumeratorBlock)(struct segment_command_64 *segment, bool *stop));
int macho_enumerate_sections(MachO *macho, void (^enumeratorBlock)(struct section_64 *section, struct segment_command_64 *segment, bool *stop));
int macho_enumerate_symbols(MachO *macho, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop));
int macho_enumerate_dependencies(MachO *macho, void (^enumeratorBlock)(const char *dylibPath, uint32_t cmd, struct dylib* dylib, bool *stop));
int macho_enumerate_rpaths(MachO *macho, void (^enumeratorBlock)(const char *rpath, bool *stop));
int macho_enumerate_function_starts(MachO *macho, void (^enumeratorBlock)(uint64_t funcAddr, bool *stop));
int macho_lookup_segment_by_addr(MachO *macho, uint64_t vmaddr, struct segment_command_64 *segmentOut);
int macho_lookup_section_by_addr(MachO *macho, uint64_t vmaddr, struct section_64 *sectionOut);
// Initialise a MachO object from a MemoryStream and it's corresponding Fat arch descriptor
MachO *macho_init(MemoryStream *stream, struct fat_arch_64 archDescriptor);
// Initialize a single slice macho for writing to it
MachO *macho_init_for_writing(const char *filePath);
// Create an array of MachO objects from an array of paths
MachO **macho_array_create_for_paths(char **inputPaths, int inputPathsCount);
// Check if a MachO is encrypted
bool macho_is_encrypted(MachO *macho);
void macho_free(MachO *macho);
#endif // MACHO_SLICE_H
+229
View File
@@ -0,0 +1,229 @@
#ifndef MACHO_BYTE_ORDER_H
#define MACHO_BYTE_ORDER_H
#include <stdio.h>
#include <stdlib.h>
#include <libkern/OSByteOrder.h>
// 8-bit integers needed for CodeDirectory
#define BIG_TO_HOST(n) _Generic((n), \
int8_t: n, \
uint8_t: n, \
int16_t: OSSwapBigToHostInt16(n), \
uint16_t: OSSwapBigToHostInt16(n), \
int32_t: OSSwapBigToHostInt32(n), \
uint32_t: OSSwapBigToHostInt32(n), \
int64_t: OSSwapBigToHostInt64(n), \
uint64_t: OSSwapBigToHostInt64(n) \
)
#define HOST_TO_BIG(n) _Generic((n), \
int8_t: n, \
uint8_t: n, \
uint16_t: OSSwapHostToBigInt16(n), \
int16_t: OSSwapHostToBigInt16(n), \
int32_t: OSSwapHostToBigInt32(n), \
uint32_t: OSSwapHostToBigInt32(n), \
int64_t: OSSwapHostToBigInt64(n), \
uint64_t: OSSwapHostToBigInt64(n) \
)
#define LITTLE_TO_HOST(n) _Generic((n), \
int8_t: n, \
uint8_t: n, \
int16_t: OSSwapLittleToHostInt16(n), \
uint16_t: OSSwapLittleToHostInt16(n), \
int32_t: OSSwapLittleToHostInt32(n), \
uint32_t: OSSwapLittleToHostInt32(n), \
int64_t: OSSwapLittleToHostInt64(n), \
uint64_t: OSSwapLittleToHostInt64(n) \
)
#define HOST_TO_LITTLE(n) _Generic((n), \
int8_t: n, \
uint8_t: n, \
int16_t: OSSwapHostToLittleInt16(n), \
uint16_t: OSSwapHostToLittleInt16(n), \
int32_t: OSSwapHostToLittleInt32(n), \
uint32_t: OSSwapHostToLittleInt32(n), \
int64_t: OSSwapHostToLittleInt64(n), \
uint64_t: OSSwapHostToLittleInt64(n) \
)
#define HOST_TO_LITTLE_APPLIER(instance, member) \
(instance)->member = HOST_TO_LITTLE((instance)->member)
#define HOST_TO_BIG_APPLIER(instance, member) \
(instance)->member = HOST_TO_BIG((instance)->member)
#define LITTLE_TO_HOST_APPLIER(instance, member) \
(instance)->member = LITTLE_TO_HOST((instance)->member)
#define BIG_TO_HOST_APPLIER(instance, member) \
(instance)->member = BIG_TO_HOST((instance)->member)
#define FAT_HEADER_APPLY_BYTE_ORDER(fh, applier) \
applier(fh, magic); \
applier(fh, nfat_arch);
#define FAT_ARCH_APPLY_BYTE_ORDER(arch, applier) \
applier(arch, cputype); \
applier(arch, cpusubtype); \
applier(arch, offset); \
applier(arch, size); \
applier(arch, align);
#define FAT_ARCH_64_APPLY_BYTE_ORDER(arch, applier) \
applier(arch, cputype); \
applier(arch, cpusubtype); \
applier(arch, offset); \
applier(arch, size); \
applier(arch, align); \
applier(arch, reserved);
#define MACH_HEADER_APPLY_BYTE_ORDER(mh, applier) \
applier(mh, magic); \
applier(mh, cputype); \
applier(mh, cpusubtype); \
applier(mh, filetype); \
applier(mh, ncmds); \
applier(mh, sizeofcmds);
#define LOAD_COMMAND_APPLY_BYTE_ORDER(lc, applier) \
applier(lc, cmd); \
applier(lc, cmdsize);
#define LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(lc, applier) \
applier(lc, cmd); \
applier(lc, cmdsize); \
applier(lc, dataoff); \
applier(lc, datasize);
#define ENCRYPTION_INFO_COMMAND_APPLY_BYTE_ORDER(eic, applier) \
applier(eic, cmd); \
applier(eic, cmdsize); \
applier(eic, cryptoff); \
applier(eic, cryptsize); \
applier(eic, cryptid);
#define BLOB_INDEX_APPLY_BYTE_ORDER(bi, applier) \
applier(bi, type); \
applier(bi, offset);
#define SUPERBLOB_APPLY_BYTE_ORDER(sb, applier) \
applier(sb, magic); \
applier(sb, length); \
applier(sb, count);
#define GENERIC_BLOB_APPLY_BYTE_ORDER(gb, applier) \
applier(gb, magic); \
applier(gb, length);
#define CODE_DIRECTORY_APPLY_BYTE_ORDER(cd, applier) \
applier(cd, magic); \
applier(cd, length); \
applier(cd, version); \
applier(cd, flags); \
applier(cd, hashOffset); \
applier(cd, identOffset); \
applier(cd, nSpecialSlots); \
applier(cd, nCodeSlots); \
applier(cd, codeLimit); \
applier(cd, hashSize); \
applier(cd, hashType); \
applier(cd, platform); \
applier(cd, pageSize); \
applier(cd, spare2); \
applier(cd, scatterOffset); \
applier(cd, teamOffset);
#define SEGMENT_COMMAND_APPLY_BYTE_ORDER(sc, applier) \
applier(sc, cmd); \
applier(sc, cmdsize); \
applier(sc, fileoff); \
applier(sc, filesize); \
applier(sc, vmaddr); \
applier(sc, vmsize); \
applier(sc, flags); \
applier(sc, initprot); \
applier(sc, maxprot); \
applier(sc, nsects);
#define SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(sc64, applier) \
applier(sc64, cmd); \
applier(sc64, cmdsize); \
applier(sc64, fileoff); \
applier(sc64, filesize); \
applier(sc64, vmaddr); \
applier(sc64, vmsize); \
applier(sc64, flags); \
applier(sc64, initprot); \
applier(sc64, maxprot); \
applier(sc64, nsects);
#define SECTION_APPLY_BYTE_ORDER(sc, applier) \
applier(sc, addr); \
applier(sc, align); \
applier(sc, flags); \
applier(sc, nreloc); \
applier(sc, offset); \
applier(sc, reserved1); \
applier(sc, reserved2); \
applier(sc, size);
#define SECTION_64_APPLY_BYTE_ORDER(sc64, applier) \
applier(sc64, addr); \
applier(sc64, align); \
applier(sc64, flags); \
applier(sc64, nreloc); \
applier(sc64, offset); \
applier(sc64, reserved1); \
applier(sc64, reserved2); \
applier(sc64, reserved3); \
applier(sc64, size);
#define FILESET_ENTRY_COMMAND_APPLY_BYTE_ORDER(fse, applier) \
applier(fse, cmd); \
applier(fse, cmdsize); \
applier(fse, vmaddr); \
applier(fse, fileoff); \
applier(fse, entry_id.offset); \
applier(fse, reserved);
#define SYMTAB_COMMAND_APPLY_BYTE_ORDER(symt, applier) \
applier(symt, cmd); \
applier(symt, cmdsize); \
applier(symt, nsyms); \
applier(symt, stroff); \
applier(symt, strsize); \
applier(symt, symoff);
#define NLIST_APPLY_BYTE_ORDER(nl, applier) \
applier(nl, n_un.n_strx); \
applier(nl, n_type); \
applier(nl, n_sect); \
applier(nl, n_desc); \
applier(nl, n_value);
#define NLIST_64_APPLY_BYTE_ORDER(nl, applier) \
applier(nl, n_un.n_strx); \
applier(nl, n_type); \
applier(nl, n_sect); \
applier(nl, n_desc); \
applier(nl, n_value);
#define DYLIB_APPLY_BYTE_ORDER(dylib, applier) \
applier(dylib, name.offset); \
applier(dylib, timestamp); \
applier(dylib, current_version); \
applier(dylib, compatibility_version);
#define DYLIB_COMMAND_APPLY_BYTE_ORDER(dycmd, applier) \
LOAD_COMMAND_APPLY_BYTE_ORDER(dycmd, applier); \
DYLIB_APPLY_BYTE_ORDER((&dycmd->dylib), applier);
#define RPATH_COMMAND_APPLY_BYTE_ORDER(rpcmd, applier) \
LOAD_COMMAND_APPLY_BYTE_ORDER(rpcmd, applier); \
applier(rpcmd, path.offset);
#endif // MACHO_BYTE_ORDER_H
+189
View File
@@ -0,0 +1,189 @@
#include "MachOLoadCommand.h"
#include "Util.h"
#include "CSBlob.h"
char *load_command_to_string(int loadCommand) {
switch (loadCommand) {
case LC_SEGMENT:
return "LC_SEGMENT";
case LC_SYMTAB:
return "LC_SYMTAB";
case LC_SYMSEG:
return "LC_SYMSEG";
case LC_THREAD:
return "LC_THREAD";
case LC_UNIXTHREAD:
return "LC_UNIXTHREAD";
case LC_LOADFVMLIB:
return "LC_LOADFVMLIB";
case LC_IDFVMLIB:
return "LC_IDFVMLIB";
case LC_IDENT:
return "LC_IDENT";
case LC_FVMFILE:
return "LC_FVMFILE";
case LC_PREPAGE:
return "LC_PREPAGE";
case LC_DYSYMTAB:
return "LC_DYSYMTAB";
case LC_LOAD_DYLIB:
return "LC_LOAD_DYLIB";
case LC_ID_DYLIB:
return "LC_ID_DYLIB";
case LC_LOAD_DYLINKER:
return "LC_LOAD_DYLINKER";
case LC_ID_DYLINKER:
return "LC_ID_DYLINKER";
case LC_PREBOUND_DYLIB:
return "LC_PREBOUND_DYLIB";
case LC_ROUTINES:
return "LC_ROUTINES";
case LC_SUB_FRAMEWORK:
return "LC_SUB_FRAMEWORK";
case LC_SUB_UMBRELLA:
return "LC_SUB_UMBRELLA";
case LC_SUB_CLIENT:
return "LC_SUB_CLIENT";
case LC_SUB_LIBRARY:
return "LC_SUB_LIBRARY";
case LC_TWOLEVEL_HINTS:
return "LC_TWOLEVEL_HINTS";
case LC_PREBIND_CKSUM:
return "LC_PREBIND_CKSUM";
case LC_LOAD_WEAK_DYLIB:
return "LC_LOAD_WEAK_DYLIB";
case LC_SEGMENT_64:
return "LC_SEGMENT_64";
case LC_ROUTINES_64:
return "LC_ROUTINES_64";
case LC_UUID:
return "LC_UUID";
case LC_RPATH:
return "LC_RPATH";
case LC_CODE_SIGNATURE:
return "LC_CODE_SIGNATURE";
case LC_SEGMENT_SPLIT_INFO:
return "LC_SEGMENT_SPLIT_INFO";
case LC_REEXPORT_DYLIB:
return "LC_REEXPORT_DYLIB";
case LC_LAZY_LOAD_DYLIB:
return "LC_LAZY_LOAD_DYLIB";
case LC_ENCRYPTION_INFO:
return "LC_ENCRYPTION_INFO";
case LC_DYLD_INFO:
return "LC_DYLD_INFO";
case LC_DYLD_INFO_ONLY:
return "LC_DYLD_INFO_ONLY";
case LC_LOAD_UPWARD_DYLIB:
return "LC_LOAD_UPWARD_DYLIB";
case LC_VERSION_MIN_MACOSX:
return "LC_VERSION_MIN_MACOSX";
case LC_VERSION_MIN_IPHONEOS:
return "LC_VERSION_MIN_IPHONEOS";
case LC_FUNCTION_STARTS:
return "LC_FUNCTION_STARTS";
case LC_DYLD_ENVIRONMENT:
return "LC_DYLD_ENVIRONMENT";
case LC_MAIN:
return "LC_MAIN";
case LC_DATA_IN_CODE:
return "LC_DATA_IN_CODE";
case LC_SOURCE_VERSION:
return "LC_SOURCE_VERSION";
case LC_DYLIB_CODE_SIGN_DRS:
return "LC_DYLIB_CODE_SIGN_DRS";
case LC_ENCRYPTION_INFO_64:
return "LC_ENCRYPTION_INFO_64";
case LC_LINKER_OPTION:
return "LC_LINKER_OPTION";
case LC_LINKER_OPTIMIZATION_HINT:
return "LC_LINKER_OPTIMIZATION_HINT";
case LC_VERSION_MIN_TVOS:
return "LC_VERSION_MIN_TVOS";
case LC_VERSION_MIN_WATCHOS:
return "LC_VERSION_MIN_WATCHOS";
case LC_NOTE:
return "LC_NOTE";
case LC_BUILD_VERSION:
return "LC_BUILD_VERSION";
case LC_DYLD_EXPORTS_TRIE:
return "LC_DYLD_EXPORTS_TRIE";
case LC_DYLD_CHAINED_FIXUPS:
return "LC_DYLD_CHAINED_FIXUPS";
case LC_FILESET_ENTRY:
return "LC_FILESET_ENTRY";
default:
return "LC_UNKNOWN";
}
}
void update_segment_command_64(MachO *macho, const char *segmentName, uint64_t vmaddr, uint64_t vmsize, uint64_t fileoff, uint64_t filesize) {
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (loadCommand.cmd == LC_SEGMENT_64) {
struct segment_command_64 *segmentCommand = (struct segment_command_64 *)cmd;
SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, LITTLE_TO_HOST_APPLIER);
if (strcmp(segmentCommand->segname, segmentName) == 0) {
segmentCommand->vmaddr = vmaddr;
segmentCommand->vmsize = vmsize;
segmentCommand->fileoff = fileoff;
segmentCommand->filesize = filesize;
SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, HOST_TO_LITTLE_APPLIER);
memory_stream_write(macho->stream, offset, sizeof(struct segment_command_64), segmentCommand);
*stop = true;
}
}
});
}
void update_lc_code_signature(MachO *macho, uint64_t size) {
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (loadCommand.cmd == LC_CODE_SIGNATURE) {
struct linkedit_data_command *csLoadCommand = (struct linkedit_data_command *)cmd;
LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(csLoadCommand, LITTLE_TO_HOST_APPLIER);
csLoadCommand->datasize = size;
LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(csLoadCommand, HOST_TO_LITTLE_APPLIER);
memory_stream_write(macho->stream, offset, sizeof(struct linkedit_data_command), csLoadCommand);
*stop = true;
}
});
}
int update_load_commands_for_coretrust_bypass(MachO *macho, CS_SuperBlob *superblob, uint64_t originalCodeSignatureSize) {
uint64_t sizeOfCodeSignature = BIG_TO_HOST(superblob->length);
// Calculate how much padding we currently have
__block uint64_t blockPaddingSize = 0;
__block uint64_t vmAddress = 0;
__block uint64_t fileOffset = 0;
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
if (loadCommand.cmd == LC_SEGMENT_64) {
struct segment_command_64 *segmentCommand = (struct segment_command_64 *)cmd;
SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, LITTLE_TO_HOST_APPLIER);
if (strcmp(segmentCommand->segname, "__LINKEDIT") == 0) {
blockPaddingSize = segmentCommand->filesize - originalCodeSignatureSize;
vmAddress = segmentCommand->vmaddr;
fileOffset = segmentCommand->fileoff;
*stop = true;
}
}
});
if (blockPaddingSize == 0 || vmAddress == 0 || fileOffset == 0) {
printf("Error: failed to get existing values for __LINKEDIT segment.\n");
return -1;
}
uint64_t newSegmentSize = sizeOfCodeSignature + blockPaddingSize;
uint64_t newVMSize = align_to_size(newSegmentSize, 0x4000);
// Update the segment command
printf("Updating __LINKEDIT segment...\n");
update_segment_command_64(macho, "__LINKEDIT", vmAddress, newVMSize, fileOffset, newSegmentSize);
// Update the code signature load command
printf("Updating LC_CODE_SIGNATURE load command...\n");
update_lc_code_signature(macho, sizeOfCodeSignature);
return 0;
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef MACHO_LOAD_COMMAND_H
#define MACHO_LOAD_COMMAND_H
#include <mach-o/loader.h>
#ifndef LC_FILESET_ENTRY
#define MH_FILESET 0xc
#define LC_FILESET_ENTRY 0x80000035
struct fileset_entry_command {
uint32_t cmd; /* LC_FILESET_ENTRY */
uint32_t cmdsize; /* includes entry_id string */
uint64_t vmaddr; /* memory address of the entry */
uint64_t fileoff; /* file offset of the entry */
union lc_str entry_id; /* contained entry id */
uint32_t reserved; /* reserved */
};
#endif
#include "MachO.h"
#include "FileStream.h"
#include "MachOByteOrder.h"
#include "CSBlob.h"
// Convert load command to load command name
char *load_command_to_string(int loadCommand);
void update_segment_command_64(MachO *macho, const char *segmentName, uint64_t vmaddr, uint64_t vmsize, uint64_t fileoff, uint64_t filesize);
void update_lc_code_signature(MachO *macho, uint64_t size);
int update_load_commands_for_coretrust_bypass(MachO *macho, CS_SuperBlob *superblob, uint64_t originalCodeSignatureSize);
#endif // MACHO_LOAD_COMMAND_H
+220
View File
@@ -0,0 +1,220 @@
#include "MemoryStream.h"
#include "Util.h"
int memory_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf)
{
if (stream->read) {
int ret = stream->read(stream, offset, size, outBuf);
if (ret != size) { return -1; }
return 0;
}
return -1;
}
int memory_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf)
{
if (stream->write) {
int ret = stream->write(stream, offset, size, inBuf);
if (ret != size) { return -1; }
return 0;
}
return -1;
}
int memory_stream_insert(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf)
{
if (!(stream->flags & MEMORY_STREAM_FLAG_MUTABLE)) goto fail;
size_t streamSize = memory_stream_get_size(stream);
if (memory_stream_expand(stream, 0, size) != 0) goto fail;
if (memory_stream_copy_data(stream, offset, stream, offset + size, streamSize-offset) != 0) goto fail;
if (memory_stream_write(stream, offset, size, inBuf) != 0) goto fail;
return 0;
fail:
printf("Error: memory_stream_insert failed\n");
return -1;
}
int memory_stream_delete(MemoryStream *stream, uint64_t offset, size_t size)
{
if (size == 0) return 0;
if (!(stream->flags & MEMORY_STREAM_FLAG_MUTABLE)) goto fail;
size_t streamSize = memory_stream_get_size(stream);
if (memory_stream_copy_data(stream, offset+size, stream, offset, streamSize-(offset+size)) != 0) goto fail;
if (memory_stream_trim(stream, 0, size) != 0) goto fail;
return 0;
fail:
printf("Error: memory_stream_delete failed\n");
return -1;
}
int memory_stream_read_string(MemoryStream *stream, uint64_t offset, char **outString)
{
size_t size = 0;
char c = 1;
while (c != 0) {
int r = memory_stream_read(stream, offset+size, sizeof(c), &c);
if (r != 0) return r;
size++;
}
*outString = malloc(size+1);
return memory_stream_read(stream, offset, size+1, *outString);
}
int memory_stream_write_string(MemoryStream *stream, uint64_t offset, const char *string)
{
size_t size = strlen(string) + 1;
return memory_stream_write(stream, offset, size, string);
}
size_t memory_stream_get_size(MemoryStream *stream)
{
if (stream->getSize) {
size_t sizeOut;
if (stream->getSize(stream, &sizeOut) == 0) return sizeOut;
}
return MEMORY_STREAM_SIZE_INVALID;
}
uint8_t *memory_stream_get_raw_pointer(MemoryStream *stream)
{
if (stream->getRawPtr) {
return stream->getRawPtr(stream);
}
return NULL;
}
uint32_t memory_stream_get_flags(MemoryStream *stream)
{
return stream->flags;
}
void _memory_stream_clone(MemoryStream *clone, MemoryStream *stream)
{
clone->read = stream->read;
clone->write = stream->write;
clone->getSize = stream->getSize;
clone->getRawPtr = stream->getRawPtr;
clone->trim = stream->trim;
clone->expand = stream->expand;
clone->softclone = stream->softclone;
clone->hardclone = stream->hardclone;
clone->free = stream->free;
}
MemoryStream *memory_stream_softclone(MemoryStream *stream)
{
if (stream->softclone) {
MemoryStream *clone = stream->softclone(stream);
if (clone) {
_memory_stream_clone(clone, stream);
return clone;
}
}
return NULL;
}
MemoryStream *memory_stream_hardclone(MemoryStream *stream)
{
if (stream->hardclone) {
MemoryStream *clone = stream->hardclone(stream);
if (clone) {
_memory_stream_clone(clone, stream);
return clone;
}
}
return NULL;
}
int memory_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd)
{
if (stream->trim) {
return stream->trim(stream, trimAtStart, trimAtEnd);
}
return -1;
}
int memory_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd)
{
if (stream->expand) {
return stream->expand(stream, expandAtStart, expandAtEnd);
}
return -1;
}
void memory_stream_free(MemoryStream *stream)
{
if (stream->free) {
stream->free(stream);
}
free(stream);
}
#define COPY_DATA_BUFFER_SIZE 0x4000
int memory_stream_copy_data(MemoryStream *originStream, uint64_t originOffset, MemoryStream *targetStream, uint64_t targetOffset, size_t size)
{
size_t originSize = memory_stream_get_size(originStream);
size_t targetSize = memory_stream_get_size(targetStream);
if (originSize == MEMORY_STREAM_SIZE_INVALID || targetSize == MEMORY_STREAM_SIZE_INVALID) {
printf("Error: memory_stream_copy_data failed, invalid size detected\n");
return -1;
}
if (originOffset + size > originSize) {
printf("Error: memory_stream_copy_data failed, originOffset OOB\n");
return -1;
}
if (targetOffset + size > targetSize && !(memory_stream_get_flags(targetStream) | MEMORY_STREAM_FLAG_AUTO_EXPAND)) {
printf("Error: memory_stream_copy_data failed, targetOffset OOB\n");
return -1;
}
bool backwards = (originStream == targetStream) && (targetOffset > originOffset);
uint8_t buffer[COPY_DATA_BUFFER_SIZE];
for (uint32_t copiedSize = 0; copiedSize < size; copiedSize += COPY_DATA_BUFFER_SIZE) {
uint32_t remainingSize = size - copiedSize;
uint32_t sizeToCopy = COPY_DATA_BUFFER_SIZE;
if (remainingSize < sizeToCopy) {
sizeToCopy = remainingSize;
}
uint64_t readOffset = backwards ? ((originOffset + (size - copiedSize)) - sizeToCopy) : (originOffset + copiedSize);
uint64_t writeOffset = backwards ? ((targetOffset + (size - copiedSize)) - sizeToCopy) : (targetOffset + copiedSize);
int rr = memory_stream_read(originStream, readOffset, sizeToCopy, buffer);
if (rr != 0) {
printf("Error: memory_stream_copy_data failed on memory_stream_read (%d)\n", rr);
return rr;
}
int wr = memory_stream_write(targetStream, writeOffset, sizeToCopy, buffer);
if (wr != 0) {
printf("Error: memory_stream_copy_data failed on memory_stream_write (%d)\n", wr);
return rr;
}
}
return 0;
}
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)
{
__block int r = -1;
enumerate_range(searchStartOffset, searchEndOffset, alignment, nbytes, ^bool(uint64_t cur) {
uint8_t buf[nbytes];
memory_stream_read(stream, cur, nbytes, buf);
if (!memcmp_masked(buf, bytes, mask, nbytes)) {
*foundOffsetOut = cur;
r = 0;
return false;
}
return true;
});
return r;
}
+60
View File
@@ -0,0 +1,60 @@
#ifndef MEMORY_STREAM_H
#define MEMORY_STREAM_H
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#define MEMORY_STREAM_FLAG_OWNS_DATA (1 << 0)
#define MEMORY_STREAM_FLAG_MUTABLE (1 << 1)
#define MEMORY_STREAM_FLAG_AUTO_EXPAND (1 << 2)
#define MEMORY_STREAM_SIZE_INVALID (size_t)-1
// A generic memory IO interface that is used throughout this project
// Can be backed by anything, just the functions have to be implemented
typedef struct s_MemoryStream {
void *context;
uint32_t flags;
int (*read)(struct s_MemoryStream *stream, uint64_t offset, size_t size, void *outBuf);
int (*write)(struct s_MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf);
int (*getSize)(struct s_MemoryStream *stream, size_t *sizeOut);
uint8_t *(*getRawPtr)(struct s_MemoryStream *stream);
int (*trim)(struct s_MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd);
int (*expand)(struct s_MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd);
struct s_MemoryStream *(*hardclone)(struct s_MemoryStream *stream);
struct s_MemoryStream *(*softclone)(struct s_MemoryStream *stream);
void (*free)(struct s_MemoryStream *stream);
} MemoryStream;
int memory_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf);
int memory_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf);
int memory_stream_insert(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf);
int memory_stream_delete(MemoryStream *stream, uint64_t offset, size_t size);
int memory_stream_read_string(MemoryStream *stream, uint64_t offset, char **outString);
int memory_stream_write_string(MemoryStream *stream, uint64_t offset, const char *string);
size_t memory_stream_get_size(MemoryStream *stream);
uint8_t *memory_stream_get_raw_pointer(MemoryStream *stream);
uint32_t memory_stream_get_flags(MemoryStream *stream);
MemoryStream *memory_stream_softclone(MemoryStream *stream);
MemoryStream *memory_stream_hardclone(MemoryStream *stream);
int memory_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd);
int memory_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd);
void memory_stream_free(MemoryStream *stream);
int memory_stream_copy_data(MemoryStream *originStream, uint64_t originOffset, MemoryStream *targetStream, uint64_t targetOffset, size_t size);
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);
#endif // MEMORY_STREAM_H
+617
View File
@@ -0,0 +1,617 @@
#include "PatchFinder.h"
#include "MachO.h"
#include "MemoryStream.h"
#include "Util.h"
#include "PatchFinder_arm64.h"
#include <mach/machine.h>
void pfsec_info_populate_section(PFSectionInfo *sectionInfo, struct section_64 *section, MachOSegment *segment)
{
sectionInfo->fileoff = section->offset;
sectionInfo->vmaddr = section->addr;
sectionInfo->size = section->size;
sectionInfo->initprot = segment->command.initprot;
sectionInfo->maxprot = segment->command.maxprot;
strncpy(sectionInfo->segname, segment->command.segname, sizeof(segment->command.segname));
sectionInfo->segname[sizeof(sectionInfo->segname)-1] = '\0';
strncpy(sectionInfo->sectname, section->sectname, sizeof(section->sectname));
sectionInfo->sectname[sizeof(sectionInfo->sectname)-1] = '\0';
}
void pfsec_info_populate_segment(PFSectionInfo *sectionInfo, MachOSegment *segment)
{
sectionInfo->fileoff = segment->command.fileoff;
sectionInfo->vmaddr = segment->command.vmaddr;
sectionInfo->size = segment->command.vmsize;
sectionInfo->initprot = segment->command.initprot;
sectionInfo->maxprot = segment->command.maxprot;
strncpy(sectionInfo->segname, segment->command.segname, sizeof(segment->command.segname));
sectionInfo->segname[sizeof(sectionInfo->segname)-1] = '\0';
sectionInfo->sectname[0] = '\0';
}
void pfsec_info_populate_dsc_mapping(PFSectionInfo *sectionInfo, DyldSharedCacheMapping *dscMapping)
{
sectionInfo->fileoff = dscMapping->fileoff;
sectionInfo->vmaddr = dscMapping->vmaddr;
sectionInfo->size = dscMapping->size;
sectionInfo->initprot = dscMapping->initProt;
sectionInfo->maxprot = dscMapping->maxProt;
char dscIdxStr[3];
size_t filepathLen = strlen(dscMapping->file->filepath);
if (dscMapping->file->filepath[filepathLen-3] == '.') {
strncpy(dscIdxStr, &dscMapping->file->filepath[filepathLen-2], sizeof(dscIdxStr));
}
else {
strncpy(dscIdxStr, "00", sizeof(dscIdxStr));
}
snprintf(sectionInfo->segname, sizeof(sectionInfo->segname), "__DSC_%s", dscIdxStr);
if (dscMapping->flags & DYLD_CACHE_MAPPING_AUTH_DATA) {
strlcpy(sectionInfo->sectname, "__auth_data", sizeof(sectionInfo->sectname));
} else if (dscMapping->flags & DYLD_CACHE_MAPPING_DIRTY_DATA) {
strlcpy(sectionInfo->sectname, "__data", sizeof(sectionInfo->sectname));
} else if (dscMapping->flags & DYLD_CACHE_MAPPING_CONST_DATA) {
strlcpy(sectionInfo->sectname, "__const_data", sizeof(sectionInfo->sectname));
} else if (dscMapping->flags & DYLD_CACHE_MAPPING_TEXT_STUBS) {
strlcpy(sectionInfo->sectname, "__text_stubs", sizeof(sectionInfo->sectname));
} else if (dscMapping->flags & DYLD_CACHE_DYNAMIC_CONFIG_DATA) {
strlcpy(sectionInfo->sectname, "__config_data", sizeof(sectionInfo->sectname));
}
}
int raw_buffer_find_memory(uint8_t *buf, uint64_t searchStartOffset, uint64_t searchEndOffset, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundOffsetOut)
{
__block int r = -1;
enumerate_range(searchStartOffset, searchEndOffset, alignment, nbytes, ^bool(uint64_t cur) {
if (!memcmp_masked(&buf[cur], bytes, mask, nbytes)) {
*foundOffsetOut = cur;
r = 0;
return false;
}
return true;
});
return r;
}
PFSection *pfsec_init_from_macho(MachO *macho, const char *filesetEntryId, const char *segName, const char *sectName)
{
PFSection *pfSection = NULL;
MachO *machoToUse = NULL;
if (filesetEntryId) {
// try to find a fileset macho with this identifier
for (uint32_t i = 0; i < macho->filesetCount; i++) {
FilesetMachO *filesetMacho = &macho->filesetMachos[i];
if (filesetMacho->underlyingMachO->slicesCount == 1) {
if (!strcmp(filesetMacho->entry_id, filesetEntryId)) {
machoToUse = filesetMacho->underlyingMachO->slices[0];
break;
}
}
}
}
else {
machoToUse = macho;
}
if (machoToUse) {
MachOSegment *segment = NULL;
for (uint32_t i = 0; i < machoToUse->segmentCount; i++) {
MachOSegment *segmentCandidate = machoToUse->segments[i];
if (!strncmp(segmentCandidate->command.segname, segName, sizeof(segmentCandidate->command.segname))) {
segment = segmentCandidate;
break;
}
}
if (segment) {
if (sectName) {
struct section_64 *section = NULL;
for (uint32_t i = 0; i < segment->command.nsects; i++) {
struct section_64 *sectionCandidate = &segment->sections[i];
if (!strncmp(sectionCandidate->sectname, sectName, sizeof(sectionCandidate->sectname))) {
section = sectionCandidate;
}
}
if (section) {
pfSection = malloc(sizeof(PFSection));
memset(pfSection, 0, sizeof(PFSection));
pfsec_info_populate_section(&pfSection->info, section, segment);
}
}
else {
pfSection = malloc(sizeof(PFSection));
memset(pfSection, 0, sizeof(PFSection));
pfsec_info_populate_segment(&pfSection->info, segment);
}
}
}
if (pfSection) {
pfSection->cache = NULL;
MemoryStream *stream = memory_stream_softclone(macho->stream);
memory_stream_trim(stream, pfSection->info.fileoff, memory_stream_get_size(stream) - (pfSection->info.fileoff + pfSection->info.size));
pfSection->stream = stream;
pfSection->macho = macho;
}
return pfSection;
}
PFSection *pfsec_init_from_dsc_mapping(DyldSharedCache *sharedCache, DyldSharedCacheMapping *mapping)
{
if (!sharedCache || !mapping) return NULL;
PFSection *pfSection = malloc(sizeof(PFSection));
if (!pfSection) return NULL;
memset(pfSection, 0, sizeof(PFSection));
pfsec_info_populate_dsc_mapping(&pfSection->info, mapping);
pfSection->sharedCache = sharedCache;
return pfSection;
}
MachO *pfsec_get_macho(PFSection *section)
{
return section->macho;
}
DyldSharedCache *pfsec_get_dsc(PFSection *section)
{
return section->sharedCache;
}
void pfsec_set_pointer_decoder(PFSection *section, uint64_t (*pointerDecoder)(struct s_PFSection *section, uint64_t vmaddr, uint64_t value))
{
section->pointerDecoder = pointerDecoder;
}
int pfsec_read_reloff(PFSection *section, uint64_t rel, size_t size, void *outBuf)
{
if (rel > section->info.size) return -1;
void *rawPtr = pfsec_get_raw_pointer(section);
if (rawPtr) {
memcpy(outBuf, &rawPtr[rel], size);
return 0;
}
else {
// XXX: This does not work for MachOs that are inside the DSC
// This code path will never be used for that however, since all DSC MachOs have a raw pointer
return memory_stream_read(section->stream, rel, size, outBuf);
}
return -1;
}
uint32_t pfsec_read32_reloff(PFSection *section, uint64_t rel)
{
uint32_t r = 0;
pfsec_read_reloff(section, rel, sizeof(r), &r);
return r;
}
int pfsec_read_string_reloff(PFSection *section, uint64_t rel, char **outString)
{
if (rel > section->info.size) return -1;
uint8_t *rawPtr = pfsec_get_raw_pointer(section);
if (rawPtr) {
const char *curString = (const char *)&rawPtr[rel];
// make sure we don't end up reading OOB memory
if ((uint8_t *)&curString[strnlen(curString, section->info.size - rel)] >= &rawPtr[section->info.size]) {
return -1;
}
*outString = strdup(curString);
return 0;
}
else if (section->stream) {
// XXX: This does not work for MachOs that are inside the DSC
// This code path will never be used for that however, since all DSC MachOs have a raw pointer
return memory_stream_read_string(section->stream, rel, outString);
}
return -1;
}
int pfsec_read_string(PFSection *section, uint64_t vmaddr, char **outString)
{
if (vmaddr < section->info.vmaddr) return -1;
return pfsec_read_string_reloff(section, vmaddr - section->info.vmaddr, outString);
}
int pfsec_read_at_address(PFSection *section, uint64_t vmaddr, void *outBuf, size_t size)
{
if (vmaddr < section->info.vmaddr) return -1;
if (vmaddr + size > section->info.vmaddr + section->info.size) return -1;
uint64_t rel = vmaddr - section->info.vmaddr;
return pfsec_read_reloff(section, rel, size, outBuf);
}
uint32_t pfsec_read32(PFSection *section, uint64_t vmaddr)
{
uint32_t r = 0;
pfsec_read_at_address(section, vmaddr, &r, sizeof(r));
return r;
}
uint64_t pfsec_read64(PFSection *section, uint64_t vmaddr)
{
uint64_t r = 0;
pfsec_read_at_address(section, vmaddr, &r, sizeof(r));
return r;
}
uint64_t pfsec_read_pointer(PFSection *section, uint64_t vmaddr)
{
uint64_t value = pfsec_read64(section, vmaddr);
if (section->pointerDecoder) {
return section->pointerDecoder(section, vmaddr, value);
}
return value;
}
int pfsec_set_cached(PFSection *section, bool cached)
{
bool isCachedAlready = (bool)section->cache;
if (cached != isCachedAlready && section->stream) {
if (cached) {
// If we already have a raw pointer, caching is obsolete
if (pfsec_get_raw_pointer(section)) return 0;
void *cache = malloc(section->info.size);
int r = pfsec_read_reloff(section, 0, section->info.size, cache);
if (r != 0) {
free(cache);
return r;
}
section->cache = cache;
}
else {
free(section->cache);
section->cache = NULL;
}
}
return 0;
}
void *pfsec_get_raw_pointer(PFSection *section)
{
DyldSharedCache *sharedCache = pfsec_get_dsc(section);
if (!sharedCache) {
MachO *macho = pfsec_get_macho(section);
if (macho) {
sharedCache = macho->containingCache;
}
}
if (sharedCache) {
return dsc_find_buffer(sharedCache, section->info.vmaddr, section->info.size);
}
uint8_t *rawPtr = NULL;
if (section->stream) {
rawPtr = memory_stream_get_raw_pointer(section->stream);
}
if (rawPtr) {
return rawPtr;
}
else if(section->cache) {
return section->cache;
}
return NULL;
}
int pfsec_find_memory_rel(PFSection *section, uint64_t searchStartOffset, uint64_t searchEndOffset, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundRelOffsetOut)
{
void *rawPtr = pfsec_get_raw_pointer(section);
if (rawPtr) {
return raw_buffer_find_memory(rawPtr, searchStartOffset, searchEndOffset, bytes, mask, nbytes, alignment, foundRelOffsetOut);
}
else if (section->stream) {
// XXX: This does not work for MachOs that are inside the DSC
// This code path will never be used for that however, since all DSC MachOs have a raw pointer
return memory_stream_find_memory(section->stream, searchStartOffset, searchEndOffset, bytes, mask, nbytes, alignment, foundRelOffsetOut);
}
return -1;
}
int pfsec_find_memory(PFSection *section, uint64_t searchStartAddr, uint64_t searchEndAddr, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundAddrOut)
{
// Ensure searchStartAddr and searchEndAddr stay within section bounds
if (searchStartAddr < section->info.vmaddr) searchStartAddr = section->info.vmaddr;
if (searchEndAddr < section->info.vmaddr) searchEndAddr = section->info.vmaddr;
if (searchStartAddr > (section->info.vmaddr + section->info.size)) searchStartAddr = section->info.vmaddr + section->info.size;
if (searchEndAddr > (section->info.vmaddr + section->info.size)) searchEndAddr = section->info.vmaddr + section->info.size;
uint64_t foundRelOff = 0;
int r = pfsec_find_memory_rel(section, searchStartAddr - section->info.vmaddr, searchEndAddr - section->info.vmaddr, bytes, mask, nbytes, alignment, &foundRelOff);
if (r == 0) {
*foundAddrOut = section->info.vmaddr + foundRelOff;
}
return r;
}
// pfsec_find_prev_inst will start searching at the instruction *before* the instruction at startAddr
// + 0x1000: [first search comparison]
// + 0x1004: [startAddr]
uint64_t pfsec_find_prev_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask)
{
uint64_t out = 0;
uint64_t endAddr = searchCount ? (startAddr - (sizeof(uint32_t) * searchCount)) : section->info.vmaddr;
pfsec_find_memory(section, startAddr, endAddr, &inst, &mask, sizeof(inst), sizeof(uint32_t), &out);
if (!out) return 0;
return out;
}
// pfsec_find_next_inst will start searching at the instruction at startAddr
// + 0x1000: [startAddr, first search comparison]
uint64_t pfsec_find_next_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask)
{
uint64_t out = 0;
uint64_t endAddr = searchCount ? (startAddr + (sizeof(uint32_t) * searchCount)) : (section->info.vmaddr + section->info.size);
pfsec_find_memory(section, startAddr, endAddr, &inst, &mask, sizeof(inst), sizeof(uint32_t), &out);
if (!out) return 0;
return out;
}
uint64_t pfsec_find_function_start(PFSection *section, uint64_t midAddr)
{
uint32_t cputype = 0;
uint32_t cpusubtype = 0;
MachO *macho = pfsec_get_macho(section);
DyldSharedCache *sharedCache = pfsec_get_dsc(section);
if (macho) {
cputype = macho->machHeader.cputype;
cpusubtype = macho->machHeader.cpusubtype;
// If the MachO contains function starts, use those to determine the function start
__block uint64_t start = 0;
if (macho_enumerate_function_starts(macho, ^(uint64_t funcAddr, bool *stop){
if (funcAddr <= midAddr) {
start = funcAddr;
}
else {
*stop = true;
}
}) == 0) {
if (start >= section->info.vmaddr && start < (section->info.vmaddr + section->info.size)) {
return start;
}
}
}
else if (sharedCache) {
cputype = sharedCache->cputype;
cpusubtype = sharedCache->cpusubtype;
}
// If there are no function starts, try to find them based on a heuristic approach
if (cputype == CPU_TYPE_ARM64) {
if ((cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E) {
// Find start of function by going back until we find a PACIBSP
uint64_t addr = midAddr;
while (addr > section->info.vmaddr && addr < (section->info.vmaddr + section->info.size)) {
uint32_t curInst = pfsec_read32(section, addr);
if (curInst == 0xd503237f) return addr;
addr -= 4;
}
}
else if ((cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64_ALL) {
// Find start of function by going back until we find a stack frame push
// Technique adapted from pongoOS
uint64_t frameAddr = pfsec_find_prev_inst(section, midAddr, 0, 0x910003fd, 0xff8003ff); // add x29, sp, ?
if (frameAddr) {
uint64_t start = pfsec_find_prev_inst(section, frameAddr, 10, 0x29a003e0, 0x3be003e0); // stp ?, ?, [sp, ?]!
if (!start) {
start = pfsec_find_prev_inst(section, frameAddr, 10, 0xd10003ff, 0xff8003ff); // sub sp, sp, ?
}
return start;
}
}
}
return 0;
}
bool pfsec_contains_vmaddr(PFSection *section, uint64_t addr)
{
return (addr >= section->info.vmaddr && addr < (section->info.vmaddr + section->info.size));
}
void pfsec_free(PFSection *section)
{
if (section->stream) {
memory_stream_free(section->stream);
}
pfsec_set_cached(section, false);
free(section);
}
void _pfsec_run_pattern_metric(PFSection *section, uint64_t startAddr, uint64_t endAddr, PFPatternMetric *patternMetric, void (^matchBlock)(uint64_t vmaddr, bool *stop))
{
uint16_t alignment = patternMetric->alignment;
while (pfsec_find_memory(section, startAddr, endAddr, patternMetric->bytes, patternMetric->mask, patternMetric->nbytes, alignment, &startAddr) == 0) {
bool stop = false;
matchBlock(startAddr, &stop);
if (stop) break;
if (endAddr >= startAddr) {
startAddr += alignment;
}
else {
// When searching backwards, the search will automatically start one instruction before startAddr
// Due to that, we do not need to decrement it by anything
}
}
}
PFPatternMetric *pfmetric_pattern_init(void *bytes, void *mask, size_t nbytes, uint16_t alignment)
{
PFPatternMetric *metric = malloc(sizeof(PFPatternMetric));
metric->shared.type = PFMETRIC_TYPE_PATTERN;
metric->bytes = bytes;
metric->mask = mask;
metric->nbytes = nbytes;
metric->alignment = alignment;
return metric;
}
void _pfsec_run_string_metric(PFSection *section, uint64_t startAddr, uint64_t endAddr, PFStringMetric *stringMetric, void (^matchBlock)(uint64_t vmaddr, bool *stop))
{
char *str = NULL;
uint64_t searchOffset = 0;
while (pfsec_read_string_reloff(section, searchOffset, &str) == 0) {
if (!strcmp(str, stringMetric->string)) {
bool stop = false;
matchBlock(section->info.vmaddr + searchOffset, &stop);
if (stop) break;
}
searchOffset += strlen(str)+1;
free(str);
}
}
PFStringMetric *pfmetric_string_init(const char *string)
{
PFStringMetric *metric = malloc(sizeof(PFStringMetric));
metric->shared.type = PFMETRIC_TYPE_STRING;
metric->string = strdup(string);
return metric;
}
void _pfsec_run_arm64_xref_metric(PFSection *section, uint64_t startAddr, uint64_t endAddr, PFXrefMetric *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop))
{
Arm64XrefTypeMask arm64Types = 0;
if (metric->typeMask == 0) return;
if (metric->typeMask & XREF_TYPE_MASK_CALL) {
arm64Types |= ARM64_XREF_TYPE_MASK_CALL;
}
if (metric->typeMask & XREF_TYPE_MASK_JUMP) {
arm64Types |= ARM64_XREF_TYPE_MASK_JUMP;
}
if (metric->typeMask & XREF_TYPE_MASK_REFERENCE) {
arm64Types |= ARM64_XREF_TYPE_MASK_REFERENCE;
}
if (metric->typeMask & XREF_TYPE_MASK_POINTER) {
arm64Types |= ARM64_XREF_TYPE_MASK_POINTER;
}
pfsec_arm64_enumerate_xrefs(section, arm64Types, ^(Arm64XrefType type, uint64_t source, uint64_t target, bool *stop) {
bool match = false;
if (metric->dynamicHandler) {
match = metric->dynamicHandler(section, metric, source, target);
}
else {
match = (target == metric->address);
}
if (match) {
matchBlock(source, stop);
}
});
}
void _pfsec_run_xref_metric(PFSection *section, uint64_t startAddr, uint64_t endAddr, PFXrefMetric *xrefMetric, void (^matchBlock)(uint64_t vmaddr, bool *stop))
{
uint32_t cputype = 0;
MachO *macho = pfsec_get_macho(section);
if (macho) {
cputype = macho->machHeader.cputype;
}
else {
DyldSharedCache *sharedCache = pfsec_get_dsc(section);
if (sharedCache) {
cputype = sharedCache->cputype;
}
}
switch(cputype) {
case CPU_TYPE_ARM64:
_pfsec_run_arm64_xref_metric(section, startAddr, endAddr, xrefMetric, matchBlock);
break;
}
}
PFXrefMetric *pfmetric_xref_init(uint64_t address, PFXrefTypeMask types)
{
PFXrefMetric *metric = malloc(sizeof(PFXrefMetric));
if (!metric) return NULL;
memset(metric, 0, sizeof(PFXrefMetric));
metric->shared.type = PFMETRIC_TYPE_XREF;
metric->address = address;
metric->typeMask = types;
return metric;
}
PFXrefMetric *pfmetric_dynamic_xref_init(bool (*dynamicHandler)(PFSection *section, PFXrefMetric *metric, uint64_t location, uint64_t target), void *ctx, PFXrefTypeMask types)
{
PFXrefMetric *metric = malloc(sizeof(PFXrefMetric));
if (!metric) return NULL;
memset(metric, 0, sizeof(PFXrefMetric));
metric->shared.type = PFMETRIC_TYPE_XREF;
metric->dynamicHandler = dynamicHandler;
metric->ctx = ctx;
metric->typeMask = types;
return metric;
}
void pfmetric_free(void *metric)
{
uint32_t type = ((PFPatternMetric *)metric)->shared.type;
if (type == PFMETRIC_TYPE_STRING) {
free(((PFStringMetric *)metric)->string);
}
free(metric);
}
void pfmetric_run_in_range(PFSection *section, uint64_t startAddr, uint64_t endAddr, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop))
{
if (startAddr == -1ULL) startAddr = section->info.vmaddr;
if (endAddr == -1ULL) endAddr = section->info.vmaddr + section->info.size;
MetricShared *shared = metric;
switch (shared->type) {
case PFMETRIC_TYPE_PATTERN: {
_pfsec_run_pattern_metric(section, startAddr, endAddr, metric, matchBlock);
break;
}
case PFMETRIC_TYPE_STRING: {
_pfsec_run_string_metric(section, startAddr, endAddr, metric, matchBlock);
break;
}
case PFMETRIC_TYPE_XREF: {
_pfsec_run_xref_metric(section, startAddr, endAddr, metric, matchBlock);
break;
}
}
}
void pfmetric_run(PFSection *section, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop))
{
return pfmetric_run_in_range(section, -1, -1, metric, matchBlock);
}
+105
View File
@@ -0,0 +1,105 @@
#ifndef PATCHFINDER_H
#define PATCHFINDER_H
#include <stdint.h>
#include "MachO.h"
enum {
PFMETRIC_TYPE_PATTERN,
PFMETRIC_TYPE_STRING,
PFMETRIC_TYPE_XREF,
};
typedef struct s_PFSectionInfo {
char sectname[17];
char segname[17];
uint64_t fileoff;
uint64_t vmaddr;
uint64_t size;
uint32_t initprot;
uint32_t maxprot;
} PFSectionInfo;
void pfsec_info_populate_section64(PFSectionInfo *sectionInfo, struct section_64 *section64);
void pfsec_info_populate_segment(PFSectionInfo *sectionInfo, MachOSegment *segment);
void pfsec_info_populate_dsc_mapping(PFSectionInfo *sectionInfo, DyldSharedCacheMapping *dscMapping);
typedef struct s_PFSection {
MemoryStream *stream;
MachO *macho;
DyldSharedCache *sharedCache;
PFSectionInfo info;
uint8_t *cache;
uint64_t (*pointerDecoder)(struct s_PFSection *section, uint64_t vmaddr, uint64_t value);
} PFSection;
PFSection *pfsec_init_from_macho(MachO *macho, const char *filesetEntryId, const char *segName, const char *sectName);
PFSection *pfsec_init_from_dsc_mapping(DyldSharedCache *sharedCache, DyldSharedCacheMapping *mapping);
MachO *pfsec_get_macho(PFSection *section);
DyldSharedCache *pfsec_get_dsc(PFSection *section);
void pfsec_set_pointer_decoder(PFSection *section, uint64_t (*pointerDecoder)(struct s_PFSection *section, uint64_t vmaddr, uint64_t value));
int pfsec_read_reloff(PFSection *section, uint64_t rel, size_t size, void *outBuf);
uint32_t pfsec_read32_reloff(PFSection *section, uint64_t rel);
int pfsec_read_at_address(PFSection *section, uint64_t vmaddr, void *outBuf, size_t size);
uint32_t pfsec_read32(PFSection *section, uint64_t vmaddr);
uint64_t pfsec_read64(PFSection *section, uint64_t vmaddr);
uint64_t pfsec_read_pointer(PFSection *section, uint64_t vmaddr);
int pfsec_read_string(PFSection *section, uint64_t vmaddr, char **outString);
int pfsec_set_cached(PFSection *section, bool cached);
void *pfsec_get_raw_pointer(PFSection *section);
uint64_t pfsec_find_prev_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask);
uint64_t pfsec_find_next_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask);
uint64_t pfsec_find_function_start(PFSection *section, uint64_t midAddr);
bool pfsec_contains_vmaddr(PFSection *section, uint64_t addr);
void pfsec_free(PFSection *section);
typedef struct s_MetricShared {
uint32_t type;
} MetricShared;
typedef struct s_PFPatternMetric {
MetricShared shared;
void *bytes;
void *mask;
size_t nbytes;
uint16_t alignment;
} PFPatternMetric;
typedef struct s_PFStringMetric {
MetricShared shared;
char *string;
} PFStringMetric;
typedef enum {
XREF_TYPE_MASK_CALL = (1 << 0),
XREF_TYPE_MASK_JUMP = (1 << 1),
XREF_TYPE_MASK_REFERENCE = (1 << 2),
XREF_TYPE_MASK_POINTER = (1 << 3),
XREF_TYPE_MASK_ALL = (XREF_TYPE_MASK_CALL | XREF_TYPE_MASK_REFERENCE | XREF_TYPE_MASK_POINTER),
} PFXrefTypeMask;
typedef struct s_PFXrefMetric {
MetricShared shared;
// Normal xref
uint64_t address;
// Dynamic xref
bool (*dynamicHandler)(PFSection *section, struct s_PFXrefMetric *metric, uint64_t location, uint64_t target);
void *ctx;
PFXrefTypeMask typeMask;
} PFXrefMetric;
PFPatternMetric *pfmetric_pattern_init(void *bytes, void *mask, size_t nbytes, uint16_t alignment);
PFStringMetric *pfmetric_string_init(const char *string);
PFXrefMetric *pfmetric_xref_init(uint64_t address, PFXrefTypeMask types);
PFXrefMetric *pfmetric_dynamic_xref_init(bool (*dynamicHandler)(PFSection *section, PFXrefMetric *metric, uint64_t location, uint64_t target), void *ctx, PFXrefTypeMask types);
void pfmetric_free(void *metric);
void pfmetric_run_in_range(PFSection *section, uint64_t startAddr, uint64_t endAddr, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop));
void pfmetric_run(PFSection *section, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop));
#endif
+341
View File
@@ -0,0 +1,341 @@
#include "PatchFinder_arm64.h"
#include "PatchFinder.h"
#include "arm64.h"
#include <sys/mman.h>
// Unified check for whether anything writes to a register between firstAddr and secondAddr
bool pfsec_arm64_scan_register_write(PFSection *section, arm64_register reg, uint64_t endAddr, uint64_t startAddr)
{
if (startAddr == endAddr) {
return false;
}
else if (startAddr > endAddr) {
printf("Fatal error pfsec_arm64_scan_register_write: startAddr > endAddr\n");
return false;
}
uint16_t regNum = ARM64_REG_GET_NUM(reg);
for (uint64_t curAddr = (endAddr - sizeof(uint32_t)); curAddr > startAddr; curAddr -= sizeof(uint32_t)) {
uint32_t inst = pfsec_read32(section, curAddr);
arm64_register destinationReg, sourceReg;
// Check for immediate add writing to reg
if (arm64_dec_add_imm(inst, &destinationReg, &sourceReg, NULL) == 0) goto found_inst_match;
// Check for immediate mov writing to reg
else if (arm64_dec_mov_imm(inst, &destinationReg, NULL, NULL, NULL) == 0) goto found_inst_match;
// Check for register mov writing to reg
else if (arm64_dec_mov_reg(inst, &destinationReg, &sourceReg) == 0) goto found_inst_match;
// Check for immediate ldr writing to reg
else if (arm64_dec_ldr_imm(inst, &destinationReg, NULL, NULL, NULL, NULL) == 0) goto found_inst_match;
// Check for literal ldr writing to reg
else if (arm64_dec_ldr_lit(inst, curAddr, NULL, &destinationReg) == 0) goto found_inst_match;
// Check for immediate ldrs writing to reg
else if (arm64_dec_ldrs_imm(inst, &destinationReg, NULL, NULL, NULL, NULL) == 0) goto found_inst_match;
// Nothing matched, continue
else continue;
found_inst_match:
if (ARM64_REG_GET_NUM(destinationReg) == regNum) {
goto found_write;
}
// Didn't write to our register, continue
continue;
}
return false;
found_write:
return true;
}
uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference(PFSection *section, uint64_t adrpAddr, uint64_t ldrStrAddAddr)
{
uint32_t ldrStrAddInst = pfsec_read32(section, ldrStrAddAddr);
uint64_t imm = 0;
if (arm64_dec_ldr_imm(ldrStrAddInst, NULL, NULL, &imm, NULL, NULL) != 0) {
if (arm64_dec_ldrs_imm(ldrStrAddInst, NULL, NULL, &imm, NULL, NULL) != 0) {
if (arm64_dec_str_imm(ldrStrAddInst, NULL, NULL, &imm, NULL, NULL) != 0) {
uint16_t addImm = 0;
if (arm64_dec_add_imm(ldrStrAddInst, NULL, NULL, &addImm) == 0) {
imm = (uint64_t)addImm;
}
else {
return 0;
}
}
}
}
uint64_t adrpTarget = 0;
arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL);
return adrpTarget + imm;
}
uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(PFSection *section, uint64_t ldrStrAddAddr)
{
uint32_t inst = pfsec_read32(section, ldrStrAddAddr);
bool isAdd = false;
arm64_register reg;
if (arm64_dec_ldr_imm(inst, NULL, &reg, NULL, NULL, NULL) != 0) {
if (arm64_dec_str_imm(inst, NULL, &reg, NULL, NULL, NULL) != 0) {
if (arm64_dec_add_imm(inst, NULL, &reg, NULL) != 0) {
return 0;
}
else {
isAdd = true;
}
}
}
uint32_t adrpInst = 0, adrpInstMask = 0;
arm64_gen_adr_p(OPT_BOOL_NONE, OPT_UINT64_NONE, OPT_UINT64_NONE, reg, &adrpInst, &adrpInstMask);
uint64_t adrpAddr = pfsec_find_prev_inst(section, ldrStrAddAddr, 100, adrpInst, adrpInstMask);
if (!adrpAddr) return 0;
if (!isAdd) {
// For str and ldr, there can be the following sequence:
// adr(p) xA, <page> (adrpAddr)
// (0..n) instructions
// add xA, xA, <page_off> (specialCaseAddAddr)
// (0..n) instructions
// ldr/str xB, [xA] (ldrStrAddAddr)
// We search for the middle add instruction and if we find it, we need to handle this case
uint32_t searchCount = ((ldrStrAddAddr - adrpAddr) / 4) - 1;
if (searchCount) {
uint32_t specialCaseAddInst = 0, specialCaseAddMask = 0;
arm64_gen_add_imm(reg, reg, OPT_UINT64_NONE, &specialCaseAddInst, &specialCaseAddMask);
uint64_t specialCaseAddAddr = pfsec_find_prev_inst(section, ldrStrAddAddr, searchCount, specialCaseAddInst, specialCaseAddMask);
if (specialCaseAddAddr) {
// In this case, we need to make sure there are no writes in the two "(0..n) instructions" sections
if (pfsec_arm64_scan_register_write(section, reg, ldrStrAddAddr, specialCaseAddAddr)) return 0;
if (pfsec_arm64_scan_register_write(section, reg, specialCaseAddAddr, adrpAddr)) return 0;
uint64_t baseAddr = pfsec_arm64_resolve_adrp_ldr_str_add_reference(section, adrpAddr, specialCaseAddAddr);
if (!baseAddr) return 0;
uint32_t ldrStrAddInst = pfsec_read32(section, ldrStrAddAddr);
uint64_t imm64 = 0;
uint16_t imm16 = 0;
if (arm64_dec_add_imm(ldrStrAddInst, NULL, NULL, &imm16) != 0) {
if (arm64_dec_ldr_imm(ldrStrAddInst, NULL, NULL, &imm64, NULL, NULL) != 0) {
if (arm64_dec_str_imm(ldrStrAddInst, NULL, NULL, &imm64, NULL, NULL) != 0) {
fprintf(stderr, "Warning: failed decoding ldrStrAddInst (%#x @ %#llx)\n", ldrStrAddInst, ldrStrAddAddr);
}
}
}
else {
imm64 = imm16;
}
return baseAddr + imm64;
}
}
}
if (pfsec_arm64_scan_register_write(section, reg, ldrStrAddAddr, adrpAddr)) return 0;
return pfsec_arm64_resolve_adrp_ldr_str_add_reference(section, adrpAddr, ldrStrAddAddr);
}
uint64_t pfsec_arm64_resolve_stub(PFSection *section, uint64_t stubAddr)
{
// A stub is usually:
// adrp x16, ?
// ldr x16, ?
// br x16
// First, check if what we have actually is a stub
uint32_t inst[3];
pfsec_read_at_address(section, stubAddr, inst, sizeof(inst));
uint32_t stubInst[3], stubMask[3];
arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_X(16), &stubInst[0], &stubMask[0]);
arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_X(16), ARM64_REG_X(16), OPT_UINT64_NONE, &stubInst[1], &stubMask[1]);
stubInst[2] = 0xd61f0200;
stubMask[2] = 0xffffffff;
if ((inst[0] & stubMask[0]) == stubInst[0] ||
(inst[1] & stubMask[1]) == stubInst[1] ||
(inst[2] & stubMask[2]) == stubInst[2]) {
// This is a stub, resolve it
uint64_t ptrAddr = pfsec_arm64_resolve_adrp_ldr_str_add_reference(section, stubAddr, stubAddr + 4);
if (ptrAddr) {
uint64_t targetAddr = 0;
MachO *macho = pfsec_get_macho(section);
if (macho) {
macho_read_at_vmaddr(macho, ptrAddr, sizeof(targetAddr), &targetAddr);
}
else {
DyldSharedCache *sharedCache = pfsec_get_dsc(section);
if (sharedCache) {
dsc_read_from_vmaddr(sharedCache, ptrAddr, sizeof(targetAddr), &targetAddr);
}
}
return targetAddr;
}
}
// Not a stub, just return original address
return stubAddr;
}
void pfsec_arm64_enumerate_xrefs(PFSection *section, Arm64XrefTypeMask types, void (^xrefBlock)(Arm64XrefType type, uint64_t source, uint64_t target, bool *stop))
{
bool stop = false;
if (section->info.initprot & PROT_EXEC) {
for (uint64_t addr = section->info.vmaddr; addr < (section->info.vmaddr + section->info.size) && !stop; addr += sizeof(uint32_t)) {
uint32_t inst = pfsec_read32(section, addr);
if ((types & ARM64_XREF_TYPE_MASK_B) || (types & ARM64_XREF_TYPE_MASK_BL)) {
uint64_t target = 0;
bool isBl = 0;
if (arm64_dec_b_l(inst, addr, &target, &isBl) == 0) {
if (isBl && (types & ARM64_XREF_TYPE_MASK_BL)) {
xrefBlock(ARM64_XREF_TYPE_BL, addr, target, &stop);
}
else if (!isBl && (types & ARM64_XREF_TYPE_MASK_B)) {
xrefBlock(ARM64_XREF_TYPE_B, addr, target, &stop);
}
continue;
}
}
if (types & ARM64_XREF_TYPE_MASK_ADR) {
uint64_t target = 0;
bool isAdrp = false;
arm64_register reg;
if (arm64_dec_adr_p(inst, addr, &target, &reg, &isAdrp) == 0) {
if (!isAdrp) {
xrefBlock(ARM64_XREF_TYPE_ADR, addr, target, &stop);
}
continue;
}
}
if ((types & ARM64_XREF_TYPE_MASK_B_COND) || (types & ARM64_XREF_TYPE_MASK_BC_COND)) {
uint64_t target = 0;
bool isBc = false;
if (arm64_dec_b_c_cond(inst, addr, &target, NULL, &isBc) == 0) {
if (!isBc && (types & ARM64_XREF_TYPE_MASK_B_COND)) {
xrefBlock(ARM64_XREF_TYPE_B_COND, addr, target, &stop);
}
if (isBc && (types & ARM64_XREF_TYPE_MASK_BC_COND)) {
xrefBlock(ARM64_XREF_TYPE_BC_COND, addr, target, &stop);
}
}
}
if ((types & ARM64_XREF_TYPE_MASK_CBZ) || (types & ARM64_XREF_TYPE_MASK_CBNZ)) {
uint64_t target = 0;
bool isCbnz = false;
if (arm64_dec_cb_n_z(inst, addr, &isCbnz, NULL, &target) == 0) {
if (!isCbnz && (types & ARM64_XREF_TYPE_MASK_CBZ)) {
xrefBlock(ARM64_XREF_TYPE_CBZ, addr, target, &stop);
}
if (isCbnz && (types & ARM64_XREF_TYPE_MASK_CBNZ)) {
xrefBlock(ARM64_XREF_TYPE_CBNZ, addr, target, &stop);
}
}
}
if ((types & ARM64_XREF_TYPE_MASK_TBZ) || (types & ARM64_XREF_TYPE_MASK_TBNZ)) {
uint64_t target = 0;
bool isTbnz = false;
if (arm64_dec_tb_n_z(inst, addr, &isTbnz, NULL, &target, NULL) == 0) {
if (!isTbnz && (types & ARM64_XREF_TYPE_MASK_TBZ)) {
xrefBlock(ARM64_XREF_TYPE_CBZ, addr, target, &stop);
}
if (isTbnz && (types & ARM64_XREF_TYPE_MASK_TBNZ)) {
xrefBlock(ARM64_XREF_TYPE_CBNZ, addr, target, &stop);
}
}
}
#define ADRP_SEEK_BACK 8
if (types & ARM64_XREF_TYPE_MASK_ADRP_ADD) {
uint16_t addImm = 0;
arm64_register addDestinationReg;
arm64_register addSourceReg;
if (arm64_dec_add_imm(inst, &addDestinationReg, &addSourceReg, &addImm) == 0) {
uint32_t adrpInst = 0;
uint32_t adrpMask = 0;
if (arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, addSourceReg, &adrpInst, &adrpMask) == 0) {
uint64_t adrpAddr = pfsec_find_prev_inst(section, addr, ADRP_SEEK_BACK, adrpInst, adrpMask);
if (adrpAddr != 0) {
if (!pfsec_arm64_scan_register_write(section, addSourceReg, addr, adrpAddr)) {
uint64_t adrpTarget = 0;
arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL);
xrefBlock(ARM64_XREF_TYPE_ADRP_ADD, addr, adrpTarget + addImm, &stop);
}
}
}
}
}
if (types & ARM64_XREF_TYPE_MASK_ADRP_LDR) {
arm64_register ldrDestinationReg;
arm64_register ldrSourceReg;
uint64_t ldrImm = 0;
char ldrType = -1;
arm64_ldr_str_type instType = 0;
if (arm64_dec_ldr_imm(inst, &ldrDestinationReg, &ldrSourceReg, &ldrImm, &ldrType, &instType) == 0) {
uint32_t adrpInst = 0;
uint32_t adrpMask = 0;
if (arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ldrSourceReg, &adrpInst, &adrpMask) == 0) {
uint64_t adrpAddr = pfsec_find_prev_inst(section, addr, ADRP_SEEK_BACK, adrpInst, adrpMask);
if (adrpAddr != 0) {
if (!pfsec_arm64_scan_register_write(section, ldrSourceReg, addr, adrpAddr)) {
uint64_t adrpTarget = 0;
arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL);
xrefBlock(ARM64_XREF_TYPE_ADRP_LDR, addr, adrpTarget + ldrImm, &stop);
}
}
}
}
}
if (types & ARM64_XREF_TYPE_MASK_ADRP_STR) {
arm64_register strDestinationReg;
arm64_register strSourceReg;
uint64_t strImm = 0;
char strType = -1;
arm64_ldr_str_type instType = 0;
if (arm64_dec_str_imm(inst, &strDestinationReg, &strSourceReg, &strImm, &strType, &instType) == 0) {
uint32_t adrpInst = 0;
uint32_t adrpMask = 0;
if (arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, strSourceReg, &adrpInst, &adrpMask) == 0) {
uint64_t adrpAddr = pfsec_find_prev_inst(section, addr, ADRP_SEEK_BACK, adrpInst, adrpMask);
if (adrpAddr != 0) {
if (!pfsec_arm64_scan_register_write(section, strSourceReg, addr, adrpAddr)) {
uint64_t adrpTarget = 0;
arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL);
xrefBlock(ARM64_XREF_TYPE_ADRP_STR, addr, adrpTarget + strImm, &stop);
}
}
}
}
}
}
}
if ((types & ARM64_XREF_TYPE_MASK_POINTER)) {
if (!strncmp(section->info.sectname, "__cfstring", sizeof(section->info.sectname))) {
for (uint64_t addr = section->info.vmaddr; addr < (section->info.vmaddr + section->info.size) && !stop; addr += 0x20) {
uint32_t fileoff = pfsec_read32(section, addr + 0x10);
uint64_t vmaddr = 0;
MachO *macho = pfsec_get_macho(section);
if (macho) {
macho_translate_fileoff_to_vmaddr(macho, fileoff, &vmaddr, NULL);
xrefBlock(ARM64_XREF_TYPE_POINTER, addr, vmaddr, &stop);
}
}
}
else {
for (uint64_t addr = section->info.vmaddr; addr < (section->info.vmaddr + section->info.size) && !stop; addr += sizeof(uint64_t)) {
uint64_t ptr = pfsec_read_pointer(section, addr);
xrefBlock(ARM64_XREF_TYPE_POINTER, addr, ptr, &stop);
}
}
}
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef PATCHFINDER_ARM64_H
#define PATCHFINDER_ARM64_H
#include "PatchFinder.h"
#include "arm64.h"
typedef enum {
ARM64_XREF_TYPE_BL = 0,
ARM64_XREF_TYPE_B,
ARM64_XREF_TYPE_B_COND,
ARM64_XREF_TYPE_BC_COND,
ARM64_XREF_TYPE_CBZ,
ARM64_XREF_TYPE_CBNZ,
ARM64_XREF_TYPE_TBZ,
ARM64_XREF_TYPE_TBNZ,
ARM64_XREF_TYPE_ADR,
ARM64_XREF_TYPE_ADRP_ADD,
ARM64_XREF_TYPE_ADRP_LDR,
ARM64_XREF_TYPE_ADRP_STR,
ARM64_XREF_TYPE_POINTER,
} Arm64XrefType;
typedef enum {
ARM64_XREF_TYPE_MASK_BL = (1 << ARM64_XREF_TYPE_BL),
ARM64_XREF_TYPE_MASK_CALL = (ARM64_XREF_TYPE_MASK_BL),
ARM64_XREF_TYPE_MASK_B = (1 << ARM64_XREF_TYPE_B),
ARM64_XREF_TYPE_MASK_B_COND = (1 << ARM64_XREF_TYPE_B_COND),
ARM64_XREF_TYPE_MASK_BC_COND = (1 << ARM64_XREF_TYPE_BC_COND),
ARM64_XREF_TYPE_MASK_CBZ = (1 << ARM64_XREF_TYPE_CBZ),
ARM64_XREF_TYPE_MASK_CBNZ = (1 << ARM64_XREF_TYPE_CBNZ),
ARM64_XREF_TYPE_MASK_TBZ = (1 << ARM64_XREF_TYPE_TBZ),
ARM64_XREF_TYPE_MASK_TBNZ = (1 << ARM64_XREF_TYPE_TBNZ),
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),
ARM64_XREF_TYPE_MASK_ADR = (1 << ARM64_XREF_TYPE_ADR),
ARM64_XREF_TYPE_MASK_ADRP_ADD = (1 << ARM64_XREF_TYPE_ADRP_ADD),
ARM64_XREF_TYPE_MASK_ADRP_LDR = (1 << ARM64_XREF_TYPE_ADRP_LDR),
ARM64_XREF_TYPE_MASK_ADRP_STR = (1 << ARM64_XREF_TYPE_ADRP_STR),
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),
ARM64_XREF_TYPE_MASK_POINTER = (1 << ARM64_XREF_TYPE_POINTER),
ARM64_XREF_TYPE_ALL = (ARM64_XREF_TYPE_MASK_CALL | ARM64_XREF_TYPE_MASK_JUMP | ARM64_XREF_TYPE_MASK_REFERENCE | ARM64_XREF_TYPE_MASK_POINTER),
} Arm64XrefTypeMask;
bool pfsec_arm64_scan_register_write(PFSection *section, arm64_register reg, uint64_t endAddr, uint64_t startAddr);
uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference(PFSection *section, uint64_t adrpAddr, uint64_t ldrStrAddAddr);
uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(PFSection *section, uint64_t ldrStrAddAddr);
uint64_t pfsec_arm64_resolve_stub(PFSection *section, uint64_t stubAddr);
void pfsec_arm64_enumerate_xrefs(PFSection *section, Arm64XrefTypeMask types, void (^xrefBlock)(Arm64XrefType type, uint64_t source, uint64_t target, bool *stop));
#endif
+113
View File
@@ -0,0 +1,113 @@
#include "Util.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int64_t sxt64(int64_t value, uint8_t bits)
{
value = ((uint64_t)value) << (64 - bits);
value >>= (64 - bits);
return value;
}
int memcmp_masked(const void *str1, const void *str2, unsigned char* mask, size_t n)
{
const unsigned char* p = (const unsigned char*)str1;
const unsigned char* q = (const unsigned char*)str2;
if (p == q) return 0;
for (int i = 0; i < n; i++) {
unsigned char cMask = 0xFF;
if (mask) {
cMask = mask[i];
}
if((p[i] & cMask) != (q[i] & cMask)) {
// we do not care about 1 / -1
return -1;
}
}
return 0;
}
uint64_t align_to_size(int size, int alignment)
{
return (size + alignment - 1) & ~(alignment - 1);
}
void print_hash(uint8_t *hash, size_t size)
{
for (int j = 0; j < size; j++) {
printf("%02x", hash[j]);
}
}
void enumerate_range(uint64_t start, uint64_t end, uint16_t alignment, size_t nbytes, bool (^enumerator)(uint64_t))
{
if (start == end) return;
if (alignment == 0) return;
if (nbytes == 0) return;
if (nbytes % alignment) return;
int dir = start < end ? 1 : -1;
uint64_t absStart = dir == 1 ? start : end;
uint64_t absEnd = dir == 1 ? end : start;
uint64_t absSpace = absEnd - absStart;
if (alignment > absSpace) return;
if (absSpace % alignment) return;
uint64_t absSteps = absSpace / alignment;
for (uint64_t i = 0; i < absSteps; i++) {
uint64_t cur = start + (i * alignment * dir) - (dir == -1 ? alignment : 0);
if (!enumerator(cur)) break;
}
}
int read_string(int fd, char **strOut)
{
uint32_t sz = 0;
off_t pos = lseek(fd, 0, SEEK_CUR);
char c = 0;
do {
if (read(fd, &c, sizeof(c)) != sizeof(c)) return -1;
sz++;
} while(c != 0);
lseek(fd, pos, SEEK_SET);
*strOut = malloc(sz);
read(fd, *strOut, sz);
return 0;
}
bool string_has_prefix(const char *str, const char *prefix)
{
if (!str || !prefix) {
return false;
}
size_t str_len = strlen(str);
size_t prefix_len = strlen(prefix);
if (str_len < prefix_len) {
return false;
}
return !strncmp(str, prefix, prefix_len);
}
bool string_has_suffix(const char *str, const char *suffix)
{
if (!str || !suffix) {
return false;
}
size_t str_len = strlen(str);
size_t suffix_len = strlen(suffix);
if (str_len < suffix_len) {
return false;
}
return !strcmp(str + str_len - suffix_len, suffix);
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef UTIL_H
#define UTIL_H
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct s_optional_uint64 {
bool isSet;
uint64_t value;
} optional_uint64_t;
#define OPT_UINT64_IS_SET(x) (x.isSet)
#define OPT_UINT64_GET_VAL(x) (x.value)
#define OPT_UINT64_NONE (optional_uint64_t){.isSet = false, .value = 0}
#define OPT_UINT64(x) (optional_uint64_t){.isSet = true, .value = x}
typedef struct s_optional_bool {
bool isSet;
bool value;
} optional_bool;
#define OPT_BOOL_IS_SET(x) (x.isSet)
#define OPT_BOOL_GET_VAL(x) (x.value)
#define OPT_BOOL_NONE (optional_bool){.isSet = false, .value = false}
#define OPT_BOOL(x) (optional_bool){.isSet = true, .value = x}
int64_t sxt64(int64_t value, uint8_t bits);
int memcmp_masked(const void *str1, const void *str2, unsigned char* mask, size_t n);
uint64_t align_to_size(int size, int alignment);
void print_hash(uint8_t *hash, size_t size);
void enumerate_range(uint64_t start, uint64_t end, uint16_t alignment, size_t nbytes, bool (^enumerator)(uint64_t cur));
int read_string(int fd, char **strOut);
bool string_has_prefix(const char *str, const char *prefix);
bool string_has_suffix(const char *str, const char *suffix);
#endif
+1008
View File
File diff suppressed because it is too large. Load diff
+111
View File
@@ -0,0 +1,111 @@
#ifndef ARM64_H
#define ARM64_H
#include "Util.h"
typedef enum {
// registers
ARM64_REG_TYPE_X,
ARM64_REG_TYPE_W,
// vector shit
ARM64_REG_TYPE_Q,
ARM64_REG_TYPE_D,
ARM64_REG_TYPE_S,
ARM64_REG_TYPE_H,
ARM64_REG_TYPE_B,
} arm64_register_type;
enum {
ARM64_REG_MASK_ANY_FLAG = (1 << 0),
ARM64_REG_MASK_X_W = (1 << 1),
ARM64_REG_MASK_VECTOR = (1 << 2),
ARM64_REG_MASK_ALL = (ARM64_REG_MASK_X_W | ARM64_REG_MASK_VECTOR),
ARM64_REG_MASK_ANY_X_W = (ARM64_REG_MASK_X_W | ARM64_REG_MASK_ANY_FLAG),
ARM64_REG_MASK_ANY_VECTOR = (ARM64_REG_MASK_VECTOR | ARM64_REG_MASK_ANY_FLAG),
ARM64_REG_MASK_ANY_ALL = (ARM64_REG_MASK_ALL | ARM64_REG_MASK_ANY_FLAG),
};
typedef enum {
LDR_STR_TYPE_ANY, // NOTE: "ANY" will inevitably also match STUR and LDUR instructions
LDR_STR_TYPE_POST_INDEX,
LDR_STR_TYPE_PRE_INDEX,
LDR_STR_TYPE_UNSIGNED,
} arm64_ldr_str_type;
typedef enum {
ARM64_LDP_STP_TYPE_ANY,
ARM64_LDP_STP_TYPE_POST_INDEX,
ARM64_LDP_STP_TYPE_PRE_INDEX,
ARM64_LDP_STP_TYPE_SIGNED,
} arm64_ldp_stp_type;
typedef struct s_arm64_register {
uint8_t mask;
arm64_register_type type;
uint8_t num;
} arm64_register;
#define ARM64_REG(type_, num_) (arm64_register){.mask = ARM64_REG_MASK_ALL, .type = type_, .num = num_}
#define ARM64_REG_X(x) ARM64_REG(ARM64_REG_TYPE_X, x)
#define ARM64_REG_W(x) ARM64_REG(ARM64_REG_TYPE_W, x)
#define ARM64_REG_Q(x) ARM64_REG(ARM64_REG_TYPE_Q, x)
#define ARM64_REG_S(x) ARM64_REG(ARM64_REG_TYPE_S, x)
#define ARM64_REG_H(x) ARM64_REG(ARM64_REG_TYPE_H, x)
#define ARM64_REG_B(x) ARM64_REG(ARM64_REG_TYPE_B, x)
#define ARM64_REG_ANY (arm64_register){.mask = ARM64_REG_MASK_ANY_ALL, .type = 0, .num = 0}
#define ARM64_REG_ANY_X_W (arm64_register){.mask = ARM64_REG_MASK_ANY_X_W, .type = 0, .num = 0}
#define ARM64_REG_ANY_VECTOR (arm64_register){.mask = ARM64_REG_MASK_ANY_VECTOR, .type = 0, .num = 0}
#define ARM64_REG_GET_TYPE(x) (x.type)
#define ARM64_REG_IS_X(x) (x.type == ARM64_REG_TYPE_X)
#define ARM64_REG_IS_W(x) (x.type == ARM64_REG_TYPE_W)
#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)
#define ARM64_REG_GET_NUM(x) (x.num & 0x1f)
#define ARM64_REG_IS_ANY(x) (x.mask == ARM64_REG_MASK_ANY_ALL)
#define ARM64_REG_IS_ANY_X_W(x) (x.mask == ARM64_REG_MASK_ANY_X_W)
#define ARM64_REG_IS_ANY_VECTOR(x) (x.mask == ARM64_REG_MASK_ANY_VECTOR)
uint8_t arm64_reg_type_get_width(arm64_register_type type);
const char *arm64_reg_type_get_string(arm64_register_type type);
const char *arm64_reg_get_type_string(arm64_register reg);
#define ARM64_REG_NUM_SP 31
#define ARM64_REG_NUM_ZR 31
typedef struct s_arm64_cond {
bool isSet;
uint8_t value;
} arm64_cond;
#define ARM64_COND(x) (arm64_cond){.isSet = true, .value = x}
#define ARM64_COND_ANY (arm64_cond){.isSet = false, .value = 0}
#define ARM64_COND_GET_VAL(x) (x.value & 0xf)
#define ARM64_COND_IS_SET(x) x.isSet
int arm64_gen_b_l(optional_bool optIsBl, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_b_l(uint32_t inst, uint64_t origin, uint64_t *targetOut, bool *isBlOut);
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);
int arm64_dec_b_c_cond(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_cond *condOut, bool *isBcOut);
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);
int arm64_dec_adr_p(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *registerOut, bool *isAdrpOut);
int arm64_gen_mov_imm(char type, arm64_register destinationReg, optional_uint64_t optImm, optional_uint64_t optShift, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_mov_imm(uint32_t inst, arm64_register *destinationRegOut, uint64_t *immOut, uint64_t *shiftOut, char *typeOut);
int arm64_gen_mov_reg(arm64_register destinationReg, arm64_register sourceReg, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_mov_reg(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut);
int arm64_gen_add_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_add_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut);
int arm64_gen_sub_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, optional_bool optS, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_sub_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut, bool *sOut);
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);
int arm64_dec_ldr_imm(uint32_t inst, arm64_register *destinationReg, arm64_register *addrReg, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut);
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);
int arm64_dec_ldrs_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut);
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);
int arm64_dec_str_imm(uint32_t inst, arm64_register *sourceRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut);
int arm64_gen_ldr_lit(arm64_register destinationReg, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_ldr_lit(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *destinationReg);
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);
int arm64_gen_cb_n_z(optional_bool isCbnz, arm64_register reg, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut);
int arm64_dec_cb_n_z(uint32_t inst, uint64_t origin, bool *isCbnzOut, arm64_register *regOut, uint64_t *targetOut);
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);
int arm64_dec_tb_n_z(uint32_t inst, uint64_t origin, bool *isTbnzOut, arm64_register *regOut, uint64_t *targetOut, uint64_t *bitOut);
#endif
+624
View File
@@ -0,0 +1,624 @@
/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
*
* Copyright (c) 2006-2015 Apple Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#ifndef __DYLD_CACHE_FORMAT__
#define __DYLD_CACHE_FORMAT__
#include <stdint.h>
#include "fixup-chains.h"
struct dyld_cache_header
{
char magic[16]; // e.g. "dyld_v0 i386"
uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info
uint32_t mappingCount; // number of dyld_cache_mapping_info entries
uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing
uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing
uint64_t dyldBaseAddress; // base address of dyld when cache was built
uint64_t codeSignatureOffset; // file offset of code signature blob
uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file)
uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info
uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info
uint64_t localSymbolsOffset; // file offset of where local symbols are stored
uint64_t localSymbolsSize; // size of local symbols information
uint8_t uuid[16]; // unique value for each shared cache file
uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache
uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses
uint32_t branchPoolsCount; // number of uint64_t entries
uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache
uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache
uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info
uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries
uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info
uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info
uint64_t otherImageGroupAddrUnused; // unused
uint64_t otherImageGroupSizeUnused; // unused
uint64_t progClosuresAddr; // (unslid) address of list of program launch closures
uint64_t progClosuresSize; // size of list of program launch closures
uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures
uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures
uint32_t platform; // platform number (macOS=1, etc)
uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion
dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid
simulator : 1, // for simulator of specified platform
locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache
builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used for overrides
padding : 20; // TBD
uint64_t sharedRegionStart; // base load address of cache if not slid
uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any
uint64_t maxSlide; // runtime slide of cache can be between zero and this value
uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache
uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache
uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs
uint64_t dylibsTrieSize; // size of trie of cached dylib paths
uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures
uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures
uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures
uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures
uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info
uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries
uint64_t dylibsPBLStateArrayAddrUnused; // unused
uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs
uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program
uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program
uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet
uint32_t programTrieSize;
uint32_t osVersion; // OS Version of dylibs in this cache for the main platform
uint32_t altPlatform; // e.g. iOSMac on macOS
uint32_t altOsVersion; // e.g. 14.0 for iOSMac
uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header
uint64_t swiftOptsSize; // size of Swift optimizations header
uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry
uint32_t subCacheArrayCount; // number of subCache entries
uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols
uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data
uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region
uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data
uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region
uint32_t imagesOffset; // file offset to first dyld_cache_image_info
uint32_t imagesCount; // number of dyld_cache_image_info entries
uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2)
uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header
uint64_t objcOptsSize; // size of ObjC optimizations header
uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection
uint64_t cacheAtlasSize; // size of embedded cache atlas
uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header
uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data
uint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info
uint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries
};
// Uncomment this and check the build errors for the current mapping offset to check against when adding new fields.
// template<size_t size> class A { int x[-size]; }; A<sizeof(dyld_cache_header)> a;
struct dyld_cache_mapping_info {
uint64_t address;
uint64_t size;
uint64_t fileOffset;
uint32_t maxProt;
uint32_t initProt;
};
// Contains the flags for the dyld_cache_mapping_and_slide_info flgs field
enum {
DYLD_CACHE_MAPPING_AUTH_DATA = 1 << 0U,
DYLD_CACHE_MAPPING_DIRTY_DATA = 1 << 1U,
DYLD_CACHE_MAPPING_CONST_DATA = 1 << 2U,
DYLD_CACHE_MAPPING_TEXT_STUBS = 1 << 3U,
DYLD_CACHE_DYNAMIC_CONFIG_DATA = 1 << 4U,
DYLD_CACHE_READ_ONLY_DATA = 1 << 5U,
DYLD_CACHE_MAPPING_CONST_TPRO_DATA = 1 << 6U,
};
struct dyld_cache_mapping_and_slide_info {
uint64_t address;
uint64_t size;
uint64_t fileOffset;
uint64_t slideInfoFileOffset;
uint64_t slideInfoFileSize;
uint64_t flags;
uint32_t maxProt;
uint32_t initProt;
};
struct dyld_cache_tpro_mapping_info {
uint64_t unslidAddress;
uint64_t size;
};
struct dyld_cache_image_info
{
uint64_t address;
uint64_t modTime;
uint64_t inode;
uint32_t pathFileOffset;
uint32_t pad;
};
struct dyld_cache_image_info_extra
{
uint64_t exportsTrieAddr; // address of trie in unslid cache
uint64_t weakBindingsAddr;
uint32_t exportsTrieSize;
uint32_t weakBindingsSize;
uint32_t dependentsStartArrayIndex;
uint32_t reExportsStartArrayIndex;
};
struct dyld_cache_accelerator_info
{
uint32_t version; // currently 1
uint32_t imageExtrasCount; // does not include aliases
uint32_t imagesExtrasOffset; // offset into this chunk of first dyld_cache_image_info_extra
uint32_t bottomUpListOffset; // offset into this chunk to start of 16-bit array of sorted image indexes
uint32_t dylibTrieOffset; // offset into this chunk to start of trie containing all dylib paths
uint32_t dylibTrieSize; // size of trie containing all dylib paths
uint32_t initializersOffset; // offset into this chunk to start of initializers list
uint32_t initializersCount; // size of initializers list
uint32_t dofSectionsOffset; // offset into this chunk to start of DOF sections list
uint32_t dofSectionsCount; // size of initializers list
uint32_t reExportListOffset; // offset into this chunk to start of 16-bit array of re-exports
uint32_t reExportCount; // size of re-exports
uint32_t depListOffset; // offset into this chunk to start of 16-bit array of dependencies (0x8000 bit set if upward)
uint32_t depListCount; // size of dependencies
uint32_t rangeTableOffset; // offset into this chunk to start of ss
uint32_t rangeTableCount; // size of dependencies
uint64_t dyldSectionAddr; // address of libdyld's __dyld section in unslid cache
};
struct dyld_cache_accelerator_initializer
{
uint32_t functionOffset; // address offset from start of cache mapping
uint32_t imageIndex;
};
struct dyld_cache_range_entry
{
uint64_t startAddress; // unslid address of start of region
uint32_t size;
uint32_t imageIndex;
};
struct dyld_cache_accelerator_dof
{
uint64_t sectionAddress; // unslid address of start of region
uint32_t sectionSize;
uint32_t imageIndex;
};
struct dyld_cache_image_text_info
{
uint8_t uuid[16];
uint64_t loadAddress; // unslid address of start of __TEXT
uint32_t textSegmentSize;
uint32_t pathOffset; // offset from start of cache file
};
// The rebasing info is to allow the kernel to lazily rebase DATA pages of the
// dyld shared cache. Rebasing is adding the slide to interior pointers.
struct dyld_cache_slide_info
{
uint32_t version; // currently 1
uint32_t toc_offset;
uint32_t toc_count;
uint32_t entries_offset;
uint32_t entries_count;
uint32_t entries_size; // currently 128
// uint16_t toc[toc_count];
// entrybitmap entries[entries_count];
};
struct dyld_cache_slide_info_entry {
uint8_t bits[4096/(8*4)]; // 128-byte bitmap
};
// The version 2 of the slide info uses a different compression scheme. Since
// only interior pointers (pointers that point within the cache) are rebased
// (slid), we know the possible range of the pointers and thus know there are
// unused bits in each pointer. We use those bits to form a linked list of
// locations needing rebasing in each page.
//
// Definitions:
//
// pageIndex = (pageAddress - startOfAllDataAddress)/info->page_size
// pageStarts[] = info + info->page_starts_offset
// pageExtras[] = info + info->page_extras_offset
// valueMask = ~(info->delta_mask)
// deltaShift = __builtin_ctzll(info->delta_mask) - 2
//
// There are three cases:
//
// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE
// The page contains no values that need rebasing.
//
// 2) (pageStarts[pageIndex] & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) == 0
// All rebase locations are in one linked list. The offset of the first
// rebase location in the page is pageStarts[pageIndex] * 4.
//
// 3) pageStarts[pageIndex] & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA
// Multiple linked lists are needed for all rebase locations in a page.
// The pagesExtras array contains 2 or more entries each of which is the
// start of a new linked list in the page. The first is at:
// extrasStartIndex = (pageStarts[pageIndex] & 0x3FFF)
// The next is at extrasStartIndex+1. The last is denoted by
// having the high bit (DYLD_CACHE_SLIDE_PAGE_ATTR_END) of the pageExtras[]
// set.
//
// For 64-bit architectures, there is always enough free bits to encode all
// possible deltas. The info->delta_mask field shows where the delta is located
// in the pointer. That value must be masked off (valueMask) before the slide
// is added to the pointer.
//
// For 32-bit architectures, there are only three bits free (the three most
// significant bits). To extract the delta, you must first subtract value_add
// from the pointer value, then AND with delta_mask, then shift by deltaShift.
// That still leaves a maximum delta to the next rebase location of 28 bytes.
// To reduce the number or chains needed, an optimization was added. Turns
// out zero is common in the DATA region. A zero can be turned into a
// non-rebasing entry in the linked list. The can be done because nothing
// in the shared cache should point out of its dylib to the start of the shared
// cache.
//
// The code for processing a linked list (chain) is:
//
// uint32_t delta = 1;
// while ( delta != 0 ) {
// uint8_t* loc = pageStart + pageOffset;
// uintptr_t rawValue = *((uintptr_t*)loc);
// delta = ((rawValue & deltaMask) >> deltaShift);
// uintptr_t newValue = (rawValue & valueMask);
// if ( newValue != 0 ) {
// newValue += valueAdd;
// newValue += slideAmount;
// }
// *((uintptr_t*)loc) = newValue;
// pageOffset += delta;
// }
//
//
struct dyld_cache_slide_info2
{
uint32_t version; // currently 2
uint32_t page_size; // currently 4096 (may also be 16384)
uint32_t page_starts_offset;
uint32_t page_starts_count;
uint32_t page_extras_offset;
uint32_t page_extras_count;
uint64_t delta_mask; // which (contiguous) set of bits contains the delta to the next rebase location
uint64_t value_add;
//uint16_t page_starts[page_starts_count];
//uint16_t page_extras[page_extras_count];
};
#define DYLD_CACHE_SLIDE_PAGE_ATTRS 0xC000 // high bits of uint16_t are flags
#define DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA 0x8000 // index is into extras array (not starts array)
#define DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE 0x4000 // page has no rebasing
#define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page
// The version 3 of the slide info uses a different compression scheme. Since
// only interior pointers (pointers that point within the cache) are rebased
// (slid), we know the possible range of the pointers and thus know there are
// unused bits in each pointer. We use those bits to form a linked list of
// locations needing rebasing in each page.
//
// Definitions:
//
// pageIndex = (pageAddress - startOfAllDataAddress)/info->page_size
// pageStarts[] = info + info->page_starts_offset
//
// There are two cases:
//
// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE
// The page contains no values that need rebasing.
//
// 2) otherwise...
// All rebase locations are in one linked list. The offset of the first
// rebase location in the page is pageStarts[pageIndex].
//
// A pointer is one of of the variants in dyld_cache_slide_pointer3
//
// The code for processing a linked list (chain) is:
//
// uint32_t delta = pageStarts[pageIndex];
// dyld_cache_slide_pointer3* loc = pageStart;
// do {
// loc += delta;
// delta = loc->offsetToNextPointer;
// if ( loc->auth.authenticated ) {
// newValue = loc->offsetFromSharedCacheBase + results->slide + auth_value_add;
// newValue = sign_using_the_various_bits(newValue);
// }
// else {
// uint64_t value51 = loc->pointerValue;
// uint64_t top8Bits = value51 & 0x0007F80000000000ULL;
// uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFFULL;
// uint64_t targetValue = ( top8Bits << 13 ) | bottom43Bits;
// newValue = targetValue + results->slide;
// }
// loc->raw = newValue;
// } while (delta != 0);
//
//
struct dyld_cache_slide_info3
{
uint32_t version; // currently 3
uint32_t page_size; // currently 4096 (may also be 16384)
uint32_t page_starts_count;
uint64_t auth_value_add;
uint16_t page_starts[/* page_starts_count */];
};
#define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing
union dyld_cache_slide_pointer3
{
uint64_t raw;
struct {
uint64_t pointerValue : 51,
offsetToNextPointer : 11,
unused : 2;
} plain;
struct {
uint64_t offsetFromSharedCacheBase : 32,
diversityData : 16,
hasAddressDiversity : 1,
key : 2,
offsetToNextPointer : 11,
unused : 1,
authenticated : 1; // = 1;
} auth;
};
// The version 4 of the slide info is optimized for 32-bit caches up to 1GB.
// Since only interior pointers (pointers that point within the cache) are rebased
// (slid), we know the possible range of the pointers takes 30 bits. That
// gives us two bits to use to chain to the next rebase.
//
// Definitions:
//
// pageIndex = (pageAddress - startOfAllDataAddress)/info->page_size
// pageStarts[] = info + info->page_starts_offset
// pageExtras[] = info + info->page_extras_offset
// valueMask = ~(info->delta_mask)
// deltaShift = __builtin_ctzll(info->delta_mask) - 2
//
// There are three cases:
//
// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE4_PAGE_NO_REBASE
// The page contains no values that need rebasing.
//
// 2) (pageStarts[pageIndex] & DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA) == 0
// All rebase locations are in one linked list. The offset of the first
// rebase location in the page is pageStarts[pageIndex] * 4.
//
// 3) pageStarts[pageIndex] & DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA
// Multiple chains are needed for all rebase locations in a page.
// The pagesExtras array contains 2 or more entries each of which is the
// start of a new chain in the page. The first is at:
// extrasStartIndex = (pageStarts[pageIndex] & DYLD_CACHE_SLIDE4_PAGE_INDEX)
// The next is at extrasStartIndex+1. The last is denoted by
// having the high bit (DYLD_CACHE_SLIDE4_PAGE_EXTRA_END) of the pageExtras[].
//
// For 32-bit architectures, there are only two bits free (the two most
// significant bits). To extract the delta, you must first subtract value_add
// from the pointer value, then AND with delta_mask, then shift by deltaShift.
// That still leaves a maximum delta to the next rebase location of 12 bytes.
// To reduce the number or chains needed, an optimization was added. Turns
// most of the non-rebased data are small values and can be co-opt'ed into
// being used in the chain. The can be done because nothing
// in the shared cache should point to the first 64KB which are in the shared
// cache header information. So if the resulting pointer points to the
// start of the cache +/-32KB, then it is actually a small number that should
// not be rebased, but just reconstituted.
//
// The code for processing a linked list (chain) is:
//
// uint32_t delta = 1;
// while ( delta != 0 ) {
// uint8_t* loc = pageStart + pageOffset;
// uint32_t rawValue = *((uint32_t*)loc);
// delta = ((rawValue & deltaMask) >> deltaShift);
// uintptr_t newValue = (rawValue & valueMask);
// if ( (newValue & 0xFFFF8000) == 0 ) {
// // small positive non-pointer, use as-is
// }
// else if ( (newValue & 0x3FFF8000) == 0x3FFF8000 ) {
// // small negative non-pointer
// newValue |= 0xC0000000;
// }
// else {
// // pointer that needs rebasing
// newValue += valueAdd;
// newValue += slideAmount;
// }
// *((uint32_t*)loc) = newValue;
// pageOffset += delta;
// }
//
//
struct dyld_cache_slide_info4
{
uint32_t version; // currently 4
uint32_t page_size; // currently 4096 (may also be 16384)
uint32_t page_starts_offset;
uint32_t page_starts_count;
uint32_t page_extras_offset;
uint32_t page_extras_count;
uint64_t delta_mask; // which (contiguous) set of bits contains the delta to the next rebase location (0xC0000000)
uint64_t value_add; // base address of cache
//uint16_t page_starts[page_starts_count];
//uint16_t page_extras[page_extras_count];
};
#define DYLD_CACHE_SLIDE4_PAGE_NO_REBASE 0xFFFF // page has no rebasing
#define DYLD_CACHE_SLIDE4_PAGE_INDEX 0x7FFF // mask of page_starts[] values
#define DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA 0x8000 // index is into extras array (not a chain start offset)
#define DYLD_CACHE_SLIDE4_PAGE_EXTRA_END 0x8000 // last chain entry for page
// The version 5 of the slide info uses a different compression scheme. Since
// only interior pointers (pointers that point within the cache) are rebased
// (slid), we know the possible range of the pointers and thus know there are
// unused bits in each pointer. We use those bits to form a linked list of
// locations needing rebasing in each page.
//
// Definitions:
//
// pageIndex = (pageAddress - startOfAllDataAddress)/info->page_size
// pageStarts[] = info + info->page_starts_offset
//
// There are two cases:
//
// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE
// The page contains no values that need rebasing.
//
// 2) otherwise...
// All rebase locations are in one linked list. The offset of the first
// rebase location in the page is pageStarts[pageIndex].
//
// A pointer is one of of the variants in dyld_cache_slide_pointer5
//
// The code for processing a linked list (chain) is:
//
// uint32_t delta = pageStarts[pageIndex];
// dyld_cache_slide_pointer5* loc = pageStart;
// do {
// loc += delta;
// delta = loc->offsetToNextPointer;
// newValue = loc->regular.target + value_add + results->slide;
// if ( loc->auth.authenticated ) {
// newValue = sign_using_the_various_bits(newValue);
// }
// else {
// newValue = newValue | (loc->regular.high8 < 56);
// }
// loc->raw = newValue;
// } while (delta != 0);
//
//
struct dyld_cache_slide_info5
{
uint32_t version; // currently 5
uint32_t page_size; // currently 4096 (may also be 16384)
uint32_t page_starts_count;
uint64_t value_add;
uint16_t page_starts[/* page_starts_count */];
};
#define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing
union dyld_cache_slide_pointer5
{
uint64_t raw;
struct dyld_chained_ptr_arm64e_shared_cache_rebase regular;
struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase auth;
};
struct dyld_cache_local_symbols_info
{
uint32_t nlistOffset; // offset into this chunk of nlist entries
uint32_t nlistCount; // count of nlist entries
uint32_t stringsOffset; // offset into this chunk of string pool
uint32_t stringsSize; // byte count of string pool
uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry
uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array
};
struct dyld_cache_local_symbols_entry
{
uint32_t dylibOffset; // offset in cache file of start of dylib
uint32_t nlistStartIndex; // start index of locals for this dylib
uint32_t nlistCount; // number of local symbols for this dylib
};
struct dyld_cache_local_symbols_entry_64
{
uint64_t dylibOffset; // offset in cache buffer of start of dylib
uint32_t nlistStartIndex; // start index of locals for this dylib
uint32_t nlistCount; // number of local symbols for this dylib
};
struct dyld_subcache_entry_v1
{
uint8_t uuid[16]; // The UUID of the subCache file
uint64_t cacheVMOffset; // The offset of this subcache from the main cache base address
};
struct dyld_subcache_entry
{
uint8_t uuid[16]; // The UUID of the subCache file
uint64_t cacheVMOffset; // The offset of this subcache from the main cache base address
char fileSuffix[32]; // The file name suffix of the subCache file e.g. ".25.data", ".03.development"
};
// This struct is a small piece of dynamic data that can be included in the shared region, and contains configuration
// data about the shared cache in use by the process. It is located
struct dyld_cache_dynamic_data_header
{
char magic[16]; // e.g. "dyld_data v0"
uint64_t fsId; // The fsid_t of the shared cache being used by a process
uint64_t fsObjId; // The fs_obj_id_t of the shared cache being used by a process
};
// This is the location of the macOS shared cache on macOS 11.0 and later
#define MACOSX_MRM_DYLD_SHARED_CACHE_DIR "/System/Library/dyld/"
// This is old define for the old location of the dyld cache
#define MACOSX_DYLD_SHARED_CACHE_DIR MACOSX_MRM_DYLD_SHARED_CACHE_DIR
#define IPHONE_DYLD_SHARED_CACHE_DIR "/System/Library/Caches/com.apple.dyld/"
#define DRIVERKIT_DYLD_SHARED_CACHE_DIR "/System/DriverKit/System/Library/dyld/"
#define EXCLAVEKIT_DYLD_SHARED_CACHE_DIR "/System/ExclaveKit/System/Library/dyld/"
#define DYLD_SHARED_CACHE_DEVELOPMENT_EXT ".development"
#define DYLD_SHARED_CACHE_DYNAMIC_DATA_MAGIC "dyld_data v0"
static const char* cryptexPrefixes[] = {
"/System/Volumes/Preboot/Cryptexes/OS/",
"/private/preboot/Cryptexes/OS/",
"/System/Cryptexes/OS"
};
static const uint64_t kDyldSharedCacheTypeDevelopment = 0;
static const uint64_t kDyldSharedCacheTypeProduction = 1;
static const uint64_t kDyldSharedCacheTypeUniversal = 2;
#endif // __DYLD_CACHE_FORMAT__
+306
View File
@@ -0,0 +1,306 @@
/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
*
* Copyright (c) 2018 Apple Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#ifndef __MACH_O_FIXUP_CHAINS__
#define __MACH_O_FIXUP_CHAINS__ 6
#include <stdint.h>
//#define LC_DYLD_EXPORTS_TRIE 0x80000033 // used with linkedit_data_command
//#define LC_DYLD_CHAINED_FIXUPS 0x80000034 // used with linkedit_data_command, payload is dyld_chained_fixups_header
// header of the LC_DYLD_CHAINED_FIXUPS payload
struct dyld_chained_fixups_header
{
uint32_t fixups_version; // 0
uint32_t starts_offset; // offset of dyld_chained_starts_in_image in chain_data
uint32_t imports_offset; // offset of imports table in chain_data
uint32_t symbols_offset; // offset of symbol strings in chain_data
uint32_t imports_count; // number of imported symbol names
uint32_t imports_format; // DYLD_CHAINED_IMPORT*
uint32_t symbols_format; // 0 => uncompressed, 1 => zlib compressed
};
// This struct is embedded in LC_DYLD_CHAINED_FIXUPS payload
struct dyld_chained_starts_in_image
{
uint32_t seg_count;
uint32_t seg_info_offset[1]; // each entry is offset into this struct for that segment
// followed by pool of dyld_chain_starts_in_segment data
};
// This struct is embedded in dyld_chain_starts_in_image
// and passed down to the kernel for page-in linking
struct dyld_chained_starts_in_segment
{
uint32_t size; // size of this (amount kernel needs to copy)
uint16_t page_size; // 0x1000 or 0x4000
uint16_t pointer_format; // DYLD_CHAINED_PTR_*
uint64_t segment_offset; // offset in memory to start of segment
uint32_t max_valid_pointer; // for 32-bit OS, any value beyond this is not a pointer
uint16_t page_count; // how many pages are in array
uint16_t page_start[1]; // each entry is offset in each page of first element in chain
// or DYLD_CHAINED_PTR_START_NONE if no fixups on page
// uint16_t chain_starts[1]; // some 32-bit formats may require multiple starts per page.
// for those, if high bit is set in page_starts[], then it
// is index into chain_starts[] which is a list of starts
// the last of which has the high bit set
};
enum {
DYLD_CHAINED_PTR_START_NONE = 0xFFFF, // used in page_start[] to denote a page with no fixups
DYLD_CHAINED_PTR_START_MULTI = 0x8000, // used in page_start[] to denote a page which has multiple starts
DYLD_CHAINED_PTR_START_LAST = 0x8000, // used in chain_starts[] to denote last start in list for page
};
// This struct is embedded in __TEXT,__chain_starts section in firmware
struct dyld_chained_starts_offsets
{
uint32_t pointer_format; // DYLD_CHAINED_PTR_32_FIRMWARE
uint32_t starts_count; // number of starts in array
uint32_t chain_starts[1]; // array chain start offsets
};
// values for dyld_chained_starts_in_segment.pointer_format
enum {
DYLD_CHAINED_PTR_ARM64E = 1, // stride 8, unauth target is vmaddr
DYLD_CHAINED_PTR_64 = 2, // target is vmaddr
DYLD_CHAINED_PTR_32 = 3,
DYLD_CHAINED_PTR_32_CACHE = 4,
DYLD_CHAINED_PTR_32_FIRMWARE = 5,
DYLD_CHAINED_PTR_64_OFFSET = 6, // target is vm offset
DYLD_CHAINED_PTR_ARM64E_OFFSET = 7, // old name
DYLD_CHAINED_PTR_ARM64E_KERNEL = 7, // stride 4, unauth target is vm offset
DYLD_CHAINED_PTR_64_KERNEL_CACHE = 8,
DYLD_CHAINED_PTR_ARM64E_USERLAND = 9, // stride 8, unauth target is vm offset
DYLD_CHAINED_PTR_ARM64E_FIRMWARE = 10, // stride 4, unauth target is vmaddr
DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE = 11, // stride 1, x86_64 kernel caches
DYLD_CHAINED_PTR_ARM64E_USERLAND24 = 12, // stride 8, unauth target is vm offset, 24-bit bind
DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE = 13, // stride 8, regular/auth targets both vm offsets. Only A keys supported
};
// DYLD_CHAINED_PTR_ARM64E
struct dyld_chained_ptr_arm64e_rebase
{
uint64_t target : 43,
high8 : 8,
next : 11, // 4 or 8-byte stide
bind : 1, // == 0
auth : 1; // == 0
};
// DYLD_CHAINED_PTR_ARM64E
struct dyld_chained_ptr_arm64e_bind
{
uint64_t ordinal : 16,
zero : 16,
addend : 19, // +/-256K
next : 11, // 4 or 8-byte stide
bind : 1, // == 1
auth : 1; // == 0
};
// DYLD_CHAINED_PTR_ARM64E
struct dyld_chained_ptr_arm64e_auth_rebase
{
uint64_t target : 32, // runtimeOffset
diversity : 16,
addrDiv : 1,
key : 2,
next : 11, // 4 or 8-byte stide
bind : 1, // == 0
auth : 1; // == 1
};
// DYLD_CHAINED_PTR_ARM64E
struct dyld_chained_ptr_arm64e_auth_bind
{
uint64_t ordinal : 16,
zero : 16,
diversity : 16,
addrDiv : 1,
key : 2,
next : 11, // 4 or 8-byte stide
bind : 1, // == 1
auth : 1; // == 1
};
// DYLD_CHAINED_PTR_64/DYLD_CHAINED_PTR_64_OFFSET
struct dyld_chained_ptr_64_rebase
{
uint64_t target : 36, // 64GB max image size (DYLD_CHAINED_PTR_64 => vmAddr, DYLD_CHAINED_PTR_64_OFFSET => runtimeOffset)
high8 : 8, // top 8 bits set to this (DYLD_CHAINED_PTR_64 => after slide added, DYLD_CHAINED_PTR_64_OFFSET => before slide added)
reserved : 7, // all zeros
next : 12, // 4-byte stride
bind : 1; // == 0
};
// DYLD_CHAINED_PTR_ARM64E_USERLAND24
struct dyld_chained_ptr_arm64e_bind24
{
uint64_t ordinal : 24,
zero : 8,
addend : 19, // +/-256K
next : 11, // 8-byte stide
bind : 1, // == 1
auth : 1; // == 0
};
// DYLD_CHAINED_PTR_ARM64E_USERLAND24
struct dyld_chained_ptr_arm64e_auth_bind24
{
uint64_t ordinal : 24,
zero : 8,
diversity : 16,
addrDiv : 1,
key : 2,
next : 11, // 8-byte stide
bind : 1, // == 1
auth : 1; // == 1
};
// DYLD_CHAINED_PTR_64
struct dyld_chained_ptr_64_bind
{
uint64_t ordinal : 24,
addend : 8, // 0 thru 255
reserved : 19, // all zeros
next : 12, // 4-byte stride
bind : 1; // == 1
};
// DYLD_CHAINED_PTR_64_KERNEL_CACHE, DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE
struct dyld_chained_ptr_64_kernel_cache_rebase
{
uint64_t target : 30, // basePointers[cacheLevel] + target
cacheLevel : 2, // what level of cache to bind to (indexes a mach_header array)
diversity : 16,
addrDiv : 1,
key : 2,
next : 12, // 1 or 4-byte stide
isAuth : 1; // 0 -> not authenticated. 1 -> authenticated
};
// DYLD_CHAINED_PTR_32
// Note: for DYLD_CHAINED_PTR_32 some non-pointer values are co-opted into the chain
// as out of range rebases. If an entry in the chain is > max_valid_pointer, then it
// is not a pointer. To restore the value, subtract off the bias, which is
// (64MB+max_valid_pointer)/2.
struct dyld_chained_ptr_32_rebase
{
uint32_t target : 26, // vmaddr, 64MB max image size
next : 5, // 4-byte stride
bind : 1; // == 0
};
// DYLD_CHAINED_PTR_32
struct dyld_chained_ptr_32_bind
{
uint32_t ordinal : 20,
addend : 6, // 0 thru 63
next : 5, // 4-byte stride
bind : 1; // == 1
};
// DYLD_CHAINED_PTR_32_CACHE
struct dyld_chained_ptr_32_cache_rebase
{
uint32_t target : 30, // 1GB max dyld cache TEXT and DATA
next : 2; // 4-byte stride
};
// DYLD_CHAINED_PTR_32_FIRMWARE
struct dyld_chained_ptr_32_firmware_rebase
{
uint32_t target : 26, // 64MB max firmware TEXT and DATA
next : 6; // 4-byte stride
};
// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE
struct dyld_chained_ptr_arm64e_shared_cache_rebase
{
uint64_t runtimeOffset : 34, // offset from the start of the shared cache
high8 : 8,
unused : 10,
next : 11, // 8-byte stide
auth : 1; // == 0
};
// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE
struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase
{
uint64_t runtimeOffset : 34, // offset from the start of the shared cache
diversity : 16,
addrDiv : 1,
keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA
next : 11, // 8-byte stide
auth : 1; // == 1
};
// values for dyld_chained_fixups_header.imports_format
enum {
DYLD_CHAINED_IMPORT = 1,
DYLD_CHAINED_IMPORT_ADDEND = 2,
DYLD_CHAINED_IMPORT_ADDEND64 = 3,
};
// DYLD_CHAINED_IMPORT
struct dyld_chained_import
{
uint32_t lib_ordinal : 8, // -15 .. 240 (0xF1 .. 0xF0)
weak_import : 1,
name_offset : 23;
};
// DYLD_CHAINED_IMPORT_ADDEND
struct dyld_chained_import_addend
{
uint32_t lib_ordinal : 8, // -15 .. 240 (0xF1 .. 0xF0)
weak_import : 1,
name_offset : 23;
int32_t addend;
};
// DYLD_CHAINED_IMPORT_ADDEND64
struct dyld_chained_import_addend64
{
uint64_t lib_ordinal : 16, // -15 .. 65520 (0xFFF1 .. 0xFFF0)
weak_import : 1,
reserved : 15,
name_offset : 32;
uint64_t addend;
};
#endif // __MACH_O_FIXUP_CHAINS__