#include "macho++.h"
#include <security_utilities/memutils.h>
#include <security_utilities/endian.h>
#include <mach-o/dyld.h>
namespace Security {
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:
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 ? length : (fd.fileSize() - offset))
{
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);
}
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 )
: FileDesc(fd), mBase(offset)
{
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);
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);
}
MachO *Universal::architecture() const
{
if (isUniversal())
return findImage(bestNativeArch());
else
return new MachO(*this, mBase);
}
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);
}
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)
{
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;
}
}