// MachoHandler.cpp #include "StdAfx.h" #include "../../../C/CpuArch.h" #include "Common/Buffer.h" #include "Common/ComTry.h" #include "Windows/PropVariantUtils.h" #include "../Common/LimitedStreams.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" static UInt32 Get32(const Byte *p, int be) { if (be) return GetBe32(p); return GetUi32(p); } static UInt64 Get64(const Byte *p, int be) { if (be) return GetBe64(p); return GetUi64(p); } using namespace NWindows; namespace NArchive { namespace NMacho { #define MACH_ARCH_ABI64 (1 << 24) #define MACH_MACHINE_386 7 #define MACH_MACHINE_ARM 12 #define MACH_MACHINE_SPARC 14 #define MACH_MACHINE_PPC 18 #define MACH_MACHINE_PPC64 (MACH_ARCH_ABI64 | MACH_MACHINE_PPC) #define MACH_MACHINE_AMD64 (MACH_ARCH_ABI64 | MACH_MACHINE_386) #define MACH_CMD_SEGMENT_32 1 #define MACH_CMD_SEGMENT_64 0x19 #define MACH_SECT_TYPE_MASK 0x000000FF #define MACH_SECT_ATTR_MASK 0xFFFFFF00 #define MACH_SECT_ATTR_ZEROFILL 1 static const char *g_SectTypes[] = { "REGULAR", "ZEROFILL", "CSTRINGS", "4BYTE_LITERALS", "8BYTE_LITERALS", "LITERAL_POINTERS", "NON_LAZY_SYMBOL_POINTERS", "LAZY_SYMBOL_POINTERS", "SYMBOL_STUBS", "MOD_INIT_FUNC_POINTERS", "MOD_TERM_FUNC_POINTERS", "COALESCED", "GB_ZEROFILL", "INTERPOSING", "16BYTE_LITERALS" }; static const char *g_FileTypes[] = { "0", "OBJECT", "EXECUTE", "FVMLIB", "CORE", "PRELOAD", "DYLIB", "DYLINKER", "BUNDLE", "DYLIB_STUB", "DSYM" }; static const CUInt32PCharPair g_Flags[] = { { 31, "PURE_INSTRUCTIONS" }, { 30, "NO_TOC" }, { 29, "STRIP_STATIC_SYMS" }, { 28, "NO_DEAD_STRIP" }, { 27, "LIVE_SUPPORT" }, { 26, "SELF_MODIFYING_CODE" }, { 25, "DEBUG" }, { 10, "SOME_INSTRUCTIONS" }, { 9, "EXT_RELOC" }, { 8, "LOC_RELOC" } }; static const CUInt32PCharPair g_MachinePairs[] = { { MACH_MACHINE_386, "x86" }, { MACH_MACHINE_ARM, "ARM" }, { MACH_MACHINE_SPARC, "SPARC" }, { MACH_MACHINE_PPC, "PowerPC" }, { MACH_MACHINE_PPC64, "PowerPC 64-bit" }, { MACH_MACHINE_AMD64, "x64" } }; static const int kNameSize = 16; struct CSegment { char Name[kNameSize]; }; struct CSection { char Name[kNameSize]; char SegName[kNameSize]; UInt64 Va; UInt64 Pa; UInt64 VSize; UInt64 PSize; UInt32 Flags; int SegmentIndex; bool IsDummy; CSection(): IsDummy(false) {} // UInt64 GetPackSize() const { return Flags == MACH_SECT_ATTR_ZEROFILL ? 0 : Size; } UInt64 GetPackSize() const { return PSize; } }; class CHandler: public IInArchive, public CMyUnknownImp { CMyComPtr _inStream; CObjectVector _segments; CObjectVector _sections; bool _mode64; bool _be; UInt32 _machine; UInt32 _type; UInt32 _headersSize; UInt64 _totalSize; HRESULT Open2(ISequentialInStream *stream); bool Parse(const Byte *buf, UInt32 size); public: MY_UNKNOWN_IMP1(IInArchive) INTERFACE_IInArchive(;) }; bool CHandler::Parse(const Byte *buf, UInt32 size) { bool mode64 = _mode64; bool be = _be; const Byte *bufStart = buf; bool reduceCommands = false; if (size < 512) return false; _machine = Get32(buf + 4, be); _type = Get32(buf + 0xC, be); UInt32 numCommands = Get32(buf + 0x10, be); UInt32 commandsSize = Get32(buf + 0x14, be); if (commandsSize > size) return false; if (commandsSize > (1 << 24) || numCommands > (1 << 18)) return false; if (numCommands > 16) { reduceCommands = true; numCommands = 16; } _headersSize = 0; buf += 0x1C; size -= 0x1C; if (mode64) { buf += 4; size -= 4; } _totalSize = (UInt32)(buf - bufStart); if (commandsSize < size) size = commandsSize; for (UInt32 cmdIndex = 0; cmdIndex < numCommands; cmdIndex++) { if (size < 8) return false; UInt32 cmd = Get32(buf, be); UInt32 cmdSize = Get32(buf + 4, be); if (size < cmdSize) return false; if (cmd == MACH_CMD_SEGMENT_32 || cmd == MACH_CMD_SEGMENT_64) { UInt32 offs = (cmd == MACH_CMD_SEGMENT_64) ? 0x48 : 0x38; if (cmdSize < offs) break; UInt64 vmAddr, vmSize, phAddr, phSize; { if (cmd == MACH_CMD_SEGMENT_64) { vmAddr = Get64(buf + 0x18, be); vmSize = Get64(buf + 0x20, be); phAddr = Get64(buf + 0x28, be); phSize = Get64(buf + 0x30, be); } else { vmAddr = Get32(buf + 0x18, be); vmSize = Get32(buf + 0x1C, be); phAddr = Get32(buf + 0x20, be); phSize = Get32(buf + 0x24, be); } { UInt64 totalSize = phAddr + phSize; if (totalSize > _totalSize) _totalSize = totalSize; } } CSegment seg; memcpy(seg.Name, buf + 8, kNameSize); _segments.Add(seg); UInt32 numSections = Get32(buf + offs - 8, be); if (numSections > (1 << 8)) return false; if (numSections == 0) { CSection section; section.IsDummy = true; section.SegmentIndex = _segments.Size() - 1; section.Va = vmAddr; section.PSize = phSize; section.VSize = vmSize; section.Pa = phAddr; section.Flags = 0; _sections.Add(section); } else do { CSection section; UInt32 headerSize = (cmd == MACH_CMD_SEGMENT_64) ? 0x50 : 0x44; const Byte *p = buf + offs; if (cmdSize - offs < headerSize) break; if (cmd == MACH_CMD_SEGMENT_64) { section.Va = Get64(p + 0x20, be); section.VSize = Get64(p + 0x28, be); section.Pa = Get32(p + 0x30, be); section.Flags = Get32(p + 0x40, be); } else { section.Va = Get32(p + 0x20, be); section.VSize = Get32(p + 0x24, be); section.Pa = Get32(p + 0x28, be); section.Flags = Get32(p + 0x38, be); } if (section.Flags == MACH_SECT_ATTR_ZEROFILL) section.PSize = 0; else section.PSize = section.VSize; memcpy(section.Name, p, kNameSize); memcpy(section.SegName, p + kNameSize, kNameSize); section.SegmentIndex = _segments.Size() - 1; _sections.Add(section); offs += headerSize; } while (--numSections); if (offs != cmdSize) return false; } buf += cmdSize; size -= cmdSize; } _headersSize = (UInt32)(buf - bufStart); return reduceCommands || (size == 0); } static STATPROPSTG kArcProps[] = { { NULL, kpidCpu, VT_BSTR}, { NULL, kpidBit64, VT_BOOL}, { NULL, kpidBigEndian, VT_BOOL}, { NULL, kpidCharacts, VT_BSTR}, { NULL, kpidPhySize, VT_UI8}, { NULL, kpidHeadersSize, VT_UI4} }; static STATPROPSTG kProps[] = { { NULL, kpidPath, VT_BSTR}, { NULL, kpidSize, VT_UI8}, { NULL, kpidPackSize, VT_UI8}, { NULL, kpidCharacts, VT_BSTR}, { NULL, kpidOffset, VT_UI8}, { NULL, kpidVa, VT_UI8} }; IMP_IInArchive_Props IMP_IInArchive_ArcProps STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch(propID) { case kpidCpu: PAIR_TO_PROP(g_MachinePairs, _machine, prop); break; case kpidCharacts: TYPE_TO_PROP(g_FileTypes, _type, prop); break; case kpidPhySize: prop = _totalSize; break; case kpidHeadersSize: prop = _headersSize; break; case kpidBit64: if (_mode64) prop = _mode64; break; case kpidBigEndian: if (_be) prop = _be; break; } prop.Detach(value); return S_OK; COM_TRY_END } static AString GetName(const char *name) { char res[kNameSize + 1]; memcpy(res, name, kNameSize); res[kNameSize] = 0; return res; } static AString SectFlagsToString(UInt32 flags) { AString res = TypeToString(g_SectTypes, sizeof(g_SectTypes) / sizeof(g_SectTypes[0]), flags & MACH_SECT_TYPE_MASK); AString s = FlagsToString(g_Flags, sizeof(g_Flags) / sizeof(g_Flags[0]), flags & MACH_SECT_ATTR_MASK); if (!s.IsEmpty()) { res += ' '; res += s; } return res; } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; const CSection &item = _sections[index]; switch(propID) { case kpidPath: { AString s = GetName(_segments[item.SegmentIndex].Name); if (!item.IsDummy) s += GetName(item.Name); StringToProp(s, prop); break; } case kpidSize: /* prop = (UInt64)item.VSize; break; */ case kpidPackSize: prop = (UInt64)item.GetPackSize(); break; case kpidCharacts: if (!item.IsDummy) StringToProp(SectFlagsToString(item.Flags), prop); break; case kpidOffset: prop = item.Pa; break; case kpidVa: prop = item.Va; break; } prop.Detach(value); return S_OK; COM_TRY_END } HRESULT CHandler::Open2(ISequentialInStream *stream) { const UInt32 kBufSize = 1 << 18; const UInt32 kSigSize = 4; CByteBuffer buffer; buffer.SetCapacity(kBufSize); Byte *buf = buffer; size_t processed = kSigSize; RINOK(ReadStream_FALSE(stream, buf, processed)); UInt32 sig = GetUi32(buf); bool be, mode64; switch(sig) { case 0xCEFAEDFE: be = true; mode64 = false; break; case 0xCFFAEDFE: be = true; mode64 = true; break; case 0xFEEDFACE: be = false; mode64 = false; break; case 0xFEEDFACF: be = false; mode64 = true; break; default: return S_FALSE; } processed = kBufSize - kSigSize; RINOK(ReadStream(stream, buf + kSigSize, &processed)); _mode64 = mode64; _be = be; return Parse(buf, (UInt32)processed + kSigSize) ? S_OK : S_FALSE; } STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback * /* openArchiveCallback */) { COM_TRY_BEGIN Close(); RINOK(Open2(inStream)); _inStream = inStream; return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Close() { _inStream.Release(); _sections.Clear(); _segments.Clear(); return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = _sections.Size(); return S_OK; } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN bool allFilesMode = (numItems == (UInt32)-1); if (allFilesMode) numItems = _sections.Size(); if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) totalSize += _sections[allFilesMode ? i : indices[i]].GetPackSize(); extractCallback->SetTotal(totalSize); UInt64 currentTotalSize = 0; UInt64 currentItemSize; NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); CMyComPtr copyCoder = copyCoderSpec; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(streamSpec); streamSpec->SetStream(_inStream); for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize) { lps->InSize = lps->OutSize = currentTotalSize; RINOK(lps->SetCur()); Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; UInt32 index = allFilesMode ? i : indices[i]; const CSection &item = _sections[index]; currentItemSize = item.GetPackSize(); CMyComPtr outStream; RINOK(extractCallback->GetStream(index, &outStream, askMode)); if (!testMode && !outStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); RINOK(_inStream->Seek(item.Pa, STREAM_SEEK_SET, NULL)); streamSpec->Init(currentItemSize); RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); outStream.Release(); RINOK(extractCallback->SetOperationResult(copyCoderSpec->TotalSize == currentItemSize ? NExtract::NOperationResult::kOK: NExtract::NOperationResult::kDataError)); } return S_OK; COM_TRY_END } static IInArchive *CreateArc() { return new CHandler; } static CArcInfo g_ArcInfo = { L"MachO", L"", 0, 0xDF, { 0 }, 0, false, CreateArc, 0 }; REGISTER_ARC(Macho) }}