blob: c04c4fe5ccb407b3470936658ec995b87b2c3145 [file] [log] [blame]
// -*- mode: ObjC -*-
// This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files.
// Copyright (C) 1997-2019 Steve Nygard.
#import "CDLCSegment.h"
#import "CDMachOFile.h"
#import "CDSection.h"
#include <CommonCrypto/CommonCrypto.h>
#include "blowfish.h"
// Decrypt PAGE_SIZE (4096) bytes
static void BF_Decrypt_Block(BLOWFISH_CTX *ctx, const uint8_t *ptr, uint8_t *dest)
{
uint32_t left, right;
uint32_t src_offset = 0, dest_offset = 0;
uint32_t previous_left = 0, previous_right = 0;
for (NSUInteger index = 0; index < PAGE_SIZE / 8; index++) {
left = OSReadBigInt32(ptr, src_offset); src_offset += sizeof(uint32_t);
right = OSReadBigInt32(ptr, src_offset); src_offset += sizeof(uint32_t);
uint32_t left2 = left;
uint32_t right2 = right;
Blowfish_Decrypt(ctx, &left2, &right2);
left2 ^= previous_left;
right2 ^= previous_right;
previous_left = left;
previous_right = right;
OSWriteBigInt32(dest, dest_offset, left2); dest_offset += sizeof(uint32_t);
OSWriteBigInt32(dest, dest_offset, right2); dest_offset += sizeof(uint32_t);
}
}
NSString *CDSegmentEncryptionTypeName(CDSegmentEncryptionType type)
{
switch (type) {
case CDSegmentEncryptionType_None: return @"None";
case CDSegmentEncryptionType_AES: return @"Protected Segment Type 1 (prior to 10.6)";
case CDSegmentEncryptionType_Blowfish: return @"Protected Segment Type 2 (10.6)";
case CDSegmentEncryptionType_Unknown: return @"Unknown";
}
}
@implementation CDLCSegment
{
struct segment_command_64 _segmentCommand; // 64-bit, also holding 32-bit
NSString *_name;
NSArray *_sections;
NSMutableData *_decryptedData;
}
- (id)initWithDataCursor:(CDMachOFileDataCursor *)cursor;
{
if ((self = [super initWithDataCursor:cursor])) {
_segmentCommand.cmd = [cursor readInt32];
_segmentCommand.cmdsize = [cursor readInt32];
_name = [cursor readStringOfLength:16 encoding:NSASCIIStringEncoding];
size_t nameLength = [_name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
memcpy(_segmentCommand.segname, [_name UTF8String], MIN(sizeof(_segmentCommand.segname), nameLength));
_segmentCommand.vmaddr = [cursor readPtr];
_segmentCommand.vmsize = [cursor readPtr];
_segmentCommand.fileoff = [cursor readPtr];
_segmentCommand.filesize = [cursor readPtr];
_segmentCommand.maxprot = [cursor readInt32];
_segmentCommand.initprot = [cursor readInt32];
_segmentCommand.nsects = [cursor readInt32];
_segmentCommand.flags = [cursor readInt32];
NSMutableArray *sections = [[NSMutableArray alloc] init];
for (NSUInteger index = 0; index < _segmentCommand.nsects; index++) {
CDSection *section = [[CDSection alloc] initWithDataCursor:cursor segment:self];
[sections addObject:section];
}
_sections = [sections copy];
}
return self;
}
#pragma mark - Debugging
- (NSString *)extraDescription;
{
int padding = (int)self.machOFile.ptrSize * 2;
return [NSString stringWithFormat:@"vmaddr: 0x%0*llx - 0x%0*llx [0x%0*llx], offset: %lld, flags: 0x%x (%@), nsects: %d, sections: %@",
padding, _segmentCommand.vmaddr, padding, _segmentCommand.vmaddr + _segmentCommand.vmsize - 1, padding, _segmentCommand.vmsize,
_segmentCommand.fileoff, self.flags, [self flagDescription], _segmentCommand.nsects, self.sections.count > 0 ? self.sections : @"N/A"];
}
#pragma mark -
- (uint32_t)cmd;
{
return _segmentCommand.cmd;
}
- (uint32_t)cmdsize;
{
return _segmentCommand.cmdsize;
}
- (NSUInteger)vmaddr;
{
return _segmentCommand.vmaddr;
}
- (NSUInteger)fileoff;
{
return _segmentCommand.fileoff;
}
- (NSUInteger)filesize;
{
return _segmentCommand.filesize;
}
- (vm_prot_t)initprot;
{
return _segmentCommand.initprot;
}
- (uint32_t)flags;
{
return _segmentCommand.flags;
}
- (BOOL)isProtected;
{
return (self.flags & SG_PROTECTED_VERSION_1) == SG_PROTECTED_VERSION_1;
}
- (CDSegmentEncryptionType)encryptionType;
{
//NSLog(@"%s, isProtected? %u, filesize: %lu, fileoff: %lu", __cmd, [self isProtected], [self filesize], [self fileoff]);
if (self.isProtected) {
if (self.filesize <= 3 * PAGE_SIZE) {
// First three pages aren't encrypted, so we can't tell. Let's pretent it's something we can decrypt.
return CDSegmentEncryptionType_AES;
} else {
const void *src = (uint8_t *)[self.machOFile.data bytes] + self.fileoff + 3 * PAGE_SIZE;
uint32_t magic = OSReadLittleInt32(src, 0);
//NSLog(@"%s, magic= 0x%08x", __cmd, magic);
switch (magic) {
case CDSegmentProtectedMagic_None: return CDSegmentEncryptionType_None;
case CDSegmentProtectedMagic_AES: return CDSegmentEncryptionType_AES;
case CDSegmentProtectedMagic_Blowfish: return CDSegmentEncryptionType_Blowfish;
}
return CDSegmentEncryptionType_Unknown;
}
}
return CDSegmentEncryptionType_None;
}
- (BOOL)canDecrypt;
{
CDSegmentEncryptionType encryptionType = self.encryptionType;
return (encryptionType == CDSegmentEncryptionType_None)
|| (encryptionType == CDSegmentEncryptionType_AES)
|| (encryptionType == CDSegmentEncryptionType_Blowfish);
}
- (NSString *)flagDescription;
{
NSMutableArray *setFlags = [NSMutableArray array];
uint32_t flags = self.flags;
if (flags & SG_HIGHVM) [setFlags addObject:@"HIGHVM"];
if (flags & SG_FVMLIB) [setFlags addObject:@"FVMLIB"];
if (flags & SG_NORELOC) [setFlags addObject:@"NORELOC"];
if (flags & SG_PROTECTED_VERSION_1) [setFlags addObject:@"PROTECTED_VERSION_1"];
if ([setFlags count] == 0)
return @"none";
return [setFlags componentsJoinedByString:@" "];
}
- (BOOL)containsAddress:(NSUInteger)address;
{
return (address >= _segmentCommand.vmaddr) && (address < _segmentCommand.vmaddr + _segmentCommand.vmsize);
}
- (CDSection *)sectionContainingAddress:(NSUInteger)address;
{
for (CDSection *section in self.sections) {
if ([section containsAddress:address])
return section;
}
return nil;
}
- (CDSection *)sectionWithName:(NSString *)name;
{
for (CDSection *section in self.sections) {
if ([[section sectionName] isEqual:name])
return section;
}
return nil;
}
- (NSUInteger)fileOffsetForAddress:(NSUInteger)address;
{
return [[self sectionContainingAddress:address] fileOffsetForAddress:address];
}
- (NSUInteger)segmentOffsetForAddress:(NSUInteger)address;
{
return [self fileOffsetForAddress:address] - self.fileoff;
}
- (void)appendToString:(NSMutableString *)resultString verbose:(BOOL)isVerbose;
{
[super appendToString:resultString verbose:isVerbose];
#if 0
int padding = (int)self.machOFile.ptrSize * 2;
[resultString appendFormat:@" segname %@\n", self.name];
[resultString appendFormat:@" vmaddr 0x%0*llx\n", padding, _segmentCommand.vmaddr];
[resultString appendFormat:@" vmsize 0x%0*llx\n", padding, _segmentCommand.vmsize];
[resultString appendFormat:@" fileoff %lld\n", _segmentCommand.fileoff];
[resultString appendFormat:@" filesize %lld\n", _segmentCommand.filesize];
[resultString appendFormat:@" maxprot 0x%08x\n", _segmentCommand.maxprot];
[resultString appendFormat:@" initprot 0x%08x\n", _segmentCommand.initprot];
[resultString appendFormat:@" nsects %d\n", _segmentCommand.nsects];
if (isVerbose)
[resultString appendFormat:@" flags %@\n", [self flagDescription]];
else
[resultString appendFormat:@" flags 0x%x\n", _segmentCommand.flags];
#endif
}
- (void)writeSectionData;
{
[self.sections enumerateObjectsUsingBlock:^(CDSection *section, NSUInteger index, BOOL *stop){
[[section data] writeToFile:[NSString stringWithFormat:@"/tmp/%02ld-%@", index, section.sectionName] atomically:NO];
}];
}
- (NSData *)decryptedData;
{
if (self.isProtected == NO)
return nil;
if (_decryptedData == nil) {
//NSLog(@"filesize: %08x, pagesize: %04x", [self filesize], PAGE_SIZE);
NSParameterAssert((self.filesize % PAGE_SIZE) == 0);
_decryptedData = [[NSMutableData alloc] initWithLength:self.filesize];
const uint8_t *src = (uint8_t *)[self.machOFile.data bytes] + self.fileoff;
uint8_t *dest = [_decryptedData mutableBytes];
if (self.filesize <= PAGE_SIZE * 3) {
memcpy(dest, src, [self filesize]);
} else {
uint8_t keyData[64] = { 0x6f, 0x75, 0x72, 0x68, 0x61, 0x72, 0x64, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x79, 0x74, 0x68, 0x65,
0x73, 0x65, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x67, 0x75, 0x61, 0x72, 0x64, 0x65, 0x64, 0x70, 0x6c,
0x65, 0x61, 0x73, 0x65, 0x64, 0x6f, 0x6e, 0x74, 0x73, 0x74, 0x65, 0x61, 0x6c, 0x28, 0x63, 0x29,
0x41, 0x70, 0x70, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x63, };
// First three pages aren't encrypted, just copy
memcpy(dest, src, PAGE_SIZE * 3);
src += PAGE_SIZE * 3;
dest += PAGE_SIZE * 3;
NSUInteger count = (self.filesize / PAGE_SIZE) - 3;
uint32_t magic = OSReadLittleInt32(src, 0);
if (magic == CDSegmentProtectedMagic_None) {
memcpy(dest, src, [self filesize] - PAGE_SIZE * 3);
} else if (magic == CDSegmentProtectedMagic_Blowfish) {
// 10.6 decryption
#if 0
// CommonCrypto 60026 (OS X 10.8) is first to include kCCKeySizeMaxBlowfish.
// CommonCrypto 60075.50.1 (OS X 10.11.5) is the first to check the keysize.
CCCryptorRef cryptor;
CCCryptorStatus status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmBlowfish, 0, keyData, sizeof(keyData), NULL, &cryptor);
NSParameterAssert(status == kCCSuccess);
for (NSUInteger index = 0; index < count; index++) {
status = CCCryptorReset(cryptor, NULL);
NSParameterAssert(status == kCCSuccess);
size_t moved;
status = CCCryptorUpdate(cryptor, src, PAGE_SIZE, dest, PAGE_SIZE, &moved);
NSParameterAssert(status == kCCSuccess);
NSParameterAssert(moved == PAGE_SIZE);
src += PAGE_SIZE;
dest += PAGE_SIZE;
}
CCCryptorRelease(cryptor);
#else
// This uses a 64 byte keysize, which is too big for the enforced keysize check of CommonCrypto.
BLOWFISH_CTX ctx;
Blowfish_Init(&ctx, keyData, sizeof(keyData));
for (NSUInteger index = 0; index < count; index++) {
BF_Decrypt_Block(&ctx, src, dest);
src += PAGE_SIZE;
dest += PAGE_SIZE;
}
#endif
} else if (magic == CDSegmentProtectedMagic_AES) {
// 10.5 decryption
CCCryptorRef cryptor1, cryptor2;
CCCryptorStatus status;
status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, 0, keyData, 32, NULL, &cryptor1);
NSParameterAssert(status == kCCSuccess);
status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, 0, keyData + 32, 32, NULL, &cryptor2);
NSParameterAssert(status == kCCSuccess);
size_t halfPageSize = PAGE_SIZE / 2;
for (NSUInteger index = 0; index < count; index++) {
status = CCCryptorReset(cryptor1, NULL);
NSParameterAssert(status == kCCSuccess);
status = CCCryptorReset(cryptor2, NULL);
NSParameterAssert(status == kCCSuccess);
size_t moved;
status = CCCryptorUpdate(cryptor1, src, halfPageSize, dest, halfPageSize, &moved);
NSParameterAssert(status == kCCSuccess);
NSParameterAssert(moved == halfPageSize);
status = CCCryptorUpdate(cryptor2, src + halfPageSize, halfPageSize, dest + halfPageSize, halfPageSize, &moved);
NSParameterAssert(status == kCCSuccess);
NSParameterAssert(moved == halfPageSize);
src += PAGE_SIZE;
dest += PAGE_SIZE;
}
CCCryptorRelease(cryptor1);
CCCryptorRelease(cryptor2);
} else {
NSLog(@"Unknown encryption type: 0x%08x", magic);
exit(99);
}
}
}
return _decryptedData;
}
@end