#include "macho++.h"
#include <security_utilities/alloc.h>
#include <security_utilities/memutils.h>
#include <security_utilities/endian.h>
#include <mach-o/dyld.h>
#include <list>
#include <algorithm>
#include <iterator>
namespace Security {
static const int MAX_ARCH_COUNT = 100;
static const int MAX_ALIGN = 30;
Architecture::Architecture(const fat_arch &arch)
: pair<cpu_type_t, cpu_subtype_t>(arch.cputype, arch.cpusubtype)
{
}
Architecture::Architecture(const char *name)
{
if (const NXArchInfo *nxa = NXGetArchInfoFromName(name)) {
this->first = nxa->cputype;
this->second = nxa->cpusubtype;
} else {
this->first = this->second = none;
}
}
Architecture Architecture::local()
{
return MainMachOImage().architecture();
}
const char *Architecture::name() const
{
if (const NXArchInfo *info = NXGetArchInfoFromCpuType(cpuType(), cpuSubtype()))
return info->name;
else
return NULL;
}
std::string Architecture::displayName() const
{
if (const char *s = this->name())
return s;
char buf[20];
snprintf(buf, sizeof(buf), "(%d:%d)", cpuType(), cpuSubtype());
return buf;
}
bool Architecture::matches(const Architecture &templ) const
{
if (first != templ.first)
return false; if (templ.second == CPU_SUBTYPE_MULTIPLE)
return true; return ((second ^ templ.second) & ~CPU_SUBTYPE_MASK) == 0;
}
MachOBase::~MachOBase()
{ }
void MachOBase::initHeader(const mach_header *header)
{
mHeader = header;
switch (mHeader->magic) {
case MH_MAGIC:
mFlip = false;
m64 = false;
break;
case MH_CIGAM:
mFlip = true;
m64 = false;
break;
case MH_MAGIC_64:
mFlip = false;
m64 = true;
break;
case MH_CIGAM_64:
mFlip = true;
m64 = true;
break;
default:
secdebug("macho", "%p: unrecognized header magic (%x)", this, mHeader->magic);
UnixError::throwMe(ENOEXEC);
}
}
void MachOBase::initCommands(const load_command *commands)
{
mCommands = commands;
mEndCommands = LowLevelMemoryUtilities::increment<load_command>(commands, flip(mHeader->sizeofcmds));
if (mCommands + 1 > mEndCommands) UnixError::throwMe(ENOEXEC);
}
size_t MachOBase::headerSize() const
{
return m64 ? sizeof(mach_header_64) : sizeof(mach_header);
}
size_t MachOBase::commandSize() const
{
return flip(mHeader->sizeofcmds);
}
MachO::MachO(FileDesc fd, size_t offset, size_t length)
: FileDesc(fd), mOffset(offset), mLength(length), mSuspicious(false)
{
if (mOffset == 0)
mLength = fd.fileSize();
size_t size = fd.read(&mHeaderBuffer, sizeof(mHeaderBuffer), mOffset);
if (size != sizeof(mHeaderBuffer))
UnixError::throwMe(ENOEXEC);
this->initHeader(&mHeaderBuffer);
size_t cmdSize = this->commandSize();
mCommandBuffer = (load_command *)malloc(cmdSize);
if (!mCommandBuffer)
UnixError::throwMe();
if (fd.read(mCommandBuffer, cmdSize, this->headerSize() + mOffset) != cmdSize)
UnixError::throwMe(ENOEXEC);
this->initCommands(mCommandBuffer);
if (mLength != 0)
this->validateStructure();
}
void MachO::validateStructure()
{
bool isValid = false;
for (const struct load_command *cmd = loadCommands(); cmd != NULL; cmd = nextCommand(cmd)) {
uint32_t cmd_type = flip(cmd->cmd);
struct segment_command *seg = NULL;
struct segment_command_64 *seg64 = NULL;
struct symtab_command *symtab = NULL;
if (cmd_type == LC_SEGMENT) {
seg = (struct segment_command *)cmd;
if (strcmp(seg->segname, SEG_LINKEDIT) == 0) {
isValid = flip(seg->fileoff) + flip(seg->filesize) == this->length();
break;
}
} else if (cmd_type == LC_SEGMENT_64) {
seg64 = (struct segment_command_64 *)cmd;
if (strcmp(seg64->segname, SEG_LINKEDIT) == 0) {
isValid = flip(seg64->fileoff) + flip(seg64->filesize) == this->length();
break;
}
} else if (cmd_type == LC_SYMTAB) {
symtab = (struct symtab_command *)cmd;
isValid = flip(symtab->stroff) + flip(symtab->strsize) == this->length();
break;
}
}
if (!isValid)
mSuspicious = true;
}
MachO::~MachO()
{
::free(mCommandBuffer);
}
MachOImage::MachOImage(const void *address)
{
this->initHeader((const mach_header *)address);
this->initCommands(LowLevelMemoryUtilities::increment<const load_command>(address, this->headerSize()));
}
MainMachOImage::MainMachOImage()
: MachOImage(mainImageAddress())
{
}
const void *MainMachOImage::mainImageAddress()
{
return _dyld_get_image_header(0);
}
Architecture MachOBase::architecture() const
{
return Architecture(flip(mHeader->cputype), flip(mHeader->cpusubtype));
}
uint32_t MachOBase::type() const
{
return flip(mHeader->filetype);
}
uint32_t MachOBase::flags() const
{
return flip(mHeader->flags);
}
const load_command *MachOBase::nextCommand(const load_command *command) const
{
using LowLevelMemoryUtilities::increment;
command = increment<const load_command>(command, flip(command->cmdsize));
if (command >= mEndCommands) return NULL;
if (increment(command, sizeof(load_command)) > mEndCommands
|| increment(command, flip(command->cmdsize)) > mEndCommands)
UnixError::throwMe(ENOEXEC);
return command;
}
const load_command *MachOBase::findCommand(uint32_t cmd) const
{
for (const load_command *command = loadCommands(); command; command = nextCommand(command))
if (flip(command->cmd) == cmd)
return command;
return NULL;
}
const segment_command *MachOBase::findSegment(const char *segname) const
{
for (const load_command *command = loadCommands(); command; command = nextCommand(command)) {
switch (flip(command->cmd)) {
case LC_SEGMENT:
case LC_SEGMENT_64:
{
const segment_command *seg = reinterpret_cast<const segment_command *>(command);
if (!strcmp(seg->segname, segname))
return seg;
break;
}
default:
break;
}
}
return NULL;
}
const section *MachOBase::findSection(const char *segname, const char *sectname) const
{
using LowLevelMemoryUtilities::increment;
if (const segment_command *seg = findSegment(segname)) {
if (is64()) {
const segment_command_64 *seg64 = reinterpret_cast<const segment_command_64 *>(seg);
const section_64 *sect = increment<const section_64>(seg64 + 1, 0);
for (unsigned n = flip(seg64->nsects); n > 0; n--, sect++) {
if (!strcmp(sect->sectname, sectname))
return reinterpret_cast<const section *>(sect);
}
} else {
const section *sect = increment<const section>(seg + 1, 0);
for (unsigned n = flip(seg->nsects); n > 0; n--, sect++) {
if (!strcmp(sect->sectname, sectname))
return sect;
}
}
}
return NULL;
}
const char *MachOBase::string(const load_command *cmd, const lc_str &str) const
{
size_t offset = flip(str.offset);
const char *sp = LowLevelMemoryUtilities::increment<const char>(cmd, offset);
if (offset + strlen(sp) + 1 > flip(cmd->cmdsize)) return NULL;
return sp;
}
const linkedit_data_command *MachOBase::findCodeSignature() const
{
if (const load_command *cmd = findCommand(LC_CODE_SIGNATURE))
return reinterpret_cast<const linkedit_data_command *>(cmd);
return NULL; }
size_t MachOBase::signingOffset() const
{
if (const linkedit_data_command *lec = findCodeSignature())
return flip(lec->dataoff);
else
return 0;
}
size_t MachOBase::signingLength() const
{
if (const linkedit_data_command *lec = findCodeSignature())
return flip(lec->datasize);
else
return 0;
}
const linkedit_data_command *MachOBase::findLibraryDependencies() const
{
if (const load_command *cmd = findCommand(LC_DYLIB_CODE_SIGN_DRS))
return reinterpret_cast<const linkedit_data_command *>(cmd);
return NULL; }
size_t MachO::signingExtent() const
{
if (size_t offset = signingOffset())
return offset;
else
return length();
}
void MachO::seek(size_t offset)
{
FileDesc::seek(mOffset + offset);
}
CFDataRef MachO::dataAt(size_t offset, size_t size)
{
CFMallocData buffer(size);
if (this->read(buffer, size, mOffset + offset) != size)
UnixError::throwMe();
return buffer;
}
Universal::Universal(FileDesc fd, size_t offset , size_t length )
: FileDesc(fd), mBase(offset), mLength(length), mSuspicious(false)
{
union {
fat_header header; mach_header mheader; };
const size_t size = max(sizeof(header), sizeof(mheader));
if (fd.read(&header, size, offset) != size)
UnixError::throwMe(ENOEXEC);
switch (header.magic) {
case FAT_MAGIC:
case FAT_CIGAM:
{
mArchCount = ntohl(header.nfat_arch);
size_t archSize = sizeof(fat_arch) * (mArchCount + 1);
mArchList = (fat_arch *)malloc(archSize);
if (!mArchList)
UnixError::throwMe();
if (fd.read(mArchList, archSize, mBase + sizeof(header)) != archSize) {
::free(mArchList);
UnixError::throwMe(ENOEXEC);
}
for (fat_arch *arch = mArchList; arch <= mArchList + mArchCount; arch++) {
n2hi(arch->cputype);
n2hi(arch->cpusubtype);
n2hi(arch->offset);
n2hi(arch->size);
n2hi(arch->align);
}
const fat_arch *last_arch = mArchList + mArchCount;
if (last_arch->cputype == (CPU_ARCH_ABI64 | CPU_TYPE_ARM)) {
mArchCount++;
}
secdebug("macho", "%p is a fat file with %d architectures",
this, mArchCount);
std::list<struct fat_arch *> sortedList;
for (unsigned i = 0; i < mArchCount; i++)
sortedList.push_back(mArchList + i);
sortedList.sort(^ bool (const struct fat_arch *arch1, const struct fat_arch *arch2) { return arch1->offset < arch2->offset; });
const size_t universalHeaderEnd = mBase + sizeof(header) + (sizeof(fat_arch) * mArchCount);
size_t prevHeaderEnd = universalHeaderEnd;
size_t prevArchSize = 0, prevArchStart = 0;
for (auto iterator = sortedList.begin(); iterator != sortedList.end(); ++iterator) {
auto ret = mSizes.insert(std::pair<size_t, size_t>((*iterator)->offset, (*iterator)->size));
if (ret.second == false) {
::free(mArchList);
MacOSError::throwMe(errSecInternalError); }
size_t gapSize = (*iterator)->offset - prevHeaderEnd;
if (prevHeaderEnd != universalHeaderEnd) {
if (((*iterator)->align > MAX_ALIGN) || gapSize >= (1 << (*iterator)->align)) {
mSuspicious = true;
break;
}
}
CssmAutoPtr<uint8_t> gapBytes(Allocator::standard().malloc<uint8_t>(PAGE_SIZE));
size_t off = 0;
while (off < gapSize) {
size_t want = min(gapSize - off, (size_t)PAGE_SIZE);
size_t got = fd.read(gapBytes, want, prevHeaderEnd + off);
off += got;
for (size_t x = 0; x < got; x++) {
if (gapBytes[x] != 0) {
mSuspicious = true;
break;
}
}
if (mSuspicious)
break;
}
if (off != gapSize)
mSuspicious = true;
if (mSuspicious)
break;
prevHeaderEnd = (*iterator)->offset + (*iterator)->size;
prevArchSize = (*iterator)->size;
prevArchStart = (*iterator)->offset;
}
if (!mSuspicious && (prevArchStart + prevArchSize != fd.fileSize()))
mSuspicious = true;
break;
}
case MH_MAGIC:
case MH_MAGIC_64:
mArchList = NULL;
mArchCount = 0;
mThinArch = Architecture(mheader.cputype, mheader.cpusubtype);
secdebug("macho", "%p is a thin file (%s)", this, mThinArch.name());
break;
case MH_CIGAM:
case MH_CIGAM_64:
mArchList = NULL;
mArchCount = 0;
mThinArch = Architecture(flip(mheader.cputype), flip(mheader.cpusubtype));
secdebug("macho", "%p is a thin file (%s)", this, mThinArch.name());
break;
default:
UnixError::throwMe(ENOEXEC);
}
}
Universal::~Universal()
{
::free(mArchList);
}
const size_t Universal::lengthOfSlice(size_t offset) const
{
auto ret = mSizes.find(offset);
if (ret == mSizes.end())
MacOSError::throwMe(errSecInternalError);
return ret->second;
}
MachO *Universal::architecture() const
{
if (isUniversal())
return findImage(bestNativeArch());
else
return new MachO(*this, mBase, mLength);
}
size_t Universal::archOffset() const
{
if (isUniversal())
return mBase + findArch(bestNativeArch())->offset;
else
return mBase;
}
MachO *Universal::architecture(const Architecture &arch) const
{
if (isUniversal())
return findImage(arch);
else if (mThinArch.matches(arch))
return new MachO(*this, mBase);
else
UnixError::throwMe(ENOEXEC);
}
size_t Universal::archOffset(const Architecture &arch) const
{
if (isUniversal())
return mBase + findArch(arch)->offset;
else if (mThinArch.matches(arch))
return 0;
else
UnixError::throwMe(ENOEXEC);
}
size_t Universal::archLength(const Architecture &arch) const
{
if (isUniversal())
return mBase + findArch(arch)->size;
else if (mThinArch.matches(arch))
return this->fileSize();
else
UnixError::throwMe(ENOEXEC);
}
MachO *Universal::architecture(size_t offset) const
{
if (isUniversal())
return new MachO(*this, offset);
else if (offset == mBase)
return new MachO(*this);
else
UnixError::throwMe(ENOEXEC);
}
const fat_arch *Universal::findArch(const Architecture &target) const
{
assert(isUniversal());
const fat_arch *end = mArchList + mArchCount;
for (const fat_arch *arch = mArchList; arch < end; ++arch)
if (arch->cputype == target.cpuType()
&& arch->cpusubtype == target.cpuSubtype())
return arch;
for (const fat_arch *arch = mArchList; arch < end; ++arch)
if (arch->cputype == target.cpuType() && arch->cpusubtype == 0)
return arch;
for (const fat_arch *arch = mArchList; arch < end; ++arch)
if (arch->cputype == target.cpuType())
return arch;
UnixError::throwMe(ENOEXEC); }
MachO *Universal::findImage(const Architecture &target) const
{
const fat_arch *arch = findArch(target);
return new MachO(*this, mBase + arch->offset, arch->size);
}
Architecture Universal::bestNativeArch() const
{
if (isUniversal()) {
const Architecture native = Architecture::local();
if (fat_arch *match = NXFindBestFatArch(native.cpuType(), native.cpuSubtype(), mArchList, mArchCount))
return *match;
return mArchList[0];
} else
return mThinArch;
}
void Universal::architectures(Architectures &archs) const
{
if (isUniversal()) {
for (unsigned n = 0; n < mArchCount; n++)
archs.insert(mArchList[n]);
} else {
auto_ptr<MachO> macho(architecture());
archs.insert(macho->architecture());
}
}
uint32_t Universal::typeOf(FileDesc fd)
{
mach_header header;
int max_tries = 3;
if (fd.read(&header, sizeof(header), 0) != sizeof(header))
return 0;
while (max_tries > 0) {
switch (header.magic) {
case MH_MAGIC:
case MH_MAGIC_64:
return header.filetype;
break;
case MH_CIGAM:
case MH_CIGAM_64:
return flip(header.filetype);
break;
case FAT_MAGIC:
case FAT_CIGAM:
{
const fat_arch *arch1 =
LowLevelMemoryUtilities::increment<fat_arch>(&header, sizeof(fat_header));
if (fd.read(&header, sizeof(header), ntohl(arch1->offset)) != sizeof(header))
return 0;
max_tries--;
continue;
}
default:
return 0;
}
}
return 0;
}
bool Universal::isSuspicious() const
{
if (mSuspicious)
return true;
Universal::Architectures archList;
architectures(archList);
for (Universal::Architectures::const_iterator it = archList.begin(); it != archList.end(); ++it) {
auto_ptr<MachO> macho(architecture(*it));
if (macho->isSuspicious())
return true;
}
return false;
}
}