Filza 27.2 worse_query: public and developer builds
This commit is contained in:
commit
91a6adf723
267 files changed
+42015
No files matched your search
+1
@@ -0,0 +1 @@
|
||||
../src
|
||||
Vendored
+42
@@ -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;
|
||||
}
|
||||
Vendored
+11
@@ -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
@@ -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
@@ -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
|
||||
Vendored
+523
@@ -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);
|
||||
}
|
||||
Vendored
+120
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Vendored
+232
@@ -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;
|
||||
}
|
||||
Vendored
+24
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Vendored
+219
@@ -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;
|
||||
}
|
||||
Vendored
+57
@@ -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
|
||||
Vendored
+210
@@ -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;
|
||||
}
|
||||
Vendored
+21
@@ -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
|
||||
Vendored
+89
@@ -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;
|
||||
}
|
||||
Vendored
+14
@@ -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
|
||||
Vendored
+812
@@ -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(§ion, cmd + sizeof(struct segment_command) + (i * sizeof(struct section)), sizeof(section));
|
||||
SECTION_APPLY_BYTE_ORDER(§ion, 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);
|
||||
}
|
||||
Vendored
+91
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Vendored
+617
@@ -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);
|
||||
}
|
||||
Vendored
+105
@@ -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
@@ -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, ®, NULL, NULL, NULL) != 0) {
|
||||
if (arm64_dec_str_imm(inst, NULL, ®, NULL, NULL, NULL) != 0) {
|
||||
if (arm64_dec_add_imm(inst, NULL, ®, 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, ®, &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
@@ -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
|
||||
Vendored
+113
@@ -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);
|
||||
}
|
||||
Vendored
+35
@@ -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
|
||||
Vendored
+1008
File diff suppressed because it is too large.
Load diff
Vendored
+111
@@ -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
@@ -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
@@ -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__
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
#include "xpf.h"
|
||||
|
||||
// Offsets required for the Fugu15 PAC bypass
|
||||
|
||||
bool xpf_bad_recovery_supported(void)
|
||||
{
|
||||
if (!gXPF.kernelIsArm64e) {
|
||||
// non-PACed devices
|
||||
return false;
|
||||
}
|
||||
if (strcmp(gXPF.darwinVersion, "21.0.0") >= 0 && strcmp(gXPF.darwinVersion, "21.5.0") < 0) {
|
||||
// iOS 15.0 - 15.4.1: Supported
|
||||
return true;
|
||||
}
|
||||
else if (
|
||||
(strcmp(gXPF.xnuBuild, "8020.120.43.112.1~1") == 0) ||
|
||||
(strcmp(gXPF.xnuBuild, "8020.120.51.122.2~1") == 0) ||
|
||||
(strcmp(gXPF.xnuBuild, "8020.120.68.132.1~1") == 0)
|
||||
) {
|
||||
// iOS 15.5b1 - 15.5b3: Supported
|
||||
return true;
|
||||
}
|
||||
|
||||
// Anything else: Not supported
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_hw_lck_ticket_reserve_orig_allow_invalid_signed(void)
|
||||
{
|
||||
uint32_t strX10X16Any = 0, strX10X16AnyMask = 0;
|
||||
arm64_gen_str_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_X(10), ARM64_REG_X(16), OPT_UINT64_NONE, &strX10X16Any, &strX10X16AnyMask);
|
||||
|
||||
uint32_t movzW0_0 = 0, movzW0_0Mask = 0;
|
||||
arm64_gen_mov_imm('z', ARM64_REG_W(0), OPT_UINT64(0), OPT_UINT64(0), &movzW0_0, &movzW0_0Mask);
|
||||
|
||||
uint32_t inst[] = (uint32_t[]) {
|
||||
strX10X16Any, // str x10, [x16, ?]
|
||||
movzW0_0, // movz w0, #0
|
||||
0xd65f03c0, // ret
|
||||
};
|
||||
uint32_t mask[] = (uint32_t[]) {
|
||||
strX10X16AnyMask,
|
||||
movzW0_0Mask,
|
||||
0xffffffff,
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t hw_lck_ticket_reserve_orig_allow_invalid_signed = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) {
|
||||
// Filter out anything with a wfe instruction before it
|
||||
if (pfsec_read32(gXPF.kernelTextSection, vmaddr-4) != 0xd503205f /* wfe */) {
|
||||
hw_lck_ticket_reserve_orig_allow_invalid_signed = vmaddr;
|
||||
*stop = true;
|
||||
}
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
return hw_lck_ticket_reserve_orig_allow_invalid_signed;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_hw_lck_ticket_reserve_orig_allow_invalid(void)
|
||||
{
|
||||
uint32_t adrAny = 0, adrAnyMask = 0;
|
||||
arm64_gen_adr_p(OPT_BOOL(false), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrAny, &adrAnyMask);
|
||||
|
||||
uint64_t hw_lck_ticket_reserve_orig_allow_invalid_signed = xpf_item_resolve("kernelGadget.hw_lck_ticket_reserve_orig_allow_invalid_signed");
|
||||
XPF_ASSERT(hw_lck_ticket_reserve_orig_allow_invalid_signed);
|
||||
return pfsec_find_prev_inst(gXPF.kernelTextSection, hw_lck_ticket_reserve_orig_allow_invalid_signed, 40, adrAny, adrAnyMask);
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_br_x22_gadget(void)
|
||||
{
|
||||
// Gadget:
|
||||
// pacia x22, sp
|
||||
// (... some code ...)
|
||||
// braa x22, sp
|
||||
|
||||
uint32_t inst[] = (uint32_t[]){
|
||||
0xd71f0adf // braa x22, sp
|
||||
};
|
||||
|
||||
__block uint64_t brX22Gadget = 0;
|
||||
PFPatternMetric *pacMetric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t));
|
||||
pfmetric_run(gXPF.kernelTextSection, pacMetric, ^(uint64_t signCandidate, bool *stop) {
|
||||
uint32_t brX22 = 0xdac103f6; // pacia x22, sp
|
||||
uint64_t brX22Addr = pfsec_find_prev_inst(gXPF.kernelTextSection, signCandidate, 50, brX22, 0xffffffff);
|
||||
if (brX22Addr) {
|
||||
brX22Gadget = brX22Addr;
|
||||
*stop = true;
|
||||
}
|
||||
});
|
||||
pfmetric_free(pacMetric);
|
||||
|
||||
return brX22Gadget;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_exception_return_after_check(void)
|
||||
{
|
||||
uint64_t exception_return = xpf_item_resolve("kernelSymbol.exception_return");
|
||||
|
||||
uint32_t inst[] = (uint32_t[]){
|
||||
0xaa0303fe, // mov x30, x3
|
||||
0xaa1603e3, // mov x3, x22
|
||||
0xaa1703e4, // mov x4, x23
|
||||
0xaa1803e5 // mov x5, x24
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t exception_return_after_check = 0;
|
||||
pfmetric_run_in_range(gXPF.kernelTextSection, exception_return, -1, metric, ^(uint64_t vmaddr, bool *stop){
|
||||
exception_return_after_check = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
return exception_return_after_check;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_exception_return_after_check_no_restore(void)
|
||||
{
|
||||
uint64_t exception_return = xpf_item_resolve("kernelSymbol.exception_return");
|
||||
|
||||
uint32_t inst[] = (uint32_t[]){
|
||||
0xd5184021 // msr elr_el1, x1
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t exception_return_after_check_no_restore = 0;
|
||||
pfmetric_run_in_range(gXPF.kernelTextSection, exception_return, -1, metric, ^(uint64_t vmaddr, bool *stop){
|
||||
exception_return_after_check_no_restore = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
return exception_return_after_check_no_restore;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_ldp_x0_x1_x8_gadget(void)
|
||||
{
|
||||
uint32_t inst[] = (uint32_t[]){
|
||||
0xa9400500, // ldp x0, x1, [x8]
|
||||
0xd65f03c0 // ret
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t ldp_x0_x1_x8_gadget = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) {
|
||||
ldp_x0_x1_x8_gadget = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
return ldp_x0_x1_x8_gadget;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_str_x8_x9_gadget(void)
|
||||
{
|
||||
uint32_t inst[] = (uint32_t[]){
|
||||
0xf9000128, // str x8, [x9]
|
||||
0xd65f03c0 // ret
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t str_x8_x9_gadget = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) {
|
||||
str_x8_x9_gadget = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
return str_x8_x9_gadget;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_str_x0_x19_ldr_x20_gadget(void)
|
||||
{
|
||||
uint32_t ldrAnyX20Any = 0, ldrAnyX20AnyMask = 0;
|
||||
arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_X(20), OPT_UINT64_NONE, &ldrAnyX20Any, &ldrAnyX20AnyMask);
|
||||
|
||||
uint32_t inst[] = (uint32_t[]){
|
||||
0xf9000260, // str x0, [x19]
|
||||
ldrAnyX20Any // ldr x?, [x20, ?]
|
||||
};
|
||||
uint32_t mask[] = (uint32_t[]){
|
||||
0xffffffff,
|
||||
ldrAnyX20AnyMask
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t str_x0_x19_ldr_x20_gadget = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) {
|
||||
str_x0_x19_ldr_x20_gadget = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
return str_x0_x19_ldr_x20_gadget;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pacda_gadget(void)
|
||||
{
|
||||
uint32_t inst[] = (uint32_t[]){
|
||||
0xf100003f, // cmp x1, #0
|
||||
0xdac10921, // pacda x1, x9
|
||||
0x9a8103e9, // str x9, [x8]
|
||||
0xf9000109, // csel x9, xzr, x1, eq
|
||||
0xd65f03c0 // ret
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t pacda_gadget = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pacda_gadget = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
return pacda_gadget;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_ml_sign_thread_state(void)
|
||||
{
|
||||
uint32_t inst[] = (uint32_t[]){
|
||||
0x9ac03021, // pacga x1, x1, x0
|
||||
0x9262f842, // and x2, x2, #0xffffffffdfffffff
|
||||
0x9ac13041, // pacga x1, x2, x1
|
||||
0x9ac13061, // pacga x1, x3, x1
|
||||
0x9ac13081, // pacga x1, x4, x1
|
||||
0x9ac130a1, // pacga x1, x5, x1
|
||||
0xf9009401, // str x1, [x0, #0x128]
|
||||
0xd65f03c0 // ret
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t ml_sign_thread_state = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) {
|
||||
ml_sign_thread_state = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
return ml_sign_thread_state;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_thread_recover(void)
|
||||
{
|
||||
uint64_t hw_lck_ticket_reserve_orig_allow_invalid_signed = xpf_item_resolve("kernelGadget.hw_lck_ticket_reserve_orig_allow_invalid_signed");
|
||||
XPF_ASSERT(hw_lck_ticket_reserve_orig_allow_invalid_signed);
|
||||
uint32_t strInst = pfsec_read32(gXPF.kernelTextSection, hw_lck_ticket_reserve_orig_allow_invalid_signed);
|
||||
uint64_t imm = 0;
|
||||
arm64_dec_str_imm(strInst, NULL, NULL, &imm, NULL, NULL);
|
||||
return imm;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_thread_machine_kstackptr(void)
|
||||
{
|
||||
uint32_t ldrAnyInst = 0, ldrAnyMask = 0;
|
||||
arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAnyInst, &ldrAnyMask);
|
||||
|
||||
uint32_t inst[3] = {
|
||||
0xd538d08a, // mrs x10, tpidr_el1
|
||||
ldrAnyInst, // ldr x?, [x?, TH_KSTACKPTR]
|
||||
0xd503233f, // paciasp
|
||||
};
|
||||
uint32_t mask[3] = {
|
||||
0xffffffff,
|
||||
ldrAnyMask,
|
||||
0xffffffff,
|
||||
};
|
||||
|
||||
__block uint64_t machine_kstackptr = 0;
|
||||
PFPatternMetric *patternMetric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t));
|
||||
pfmetric_run(gXPF.kernelTextSection, patternMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
arm64_dec_ldr_imm(pfsec_read32(gXPF.kernelTextSection, vmaddr + 4), NULL, NULL, &machine_kstackptr, NULL, NULL);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(patternMetric);
|
||||
|
||||
if (!machine_kstackptr) {
|
||||
uint32_t inst2[] = {
|
||||
0xd538d08a, // mrs x10, tpidr_el1
|
||||
ldrAnyInst, // ldr x?, [x?, TH_KSTACKPTR]
|
||||
};
|
||||
uint32_t mask2[] = {
|
||||
0xffffffff,
|
||||
ldrAnyMask,
|
||||
};
|
||||
|
||||
uint32_t movk_w12_0x4Inst = 0, movk_w12_0x4Mask = 0;
|
||||
arm64_gen_mov_imm('k', ARM64_REG_W(12), OPT_UINT64(0x4), OPT_UINT64(0), &movk_w12_0x4Inst, &movk_w12_0x4Mask);
|
||||
|
||||
PFPatternMetric *patternMetric2 = pfmetric_pattern_init(inst2, mask2, sizeof(inst2), sizeof(uint32_t));
|
||||
pfmetric_run(gXPF.kernelTextSection, patternMetric2, ^(uint64_t vmaddr, bool *stop) {
|
||||
if (pfsec_find_next_inst(gXPF.kernelTextSection, vmaddr, 0x10, movk_w12_0x4Inst, movk_w12_0x4Mask)) {
|
||||
arm64_dec_ldr_imm(pfsec_read32(gXPF.kernelTextSection, vmaddr + 4), NULL, NULL, &machine_kstackptr, NULL, NULL);
|
||||
*stop = true;
|
||||
}
|
||||
});
|
||||
pfmetric_free(patternMetric2);
|
||||
}
|
||||
|
||||
return machine_kstackptr;
|
||||
}
|
||||
|
||||
void xpf_bad_recovery_init(void)
|
||||
{
|
||||
if (gXPF.kernelIsArm64e && xpf_bad_recovery_supported()) {
|
||||
xpf_item_register("kernelSymbol.hw_lck_ticket_reserve_orig_allow_invalid", xpf_find_hw_lck_ticket_reserve_orig_allow_invalid, NULL);
|
||||
xpf_item_register("kernelGadget.hw_lck_ticket_reserve_orig_allow_invalid_signed", xpf_find_hw_lck_ticket_reserve_orig_allow_invalid_signed, NULL);
|
||||
xpf_item_register("kernelGadget.br_x22", xpf_find_br_x22_gadget, NULL);
|
||||
xpf_item_register("kernelGadget.exception_return_after_check", xpf_find_exception_return_after_check, NULL);
|
||||
xpf_item_register("kernelGadget.exception_return_after_check_no_restore", xpf_find_exception_return_after_check_no_restore, NULL);
|
||||
xpf_item_register("kernelGadget.ldp_x0_x1_x8", xpf_find_ldp_x0_x1_x8_gadget, NULL);
|
||||
xpf_item_register("kernelGadget.str_x8_x9", xpf_find_str_x8_x9_gadget, NULL);
|
||||
xpf_item_register("kernelGadget.str_x0_x19_ldr_x20", xpf_find_str_x0_x19_ldr_x20_gadget, NULL);
|
||||
xpf_item_register("kernelGadget.pacda", xpf_find_pacda_gadget, NULL);
|
||||
xpf_item_register("kernelSymbol.ml_sign_thread_state", xpf_find_ml_sign_thread_state, NULL);
|
||||
xpf_item_register("kernelStruct.thread.recover", xpf_find_thread_recover, NULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
void xpf_bad_recovery_init(void);
|
||||
bool xpf_bad_recovery_supported(void);
|
||||
+1385
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1 @@
|
||||
void xpf_common_init(void);
|
||||
@@ -0,0 +1,132 @@
|
||||
// Taken from golb: https://github.com/0x7ff/golb
|
||||
// Licensed under Apache 2.0
|
||||
// See decompress.LICENSE
|
||||
|
||||
#include "decompress.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <compression.h>
|
||||
|
||||
#define DER_INT (0x2U)
|
||||
#define DER_SEQ (0x30U)
|
||||
#define LZSS_F (18)
|
||||
#define LZSS_N (4096)
|
||||
#define LZSS_THRESHOLD (2)
|
||||
#define KCOMP_HDR_PAD_SZ (0x16C)
|
||||
#define DER_IA5_STR (0x16U)
|
||||
#define DER_OCTET_STR (0x4U)
|
||||
#define KCOMP_HDR_MAGIC (0x636F6D70U)
|
||||
#define KCOMP_HDR_TYPE_LZSS (0x6C7A7373U)
|
||||
|
||||
const uint8_t *
|
||||
der_decode(uint8_t tag, const uint8_t *der, const uint8_t *der_end, size_t *out_len) {
|
||||
size_t der_len;
|
||||
|
||||
if(der_end - der > 2 && tag == *der++) {
|
||||
if(((der_len = *der++) & 0x80U) != 0) {
|
||||
*out_len = 0;
|
||||
if((der_len &= 0x7FU) <= sizeof(*out_len) && (size_t)(der_end - der) >= der_len) {
|
||||
while(der_len-- != 0) {
|
||||
*out_len = (*out_len << 8U) | *der++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*out_len = der_len;
|
||||
}
|
||||
if(*out_len != 0 && (size_t)(der_end - der) >= *out_len) {
|
||||
return der;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const uint8_t *
|
||||
der_decode_seq(const uint8_t *der, const uint8_t *der_end, const uint8_t **seq_end) {
|
||||
size_t der_len;
|
||||
|
||||
if((der = der_decode(DER_SEQ, der, der_end, &der_len)) != NULL) {
|
||||
*seq_end = der + der_len;
|
||||
}
|
||||
return der;
|
||||
}
|
||||
|
||||
const uint8_t *
|
||||
der_decode_uint64(const uint8_t *der, const uint8_t *der_end, uint64_t *r) {
|
||||
size_t der_len;
|
||||
|
||||
if((der = der_decode(DER_INT, der, der_end, &der_len)) != NULL && (*der & 0x80U) == 0 && (der_len <= sizeof(*r) || (--der_len == sizeof(*r) && *der++ == 0))) {
|
||||
*r = 0;
|
||||
while(der_len-- != 0) {
|
||||
*r = (*r << 8U) | *der++;
|
||||
}
|
||||
return der;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t
|
||||
decompress_lzss(const uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_len) {
|
||||
const uint8_t *src_end = src + src_len, *dst_start = dst, *dst_end = dst + dst_len;
|
||||
uint16_t i, r = LZSS_N - LZSS_F, flags = 0;
|
||||
uint8_t text_buf[LZSS_N + LZSS_F - 1], j;
|
||||
|
||||
memset(text_buf, ' ', r);
|
||||
while(src != src_end && dst != dst_end) {
|
||||
if(((flags >>= 1U) & 0x100U) == 0) {
|
||||
flags = *src++ | 0xFF00U;
|
||||
if(src == src_end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if((flags & 1U) != 0) {
|
||||
text_buf[r++] = *dst++ = *src++;
|
||||
r &= LZSS_N - 1U;
|
||||
} else {
|
||||
i = *src++;
|
||||
if(src == src_end) {
|
||||
break;
|
||||
}
|
||||
j = *src++;
|
||||
i |= (j & 0xF0U) << 4U;
|
||||
j = (j & 0xFU) + LZSS_THRESHOLD;
|
||||
do {
|
||||
*dst++ = text_buf[r++] = text_buf[i++ & (LZSS_N - 1U)];
|
||||
r &= LZSS_N - 1U;
|
||||
} while(j-- != 0 && dst != dst_end);
|
||||
}
|
||||
}
|
||||
return (size_t)(dst - dst_start);
|
||||
}
|
||||
|
||||
void *
|
||||
kdecompress(const void *src, size_t src_len, size_t *dst_len) {
|
||||
const uint8_t *der, *octet, *der_end, *src_end = (const uint8_t *)src + src_len;
|
||||
struct {
|
||||
uint32_t magic, type, adler32, uncomp_sz, comp_sz;
|
||||
uint8_t pad[KCOMP_HDR_PAD_SZ];
|
||||
} kcomp_hdr;
|
||||
size_t der_len;
|
||||
uint64_t r;
|
||||
void *dst;
|
||||
|
||||
if((der = der_decode_seq(src, src_end, &der_end)) != NULL && (der = der_decode(DER_IA5_STR, der, der_end, &der_len)) != NULL && der_len == 4 && (memcmp(der, "IMG4", der_len) != 0 || ((der = der_decode_seq(der + der_len, src_end, &der_end)) != NULL && (der = der_decode(DER_IA5_STR, der, der_end, &der_len)) != NULL && der_len == 4)) && memcmp(der, "IM4P", der_len) == 0 && (der = der_decode(DER_IA5_STR, der + der_len, der_end, &der_len)) != NULL && der_len == 4 && memcmp(der, "krnl", der_len) == 0 && (der = der_decode(DER_IA5_STR, der + der_len, der_end, &der_len)) != NULL && (der = der_decode(DER_OCTET_STR, der + der_len, der_end, &der_len)) != NULL && der_len > sizeof(kcomp_hdr)) {
|
||||
octet = der;
|
||||
memcpy(&kcomp_hdr, octet, sizeof(kcomp_hdr));
|
||||
if(kcomp_hdr.magic == __builtin_bswap32(KCOMP_HDR_MAGIC)) {
|
||||
if(kcomp_hdr.type == __builtin_bswap32(KCOMP_HDR_TYPE_LZSS) && (kcomp_hdr.comp_sz = __builtin_bswap32(kcomp_hdr.comp_sz)) <= der_len - sizeof(kcomp_hdr) && (kcomp_hdr.uncomp_sz = __builtin_bswap32(kcomp_hdr.uncomp_sz)) != 0 && (dst = malloc(kcomp_hdr.uncomp_sz)) != NULL) {
|
||||
if(decompress_lzss(octet + sizeof(kcomp_hdr), kcomp_hdr.comp_sz, dst, kcomp_hdr.uncomp_sz) == kcomp_hdr.uncomp_sz) {
|
||||
*dst_len = kcomp_hdr.uncomp_sz;
|
||||
return dst;
|
||||
}
|
||||
free(dst);
|
||||
}
|
||||
} else if((der = der_decode_seq(der + der_len, src_end, &der_end)) != NULL && (der = der_decode_uint64(der, der_end, &r)) != NULL && r == 1 && der_decode_uint64(der, der_end, &r) != NULL && r != 0 && (dst = malloc(r)) != NULL) {
|
||||
if(compression_decode_buffer(dst, r, octet, der_len, NULL, COMPRESSION_LZFSE) == r) {
|
||||
*dst_len = r;
|
||||
return dst;
|
||||
}
|
||||
free(dst);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
void *kdecompress(const void *src, size_t src_len, size_t *dst_len);
|
||||
@@ -0,0 +1,234 @@
|
||||
#include "choma/PatchFinder.h"
|
||||
#include "xpf.h"
|
||||
|
||||
static uint64_t xpf_find_pmap_image4_trust_caches(void)
|
||||
{
|
||||
// search pmap_is_trust_cache_loaded
|
||||
uint32_t inst[] = {
|
||||
0x39400400, // ldrb wN, [xM, #0x1]
|
||||
0x39401400, // ldrb wN, [xM, #0x5]
|
||||
0x6b00001f, // cmp wN, wM
|
||||
0x54000001, // b.ne
|
||||
0x39400800, // ldrb wN, [xM, #0x2]
|
||||
0x39401800, // ldrb wN, [xM, #0x6]
|
||||
0x6b00001f, // cmp wN, wM
|
||||
0x54000001, // b.ne
|
||||
};
|
||||
uint32_t mask[] = {
|
||||
0xff40fc00,
|
||||
0xff40fc00,
|
||||
0xffe0fc1f,
|
||||
0xff00001f,
|
||||
0xff40fc00,
|
||||
0xff40fc00,
|
||||
0xffe0fc1f,
|
||||
0xff00001f,
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(&inst, &mask, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t found = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) {
|
||||
found = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
XPF_ASSERT(found);
|
||||
|
||||
uint32_t adrpInst = 0, adrpInstAny = 0;
|
||||
arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrpInst, &adrpInstAny);
|
||||
uint64_t adrpAddr = pfsec_find_prev_inst(gXPF.kernelTextSection, found, 25, adrpInst, adrpInstAny);
|
||||
XPF_ASSERT(adrpAddr);
|
||||
|
||||
return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr + 4);
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_trust_cache_rt(void)
|
||||
{
|
||||
PFStringMetric *stringMetric = pfmetric_string_init("unexpected size for TrustCache property: %u != %zu @%s:%d");
|
||||
__block uint64_t non_ppl_trust_cache_rt_stringAddr = 0;
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){
|
||||
non_ppl_trust_cache_rt_stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
XPF_ASSERT(non_ppl_trust_cache_rt_stringAddr);
|
||||
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(non_ppl_trust_cache_rt_stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
__block uint64_t non_ppl_trust_cache_rt = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
non_ppl_trust_cache_rt = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
XPF_ASSERT(non_ppl_trust_cache_rt);
|
||||
|
||||
// 2nd adrp
|
||||
uint32_t adrpInst = 0, adrpInstAny = 0;
|
||||
arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrpInst, &adrpInstAny);
|
||||
|
||||
uint64_t toCheck = non_ppl_trust_cache_rt;
|
||||
uint64_t adrpAddr = 0;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
adrpAddr = pfsec_find_next_inst(gXPF.kernelTextSection, toCheck, 80, adrpInst, adrpInstAny);
|
||||
toCheck = adrpAddr + 4;
|
||||
}
|
||||
XPF_ASSERT(adrpAddr);
|
||||
|
||||
return pfsec_read_pointer(gXPF.kernelDataConstSection, pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr + 4));
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_tt_deallocate(void)
|
||||
{
|
||||
PFStringMetric *stringMetric = pfmetric_string_init("pmap_tt_deallocate(): ptdp %p, count %d @%s:%d");
|
||||
__block uint64_t pmap_tt_deallocate_stringAddr = 0;
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){
|
||||
pmap_tt_deallocate_stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
|
||||
if (!pmap_tt_deallocate_stringAddr) {
|
||||
stringMetric = pfmetric_string_init("\"pmap_tt_deallocate(): ptdp %p, count %d\\n\"");
|
||||
pmap_tt_deallocate_stringAddr = 0;
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pmap_tt_deallocate_stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
}
|
||||
|
||||
XPF_ASSERT(pmap_tt_deallocate_stringAddr);
|
||||
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_tt_deallocate_stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
__block uint64_t pmap_tt_deallocate = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pmap_tt_deallocate = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
|
||||
return pmap_tt_deallocate;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_tt_deallocate_reference(uint32_t n)
|
||||
{
|
||||
uint64_t pmap_tt_deallocate = xpf_item_resolve("kernelSymbol.pmap_tt_deallocate");
|
||||
XPF_ASSERT(pmap_tt_deallocate);
|
||||
|
||||
uint32_t blAny = 0, blAnyMask = 0;
|
||||
arm64_gen_b_l(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, &blAny, &blAnyMask);
|
||||
|
||||
uint64_t toCheck = pmap_tt_deallocate;
|
||||
uint64_t blAddr = 0;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
blAddr = pfsec_find_next_inst(gXPF.kernelTextSection, toCheck, 80, blAny, blAnyMask);
|
||||
toCheck = blAddr - 12;
|
||||
}
|
||||
XPF_ASSERT(blAddr);
|
||||
|
||||
uint32_t adrpInst = 0, adrpInstAny = 0;
|
||||
arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrpInst, &adrpInstAny);
|
||||
|
||||
toCheck = blAddr;
|
||||
uint64_t adrpAddr = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
adrpAddr = pfsec_find_next_inst(gXPF.kernelTextSection, toCheck, 80, adrpInst, adrpInstAny);
|
||||
toCheck = adrpAddr + 4;
|
||||
}
|
||||
XPF_ASSERT(adrpAddr);
|
||||
|
||||
return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr + 4);
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_enter_options_addr(void)
|
||||
{
|
||||
__block uint64_t stringAddr = 0;
|
||||
|
||||
PFStringMetric *stringMetric = pfmetric_string_init("pmap_enter_options_internal");
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
|
||||
if (!stringAddr) {
|
||||
stringMetric = pfmetric_string_init("\"pmap_enter_options() pmap %p v 0x%llx\\n\"");
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
}
|
||||
XPF_ASSERT(stringAddr);
|
||||
|
||||
__block uint64_t pmap_enter_options_internal = 0;
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pmap_enter_options_internal = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
|
||||
// On arm64, pmap_enter_options_internal is equivalent to pmap_enter_options_addr
|
||||
return pmap_enter_options_internal;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_remove_options(void)
|
||||
{
|
||||
__block uint64_t stringAddr = 0;
|
||||
|
||||
PFStringMetric *stringMetric = pfmetric_string_init("pmap_remove_options_internal");
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
XPF_ASSERT(stringAddr);
|
||||
|
||||
__block uint64_t pmap_remove_options_internal = 0;
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pmap_remove_options_internal = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
|
||||
// On arm64, pmap_remove_options_internal is equivalent to pmap_remove_options
|
||||
return pmap_remove_options_internal;
|
||||
}
|
||||
|
||||
// TODO
|
||||
static uint64_t xpf_find_vm_last_phys(void)
|
||||
{
|
||||
return 0xFFFFFF8000000000;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pp_attr_table(void)
|
||||
{
|
||||
return 0xFFFFFF8000000000;
|
||||
}
|
||||
|
||||
void xpf_non_ppl_init(void)
|
||||
{
|
||||
if (!gXPF.kernelIsArm64e) {
|
||||
bool iOS14 = strcmp(gXPF.darwinVersion, "21.0.0") < 0;
|
||||
xpf_item_register("kernelSymbol.pmap_tt_deallocate", xpf_find_pmap_tt_deallocate, NULL);
|
||||
xpf_item_register("kernelSymbol.vm_first_phys", xpf_find_pmap_tt_deallocate_reference, iOS14 ? (void*)(uint32_t)2 : (void*)(uint32_t)1);
|
||||
xpf_item_register("kernelSymbol.pv_head_table", xpf_find_pmap_tt_deallocate_reference, iOS14 ? (void*)(uint32_t)1 : (void*)(uint32_t)2);
|
||||
|
||||
xpf_item_register("kernelSymbol.vm_last_phys", xpf_find_vm_last_phys, NULL);
|
||||
xpf_item_register("kernelSymbol.pp_attr_table", xpf_find_pp_attr_table, NULL);
|
||||
|
||||
xpf_item_register("kernelSymbol.pmap_enter_options_addr", xpf_find_pmap_enter_options_addr, NULL);
|
||||
xpf_item_register("kernelSymbol.pmap_remove_options", xpf_find_pmap_remove_options, NULL);
|
||||
|
||||
if (strcmp(gXPF.darwinVersion, "22.0.0") >= 0) {
|
||||
// iOS >=16
|
||||
xpf_item_register("kernelSymbol.ppl_trust_cache_rt", xpf_find_trust_cache_rt, NULL);
|
||||
}
|
||||
else {
|
||||
// iOS <=15
|
||||
xpf_item_register("kernelSymbol.pmap_image4_trust_caches", xpf_find_pmap_image4_trust_caches, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
void xpf_non_ppl_init(void);
|
||||
+577
@@ -0,0 +1,577 @@
|
||||
#import "ppl.h"
|
||||
#import "xpf.h"
|
||||
#include <choma/arm64.h>
|
||||
#include <choma/PatchFinder.h>
|
||||
|
||||
static uint64_t xpf_find_ppl_dispatch_section(void)
|
||||
{
|
||||
uint32_t bAny = 0, bAnyMask = 0, movX15Any = 0, movX15AnyMask = 0;
|
||||
arm64_gen_b_l(OPT_BOOL(false), OPT_UINT64_NONE, OPT_UINT64_NONE, &bAny, &bAnyMask);
|
||||
arm64_gen_mov_imm('z', ARM64_REG_X(15), OPT_UINT64_NONE, OPT_UINT64(0), &movX15Any, &movX15AnyMask);
|
||||
|
||||
uint32_t pplCallerInst[] = {
|
||||
movX15Any,
|
||||
bAny,
|
||||
movX15Any,
|
||||
bAny,
|
||||
};
|
||||
uint32_t pplCallerMask[] = {
|
||||
movX15AnyMask,
|
||||
bAnyMask,
|
||||
movX15AnyMask,
|
||||
bAnyMask,
|
||||
};
|
||||
|
||||
__block uint64_t ppl_dispatch_section = 0;
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(pplCallerInst,pplCallerMask,sizeof(pplCallerInst), sizeof(uint32_t));
|
||||
pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) {
|
||||
ppl_dispatch_section = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
return ppl_dispatch_section;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_ppl_enter(void)
|
||||
{
|
||||
uint64_t ppl_dispatch_section = xpf_item_resolve("kernelSymbol.ppl_dispatch_section");
|
||||
XPF_ASSERT(ppl_dispatch_section);
|
||||
__block uint64_t ppl_enter = 0;
|
||||
arm64_dec_b_l(pfsec_read32(gXPF.kernelTextSection, ppl_dispatch_section+4), ppl_dispatch_section+4, &ppl_enter, NULL);
|
||||
return ppl_enter;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_ppl_bootstrap_dispatch(void)
|
||||
{
|
||||
uint64_t ppl_enter = xpf_item_resolve("kernelSymbol.ppl_enter");
|
||||
XPF_ASSERT(ppl_enter);
|
||||
|
||||
uint32_t cbzAny = 0, cbzAnyMask = 0;
|
||||
arm64_gen_cb_n_z(OPT_BOOL(false), ARM64_REG_ANY, OPT_UINT64_NONE, &cbzAny, &cbzAnyMask);
|
||||
|
||||
uint64_t cbzPPLDispatchAddr = pfsec_find_next_inst(gXPF.kernelTextSection, ppl_enter, 30, cbzAny, cbzAnyMask);
|
||||
if (cbzPPLDispatchAddr) {
|
||||
uint64_t ppl_bootstrap_dispatch = 0;
|
||||
arm64_dec_cb_n_z(pfsec_read32(gXPF.kernelTextSection, cbzPPLDispatchAddr), cbzPPLDispatchAddr, NULL, NULL, &ppl_bootstrap_dispatch);
|
||||
return ppl_bootstrap_dispatch;
|
||||
}
|
||||
else {
|
||||
uint32_t bCondAnyInst = 0, bCondAnyMask = 0;
|
||||
arm64_gen_b_c_cond(OPT_BOOL(false), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_COND_ANY, &bCondAnyInst, &bCondAnyMask);
|
||||
uint64_t bcondPPLDispatchAddr = pfsec_find_next_inst(gXPF.kernelTextSection, ppl_enter, 30, bCondAnyInst, bCondAnyMask);
|
||||
XPF_ASSERT(bcondPPLDispatchAddr);
|
||||
|
||||
uint64_t ppl_bootstrap_dispatch = 0;
|
||||
arm64_dec_b_c_cond(pfsec_read32(gXPF.kernelTextSection, bcondPPLDispatchAddr), bcondPPLDispatchAddr, &ppl_bootstrap_dispatch, NULL, NULL);
|
||||
return ppl_bootstrap_dispatch;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_ppl_handler_table(void)
|
||||
{
|
||||
uint64_t ppl_bootstrap_dispatch = xpf_item_resolve("kernelSymbol.ppl_bootstrap_dispatch");
|
||||
XPF_ASSERT(ppl_bootstrap_dispatch);
|
||||
|
||||
uint32_t addAny = 0, addAnyMask = 0;
|
||||
arm64_gen_add_imm(ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &addAny, &addAnyMask);
|
||||
|
||||
uint64_t addAddr = pfsec_find_next_inst(gXPF.kernelTextSection, ppl_bootstrap_dispatch, 30, addAny, addAnyMask);
|
||||
return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, addAddr);
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_ppl_routine(uint32_t idx)
|
||||
{
|
||||
uint64_t ppl_handler_table = xpf_item_resolve("kernelSymbol.ppl_handler_table");
|
||||
XPF_ASSERT(ppl_handler_table);
|
||||
return pfsec_read_pointer(gXPF.kernelDataConstSection, ppl_handler_table + (sizeof(uint64_t) * idx));
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_ppl_dispatch_func(uint32_t idx)
|
||||
{
|
||||
uint64_t ppl_dispatch_section = xpf_item_resolve("kernelSymbol.ppl_dispatch_section");
|
||||
XPF_ASSERT(ppl_dispatch_section);
|
||||
|
||||
uint32_t movToFind = 0, movMaskToFind = 0;
|
||||
arm64_gen_mov_imm('z', ARM64_REG_X(15), OPT_UINT64(idx), OPT_UINT64(0), &movToFind, &movMaskToFind);
|
||||
|
||||
return pfsec_find_next_inst(gXPF.kernelTextSection, ppl_dispatch_section, 1000, movToFind, movMaskToFind);
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_image4_trust_caches(void)
|
||||
{
|
||||
uint64_t pmap_lookup_in_loaded_trust_caches_internal = xpf_item_resolve("kernelSymbol.pmap_lookup_in_loaded_trust_caches_internal");
|
||||
XPF_ASSERT(pmap_lookup_in_loaded_trust_caches_internal);
|
||||
|
||||
uint32_t ldrLitAny = 0, ldrLitAnyMask = 0;
|
||||
arm64_gen_ldr_lit(ARM64_REG_ANY, OPT_UINT64_NONE, OPT_UINT64_NONE, &ldrLitAny, &ldrLitAnyMask);
|
||||
uint64_t ldrLitAddr = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_lookup_in_loaded_trust_caches_internal, 20, ldrLitAny, ldrLitAnyMask);
|
||||
if (ldrLitAddr) {
|
||||
uint64_t ldrTarget = 0;
|
||||
arm64_dec_ldr_lit(pfsec_read32(gXPF.kernelPPLTextSection, ldrLitAddr), ldrLitAddr, &ldrTarget, NULL);
|
||||
return ldrTarget;
|
||||
}
|
||||
|
||||
/*uint32_t ldrAny = 0, ldrAnyMask = 0;
|
||||
arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAny, &ldrAnyMask);
|
||||
uint64_t ldrAddr = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_lookup_in_loaded_trust_caches_internal, 20, ldrAny, ldrAnyMask);
|
||||
if (ldrAddr) {
|
||||
uint32_t ldrInst = pfsec_read32(gXPF.kernelPPLTextSection, ldrAddr);
|
||||
arm64_register reg;
|
||||
uint64_t ldrImm = 0;
|
||||
arm64_dec_ldr_imm(ldrInst, NULL, ®, &ldrImm, NULL);
|
||||
uint32_t adrpInst = 0, adrpInstAny = 0;
|
||||
arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, reg, &adrpInst, &adrpInstAny);
|
||||
uint64_t adrpAddr = pfsec_find_prev_inst(gXPF.kernelPPLTextSection, ldrAddr, 20, adrpInst, adrpInstAny);
|
||||
if (adrpAddr) {
|
||||
uint32_t adrpInst = pfsec_read32(gXPF.kernelPPLTextSection, adrpAddr);
|
||||
uint64_t adrpTarget = 0;
|
||||
arm64_dec_adr_p(adrpInst, adrpAddr, &adrpTarget, NULL, NULL);
|
||||
return adrpTarget + ldrImm;
|
||||
}
|
||||
}*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_query_trust_cache_safe(void)
|
||||
{
|
||||
uint64_t pmap_lookup_in_loaded_trust_caches_internal = xpf_item_resolve("kernelSymbol.pmap_lookup_in_loaded_trust_caches_internal");
|
||||
XPF_ASSERT(pmap_lookup_in_loaded_trust_caches_internal);
|
||||
|
||||
uint32_t blAny = 0, blAnyMask = 0;
|
||||
arm64_gen_b_l(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, &blAny, &blAnyMask);
|
||||
|
||||
uint64_t blAddr = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_lookup_in_loaded_trust_caches_internal, 30, blAny, blAnyMask);
|
||||
XPF_ASSERT(blAddr);
|
||||
|
||||
uint32_t blInst = pfsec_read32(gXPF.kernelPPLTextSection, blAddr);
|
||||
|
||||
uint64_t pmap_query_trust_cache_safe = 0;
|
||||
arm64_dec_b_l(blInst, blAddr, &pmap_query_trust_cache_safe, NULL);
|
||||
return pmap_query_trust_cache_safe;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_ppl_trust_cache_rt(void)
|
||||
{
|
||||
uint64_t pmap_query_trust_cache_safe = xpf_item_resolve("kernelSymbol.pmap_query_trust_cache_safe");
|
||||
XPF_ASSERT(pmap_query_trust_cache_safe);
|
||||
|
||||
uint32_t pacMovInst = 0, pacMovMask = 0;
|
||||
arm64_gen_mov_imm('z', ARM64_REG_ANY, OPT_UINT64(0x6653), OPT_UINT64(0), &pacMovInst, &pacMovMask);
|
||||
uint64_t pacMovAddr = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_query_trust_cache_safe, 50, pacMovInst, pacMovMask);
|
||||
XPF_ASSERT(pacMovAddr);
|
||||
|
||||
uint32_t addX0AnyInst = 0, addX0AnyMask = 0;
|
||||
arm64_gen_add_imm(ARM64_REG_X(0), ARM64_REG_ANY, OPT_UINT64_NONE, &addX0AnyInst, &addX0AnyMask);
|
||||
uint64_t ppl_trust_cache_rt_refAddr = pfsec_find_prev_inst(gXPF.kernelPPLTextSection, pacMovAddr, 10, addX0AnyInst, addX0AnyMask);
|
||||
|
||||
return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelPPLTextSection, ppl_trust_cache_rt_refAddr);
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_load_static_trust_cache(void)
|
||||
{
|
||||
PFStringMetric *trustcacheSegmentIsZeroMetric = pfmetric_string_init("trust cache segment is zero length but trust caches are available: %u @%s:%d");
|
||||
__block uint64_t trustcacheSegmentIsZeroAddr = 0;
|
||||
pfmetric_run(gXPF.kernelStringSection, trustcacheSegmentIsZeroMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
trustcacheSegmentIsZeroAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(trustcacheSegmentIsZeroMetric);
|
||||
XPF_ASSERT(trustcacheSegmentIsZeroAddr);
|
||||
|
||||
__block uint64_t load_static_trust_cache_mid = 0;
|
||||
PFXrefMetric *trustcacheSegmentIsZeroXrefMetric = pfmetric_xref_init(trustcacheSegmentIsZeroAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
pfmetric_run(gXPF.kernelTextSection, trustcacheSegmentIsZeroXrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
load_static_trust_cache_mid = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(trustcacheSegmentIsZeroXrefMetric);
|
||||
XPF_ASSERT(load_static_trust_cache_mid);
|
||||
|
||||
uint64_t ret = pfsec_find_function_start(gXPF.kernelTextSection, load_static_trust_cache_mid);
|
||||
printf("load_static_trust_cache found? 0x%llx\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_sptm_trust_cache_rt(void)
|
||||
{
|
||||
uint64_t load_static_trust_cache = xpf_item_resolve("kernelSymbol.load_static_trust_cache");
|
||||
XPF_ASSERT(load_static_trust_cache);
|
||||
|
||||
// search ADRP xN, #_trust_cache_rt@PAGE;
|
||||
uint32_t inst[] = {
|
||||
0x90000000, // adrp xN, #0x...
|
||||
0xf9400000, // ldr xN, [xN, #0x...]
|
||||
0xB4000000, // cbz xN, loc_...
|
||||
};
|
||||
uint32_t mask[] = {
|
||||
0x9F000000,
|
||||
0xFFC00000,
|
||||
0xFF000000,
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t adrpAddr = 0;
|
||||
pfmetric_run_in_range(gXPF.kernelTextSection, load_static_trust_cache, -1, metric, ^(uint64_t vmaddr, bool *stop){
|
||||
adrpAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(metric);
|
||||
|
||||
uint64_t ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4);
|
||||
printf("trust_cache_rt found? 0x%llx\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_pin_kernel_pages(void)
|
||||
{
|
||||
PFStringMetric *stringMetric = pfmetric_string_init("pmap_pin_kernel_pages");
|
||||
__block uint64_t pmap_pin_kernel_pages_stringAddr = 0;
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){
|
||||
pmap_pin_kernel_pages_stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
XPF_ASSERT(pmap_pin_kernel_pages_stringAddr);
|
||||
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_pin_kernel_pages_stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
__block uint64_t pmap_pin_kernel_pages = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pmap_pin_kernel_pages = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
XPF_ASSERT(pmap_pin_kernel_pages);
|
||||
|
||||
return pmap_pin_kernel_pages;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_pin_kernel_pages_reference(uint32_t idx)
|
||||
{
|
||||
uint64_t pmap_pin_kernel_pages = xpf_item_resolve("kernelSymbol.pmap_pin_kernel_pages");
|
||||
XPF_ASSERT(pmap_pin_kernel_pages);
|
||||
|
||||
uint32_t ldrAnyInst = 0, ldrAnyMask = 0;
|
||||
arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAnyInst, &ldrAnyMask);
|
||||
|
||||
__block uint64_t ref = 0;
|
||||
__block uint32_t f = 0;
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(&ldrAnyInst, &ldrAnyMask, sizeof(ldrAnyInst), sizeof(uint32_t));
|
||||
pfmetric_run_in_range(gXPF.kernelTextSection, pmap_pin_kernel_pages, -1, metric, ^(uint64_t vmaddr, bool *stop) {
|
||||
arm64_register destinationReg;
|
||||
arm64_dec_ldr_imm(pfsec_read32(gXPF.kernelTextSection, vmaddr), &destinationReg, NULL, NULL, NULL, NULL);
|
||||
// On some kernels there is one additional ldr before the ones we're looking for
|
||||
// As this always loads into x0 and the other ones don't, we can filter it out based on that metric
|
||||
if (ARM64_REG_GET_NUM(destinationReg) != 0) {
|
||||
if (f == idx) {
|
||||
ref = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
}
|
||||
f++;
|
||||
}
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_phys_attribute_clear_with_flush_range(void) {
|
||||
__block uint64_t phys_attribute_clear_stringAddr = 0;
|
||||
PFStringMetric *physAttributeMetric = pfmetric_string_init("phys_attribute_clear(%#010x,%#010x,%#010x,%p,%p): invalid options @%s:%d");
|
||||
pfmetric_run(gXPF.kernelStringSection, physAttributeMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
phys_attribute_clear_stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(physAttributeMetric);
|
||||
XPF_ASSERT(phys_attribute_clear_stringAddr);
|
||||
|
||||
__block uint64_t phys_attribute_clear_with_flush_range = 0;
|
||||
PFXrefMetric *physAttributeXrefMetric = pfmetric_xref_init(phys_attribute_clear_stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
pfmetric_run(gXPF.kernelTextSection, physAttributeXrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
phys_attribute_clear_with_flush_range = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(physAttributeXrefMetric);
|
||||
printf("phys_attribute_clear_with_flush_range found? 0x%llx\n", phys_attribute_clear_with_flush_range);
|
||||
return phys_attribute_clear_with_flush_range;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_phys_attribute_clear_with_flush_range_reference(uint32_t idx)
|
||||
{
|
||||
uint64_t phys_attribute_clear_with_flush_range = xpf_item_resolve("kernelSymbol.phys_attribute_clear_with_flush_range");
|
||||
XPF_ASSERT(phys_attribute_clear_with_flush_range);
|
||||
|
||||
uint32_t inst[] = {
|
||||
0x90000000, // adrp xN, #0x...
|
||||
0xf9400000, // ldr xN, [xN, #0x...]
|
||||
};
|
||||
uint32_t mask[] = {
|
||||
0x9F000000,
|
||||
0xFFC00000,
|
||||
};
|
||||
|
||||
// find vm_first_phys from 1st adrp
|
||||
uint64_t ret = 0;
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t adrpAddr = 0;
|
||||
pfmetric_run_in_range(gXPF.kernelTextSection, phys_attribute_clear_with_flush_range, -1, metric, ^(uint64_t vmaddr, bool *stop){
|
||||
adrpAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
if(idx == 0) {
|
||||
ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4);
|
||||
printf("vm_first_phys found? 0x%llx\n", ret);
|
||||
}
|
||||
|
||||
// find vm_last_phys from 2nd adrp
|
||||
pfmetric_run_in_range(gXPF.kernelTextSection, adrpAddr+8, -1, metric, ^(uint64_t vmaddr, bool *stop){
|
||||
adrpAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
if(idx == 1) {
|
||||
ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4);
|
||||
printf("vm_last_phys found? 0x%llx\n", ret);
|
||||
}
|
||||
|
||||
// find pp_attr_table from 3rd adrp
|
||||
pfmetric_run_in_range(gXPF.kernelTextSection, adrpAddr+8, -1, metric, ^(uint64_t vmaddr, bool *stop){
|
||||
adrpAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
if(idx == 2) {
|
||||
ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4);
|
||||
printf("pp_attr_table found? 0x%llx\n", ret);
|
||||
}
|
||||
|
||||
pfmetric_free(metric);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_enter_pv(void)
|
||||
{
|
||||
PFStringMetric *stringMetric = pfmetric_string_init("pmap_enter_pv");
|
||||
XPF_ASSERT(stringMetric);
|
||||
|
||||
__block uint64_t pmap_enter_pv_stringAddr = 0;
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){
|
||||
pmap_enter_pv_stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
XPF_ASSERT(pmap_enter_pv_stringAddr);
|
||||
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_enter_pv_stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
__block uint64_t pmap_enter_pv = 0;
|
||||
pfmetric_run(gXPF.kernelPPLTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pmap_enter_pv = pfsec_find_function_start(gXPF.kernelPPLTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
|
||||
return pmap_enter_pv;
|
||||
}
|
||||
|
||||
static uint64_t xpf_sptm_find_pv_head_table(void)
|
||||
{
|
||||
//find `pte_update_wiredcnt` by xref "pmap %p (pte %p): wired count underflow @%s:%d"
|
||||
PFStringMetric *stringMetric = pfmetric_string_init("pmap %p (pte %p): wired count underflow @%s:%d");
|
||||
XPF_ASSERT(stringMetric);
|
||||
|
||||
__block uint64_t wired_count_underflow_stringAddr = 0;
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){
|
||||
wired_count_underflow_stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
XPF_ASSERT(wired_count_underflow_stringAddr);
|
||||
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(wired_count_underflow_stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
__block uint64_t pte_update_wiredcnt = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pte_update_wiredcnt = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
printf("pte_update_wiredcnt found? 0x%llx\n", pte_update_wiredcnt);
|
||||
|
||||
// Find second adrp; first is vm_first_phys, second is pv_head_table
|
||||
uint32_t inst[] = {
|
||||
0x90000000, // adrp xN, #0x...
|
||||
0xf9400000, // ldr xN, [xN, #0x...]
|
||||
};
|
||||
uint32_t mask[] = {
|
||||
0x9F000000,
|
||||
0xFFC00000,
|
||||
};
|
||||
|
||||
PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t));
|
||||
__block uint64_t adrpAddr = 0;
|
||||
pfmetric_run_in_range(gXPF.kernelTextSection, pte_update_wiredcnt, -1, metric, ^(uint64_t vmaddr, bool *stop){
|
||||
adrpAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
|
||||
pfmetric_run_in_range(gXPF.kernelTextSection, adrpAddr+8, -1, metric, ^(uint64_t vmaddr, bool *stop){
|
||||
adrpAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
uint64_t ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4);
|
||||
printf("pv_head_table found? 0x%llx\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pv_head_table(void)
|
||||
{
|
||||
uint64_t pmap_enter_pv = xpf_item_resolve("kernelSymbol.pmap_enter_pv");
|
||||
XPF_ASSERT(pmap_enter_pv);
|
||||
|
||||
uint32_t ldrAnyInst = 0, ldrAnyMask = 0;
|
||||
arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAnyInst, &ldrAnyMask);
|
||||
|
||||
uint64_t ref = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_enter_pv, 0, ldrAnyInst, ldrAnyMask);
|
||||
XPF_ASSERT(ref);
|
||||
return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelPPLTextSection, ref);
|
||||
}
|
||||
|
||||
static uint64_t xpf_sptm_find_pmap_enter_options_addr(void)
|
||||
{
|
||||
// Find `pmap_enter_options_internal` by xref "pmap_enter_options(): attempt to add executable mapping to kernel_pmap @%s:%d"
|
||||
PFStringMetric *stringMetric = pfmetric_string_init("pmap_enter_options(): attempt to add executable mapping to kernel_pmap @%s:%d");
|
||||
XPF_ASSERT(stringMetric);
|
||||
|
||||
__block uint64_t stringAddr = 0;
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){
|
||||
stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
XPF_ASSERT(stringAddr);
|
||||
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
__block uint64_t pmap_enter_options_addr = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pmap_enter_options_addr = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
printf("pmap_enter_options_addr found? 0x%llx\n", pmap_enter_options_addr);
|
||||
return pmap_enter_options_addr;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_enter_options_addr(void)
|
||||
{
|
||||
uint64_t pmap_enter_options_ppl = xpf_item_resolve("kernelSymbol.pmap_enter_options_ppl");
|
||||
XPF_ASSERT(pmap_enter_options_ppl);
|
||||
|
||||
__block uint64_t pmap_enter_options_addr = 0;
|
||||
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_enter_options_ppl, XREF_TYPE_MASK_CALL);
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
// Find an Xref, that within the the previous 25 instructions...
|
||||
if (pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 25, 0x12000000, 0x7F800000) && // Has an AND
|
||||
pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 25, 0x32000000, 0x7F800000) && // Has an ORR
|
||||
!pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 25, 0x53000000, 0x7F800000)) { // Has no LSL
|
||||
pmap_enter_options_addr = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
}
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
|
||||
return pmap_enter_options_addr;
|
||||
}
|
||||
|
||||
static uint64_t xpf_sptm_find_pmap_remove_options(void)
|
||||
{
|
||||
// Find `pmap_remove_options` by xref "pmap_remove_options_internal"
|
||||
PFStringMetric *stringMetric = pfmetric_string_init("pmap_remove_options_internal");
|
||||
XPF_ASSERT(stringMetric);
|
||||
|
||||
__block uint64_t stringAddr = 0;
|
||||
pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){
|
||||
stringAddr = vmaddr;
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(stringMetric);
|
||||
XPF_ASSERT(stringAddr);
|
||||
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE);
|
||||
__block uint64_t pmap_remove_options = 0;
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
pmap_remove_options = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
printf("pmap_remove_options found? 0x%llx\n", pmap_remove_options);
|
||||
return pmap_remove_options;
|
||||
}
|
||||
|
||||
static uint64_t xpf_find_pmap_remove_options(void)
|
||||
{
|
||||
uint64_t pmap_remove_options_ppl = xpf_item_resolve("kernelSymbol.pmap_remove_options_ppl");
|
||||
XPF_ASSERT(pmap_remove_options_ppl);
|
||||
|
||||
__block uint64_t pmap_remove_options = 0;
|
||||
|
||||
PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_remove_options_ppl, XREF_TYPE_MASK_CALL);
|
||||
pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
if (pfsec_read32(gXPF.kernelTextSection, vmaddr - 4) != 0x52802003) {
|
||||
pmap_remove_options = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr);
|
||||
*stop = true;
|
||||
}
|
||||
});
|
||||
pfmetric_free(xrefMetric);
|
||||
|
||||
return pmap_remove_options;
|
||||
}
|
||||
|
||||
void xpf_ppl_init(void)
|
||||
{
|
||||
if (gXPF.kernelIsArm64e) {
|
||||
xpf_item_register("kernelSymbol.ppl_enter", xpf_find_ppl_enter, NULL);
|
||||
xpf_item_register("kernelSymbol.ppl_bootstrap_dispatch", xpf_find_ppl_bootstrap_dispatch, NULL);
|
||||
xpf_item_register("kernelSymbol.ppl_dispatch_section", xpf_find_ppl_dispatch_section, NULL);
|
||||
xpf_item_register("kernelSymbol.ppl_handler_table", xpf_find_ppl_handler_table, NULL);
|
||||
xpf_item_register("kernelSymbol.pmap_enter_options_internal", xpf_find_ppl_routine, (void *)(uint32_t)10);
|
||||
xpf_item_register("kernelSymbol.pmap_enter_options_ppl", xpf_find_ppl_dispatch_func, (void *)(uint32_t)10);
|
||||
xpf_item_register("kernelSymbol.pmap_remove_options_ppl", xpf_find_ppl_dispatch_func, (void *)(uint32_t)23);
|
||||
xpf_item_register("kernelSymbol.pmap_lookup_in_loaded_trust_caches_internal", xpf_find_ppl_routine, (void *)(uint32_t)41);
|
||||
xpf_item_register("kernelSymbol.pmap_pin_kernel_pages", xpf_find_pmap_pin_kernel_pages, NULL);
|
||||
xpf_item_register("kernelSymbol.pmap_enter_pv", xpf_find_pmap_enter_pv, NULL);
|
||||
|
||||
|
||||
if(gXPF.isSPTMDevice) {
|
||||
xpf_item_register("kernelSymbol.pmap_enter_options_addr", xpf_sptm_find_pmap_enter_options_addr, NULL);
|
||||
xpf_item_register("kernelSymbol.pmap_remove_options", xpf_sptm_find_pmap_remove_options, NULL);
|
||||
xpf_item_register("kernelSymbol.phys_attribute_clear_with_flush_range", xpf_find_phys_attribute_clear_with_flush_range, NULL);
|
||||
xpf_item_register("kernelSymbol.vm_first_phys", xpf_find_phys_attribute_clear_with_flush_range_reference, (void *)(uint32_t)0);
|
||||
xpf_item_register("kernelSymbol.vm_last_phys", xpf_find_phys_attribute_clear_with_flush_range_reference, (void *)(uint32_t)1);
|
||||
xpf_item_register("kernelSymbol.pp_attr_table", xpf_find_phys_attribute_clear_with_flush_range_reference, (void *)(uint32_t)2);
|
||||
xpf_item_register("kernelSymbol.pv_head_table", xpf_sptm_find_pv_head_table, NULL);
|
||||
}
|
||||
else {
|
||||
xpf_item_register("kernelSymbol.pmap_enter_options_addr", xpf_find_pmap_enter_options_addr, NULL);
|
||||
xpf_item_register("kernelSymbol.pmap_remove_options", xpf_find_pmap_remove_options, NULL);
|
||||
xpf_item_register("kernelSymbol.vm_first_phys", xpf_find_pmap_pin_kernel_pages_reference, (void *)(uint32_t)0);
|
||||
xpf_item_register("kernelSymbol.vm_last_phys", xpf_find_pmap_pin_kernel_pages_reference, (void *)(uint32_t)1);
|
||||
xpf_item_register("kernelSymbol.pp_attr_table", xpf_find_pmap_pin_kernel_pages_reference, (void *)(uint32_t)2);
|
||||
xpf_item_register("kernelSymbol.pv_head_table", xpf_find_pv_head_table, NULL);
|
||||
}
|
||||
|
||||
|
||||
if (strcmp(gXPF.darwinVersion, "22.0.0") >= 0) {
|
||||
// iOS >=16
|
||||
xpf_item_register("kernelSymbol.pmap_query_trust_cache_safe", xpf_find_pmap_query_trust_cache_safe, NULL);
|
||||
if(gXPF.isSPTMDevice) {
|
||||
xpf_item_register("kernelSymbol.load_static_trust_cache", xpf_find_load_static_trust_cache, NULL);
|
||||
xpf_item_register("kernelSymbol.trust_cache_rt", xpf_find_sptm_trust_cache_rt, NULL);
|
||||
}
|
||||
else {
|
||||
xpf_item_register("kernelSymbol.trust_cache_rt", xpf_find_ppl_trust_cache_rt, NULL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// iOS <=15
|
||||
xpf_item_register("kernelSymbol.pmap_image4_trust_caches", xpf_find_pmap_image4_trust_caches, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
void xpf_ppl_init(void);
|
||||
+630
@@ -0,0 +1,630 @@
|
||||
#include <choma/Fat.h>
|
||||
#include <choma/MachO.h>
|
||||
#include <choma/PatchFinder.h>
|
||||
#include <choma/MachOByteOrder.h>
|
||||
#include <choma/BufferedStream.h>
|
||||
#include <mach/machine.h>
|
||||
#include "xpf.h"
|
||||
#include "decompress.h"
|
||||
|
||||
#include "ppl.h"
|
||||
#include "non_ppl.h"
|
||||
#include "common.h"
|
||||
#include "bad_recovery.h"
|
||||
|
||||
bool xpf_supported_always(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// iOS 15 and above
|
||||
bool xpf_supported_15up(void)
|
||||
{
|
||||
return strcmp(gXPF.darwinVersion, "21.0.0") >= 0;
|
||||
}
|
||||
|
||||
// iOS 15 and below
|
||||
bool xpf_supported_15down(void)
|
||||
{
|
||||
return strcmp(gXPF.darwinVersion, "22.0.0") < 0;
|
||||
}
|
||||
|
||||
// iOS 16 and above
|
||||
bool xpf_supported_16up(void)
|
||||
{
|
||||
return strcmp(gXPF.darwinVersion, "22.0.0") >= 0;
|
||||
}
|
||||
|
||||
// iOS 16 and below
|
||||
bool xpf_supported_16down(void)
|
||||
{
|
||||
return strcmp(gXPF.darwinVersion, "23.0.0") < 0;
|
||||
}
|
||||
|
||||
// iOS 17 and above
|
||||
bool xpf_supported_17up(void)
|
||||
{
|
||||
return strcmp(gXPF.darwinVersion, "24.0.0") >= 0;
|
||||
}
|
||||
|
||||
// iOS 15.x and 16.x
|
||||
bool xpf_supported_1516(void)
|
||||
{
|
||||
return xpf_supported_15up() && xpf_supported_16down();
|
||||
}
|
||||
|
||||
bool xpf_supported_arm64(void)
|
||||
{
|
||||
return !gXPF.kernelIsArm64e;
|
||||
}
|
||||
|
||||
bool xpf_arm64_kcall_supported(void)
|
||||
{
|
||||
return xpf_supported_arm64() && xpf_supported_16down();
|
||||
}
|
||||
|
||||
bool xpf_trigon_supported(void)
|
||||
{
|
||||
return gXPF.kernelIsArm64e && xpf_supported_15up() && !gXPF.isSPTMDevice;
|
||||
}
|
||||
|
||||
XPFSet gBaseSet = {
|
||||
.name="base",
|
||||
.supported=xpf_supported_always,
|
||||
.metrics={
|
||||
"kernelConstant.kernel_el",
|
||||
"kernelSymbol.allproc",
|
||||
"kernelSymbol.kalloc_data_external",
|
||||
"kernelSymbol.kfree_data_external",
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet gTranslationSet = {
|
||||
.name="translation",
|
||||
.supported=xpf_supported_always,
|
||||
.metrics={
|
||||
"kernelSymbol.cpu_ttep",
|
||||
"kernelSymbol.gVirtBase",
|
||||
"kernelSymbol.gPhysBase",
|
||||
"kernelSymbol.gPhysSize",
|
||||
"kernelSymbol.ptov_table",
|
||||
"kernelConstant.pointer_mask",
|
||||
"kernelConstant.T1SZ_BOOT",
|
||||
"kernelConstant.ARM_TT_L1_INDEX_MASK",
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet gPhysmapSet = {
|
||||
.name="physmap",
|
||||
.supported=xpf_supported_always,
|
||||
.metrics={
|
||||
"kernelSymbol.vm_page_array_beginning_addr",
|
||||
"kernelSymbol.vm_page_array_ending_addr",
|
||||
"kernelSymbol.vm_first_phys_ppnum",
|
||||
"kernelSymbol.vm_first_phys",
|
||||
"kernelSymbol.vm_last_phys",
|
||||
"kernelSymbol.pp_attr_table",
|
||||
"kernelSymbol.pv_head_table",
|
||||
"kernelSymbol.ptov_table",
|
||||
"kernelConstant.PT_INDEX_MAX",
|
||||
"kernelSymbol.pmap_enter_options_addr",
|
||||
"kernelSymbol.pmap_remove_options",
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet gStructSet = {
|
||||
.name="struct",
|
||||
.supported=xpf_supported_always,
|
||||
.metrics={
|
||||
"kernelStruct.proc.struct_size",
|
||||
"kernelStruct.task.itk_space",
|
||||
"kernelStruct.vm_map.pmap",
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet gTrustcache15Set = {
|
||||
.name="trustcache",
|
||||
.supported=xpf_supported_15down,
|
||||
{
|
||||
"kernelSymbol.pmap_image4_trust_caches",
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet gTrustcache16Set = {
|
||||
.name="trustcache",
|
||||
xpf_supported_16up,
|
||||
.metrics={
|
||||
"kernelSymbol.trust_cache_rt",
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet gBadRecoverySet = {
|
||||
.name="badRecovery",
|
||||
.supported=xpf_bad_recovery_supported,
|
||||
.metrics={
|
||||
"kernelSymbol.hw_lck_ticket_reserve_orig_allow_invalid",
|
||||
"kernelGadget.hw_lck_ticket_reserve_orig_allow_invalid_signed",
|
||||
"kernelGadget.br_x22",
|
||||
"kernelSymbol.exception_return",
|
||||
"kernelGadget.exception_return_after_check",
|
||||
"kernelGadget.exception_return_after_check_no_restore",
|
||||
"kernelGadget.ldp_x0_x1_x8",
|
||||
"kernelGadget.str_x8_x9",
|
||||
"kernelGadget.str_x0_x19_ldr_x20",
|
||||
"kernelGadget.pacda",
|
||||
"kernelSymbol.ml_sign_thread_state",
|
||||
"kernelStruct.thread.recover",
|
||||
"kernelStruct.thread.machine_kstackptr",
|
||||
"kernelStruct.thread.machine_CpuDatap",
|
||||
"kernelStruct.thread.machine_contextData",
|
||||
"kernelSymbol.mac_label_set",
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet gSandboxSet = {
|
||||
.name="sandbox",
|
||||
.supported=xpf_supported_15up,
|
||||
.metrics={
|
||||
"kernelConstant.nsysent",
|
||||
"kernelConstant.mach_trap_count",
|
||||
"kernelSymbol.mach_kobj_count",
|
||||
NULL,
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet gPhysRWSet = {
|
||||
.name="physrw",
|
||||
.supported=xpf_supported_always,
|
||||
.metrics={
|
||||
NULL,
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet gPerfKRWSet = {
|
||||
.name="perfkrw",
|
||||
.supported=xpf_supported_1516,
|
||||
.metrics={
|
||||
"kernelSymbol.perfmon_dev_open",
|
||||
"kernelSymbol.perfmon_devices",
|
||||
"kernelSymbol.vn_kqfilter",
|
||||
"kernelSymbol.cdevsw",
|
||||
NULL
|
||||
},
|
||||
};
|
||||
|
||||
XPFSet gDevModeSet = {
|
||||
.name="devmode",
|
||||
.supported=xpf_supported_16up,
|
||||
.metrics={
|
||||
"kernelSymbol.developer_mode_enabled",
|
||||
NULL
|
||||
},
|
||||
};
|
||||
|
||||
XPFSet gArm64KcallSet = {
|
||||
.name="arm64kcall",
|
||||
.supported=xpf_arm64_kcall_supported,
|
||||
.metrics={
|
||||
"kernelSymbol.exception_return",
|
||||
"kernelGadget.kcall_return",
|
||||
"kernelGadget.str_x8_x0",
|
||||
"kernelStruct.thread.machine_CpuDatap",
|
||||
"kernelStruct.thread.machine_kstackptr",
|
||||
"kernelStruct.thread.machine_contextData",
|
||||
NULL
|
||||
},
|
||||
};
|
||||
|
||||
XPFSet gTrigonSet = {
|
||||
.name="trigon",
|
||||
.supported=xpf_trigon_supported,
|
||||
.metrics={
|
||||
"kernelSymbol.iorvbar",
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
XPFSet *gSets[] = {
|
||||
&gBaseSet,
|
||||
&gTranslationSet,
|
||||
&gSandboxSet,
|
||||
&gPhysmapSet,
|
||||
&gStructSet,
|
||||
&gTrustcache15Set,
|
||||
&gTrustcache16Set,
|
||||
&gBadRecoverySet,
|
||||
&gPhysRWSet,
|
||||
&gPerfKRWSet,
|
||||
&gDevModeSet,
|
||||
&gArm64KcallSet,
|
||||
&gTrigonSet,
|
||||
};
|
||||
|
||||
XPF gXPF = { 0 };
|
||||
|
||||
PFSection *xpf_pfsec_init(const char *filesetEntryId, const char *segName, const char *sectName)
|
||||
{
|
||||
PFSection *section = pfsec_init_from_macho(gXPF.kernel, gXPF.kernelIsFileset ? filesetEntryId : NULL, segName, sectName);
|
||||
if (section) {
|
||||
pfsec_set_cached(section, true);
|
||||
pfsec_set_pointer_decoder(section, xpfsec_decode_pointer);
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
int xpf_start_with_kernel_path(const char *kernelPath)
|
||||
{
|
||||
gXPF.kernelFd = open(kernelPath, O_RDONLY);
|
||||
if (gXPF.kernelFd < 0) {
|
||||
xpf_set_error("Failed to open kernelcache");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct stat s;
|
||||
fstat(gXPF.kernelFd, &s);
|
||||
gXPF.kernelSize = s.st_size;
|
||||
gXPF.mappedKernel = mmap(NULL, gXPF.kernelSize, PROT_READ, MAP_PRIVATE, gXPF.kernelFd, 0);
|
||||
if (gXPF.mappedKernel == MAP_FAILED) {
|
||||
xpf_set_error("Failed to map kernelcache");
|
||||
return -1;
|
||||
}
|
||||
|
||||
MemoryStream *stream = NULL;
|
||||
if (LITTLE_TO_HOST(*(uint32_t *)(gXPF.mappedKernel)) == MH_MAGIC_64
|
||||
|| (*(uint32_t *)(gXPF.mappedKernel)) == FAT_CIGAM) {
|
||||
stream = buffered_stream_init_from_buffer_nocopy(gXPF.mappedKernel, gXPF.kernelSize, 0);
|
||||
}
|
||||
else {
|
||||
gXPF.decompressedKernel = kdecompress(gXPF.mappedKernel, gXPF.kernelSize, &gXPF.decompressedKernelSize);
|
||||
if (gXPF.decompressedKernel) {
|
||||
stream = buffered_stream_init_from_buffer_nocopy(gXPF.decompressedKernel, gXPF.decompressedKernelSize, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Fat *candidate = fat_init_from_memory_stream(stream);
|
||||
if (!candidate) {
|
||||
xpf_set_error("Failed to load kernel macho");
|
||||
return -1;
|
||||
}
|
||||
|
||||
MachO *machoCandidate = NULL;
|
||||
gXPF.kernelIsArm64e = false;
|
||||
machoCandidate = fat_find_slice(candidate, CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL);
|
||||
if (!machoCandidate) {
|
||||
gXPF.kernelIsArm64e = true;
|
||||
machoCandidate = fat_find_slice(candidate, CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64E);
|
||||
if (!machoCandidate) {
|
||||
machoCandidate = fat_find_slice(candidate, CPU_TYPE_ARM64, 0xC0000002);
|
||||
}
|
||||
}
|
||||
if (!machoCandidate) {
|
||||
xpf_set_error("Failed to load kernel macho");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// check if SPTM devices or not
|
||||
|
||||
gXPF.kernelContainer = candidate;
|
||||
gXPF.kernel = machoCandidate;
|
||||
gXPF.kernelIsFileset = macho_get_filetype(gXPF.kernel) == MH_FILESET;
|
||||
|
||||
gXPF.kernelTextSection = xpf_pfsec_init("com.apple.kernel", "__TEXT_EXEC", "__text");
|
||||
gXPF.kernelPinstSection = xpf_pfsec_init("com.apple.kernel", "__LAST", "__pinst");
|
||||
|
||||
// check if SPTM devices or not
|
||||
gXPF.kernelPPLTextSection = xpf_pfsec_init("com.apple.kernel", "__PPLTEXT", "__text");
|
||||
if(gXPF.kernelIsArm64e == true && gXPF.kernelPPLTextSection == NULL)
|
||||
gXPF.isSPTMDevice = true;
|
||||
|
||||
gXPF.kernelStringSection = xpf_pfsec_init("com.apple.kernel", "__TEXT", "__cstring");
|
||||
gXPF.kernelConstSection = xpf_pfsec_init("com.apple.kernel", "__TEXT", "__const");
|
||||
gXPF.kernelDataConstSection = xpf_pfsec_init("com.apple.kernel", "__DATA_CONST", "__const");
|
||||
gXPF.kernelDataSection = xpf_pfsec_init("com.apple.kernel", "__DATA", "__data");
|
||||
gXPF.kernelOSLogSection = xpf_pfsec_init("com.apple.kernel", "__TEXT", "__os_log");
|
||||
gXPF.kernelBootdataInit = xpf_pfsec_init("com.apple.kernel", "__BOOTDATA", "__init");
|
||||
|
||||
if (gXPF.kernelIsFileset) {
|
||||
gXPF.kernelAMFITextSection = xpf_pfsec_init("com.apple.driver.AppleMobileFileIntegrity", "__TEXT_EXEC", "__text");
|
||||
gXPF.kernelAMFIStringSection = xpf_pfsec_init("com.apple.driver.AppleMobileFileIntegrity", "__TEXT", "__cstring");
|
||||
gXPF.kernelSandboxTextSection = xpf_pfsec_init("com.apple.security.sandbox", "__TEXT_EXEC", "__text");
|
||||
gXPF.kernelSandboxStringSection = xpf_pfsec_init("com.apple.security.sandbox", "__TEXT", "__cstring");
|
||||
gXPF.kernelInfoPlistSection = xpf_pfsec_init("com.apple.security.AppleImage4", "__TEXT", "__info_plist");
|
||||
}
|
||||
else {
|
||||
gXPF.kernelPrelinkTextSection = xpf_pfsec_init(NULL, "__PRELINK_TEXT", "__text");
|
||||
gXPF.kernelPLKTextSection = xpf_pfsec_init(NULL, "__PLK_TEXT_EXEC", "__text");
|
||||
gXPF.kernelKmodInfoSection = xpf_pfsec_init(NULL, "__PRELINK_INFO", "__kmod_info");
|
||||
gXPF.kernelPrelinkInfoSection = xpf_pfsec_init(NULL, "__PRELINK_INFO", "__info");
|
||||
}
|
||||
|
||||
gXPF.kernelBase = macho_get_base_address(gXPF.kernel);
|
||||
gXPF.kernelEntry = 0;
|
||||
macho_enumerate_load_commands(gXPF.kernel, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
|
||||
if (loadCommand.cmd == LC_UNIXTHREAD) {
|
||||
uint8_t *cmdData = ((uint8_t *)cmd + sizeof(struct thread_command));
|
||||
uint8_t *cmdDataEnd = ((uint8_t *)cmd + loadCommand.cmdsize);
|
||||
|
||||
while (cmdData < cmdDataEnd) {
|
||||
uint32_t flavor = LITTLE_TO_HOST(*(uint32_t *)cmdData);
|
||||
uint32_t count = LITTLE_TO_HOST(*(uint32_t *)(cmdData + 4));
|
||||
if (flavor == ARM_THREAD_STATE64) {
|
||||
arm_thread_state64_t *threadState = (arm_thread_state64_t *)(cmdData + 8);
|
||||
#ifdef __arm64e__
|
||||
gXPF.kernelEntry = LITTLE_TO_HOST((uint64_t)threadState->__opaque_pc);
|
||||
#else
|
||||
gXPF.kernelEntry = LITTLE_TO_HOST((uint64_t)threadState->__pc);
|
||||
#endif
|
||||
}
|
||||
cmdData += (8 + count);
|
||||
}
|
||||
*stop = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (gXPF.kernelBase == UINT64_MAX) {
|
||||
xpf_set_error("Failed to find kernel base");
|
||||
return -1;
|
||||
}
|
||||
if (!gXPF.kernelEntry) {
|
||||
xpf_set_error("Failed to find kernel entry point");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *versionSearchString = "Darwin Kernel Version ";
|
||||
PFPatternMetric *versionMetric = pfmetric_pattern_init((void *)versionSearchString, NULL, strlen(versionSearchString), sizeof(uint8_t));
|
||||
pfmetric_run(gXPF.kernelConstSection, versionMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
int r = pfsec_read_string(gXPF.kernelConstSection, vmaddr, &gXPF.kernelVersionString);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(versionMetric);
|
||||
|
||||
if (gXPF.kernelInfoPlistSection) {
|
||||
const char *infoPlistString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
|
||||
PFPatternMetric *infoPlistMetric = pfmetric_pattern_init((void *)infoPlistString, NULL, strlen(infoPlistString), sizeof(uint8_t));
|
||||
pfmetric_run(gXPF.kernelInfoPlistSection, infoPlistMetric, ^(uint64_t vmaddr, bool *stop) {
|
||||
int r = pfsec_read_string(gXPF.kernelInfoPlistSection, vmaddr, &gXPF.kernelInfoPlist);
|
||||
*stop = true;
|
||||
});
|
||||
pfmetric_free(infoPlistMetric);
|
||||
}
|
||||
|
||||
if (!gXPF.kernelVersionString) {
|
||||
xpf_set_error("Failed to find kernel version");
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
char darwinVersion[100];
|
||||
char xnuBuild[100];
|
||||
char xnuPlatform[100];
|
||||
sscanf(gXPF.kernelVersionString, "Darwin Kernel Version %[^:]: %*[^;]; root:xnu-%[^/]/%s", darwinVersion, xnuBuild, xnuPlatform);
|
||||
gXPF.darwinVersion = strdup(darwinVersion);
|
||||
gXPF.xnuBuild = strdup(xnuBuild);
|
||||
gXPF.xnuPlatform = strdup(xnuPlatform);
|
||||
|
||||
if (gXPF.kernelInfoPlistSection) {
|
||||
char osVersion[100];
|
||||
const char *DTPlatformVersion = strstr(gXPF.kernelInfoPlist, "<key>DTPlatformVersion</key>");
|
||||
sscanf(DTPlatformVersion, "<key>DTPlatformVersion</key>\t<string>%9[^<]</string>", osVersion);
|
||||
gXPF.osVersion = strdup(osVersion);
|
||||
}
|
||||
}
|
||||
|
||||
xpf_ppl_init();
|
||||
xpf_non_ppl_init();
|
||||
xpf_common_init();
|
||||
xpf_bad_recovery_init();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void xpf_item_register(const char *name, void *finder, void *ctx)
|
||||
{
|
||||
XPFItem *newItem = malloc(sizeof(XPFItem));
|
||||
memset(newItem, 0x0, sizeof(XPFItem));
|
||||
newItem->name = name;
|
||||
newItem->ctx = ctx;
|
||||
newItem->finder = finder;
|
||||
newItem->cache = 0;
|
||||
newItem->cached = false;
|
||||
|
||||
XPFItem *lastItem = NULL;
|
||||
XPFItem *item = gXPF.firstItem;
|
||||
while (item) {
|
||||
lastItem = item;
|
||||
item = item->nextItem;
|
||||
}
|
||||
if (!lastItem) {
|
||||
gXPF.firstItem = newItem;
|
||||
}
|
||||
else {
|
||||
lastItem->nextItem = newItem;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t xpf_item_resolve(const char *name)
|
||||
{
|
||||
XPFItem *item = gXPF.firstItem;
|
||||
while (item) {
|
||||
if (!strcmp(item->name, name)) {
|
||||
if (!item->cached) {
|
||||
item->cache = item->finder(item->ctx);
|
||||
item->cached = true;
|
||||
}
|
||||
return item->cache;
|
||||
}
|
||||
item = item->nextItem;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void xpf_print_all_items(void)
|
||||
{
|
||||
XPFItem *item = gXPF.firstItem;
|
||||
while (item) {
|
||||
printf("0x%016llx <- %s\n", xpf_item_resolve(item->name), item->name);
|
||||
item = item->nextItem;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t xpfsec_decode_pointer(PFSection *section, uint64_t vmaddr, uint64_t value)
|
||||
{
|
||||
if ((value & 0xffff000000000000) != 0xffff000000000000) {
|
||||
// Chained fixups, other stuff
|
||||
value &= 0x00000000ffffffff;
|
||||
value += gXPF.kernelBase;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
XPFSet *xpf_find_set(const char *name, bool *foundUnsupported)
|
||||
{
|
||||
uint32_t setCount = (sizeof(gSets)/sizeof(XPFSet*));
|
||||
for (int i = 0; i < setCount; i++) {
|
||||
if (!strcmp(gSets[i]->name, name)) {
|
||||
if (!gSets[i]->supported()) {
|
||||
if (foundUnsupported) *foundUnsupported = true;
|
||||
continue;
|
||||
}
|
||||
return gSets[i];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool xpf_set_is_supported(const char *name)
|
||||
{
|
||||
XPFSet *set = xpf_find_set(name, NULL);
|
||||
return set != NULL;
|
||||
}
|
||||
|
||||
int xpf_offset_dictionary_add_set(xpc_object_t xdict, XPFSet *set)
|
||||
{
|
||||
if (!set->supported()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; set->metrics[i]; i++) {
|
||||
uint64_t resolved = xpf_item_resolve(set->metrics[i]);
|
||||
if (resolved) {
|
||||
xpc_dictionary_set_uint64(xdict, set->metrics[i], resolved);
|
||||
}
|
||||
else {
|
||||
const char *existingError = xpf_get_error();
|
||||
if (existingError) {
|
||||
xpf_set_error("Set \"%s\" failed on \"%s\" (%s)", set->name, set->metrics[i], existingError);
|
||||
}
|
||||
else {
|
||||
xpf_set_error("Set \"%s\" failed on \"%s\"", set->name, set->metrics[i]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
xpc_object_t xpf_construct_offset_dictionary(const char *sets[])
|
||||
{
|
||||
xpc_object_t offsetDictionary = xpc_dictionary_create_empty();
|
||||
|
||||
if (xpf_offset_dictionary_add_set(offsetDictionary, &gBaseSet) != 0) return NULL;
|
||||
|
||||
uint32_t setCount = (sizeof(gSets)/sizeof(XPFSet*));
|
||||
|
||||
for (int i = 0; sets[i]; i++) {
|
||||
bool foundUnsupported = false;
|
||||
XPFSet *set = xpf_find_set(sets[i], &foundUnsupported);
|
||||
if (set) {
|
||||
int r = xpf_offset_dictionary_add_set(offsetDictionary, set);
|
||||
if (r != 0) {
|
||||
xpc_release(offsetDictionary);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if (foundUnsupported) {
|
||||
xpf_set_error("Set \"%s\" is unsupported by this device", sets[i]);
|
||||
xpc_release(offsetDictionary);
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
xpf_set_error("Failed to find set \"%s\"", sets[i]);
|
||||
xpc_release(offsetDictionary);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return offsetDictionary;
|
||||
}
|
||||
|
||||
static char *gXPFError;
|
||||
|
||||
void xpf_set_error(const char *error, ...)
|
||||
{
|
||||
char *newError = NULL;
|
||||
va_list va;
|
||||
va_start(va, error);
|
||||
vasprintf(&newError, error, va);
|
||||
va_end(va);
|
||||
|
||||
if (gXPFError) {
|
||||
free(gXPFError);
|
||||
}
|
||||
gXPFError = newError;
|
||||
}
|
||||
|
||||
const char *xpf_get_error(void)
|
||||
{
|
||||
return gXPFError;
|
||||
}
|
||||
|
||||
void xpf_stop(void)
|
||||
{
|
||||
if (gXPF.mappedKernel) {
|
||||
munmap(gXPF.mappedKernel, gXPF.kernelSize);
|
||||
}
|
||||
if (gXPF.decompressedKernel) {
|
||||
free(gXPF.decompressedKernel);
|
||||
}
|
||||
if (gXPF.kernelFd >= 0) {
|
||||
close(gXPF.kernelFd);
|
||||
}
|
||||
|
||||
if (gXPF.kernelTextSection) pfsec_free(gXPF.kernelTextSection);
|
||||
if (gXPF.kernelPPLTextSection) pfsec_free(gXPF.kernelPPLTextSection);
|
||||
if (gXPF.kernelStringSection) pfsec_free(gXPF.kernelStringSection);
|
||||
if (gXPF.kernelConstSection) pfsec_free(gXPF.kernelConstSection);
|
||||
if (gXPF.kernelDataConstSection) pfsec_free(gXPF.kernelDataConstSection);
|
||||
if (gXPF.kernelDataSection) pfsec_free(gXPF.kernelDataSection);
|
||||
if (gXPF.kernelOSLogSection) pfsec_free(gXPF.kernelOSLogSection);
|
||||
if (gXPF.kernelAMFITextSection) pfsec_free(gXPF.kernelAMFITextSection);
|
||||
if (gXPF.kernelAMFIStringSection) pfsec_free(gXPF.kernelAMFIStringSection);
|
||||
if (gXPF.kernelSandboxTextSection) pfsec_free(gXPF.kernelSandboxTextSection);
|
||||
if (gXPF.kernelSandboxStringSection) pfsec_free(gXPF.kernelSandboxStringSection);
|
||||
if (gXPF.kernelPrelinkTextSection) pfsec_free(gXPF.kernelPrelinkTextSection);
|
||||
if (gXPF.kernelBootdataInit) pfsec_free(gXPF.kernelBootdataInit);
|
||||
if (gXPF.kernelPLKTextSection) pfsec_free(gXPF.kernelPLKTextSection);
|
||||
if (gXPF.kernelInfoPlistSection) pfsec_free(gXPF.kernelInfoPlistSection);
|
||||
if (gXPF.kernelContainer) fat_free(gXPF.kernelContainer);
|
||||
|
||||
if (gXPF.kernelVersionString) free(gXPF.kernelVersionString);
|
||||
if (gXPF.darwinVersion) free(gXPF.darwinVersion);
|
||||
if (gXPF.xnuBuild) free(gXPF.xnuBuild);
|
||||
if (gXPF.xnuPlatform) free(gXPF.xnuPlatform);
|
||||
if (gXPF.osVersion) free(gXPF.osVersion);
|
||||
if (gXPF.kernelInfoPlist) free(gXPF.kernelInfoPlist);
|
||||
|
||||
XPFItem *item = gXPF.firstItem;
|
||||
while (item) {
|
||||
XPFItem *curItem = item;
|
||||
item = item->nextItem;
|
||||
free(curItem);
|
||||
}
|
||||
|
||||
gXPF = (struct s_XPF){ 0 };
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <choma/Fat.h>
|
||||
#include <choma/Util.h>
|
||||
#include <choma/PatchFinder.h>
|
||||
#include <choma/PatchFinder_arm64.h>
|
||||
#include <choma/arm64.h>
|
||||
#include <xpc/xpc.h>
|
||||
|
||||
typedef struct s_XPFItem {
|
||||
struct s_XPFItem *nextItem;
|
||||
const char *name;
|
||||
uint64_t (*finder)(void *);
|
||||
void *ctx;
|
||||
bool cached;
|
||||
uint64_t cache;
|
||||
} XPFItem;
|
||||
|
||||
typedef struct s_XPFSet {
|
||||
const char *name;
|
||||
bool (*supported)(void);
|
||||
const char *metrics[];
|
||||
} XPFSet;
|
||||
|
||||
#define XPF_ASSERT(assert) if (!(assert)) { if (!xpf_get_error()) { xpf_set_error("[%s:%d] Failed assert in %s: %s", __FILE__, __LINE__, __FUNCTION__, #assert); } return 0; }
|
||||
|
||||
int xpf_start_with_kernel_path(const char *kernelPath);
|
||||
void xpf_item_register(const char *name, void *finder, void *ctx);
|
||||
uint64_t xpf_item_resolve(const char *name);
|
||||
uint64_t xpfsec_decode_pointer(PFSection *section, uint64_t vmaddr, uint64_t value);
|
||||
bool xpf_set_is_supported(const char *name);
|
||||
int xpf_offset_dictionary_add_set(xpc_object_t xdict, XPFSet *set);
|
||||
xpc_object_t xpf_construct_offset_dictionary(const char *sets[]);
|
||||
void xpf_set_error(const char *error, ...);
|
||||
const char *xpf_get_error(void);
|
||||
void xpf_print_all_items(void);
|
||||
void xpf_stop(void);
|
||||
|
||||
typedef struct s_XPF {
|
||||
int kernelFd;
|
||||
void *mappedKernel;
|
||||
size_t kernelSize;
|
||||
void *decompressedKernel;
|
||||
size_t decompressedKernelSize;
|
||||
|
||||
Fat *kernelContainer;
|
||||
MachO *kernel;
|
||||
bool kernelIsFileset;
|
||||
bool kernelIsArm64e;
|
||||
bool isSPTMDevice;
|
||||
char *kernelVersionString;
|
||||
char *kernelInfoPlist;
|
||||
char *darwinVersion;
|
||||
char *xnuBuild;
|
||||
char *xnuPlatform;
|
||||
char *osVersion;
|
||||
|
||||
uint64_t kernelBase;
|
||||
uint64_t kernelEntry;
|
||||
|
||||
PFSection *kernelTextSection;
|
||||
PFSection *kernelPinstSection;
|
||||
PFSection *kernelPPLTextSection;
|
||||
PFSection *kernelStringSection;
|
||||
PFSection *kernelConstSection;
|
||||
PFSection *kernelDataConstSection;
|
||||
PFSection *kernelDataSection;
|
||||
PFSection *kernelOSLogSection;
|
||||
PFSection *kernelPrelinkTextSection;
|
||||
PFSection *kernelPLKTextSection;
|
||||
PFSection *kernelKmodInfoSection;
|
||||
PFSection *kernelPrelinkInfoSection;
|
||||
PFSection *kernelBootdataInit;
|
||||
PFSection *kernelAMFITextSection;
|
||||
PFSection *kernelAMFIStringSection;
|
||||
PFSection *kernelSandboxTextSection;
|
||||
PFSection *kernelSandboxStringSection;
|
||||
PFSection *kernelInfoPlistSection;
|
||||
|
||||
XPFItem *firstItem;
|
||||
} XPF;
|
||||
extern XPF gXPF;
|
||||
Reference in new issue
Block a user