// PeHandler.cpp #include "StdAfx.h" #include "../../../C/CpuArch.h" #include "Common/DynamicBuffer.h" #include "Common/ComTry.h" #include "Common/IntToString.h" #include "Common/StringConvert.h" #include "Windows/PropVariantUtils.h" #include "Windows/Time.h" #include "../Common/LimitedStreams.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamObjects.h" #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) using namespace NWindows; namespace NArchive { namespace NPe { #define NUM_SCAN_SECTIONS_MAX (1 << 6) #define PE_SIG 0x00004550 #define PE_OptHeader_Magic_32 0x10B #define PE_OptHeader_Magic_64 0x20B static AString GetDecString(UInt32 v) { char sz[32]; ConvertUInt64ToString(v, sz); return sz; } struct CVersion { UInt16 Major; UInt16 Minor; void Parse(const Byte *buf); AString GetString() const { return GetDecString(Major) + '.' + GetDecString(Minor); } }; void CVersion::Parse(const Byte *p) { Major = Get16(p); Minor = Get16(p + 2); } static const UInt32 kHeaderSize = 4 + 20; struct CHeader { UInt16 NumSections; UInt32 Time; UInt32 PointerToSymbolTable; UInt32 NumSymbols; UInt16 OptHeaderSize; UInt16 Flags; UInt16 Machine; bool Parse(const Byte *buf); }; bool CHeader::Parse(const Byte *p) { if (Get32(p) != PE_SIG) return false; p += 4; Machine = Get16(p + 0); NumSections = Get16(p + 2); Time = Get32(p + 4); PointerToSymbolTable = Get32(p + 8); NumSymbols = Get32(p + 12); OptHeaderSize = Get16(p + 16); Flags = Get16(p + 18); return true; } struct CDirLink { UInt32 Va; UInt32 Size; void Parse(const Byte *p); }; void CDirLink::Parse(const Byte *p) { Va = Get32(p); Size = Get32(p + 4); } enum { kDirLink_Certificate = 4, kDirLink_Debug = 6 }; struct CDebugEntry { UInt32 Flags; UInt32 Time; CVersion Ver; UInt32 Type; UInt32 Size; UInt32 Va; UInt32 Pa; void Parse(const Byte *p); }; void CDebugEntry::Parse(const Byte *p) { Flags = Get32(p); Time = Get32(p + 4); Ver.Parse(p + 8); Type = Get32(p + 12); Size = Get32(p + 16); Va = Get32(p + 20); Pa = Get32(p + 24); } static const UInt32 kNumDirItemsMax = 16; struct COptHeader { UInt16 Magic; Byte LinkerVerMajor; Byte LinkerVerMinor; UInt32 CodeSize; UInt32 InitDataSize; UInt32 UninitDataSize; // UInt32 AddressOfEntryPoint; // UInt32 BaseOfCode; // UInt32 BaseOfData32; UInt64 ImageBase; UInt32 SectAlign; UInt32 FileAlign; CVersion OsVer; CVersion ImageVer; CVersion SubsysVer; UInt32 ImageSize; UInt32 HeadersSize; UInt32 CheckSum; UInt16 SubSystem; UInt16 DllCharacts; UInt64 StackReserve; UInt64 StackCommit; UInt64 HeapReserve; UInt64 HeapCommit; UInt32 NumDirItems; CDirLink DirItems[kNumDirItemsMax]; bool Is64Bit() const { return Magic == PE_OptHeader_Magic_64; } bool Parse(const Byte *p, UInt32 size); int GetNumFileAlignBits() const { for (int i = 9; i <= 16; i++) if (((UInt32)1 << i) == FileAlign) return i; return -1; } }; bool COptHeader::Parse(const Byte *p, UInt32 size) { Magic = Get16(p); switch (Magic) { case PE_OptHeader_Magic_32: case PE_OptHeader_Magic_64: break; default: return false; } LinkerVerMajor = p[2]; LinkerVerMinor = p[3]; bool hdr64 = Is64Bit(); CodeSize = Get32(p + 4); InitDataSize = Get32(p + 8); UninitDataSize = Get32(p + 12); // AddressOfEntryPoint = Get32(p + 16); // BaseOfCode = Get32(p + 20); // BaseOfData32 = hdr64 ? 0: Get32(p + 24); ImageBase = hdr64 ? GetUi64(p + 24) : Get32(p + 28); SectAlign = Get32(p + 32); FileAlign = Get32(p + 36); OsVer.Parse(p + 40); ImageVer.Parse(p + 44); SubsysVer.Parse(p + 48); // reserved = Get32(p + 52); ImageSize = Get32(p + 56); HeadersSize = Get32(p + 60); CheckSum = Get32(p + 64); SubSystem = Get16(p + 68); DllCharacts = Get16(p + 70); if (hdr64) { StackReserve = Get64(p + 72); StackCommit = Get64(p + 80); HeapReserve = Get64(p + 88); HeapCommit = Get64(p + 96); } else { StackReserve = Get32(p + 72); StackCommit = Get32(p + 76); HeapReserve = Get32(p + 80); HeapCommit = Get32(p + 84); } UInt32 pos = (hdr64 ? 108 : 92); NumDirItems = Get32(p + pos); pos += 4; if (pos + 8 * NumDirItems != size) return false; for (UInt32 i = 0; i < NumDirItems && i < kNumDirItemsMax; i++) DirItems[i].Parse(p + pos + i * 8); return true; } static const UInt32 kSectionSize = 40; struct CSection { AString Name; UInt32 VSize; UInt32 Va; UInt32 PSize; UInt32 Pa; UInt32 Flags; UInt32 Time; // UInt16 NumRelocs; bool IsDebug; bool IsRealSect; bool IsAdditionalSection; CSection(): IsRealSect(false), IsDebug(false), IsAdditionalSection(false) {} UInt64 GetPackSize() const { return PSize; } void UpdateTotalSize(UInt32 &totalSize) { UInt32 t = Pa + PSize; if (t > totalSize) totalSize = t; } void Parse(const Byte *p); }; static bool operator <(const CSection &a1, const CSection &a2) { return (a1.Pa < a2.Pa) || ((a1.Pa == a2.Pa) && (a1.PSize < a2.PSize)) ; } static bool operator ==(const CSection &a1, const CSection &a2) { return (a1.Pa == a2.Pa) && (a1.PSize == a2.PSize); } static AString GetName(const Byte *name) { const int kNameSize = 8; AString res; char *p = res.GetBuffer(kNameSize); memcpy(p, name, kNameSize); p[kNameSize] = 0; res.ReleaseBuffer(); return res; } void CSection::Parse(const Byte *p) { Name = GetName(p); VSize = Get32(p + 8); Va = Get32(p + 12); PSize = Get32(p + 16); Pa = Get32(p + 20); // NumRelocs = Get16(p + 32); Flags = Get32(p + 36); } static const CUInt32PCharPair g_HeaderCharacts[] = { { 1, "Executable" }, { 13, "DLL" }, { 8, "32-bit" }, { 5, "LargeAddress" }, { 0, "NoRelocs" }, { 2, "NoLineNums" }, { 3, "NoLocalSyms" }, { 4, "AggressiveWsTrim" }, { 9, "NoDebugInfo" }, { 10, "RemovableRun" }, { 11, "NetRun" }, { 12, "System" }, { 14, "UniCPU" }, { 7, "Little-Endian" }, { 15, "Big-Endian" } }; static const CUInt32PCharPair g_DllCharacts[] = { { 6, "Relocated" }, { 7, "Integrity" }, { 8, "NX-Compatible" }, { 9, "NoIsolation" }, { 10, "NoSEH" }, { 11, "NoBind" }, { 13, "WDM" }, { 15, "TerminalServerAware" } }; static const CUInt32PCharPair g_SectFlags[] = { { 3, "NoPad" }, { 5, "Code" }, { 6, "InitializedData" }, { 7, "UninitializedData" }, { 9, "Comments" }, { 11, "Remove" }, { 12, "COMDAT" }, { 15, "GP" }, { 24, "ExtendedRelocations" }, { 25, "Discardable" }, { 26, "NotCached" }, { 27, "NotPaged" }, { 28, "Shared" }, { 29, "Execute" }, { 30, "Read" }, { 31, "Write" } }; static const CUInt32PCharPair g_MachinePairs[] = { { 0x014C, "x86" }, { 0x0162, "MIPS-R3000" }, { 0x0166, "MIPS-R4000" }, { 0x0168, "MIPS-R10000" }, { 0x0169, "MIPS-V2" }, { 0x0184, "Alpha" }, { 0x01A2, "SH3" }, { 0x01A3, "SH3-DSP" }, { 0x01A4, "SH3E" }, { 0x01A6, "SH4" }, { 0x01A8, "SH5" }, { 0x01C0, "ARM" }, { 0x01C2, "ARM-Thumb" }, { 0x01F0, "PPC" }, { 0x01F1, "PPC-FP" }, { 0x0200, "IA-64" }, { 0x0284, "Alpha-64" }, { 0x0200, "IA-64" }, { 0x0366, "MIPSFPU" }, { 0x8664, "x64" }, { 0x0EBC, "EFI" } }; static const CUInt32PCharPair g_SubSystems[] = { { 0, "Unknown" }, { 1, "Native" }, { 2, "Windows GUI" }, { 3, "Windows CUI" }, { 7, "Posix" }, { 9, "Windows CE" }, { 10, "EFI" }, { 11, "EFI Boot" }, { 12, "EFI Runtime" }, { 13, "EFI ROM" }, { 14, "XBOX" } }; static const wchar_t *g_ResTypes[] = { NULL, L"CURSOR", L"BITMAP", L"ICON", L"MENU", L"DIALOG", L"STRING", L"FONTDIR", L"FONT", L"ACCELERATOR", L"RCDATA", L"MESSAGETABLE", L"GROUP_CURSOR", NULL, L"GROUP_ICON", NULL, L"VERSION", L"DLGINCLUDE", NULL, L"PLUGPLAY", L"VXD", L"ANICURSOR", L"ANIICON", L"HTML", L"MANIFEST" }; const UInt32 kFlag = (UInt32)1 << 31; const UInt32 kMask = ~kFlag; struct CTableItem { UInt32 Offset; UInt32 ID; }; const UInt32 kBmpHeaderSize = 14; const UInt32 kIconHeaderSize = 22; struct CResItem { UInt32 Type; UInt32 ID; UInt32 Lang; UInt32 Size; UInt32 Offset; UInt32 HeaderSize; Byte Header[kIconHeaderSize]; // it must be enough for max size header. bool Enabled; bool IsNameEqual(const CResItem &item) const { return Lang == item.Lang; } UInt32 GetSize() const { return Size + HeaderSize; } bool IsBmp() const { return Type == 2; } bool IsIcon() const { return Type == 3; } bool IsString() const { return Type == 6; } bool IsRcData() const { return Type == 10; } bool IsRcDataOrUnknown() const { return IsRcData() || Type > 64; } }; struct CStringItem { UInt32 Lang; UInt32 Size; CByteDynamicBuffer Buf; void AddChar(Byte c); void AddWChar(UInt16 c); }; void CStringItem::AddChar(Byte c) { Buf.EnsureCapacity(Size + 2); Buf[Size++] = c; Buf[Size++] = 0; } void CStringItem::AddWChar(UInt16 c) { if (c == '\n') { AddChar('\\'); c = 'n'; } Buf.EnsureCapacity(Size + 2); SetUi16(Buf + Size, c); Size += 2; } struct CMixItem { int SectionIndex; int ResourceIndex; int StringIndex; bool IsSectionItem() const { return ResourceIndex < 0 && StringIndex < 0; }; }; struct CUsedBitmap { CByteBuffer Buf; public: void Alloc(size_t size) { size = (size + 7) / 8; Buf.SetCapacity(size); memset(Buf, 0, size); } void Free() { Buf.SetCapacity(0); } bool SetRange(size_t from, int size) { for (int i = 0; i < size; i++) { size_t pos = (from + i) >> 3; Byte mask = (Byte)(1 << ((from + i) & 7)); Byte b = Buf[pos]; if ((b & mask) != 0) return false; Buf[pos] = b | mask; } return true; } }; class CHandler: public IInArchive, public IInArchiveGetStream, public CMyUnknownImp { CMyComPtr _stream; CObjectVector _sections; UInt32 _peOffset; CHeader _header; COptHeader _optHeader; UInt32 _totalSize; UInt32 _totalSizeLimited; Int32 _mainSubfile; CRecordVector _items; CObjectVector _strings; CByteBuffer _buf; bool _oneLang; UString _resourceFileName; CUsedBitmap _usedRes; bool _parseResources; CRecordVector _mixItems; HRESULT LoadDebugSections(IInStream *stream, bool &thereIsSection); HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback); bool Parse(const Byte *buf, UInt32 size); void AddResNameToString(UString &s, UInt32 id) const; UString GetLangPrefix(UInt32 lang); HRESULT ReadString(UInt32 offset, UString &dest) const; HRESULT ReadTable(UInt32 offset, CRecordVector &items); bool ParseStringRes(UInt32 id, UInt32 lang, const Byte *src, UInt32 size); HRESULT OpenResources(int sectIndex, IInStream *stream, IArchiveOpenCallback *callback); void CloseResources(); bool CheckItem(const CSection §, const CResItem &item, size_t offset) const { return item.Offset >= sect.Va && offset <= _buf.GetCapacity() && _buf.GetCapacity() - offset >= item.Size; } public: MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) INTERFACE_IInArchive(;) STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; bool CHandler::Parse(const Byte *buf, UInt32 size) { UInt32 i; if (size < 512) return false; _peOffset = Get32(buf + 0x3C); if (_peOffset >= 0x1000 || _peOffset + 512 > size || (_peOffset & 7) != 0) return false; UInt32 pos = _peOffset; if (!_header.Parse(buf + pos)) return false; if (_header.OptHeaderSize > 512 || _header.NumSections > NUM_SCAN_SECTIONS_MAX) return false; pos += kHeaderSize; if (!_optHeader.Parse(buf + pos, _header.OptHeaderSize)) return false; pos += _header.OptHeaderSize; _totalSize = pos; for (i = 0; i < _header.NumSections; i++, pos += kSectionSize) { CSection sect; if (pos + kSectionSize > size) return false; sect.Parse(buf + pos); sect.IsRealSect = true; sect.UpdateTotalSize(_totalSize); _sections.Add(sect); } return true; } enum { kpidSectAlign = kpidUserDefined, kpidFileAlign, kpidLinkerVer, kpidOsVer, kpidImageVer, kpidSubsysVer, kpidCodeSize, kpidImageSize, kpidInitDataSize, kpidUnInitDataSize, kpidHeadersSizeUnInitDataSize, kpidSubSystem, kpidDllCharacts, kpidStackReserve, kpidStackCommit, kpidHeapReserve, kpidHeapCommit, kpidImageBase // kpidAddressOfEntryPoint, // kpidBaseOfCode, // kpidBaseOfData32, }; STATPROPSTG kArcProps[] = { { NULL, kpidCpu, VT_BSTR}, { NULL, kpidBit64, VT_BOOL}, { NULL, kpidCharacts, VT_BSTR}, { NULL, kpidCTime, VT_FILETIME}, { NULL, kpidPhySize, VT_UI4}, { NULL, kpidHeadersSize, VT_UI4}, { NULL, kpidChecksum, VT_UI4}, { L"Image Size", kpidImageSize, VT_UI4}, { L"Section Alignment", kpidSectAlign, VT_UI4}, { L"File Alignment", kpidFileAlign, VT_UI4}, { L"Code Size", kpidCodeSize, VT_UI4}, { L"Initialized Data Size", kpidInitDataSize, VT_UI4}, { L"Uninitialized Data Size", kpidUnInitDataSize, VT_UI4}, { L"Linker Version", kpidLinkerVer, VT_BSTR}, { L"OS Version", kpidOsVer, VT_BSTR}, { L"Image Version", kpidImageVer, VT_BSTR}, { L"Subsystem Version", kpidSubsysVer, VT_BSTR}, { L"Subsystem", kpidSubSystem, VT_BSTR}, { L"DLL Characteristics", kpidDllCharacts, VT_BSTR}, { L"Stack Reserve", kpidStackReserve, VT_UI8}, { L"Stack Commit", kpidStackCommit, VT_UI8}, { L"Heap Reserve", kpidHeapReserve, VT_UI8}, { L"Heap Commit", kpidHeapCommit, VT_UI8}, { L"Image Base", kpidImageBase, VT_UI8} // { L"Address Of Entry Point", kpidAddressOfEntryPoint, VT_UI8}, // { L"Base Of Code", kpidBaseOfCode, VT_UI8}, // { L"Base Of Data", kpidBaseOfData32, VT_UI8}, }; 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_WITH_NAME static void VerToProp(const CVersion &v, NCOM::CPropVariant &prop) { StringToProp(v.GetString(), prop); } void TimeToProp(UInt32 unixTime, NCOM::CPropVariant &prop) { if (unixTime != 0) { FILETIME ft; NTime::UnixTimeToFileTime(unixTime, ft); prop = ft; } } STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch(propID) { case kpidSectAlign: prop = _optHeader.SectAlign; break; case kpidFileAlign: prop = _optHeader.FileAlign; break; case kpidLinkerVer: { CVersion v = { _optHeader.LinkerVerMajor, _optHeader.LinkerVerMinor }; VerToProp(v, prop); break; } case kpidOsVer: VerToProp(_optHeader.OsVer, prop); break; case kpidImageVer: VerToProp(_optHeader.ImageVer, prop); break; case kpidSubsysVer: VerToProp(_optHeader.SubsysVer, prop); break; case kpidCodeSize: prop = _optHeader.CodeSize; break; case kpidInitDataSize: prop = _optHeader.InitDataSize; break; case kpidUnInitDataSize: prop = _optHeader.UninitDataSize; break; case kpidImageSize: prop = _optHeader.ImageSize; break; case kpidPhySize: prop = _totalSize; break; case kpidHeadersSize: prop = _optHeader.HeadersSize; break; case kpidChecksum: prop = _optHeader.CheckSum; break; case kpidCpu: PAIR_TO_PROP(g_MachinePairs, _header.Machine, prop); break; case kpidBit64: if (_optHeader.Is64Bit()) prop = true; break; case kpidSubSystem: PAIR_TO_PROP(g_SubSystems, _optHeader.SubSystem, prop); break; case kpidMTime: case kpidCTime: TimeToProp(_header.Time, prop); break; case kpidCharacts: FLAGS_TO_PROP(g_HeaderCharacts, _header.Flags, prop); break; case kpidDllCharacts: FLAGS_TO_PROP(g_DllCharacts, _optHeader.DllCharacts, prop); break; case kpidStackReserve: prop = _optHeader.StackReserve; break; case kpidStackCommit: prop = _optHeader.StackCommit; break; case kpidHeapReserve: prop = _optHeader.HeapReserve; break; case kpidHeapCommit: prop = _optHeader.HeapCommit; break; case kpidImageBase: prop = _optHeader.ImageBase; break; // case kpidAddressOfEntryPoint: prop = _optHeader.AddressOfEntryPoint; break; // case kpidBaseOfCode: prop = _optHeader.BaseOfCode; break; // case kpidBaseOfData32: if (!_optHeader.Is64Bit()) prop = _optHeader.BaseOfData32; break; case kpidMainSubfile: if (_mainSubfile >= 0) prop = (UInt32)_mainSubfile; break; } prop.Detach(value); return S_OK; COM_TRY_END } void CHandler::AddResNameToString(UString &s, UInt32 id) const { if ((id & kFlag) != 0) { UString name; if (ReadString(id & kMask, name) == S_OK) { if (name.IsEmpty()) s += L"[]"; else { if (name.Length() > 1 && name[0] == '"' && name.Back() == '"') name = name.Mid(1, name.Length() - 2); s += name; } return; } } wchar_t sz[32]; ConvertUInt32ToString(id, sz); s += sz; } UString CHandler::GetLangPrefix(UInt32 lang) { UString s = _resourceFileName; s += WCHAR_PATH_SEPARATOR; if (!_oneLang) { AddResNameToString(s, lang); s += WCHAR_PATH_SEPARATOR; } return s; } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; const CMixItem &mixItem = _mixItems[index]; if (mixItem.StringIndex >= 0) { const CStringItem &item = _strings[mixItem.StringIndex]; switch(propID) { case kpidPath: prop = GetLangPrefix(item.Lang) + L"string.txt"; break; case kpidSize: case kpidPackSize: prop = (UInt64)item.Size; break; } } else if (mixItem.ResourceIndex < 0) { const CSection &item = _sections[mixItem.SectionIndex]; switch(propID) { case kpidPath: StringToProp(item.Name, prop); break; case kpidSize: prop = (UInt64)item.VSize; break; case kpidPackSize: prop = (UInt64)item.GetPackSize(); break; case kpidOffset: prop = item.Pa; break; case kpidVa: if (item.IsRealSect) prop = item.Va; break; case kpidMTime: case kpidCTime: TimeToProp(item.IsDebug ? item.Time : _header.Time, prop); break; case kpidCharacts: if (item.IsRealSect) FLAGS_TO_PROP(g_SectFlags, item.Flags, prop); break; } } else { const CResItem &item = _items[mixItem.ResourceIndex]; switch(propID) { case kpidPath: { UString s = GetLangPrefix(item.Lang); { const wchar_t *p = NULL; if (item.Type < sizeof(g_ResTypes) / sizeof(g_ResTypes[0])) p = g_ResTypes[item.Type]; if (p != 0) s += p; else AddResNameToString(s, item.Type); } s += WCHAR_PATH_SEPARATOR; AddResNameToString(s, item.ID); if (item.HeaderSize != 0) { if (item.IsBmp()) s += L".bmp"; else if (item.IsIcon()) s += L".ico"; } prop = s; break; } case kpidSize: prop = (UInt64)item.GetSize(); break; case kpidPackSize: prop = (UInt64)item.Size; break; } } prop.Detach(value); return S_OK; COM_TRY_END } HRESULT CHandler::LoadDebugSections(IInStream *stream, bool &thereIsSection) { thereIsSection = false; const CDirLink &debugLink = _optHeader.DirItems[kDirLink_Debug]; if (debugLink.Size == 0) return S_OK; const unsigned kEntrySize = 28; UInt32 numItems = debugLink.Size / kEntrySize; if (numItems * kEntrySize != debugLink.Size || numItems > 16) return S_FALSE; UInt64 pa = 0; int i; for (i = 0; i < _sections.Size(); i++) { const CSection § = _sections[i]; if (sect.Va < debugLink.Va && debugLink.Va + debugLink.Size <= sect.Va + sect.PSize) { pa = sect.Pa + (debugLink.Va - sect.Va); break; } } if (i == _sections.Size()) { return S_OK; // Exe for ARM requires S_OK // return S_FALSE; } CByteBuffer buffer; buffer.SetCapacity(debugLink.Size); Byte *buf = buffer; RINOK(stream->Seek(pa, STREAM_SEEK_SET, NULL)); RINOK(ReadStream_FALSE(stream, buf, debugLink.Size)); for (i = 0; i < (int)numItems; i++) { CDebugEntry de; de.Parse(buf); if (de.Size == 0) continue; CSection sect; sect.Name = ".debug" + GetDecString(i); sect.IsDebug = true; sect.Time = de.Time; sect.Va = de.Va; sect.Pa = de.Pa; sect.PSize = sect.VSize = de.Size; UInt32 totalSize = sect.Pa + sect.PSize; if (totalSize > _totalSize) { _totalSize = totalSize; _sections.Add(sect); thereIsSection = true; } buf += kEntrySize; } return S_OK; } HRESULT CHandler::ReadString(UInt32 offset, UString &dest) const { if ((offset & 1) != 0 || offset >= _buf.GetCapacity()) return S_FALSE; size_t rem = _buf.GetCapacity() - offset; if (rem < 2) return S_FALSE; unsigned length = Get16(_buf + offset); if ((rem - 2) / 2 < length) return S_FALSE; dest.Empty(); offset += 2; for (unsigned i = 0; i < length; i++) dest += (wchar_t)Get16(_buf + offset + i * 2); return S_OK; } HRESULT CHandler::ReadTable(UInt32 offset, CRecordVector &items) { if ((offset & 3) != 0 || offset >= _buf.GetCapacity()) return S_FALSE; size_t rem = _buf.GetCapacity() - offset; if (rem < 16) return S_FALSE; items.Clear(); unsigned numNameItems = Get16(_buf + offset + 12); unsigned numIdItems = Get16(_buf + offset + 14); unsigned numItems = numNameItems + numIdItems; if ((rem - 16) / 8 < numItems) return S_FALSE; if (!_usedRes.SetRange(offset, 16 + numItems * 8)) return S_FALSE; offset += 16; _oneLang = true; unsigned i; for (i = 0; i < numItems; i++) { CTableItem item; const Byte *buf = _buf + offset; offset += 8; item.ID = Get32(buf + 0); if (((item.ID & kFlag) != 0) != (i < numNameItems)) return S_FALSE; item.Offset = Get32(buf + 4); items.Add(item); } return S_OK; } static const UInt32 kFileSizeMax = (UInt32)1 << 30; static const int kNumResItemsMax = (UInt32)1 << 23; static const int kNumStringLangsMax = 128; // BITMAPINFOHEADER struct CBitmapInfoHeader { // UInt32 HeaderSize; UInt32 XSize; Int32 YSize; UInt16 Planes; UInt16 BitCount; UInt32 Compression; UInt32 SizeImage; bool Parse(const Byte *p, size_t size); }; static const UInt32 kBitmapInfoHeader_Size = 0x28; bool CBitmapInfoHeader::Parse(const Byte *p, size_t size) { if (size < kBitmapInfoHeader_Size || Get32(p) != kBitmapInfoHeader_Size) return false; XSize = Get32(p + 4); YSize = (Int32)Get32(p + 8); Planes = Get16(p + 12); BitCount = Get16(p + 14); Compression = Get32(p + 16); SizeImage = Get32(p + 20); return true; } static UInt32 GetImageSize(UInt32 xSize, UInt32 ySize, UInt32 bitCount) { return ((xSize * bitCount + 7) / 8 + 3) / 4 * 4 * ySize; } static UInt32 SetBitmapHeader(Byte *dest, const Byte *src, UInt32 size) { CBitmapInfoHeader h; if (!h.Parse(src, size)) return 0; if (h.YSize < 0) h.YSize = -h.YSize; if (h.XSize > (1 << 26) || h.YSize > (1 << 26) || h.Planes != 1 || h.BitCount > 32 || h.Compression != 0) // BI_RGB return 0; if (h.SizeImage == 0) h.SizeImage = GetImageSize(h.XSize, h.YSize, h.BitCount); UInt32 totalSize = kBmpHeaderSize + size; UInt32 offBits = totalSize - h.SizeImage; // BITMAPFILEHEADER SetUi16(dest, 0x4D42); SetUi32(dest + 2, totalSize); SetUi32(dest + 6, 0); SetUi32(dest + 10, offBits); return kBmpHeaderSize; } static UInt32 SetIconHeader(Byte *dest, const Byte *src, UInt32 size) { CBitmapInfoHeader h; if (!h.Parse(src, size)) return 0; if (h.YSize < 0) h.YSize = -h.YSize; if (h.XSize > (1 << 26) || h.YSize > (1 << 26) || h.Planes != 1 || h.Compression != 0) // BI_RGB return 0; UInt32 numBitCount = h.BitCount; if (numBitCount != 1 && numBitCount != 4 && numBitCount != 8 && numBitCount != 24 && numBitCount != 32) return 0; if ((h.YSize & 1) != 0) return 0; h.YSize /= 2; if (h.XSize > 0x100 || h.YSize > 0x100) return 0; UInt32 imageSize; // imageSize is not correct if AND mask array contains zeros // in this case it is equal image1Size // UInt32 imageSize = h.SizeImage; // if (imageSize == 0) // { UInt32 image1Size = GetImageSize(h.XSize, h.YSize, h.BitCount); UInt32 image2Size = GetImageSize(h.XSize, h.YSize, 1); imageSize = image1Size + image2Size; // } UInt32 numColors = 0; if (numBitCount < 16) numColors = 1 << numBitCount; SetUi16(dest, 0); // Reserved SetUi16(dest + 2, 1); // RES_ICON SetUi16(dest + 4, 1); // ResCount dest[6] = (Byte)h.XSize; // Width dest[7] = (Byte)h.YSize; // Height dest[8] = (Byte)numColors; // ColorCount dest[9] = 0; // Reserved SetUi32(dest + 10, 0); // Reserved1 / Reserved2 UInt32 numQuadsBytes = numColors * 4; UInt32 BytesInRes = kBitmapInfoHeader_Size + numQuadsBytes + imageSize; SetUi32(dest + 14, BytesInRes); SetUi32(dest + 18, kIconHeaderSize); /* Description = DWORDToString(xSize) + kDelimiterChar + DWORDToString(ySize) + kDelimiterChar + DWORDToString(numBitCount); */ return kIconHeaderSize; } bool CHandler::ParseStringRes(UInt32 id, UInt32 lang, const Byte *src, UInt32 size) { if ((size & 1) != 0) return false; int i; for (i = 0; i < _strings.Size(); i++) if (_strings[i].Lang == lang) break; if (i == _strings.Size()) { if (_strings.Size() >= kNumStringLangsMax) return false; CStringItem item; item.Size = 0; item.Lang = lang; i = _strings.Add(item); } CStringItem &item = _strings[i]; id = (id - 1) << 4; UInt32 pos = 0; for (i = 0; i < 16; i++) { if (size - pos < 2) return false; UInt32 len = Get16(src + pos); pos += 2; if (len != 0) { if (size - pos < len * 2) return false; char temp[32]; ConvertUInt32ToString(id + i, temp); size_t tempLen = strlen(temp); size_t j; for (j = 0; j < tempLen; j++) item.AddChar(temp[j]); item.AddChar('\t'); for (j = 0; j < len; j++, pos += 2) item.AddWChar(Get16(src + pos)); item.AddChar(0x0D); item.AddChar(0x0A); } } return (size == pos); } HRESULT CHandler::OpenResources(int sectionIndex, IInStream *stream, IArchiveOpenCallback *callback) { const CSection § = _sections[sectionIndex]; size_t fileSize = sect.PSize; // Maybe we need sect.VSize here !!! if (fileSize > kFileSizeMax) return S_FALSE; { UInt64 fileSize64 = fileSize; if (callback) RINOK(callback->SetTotal(NULL, &fileSize64)); RINOK(stream->Seek(sect.Pa, STREAM_SEEK_SET, NULL)); _buf.SetCapacity(fileSize); for (size_t pos = 0; pos < fileSize;) { UInt64 offset64 = pos; if (callback) RINOK(callback->SetCompleted(NULL, &offset64)) size_t rem = MyMin(fileSize - pos, (size_t)(1 << 20)); RINOK(ReadStream_FALSE(stream, _buf + pos, rem)); pos += rem; } } _usedRes.Alloc(fileSize); CRecordVector specItems; RINOK(ReadTable(0, specItems)); _oneLang = true; bool stringsOk = true; size_t maxOffset = 0; for (int i = 0; i < specItems.Size(); i++) { const CTableItem &item1 = specItems[i]; if ((item1.Offset & kFlag) == 0) return S_FALSE; CRecordVector specItems2; RINOK(ReadTable(item1.Offset & kMask, specItems2)); for (int j = 0; j < specItems2.Size(); j++) { const CTableItem &item2 = specItems2[j]; if ((item2.Offset & kFlag) == 0) return S_FALSE; CRecordVector specItems3; RINOK(ReadTable(item2.Offset & kMask, specItems3)); CResItem item; item.Type = item1.ID; item.ID = item2.ID; for (int k = 0; k < specItems3.Size(); k++) { if (_items.Size() >= kNumResItemsMax) return S_FALSE; const CTableItem &item3 = specItems3[k]; if ((item3.Offset & kFlag) != 0) return S_FALSE; if (item3.Offset >= _buf.GetCapacity() || _buf.GetCapacity() - item3.Offset < 16) return S_FALSE; const Byte *buf = _buf + item3.Offset; item.Lang = item3.ID; item.Offset = Get32(buf + 0); item.Size = Get32(buf + 4); // UInt32 codePage = Get32(buf + 8); if (Get32(buf + 12) != 0) return S_FALSE; if (!_items.IsEmpty() && _oneLang && !item.IsNameEqual(_items.Back())) _oneLang = false; item.HeaderSize = 0; size_t offset = item.Offset - sect.Va; if (offset > maxOffset) maxOffset = offset; if (offset + item.Size > maxOffset) maxOffset = offset + item.Size; if (CheckItem(sect, item, offset)) { const Byte *data = _buf + offset; if (item.IsBmp()) item.HeaderSize = SetBitmapHeader(item.Header, data, item.Size); else if (item.IsIcon()) item.HeaderSize = SetIconHeader(item.Header, data, item.Size); else if (item.IsString()) { if (stringsOk) stringsOk = ParseStringRes(item.ID, item.Lang, data, item.Size); } } item.Enabled = true; _items.Add(item); } } } if (stringsOk && !_strings.IsEmpty()) { int i; for (i = 0; i < _items.Size(); i++) { CResItem &item = _items[i]; if (item.IsString()) item.Enabled = false; } for (i = 0; i < _strings.Size(); i++) { if (_strings[i].Size == 0) continue; CMixItem mixItem; mixItem.ResourceIndex = -1; mixItem.StringIndex = i; mixItem.SectionIndex = sectionIndex; _mixItems.Add(mixItem); } } _usedRes.Free(); int numBits = _optHeader.GetNumFileAlignBits(); if (numBits >= 0) { UInt32 mask = (1 << numBits) - 1; size_t end = ((maxOffset + mask) & ~mask); if (end < sect.VSize && end <= sect.PSize) { CSection sect2; sect2.Flags = 0; // we skip Zeros to start of aligned block size_t i; for (i = maxOffset; i < end; i++) if (_buf[i] != 0) break; if (i == end) maxOffset = end; sect2.Pa = sect.Pa + (UInt32)maxOffset; sect2.Va = sect.Va + (UInt32)maxOffset; sect2.PSize = sect.VSize - (UInt32)maxOffset; sect2.VSize = sect2.PSize; sect2.Name = ".rsrc_1"; sect2.Time = 0; sect2.IsAdditionalSection = true; _sections.Add(sect2); } } return S_OK; } HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) { const UInt32 kBufSize = 1 << 18; const UInt32 kSigSize = 2; _mainSubfile = -1; CByteBuffer buffer; buffer.SetCapacity(kBufSize); Byte *buf = buffer; size_t processed = kSigSize; RINOK(ReadStream_FALSE(stream, buf, processed)); if (buf[0] != 'M' || buf[1] != 'Z') return S_FALSE; processed = kBufSize - kSigSize; RINOK(ReadStream(stream, buf + kSigSize, &processed)); processed += kSigSize; if (!Parse(buf, (UInt32)processed)) return S_FALSE; bool thereISDebug; RINOK(LoadDebugSections(stream, thereISDebug)); const CDirLink &certLink = _optHeader.DirItems[kDirLink_Certificate]; if (certLink.Size != 0) { CSection sect; sect.Name = "CERTIFICATE"; sect.Va = 0; sect.Pa = certLink.Va; sect.PSize = sect.VSize = certLink.Size; sect.UpdateTotalSize(_totalSize); _sections.Add(sect); } if (thereISDebug) { const UInt32 kAlign = 1 << 12; UInt32 alignPos = _totalSize & (kAlign - 1); if (alignPos != 0) { UInt32 size = kAlign - alignPos; RINOK(stream->Seek(_totalSize, STREAM_SEEK_SET, NULL)); buffer.Free(); buffer.SetCapacity(kAlign); Byte *buf = buffer; size_t processed = size; RINOK(ReadStream(stream, buf, &processed)); size_t i; for (i = 0; i < processed; i++) { if (buf[i] != 0) break; } if (processed < size && processed < 100) _totalSize += (UInt32)processed; else if (((_totalSize + i) & 0x1FF) == 0 || processed < size) _totalSize += (UInt32)i; } } if (_header.NumSymbols > 0 && _header.PointerToSymbolTable >= 512) { if (_header.NumSymbols >= (1 << 24)) return S_FALSE; CSection sect; sect.Name = "COFF_SYMBOLS"; UInt32 size = _header.NumSymbols * 18; RINOK(stream->Seek((UInt64)_header.PointerToSymbolTable + size, STREAM_SEEK_SET, NULL)); Byte buf[4]; RINOK(ReadStream_FALSE(stream, buf, 4)); UInt32 size2 = Get32(buf); if (size2 >= (1 << 28)) return S_FALSE; size += size2; sect.Va = 0; sect.Pa = _header.PointerToSymbolTable; sect.PSize = sect.VSize = size; sect.UpdateTotalSize(_totalSize); _sections.Add(sect); } UInt64 fileSize; RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize)); if (fileSize > _totalSize) return S_FALSE; _totalSizeLimited = (_totalSize < fileSize) ? _totalSize : (UInt32)fileSize; { CObjectVector sections = _sections; sections.Sort(); UInt32 limit = (1 << 12); int num = 0; int numSections = sections.Size(); for (int i = 0; i < numSections; i++) { const CSection &s = sections[i]; if (s.Pa > limit) { CSection s2; s2.Pa = s2.Va = limit; s2.PSize = s2.VSize = s.Pa - limit; s2.IsAdditionalSection = true; s2.Name = '['; s2.Name += GetDecString(num++); s2.Name += ']'; _sections.Add(s2); limit = s.Pa; } UInt32 next = s.Pa + s.PSize; if (next < s.Pa) break; if (next >= limit) limit = next; } } _parseResources = true; UInt64 mainSize = 0, mainSize2 = 0; int i; for (i = 0; i < _sections.Size(); i++) { const CSection § = _sections[i]; CMixItem mixItem; mixItem.SectionIndex = i; if (_parseResources && sect.Name == ".rsrc" && _items.IsEmpty()) { HRESULT res = OpenResources(i, stream, callback); if (res == S_OK) { _resourceFileName = GetUnicodeString(sect.Name); for (int j = 0; j < _items.Size(); j++) { const CResItem &item = _items[j]; if (item.Enabled) { mixItem.ResourceIndex = j; mixItem.StringIndex = -1; if (item.IsRcDataOrUnknown()) { if (item.Size >= mainSize) { mainSize2 = mainSize; mainSize = item.Size; _mainSubfile = _mixItems.Size(); } else if (item.Size >= mainSize2) mainSize2 = item.Size; } _mixItems.Add(mixItem); } } if (sect.PSize > sect.VSize) { int numBits = _optHeader.GetNumFileAlignBits(); if (numBits >= 0) { UInt32 mask = (1 << numBits) - 1; UInt32 end = ((sect.VSize + mask) & ~mask); if (sect.PSize > end) { CSection sect2; sect2.Flags = 0; sect2.Pa = sect.Pa + end; sect2.Va = sect.Va + end; sect2.PSize = sect.PSize - end; sect2.VSize = sect2.PSize; sect2.Name = ".rsrc_2"; sect2.Time = 0; sect2.IsAdditionalSection = true; _sections.Add(sect2); } } } continue; } if (res != S_FALSE) return res; CloseResources(); } mixItem.StringIndex = -1; mixItem.ResourceIndex = -1; if (sect.IsAdditionalSection) { if (sect.PSize >= mainSize) { mainSize2 = mainSize; mainSize = sect.PSize; _mainSubfile = _mixItems.Size(); } else mainSize2 = sect.PSize; } _mixItems.Add(mixItem); } if (mainSize2 >= (1 << 20) && mainSize < mainSize2 * 2) _mainSubfile = -1; for (i = 0; i < _mixItems.Size(); i++) { const CMixItem &mixItem = _mixItems[i]; if (mixItem.StringIndex < 0 && mixItem.ResourceIndex < 0 && _sections[mixItem.SectionIndex].Name == "_winzip_") { _mainSubfile = i; break; } } return S_OK; } HRESULT CalcCheckSum(ISequentialInStream *stream, UInt32 size, UInt32 excludePos, UInt32 &res) { // size &= ~1; const UInt32 kBufSize = 1 << 23; CByteBuffer buffer; buffer.SetCapacity(kBufSize); Byte *buf = buffer; UInt32 sum = 0; UInt32 pos = 0; for (;;) { UInt32 rem = size - pos; if (rem > kBufSize) rem = kBufSize; if (rem == 0) break; size_t processed = rem; RINOK(ReadStream(stream, buf, &processed)); /* for (; processed < rem; processed++) buf[processed] = 0; */ if ((processed & 1) != 0) buf[processed] = 0; for (int j = 0; j < 4; j++) { UInt32 p = excludePos + j; if (pos <= p && p < pos + processed) buf[p - pos] = 0; } for (size_t i = 0; i < processed; i += 2) { sum += Get16(buf + i); sum = (sum + (sum >> 16)) & 0xFFFF; } pos += (UInt32)processed; if (rem != processed) break; } sum += pos; res = sum; return S_OK; } STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback) { COM_TRY_BEGIN Close(); RINOK(Open2(inStream, callback)); _stream = inStream; return S_OK; COM_TRY_END } void CHandler::CloseResources() { _usedRes.Free(); _items.Clear(); _strings.Clear(); _buf.SetCapacity(0); } STDMETHODIMP CHandler::Close() { _stream.Release(); _sections.Clear(); _mixItems.Clear(); CloseResources(); return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = _mixItems.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 = _mixItems.Size(); if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) { const CMixItem &mixItem = _mixItems[allFilesMode ? i : indices[i]]; if (mixItem.StringIndex >= 0) totalSize += _strings[mixItem.StringIndex].Size; else if (mixItem.ResourceIndex < 0) totalSize += _sections[mixItem.SectionIndex].GetPackSize(); else totalSize += _items[mixItem.ResourceIndex].GetSize(); } 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); bool checkSumOK = true; if (_optHeader.CheckSum != 0 && (int)numItems == _mixItems.Size()) { UInt32 checkSum = 0; RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); CalcCheckSum(_stream, _totalSizeLimited, _peOffset + kHeaderSize + 64, checkSum); checkSumOK = (checkSum == _optHeader.CheckSum); } CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(streamSpec); streamSpec->SetStream(_stream); 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]; CMyComPtr outStream; RINOK(extractCallback->GetStream(index, &outStream, askMode)); const CMixItem &mixItem = _mixItems[index]; const CSection § = _sections[mixItem.SectionIndex]; bool isOk = true; if (mixItem.StringIndex >= 0) { const CStringItem &item = _strings[mixItem.StringIndex]; currentItemSize = item.Size; if (!testMode && !outStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); if (outStream) RINOK(WriteStream(outStream, item.Buf, item.Size)); } else if (mixItem.ResourceIndex < 0) { currentItemSize = sect.GetPackSize(); if (!testMode && !outStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); RINOK(_stream->Seek(sect.Pa, STREAM_SEEK_SET, NULL)); streamSpec->Init(currentItemSize); RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); isOk = (copyCoderSpec->TotalSize == currentItemSize); } else { const CResItem &item = _items[mixItem.ResourceIndex]; currentItemSize = item.GetSize(); if (!testMode && !outStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); size_t offset = item.Offset - sect.Va; if (!CheckItem(sect, item, offset)) isOk = false; else if (outStream) { if (item.HeaderSize != 0) RINOK(WriteStream(outStream, item.Header, item.HeaderSize)); RINOK(WriteStream(outStream, _buf + offset, item.Size)); } } outStream.Release(); RINOK(extractCallback->SetOperationResult(isOk ? checkSumOK ? NExtract::NOperationResult::kOK: NExtract::NOperationResult::kCRCError: NExtract::NOperationResult::kDataError)); } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) { COM_TRY_BEGIN *stream = 0; const CMixItem &mixItem = _mixItems[index]; const CSection § = _sections[mixItem.SectionIndex]; if (mixItem.IsSectionItem()) return CreateLimitedInStream(_stream, sect.Pa, sect.PSize, stream); CBufInStream *inStreamSpec = new CBufInStream; CMyComPtr streamTemp = inStreamSpec; CReferenceBuf *referenceBuf = new CReferenceBuf; CMyComPtr ref = referenceBuf; if (mixItem.StringIndex >= 0) { const CStringItem &item = _strings[mixItem.StringIndex]; referenceBuf->Buf.SetCapacity(item.Size); memcpy(referenceBuf->Buf, item.Buf, item.Size); } else { const CResItem &item = _items[mixItem.ResourceIndex]; size_t offset = item.Offset - sect.Va; if (!CheckItem(sect, item, offset)) return S_FALSE; if (item.HeaderSize == 0) { CBufInStream *streamSpec = new CBufInStream; CMyComPtr streamTemp2 = streamSpec; streamSpec->Init(_buf + offset, item.Size, (IInArchive *)this); *stream = streamTemp2.Detach(); return S_OK; } referenceBuf->Buf.SetCapacity(item.HeaderSize + item.Size); memcpy(referenceBuf->Buf, item.Header, item.HeaderSize); memcpy(referenceBuf->Buf + item.HeaderSize, _buf + offset, item.Size); } inStreamSpec->Init(referenceBuf); *stream = streamTemp.Detach(); return S_OK; COM_TRY_END } static IInArchive *CreateArc() { return new CHandler; } static CArcInfo g_ArcInfo = { L"PE", L"exe dll sys", 0, 0xDD, { 'P', 'E', 0, 0 }, 4, false, CreateArc, 0 }; REGISTER_ARC(Pe) }}