#include "pe/PEFile.h" #include "FileBuffer.h" bool PEFileBuilder::signatureMatches(AbstractByteBuffer *buf) { if (buf == NULL) return false; offset_t dosOffset = 0; WORD *magic = (WORD*) buf->getContentAt(dosOffset, sizeof(WORD)); if (magic == NULL) return false; if ((*magic) != pe::S_DOS) { return false; } offset_t newOffset = dosOffset + (sizeof(IMAGE_DOS_HEADER) - sizeof(LONG)); LONG* lfnew = (LONG*) buf->getContentAt(newOffset, sizeof(LONG)); if (lfnew == NULL) { return false; } offset_t peOffset = static_cast(*lfnew); DWORD *peMagic = (DWORD*) buf->getContentAt(peOffset, sizeof(DWORD)); if (peMagic == NULL) { return false; } if (*peMagic == pe::S_NT) { return true; } return false; } Executable* PEFileBuilder::build(AbstractByteBuffer *buf) { Executable *exe = NULL; if (signatureMatches(buf) == false) return NULL; try { exe = new PEFile(buf); } catch (ExeException) { exe = NULL; } return exe; } //------------------------------------------------------------- long PEFile::computeChecksum(const BYTE* buffer, size_t bufferSize, offset_t checksumOffset) { if (!buffer || !bufferSize) return 0; const WORD* wordsBuff = reinterpret_cast(buffer); const size_t wordsCount = bufferSize / sizeof(WORD); const size_t remainingBytes = bufferSize % sizeof(WORD); size_t checksumBgn = 0; size_t checksumEnd = 0; if (checksumOffset != INVALID_ADDR) { checksumBgn = size_t(checksumOffset); checksumEnd = checksumBgn + sizeof(DWORD); } const long long maxVal = ((long long)1) << 32; long long checksum = 0; for (int i = 0; i < wordsCount; i++) { WORD chunk = wordsBuff[i]; size_t bI = i * sizeof(WORD); if (checksumBgn != checksumEnd && bI >= checksumBgn && bI < checksumEnd) { size_t mask = (checksumEnd - bI) % sizeof(WORD); size_t shift = (sizeof(WORD) - mask) * 8; chunk = (chunk >> shift) << shift; } checksum = (checksum & 0xffffffff) + chunk + (checksum >> 32); if (checksum > maxVal) { checksum = (checksum & 0xffffffff) + (checksum >> 32); } } // Handle the remaining bytes if (remainingBytes > 0) { WORD chunk = 0; memcpy(&chunk, buffer + wordsCount * sizeof(WORD), remainingBytes); size_t bI = wordsCount * sizeof(WORD); if (checksumBgn != checksumEnd && bI >= checksumBgn && bI < checksumEnd) { size_t mask = (checksumEnd - bI) % sizeof(WORD); size_t shift = (sizeof(WORD) - mask) * 8; chunk = (chunk >> shift) << shift; } checksum = (checksum & 0xffffffff) + chunk + (checksum >> 32); if (checksum > maxVal) { checksum = (checksum & 0xffffffff) + (checksum >> 32); } } checksum = (checksum & 0xffff) + (checksum >> 16); checksum = (checksum)+(checksum >> 16); checksum = checksum & 0xffff; checksum += bufferSize; return checksum; } ///--- PEFile::PEFile(AbstractByteBuffer *v_buf) : MappedExe(v_buf, Executable::BITS_32), dosHdrWrapper(NULL), fHdr(NULL), optHdr(NULL), sects(NULL), album(NULL) { clearWrappers(); _init(v_buf); Logger::append(Logger::D_INFO,"Wrapped"); } void PEFile::_init(AbstractByteBuffer *v_buf) { // wrap the core: core.wrap(v_buf); album = new ResourcesAlbum(this); //generate wrappers: this->dosHdrWrapper = new DosHdrWrapper(this); this->wrappers[WR_DOS_HDR] = this->dosHdrWrapper; this->fHdr = new FileHdrWrapper(this); if (fHdr->getPtr() == NULL) throw ExeException("Cannot parse FileHdr: It is not PE File!"); this->wrappers[WR_FILE_HDR] = fHdr; this->wrappers[WR_RICH_HDR] = new RichHdrWrapper(this); this->optHdr = new OptHdrWrapper(this); if (optHdr->getPtr() == NULL) throw ExeException("Cannot parse OptionalHeader: It is not PE File!"); this->wrappers[WR_OPTIONAL_HDR] = optHdr; this->sects = new SectHdrsWrapper(this); this->wrappers[WR_SECTIONS] = sects; this->wrappers[WR_DATADIR] = new DataDirWrapper(this); dataDirEntries[pe::DIR_IMPORT] = new ImportDirWrapper(this); dataDirEntries[pe::DIR_DELAY_IMPORT] = new DelayImpDirWrapper(this); dataDirEntries[pe::DIR_BOUND_IMPORT] = new BoundImpDirWrapper(this); dataDirEntries[pe::DIR_DEBUG] = new DebugDirWrapper(this); dataDirEntries[pe::DIR_EXPORT] = new ExportDirWrapper(this); dataDirEntries[pe::DIR_SECURITY] = new SecurityDirWrapper(this); dataDirEntries[pe::DIR_TLS] = new TlsDirWrapper(this); dataDirEntries[pe::DIR_LOAD_CONFIG] = new LdConfigDirWrapper(this); dataDirEntries[pe::DIR_BASERELOC] = new RelocDirWrapper(this); dataDirEntries[pe::DIR_EXCEPTION] = new ExceptionDirWrapper(this); dataDirEntries[pe::DIR_RESOURCE] = new ResourceDirWrapper(this, album); dataDirEntries[pe::DIR_COM_DESCRIPTOR] = new ClrDirWrapper(this); for (int i = 0; i < pe::DIR_ENTRIES_COUNT; i++) { this->wrappers[WR_DIR_ENTRY + i] = dataDirEntries[i]; } if (this->album) { this->album->wrapLeafsContent(); } } void PEFile::clearWrappers() { initDirEntries(); MappedExe::clearWrappers(); this->dosHdrWrapper = NULL; this->fHdr = NULL; this->optHdr = NULL; this->sects = NULL; } void PEFile::initDirEntries() { for (size_t i = 0 ; i < pe::DIR_ENTRIES_COUNT; i++) { dataDirEntries[i] = NULL; } } void PEFile::wrapCore() { WatchedLocker lock(&m_peMutex, PE_SHOW_LOCK, __FUNCTION__); // rewrap the core: core.wrap(this->buf); // rewrap the PE headers: for (int i = 0; i < WR_DIR_ENTRY; i++) { this->wrappers[i]->wrap(); } // rewrap the sections: this->sects->wrap(); } bool PEFile::wrapDataDirs() { bool anyModified = false; // rewrap directories for (size_t i = 0 ; i < pe::DIR_ENTRIES_COUNT; i++) { if (dataDirEntries[i]) { if (dataDirEntries[i]->wrap()) { anyModified = true; } } } // rewrap resources if (this->album) { this->album->wrapLeafsContent(); } return anyModified; } void PEFile::wrap() { wrapCore(); wrapDataDirs(); } pe::RICH_DANS_HEADER* PEFile::getRichHeaderBgn(pe::RICH_SIGNATURE* richSign) { if (!richSign) return NULL; DWORD xorkey = richSign->checksum; const offset_t richOffset = this->getOffset(richSign); pe::RICH_DANS_HEADER* dansHdr = NULL; offset_t offset = richOffset - sizeof(pe::RICH_DANS_HEADER); while (offset > 0) { dansHdr = (pe::RICH_DANS_HEADER*) this->getContentAt(offset, sizeof(pe::RICH_DANS_HEADER)); if (!dansHdr) { break; } if (dansHdr->dansId == (pe::DANS_HDR_MAGIC ^ xorkey)) { break; //got it! } //walking back offset -= sizeof(DWORD); } if (!dansHdr || dansHdr->dansId != (pe::DANS_HDR_MAGIC ^ xorkey)) { return NULL; //not found } return dansHdr; } pe::RICH_SIGNATURE* PEFile::getRichHeaderSign() { size_t dosStubOffset = this->core.dos->e_lfarlc; size_t dosStubEnd = this->core.dos->e_lfanew; // PE header start const size_t maxSize = dosStubEnd - dosStubOffset; // Rich Header is somewhere in the space between DOS and PE headers BYTE *dosPtr = this->getContentAt(dosStubOffset, maxSize); if (!dosPtr) { return NULL; } pe::RICH_SIGNATURE* richSign = NULL; size_t toSearchSize = maxSize; const offset_t startOffset = dosStubOffset; //we are starting from the beginning of DOS stub const size_t step = sizeof(DWORD); //RichHeader is padded by DWORDS while (toSearchSize > 0) { richSign = (pe::RICH_SIGNATURE*) this->getContentAt(startOffset + toSearchSize, sizeof(pe::RICH_SIGNATURE)); if (!richSign) break; if (richSign->richId == pe::RICH_HDR_MAGIC) break; //got it! // the search goes backward. toSearchSize -= step; } if (!richSign) return NULL; if (richSign->richId != pe::RICH_HDR_MAGIC) { return NULL; //invalid } return richSign; } offset_t PEFile::getMinSecRVA() { WatchedLocker lock(&m_peMutex, PE_SHOW_LOCK, __FUNCTION__); if (!this->_getSectionsCount()) { return INVALID_ADDR; } SectionHdrWrapper* sec = this->_getSecHdr(0); if (!sec) { return INVALID_ADDR; } return sec->getContentOffset(Executable::RVA); } offset_t PEFile::peDataDirOffset() { if (this->optHdr == NULL) return INVALID_ADDR; return optHdr->getFieldOffset(OptHdrWrapper::DATA_DIR); } IMAGE_DATA_DIRECTORY* PEFile::getDataDirectory() { if (this->wrappers[WR_DATADIR] == NULL) return NULL; return static_cast(this->wrappers[WR_DATADIR]->getPtr()); } bufsize_t PEFile::getMappedSize(Executable::addr_type aType) { if (aType == Executable::NOT_ADDR) return 0; if (aType == Executable::RAW) { return this->getContentSize(); } const size_t unit_size = 0x1000; bufsize_t vSize = 0; if (aType == Executable::VA || aType == Executable::RVA) { vSize = core.getImageSize(); } if (vSize < unit_size) { return unit_size; } return vSize; } offset_t PEFile::getEntryPoint(Executable::addr_type addrType) { if (optHdr == NULL) return INVALID_ADDR; bool isOk = false; offset_t entryPoint = static_cast (optHdr->getNumValue(OptHdrWrapper::EP, &isOk)); if (isOk == false) return INVALID_ADDR; const Executable::addr_type epType = Executable::RVA; if (addrType != epType) { entryPoint = this->convertAddr(entryPoint, epType, addrType); } return entryPoint; } bool PEFile::setEntryPoint(offset_t entry, Executable::addr_type aType) { if (optHdr == NULL) return false; offset_t epRva = this->convertAddr(entry, aType, Executable::RVA); bool isOk = optHdr->setNumValue(OptHdrWrapper::EP, epRva); return isOk; } size_t PEFile::hdrSectionsNum() const { bool isOk = false; uint64_t secNum = this->fHdr->getNumValue(FileHdrWrapper::SEC_NUM , &isOk); if (isOk == false) return 0; return static_cast (secNum); } bool PEFile::setHdrSectionsNum(size_t newNum) { uint64_t count = newNum; bool canSet = fHdr->setNumValue(FileHdrWrapper::SEC_NUM , count); if (canSet == false) { Logger::append(Logger::D_ERROR,"Can not change FileHdr!"); return false; } return true; } bool PEFile::setVirtualSize(bufsize_t newSize) { uint64_t size = newSize; bool canSet = optHdr->setNumValue(OptHdrWrapper::IMAGE_SIZE, size); if (canSet == false) { Logger::append(Logger::D_ERROR, "Can not change OptHdr!"); return false; } return true; } size_t PEFile::_getSectionsCount(bool useMapped) const { if (useMapped == false) { return hdrSectionsNum(); } return (this->sects) ? this->sects->getEntriesCount() : 0; } offset_t PEFile::rawToRva(offset_t raw) { WatchedLocker lock(&m_peMutex, PE_SHOW_LOCK, __FUNCTION__); if (raw >= this->getMappedSize(Executable::RAW)) return INVALID_ADDR; SectionHdrWrapper* sec = this->_getSecHdrAtOffset(raw, Executable::RAW, true); if (sec) { offset_t bgnVA = sec->getContentOffset(Executable::VA); offset_t bgnRaw = sec->getContentOffset(Executable::RAW); if (bgnVA == INVALID_ADDR || bgnRaw == INVALID_ADDR) return INVALID_ADDR; bufsize_t curr = (raw - bgnRaw); bufsize_t vSize = sec->getContentSize(Executable::VA, true); if (curr >= vSize) { //address out of section return INVALID_ADDR; } return bgnVA + curr; } //TODO: make more tests if (this->_getSectionsCount() == 0) return raw; if (raw < this->hdrsSize()) { return raw; } //else: content that is between the end of sections headers and the first virtual section is not mapped return INVALID_ADDR; } offset_t PEFile::rvaToRaw(offset_t rva) { WatchedLocker lock(&m_peMutex, PE_SHOW_LOCK, __FUNCTION__); if (rva >= this->getMappedSize(Executable::RVA)) { return INVALID_ADDR; } SectionHdrWrapper* sec = this->_getSecHdrAtOffset(rva, Executable::RVA, true); if (sec) { offset_t bgnRVA = sec->getContentOffset(Executable::RVA); offset_t bgnRaw = sec->getContentOffset(Executable::RAW); if (bgnRVA == INVALID_ADDR || bgnRaw == INVALID_ADDR) { return INVALID_ADDR; } bufsize_t curr = (rva - bgnRVA); bufsize_t rawSize = sec->getContentSize(Executable::RAW, true); if (curr >= rawSize) { // the address might be in a virtual cave that is not related to any raw address return INVALID_ADDR; } return bgnRaw + curr; } if (rva >= this->getMappedSize(Executable::RAW)) { return INVALID_ADDR; } if (this->_getSectionsCount()) { // do this check only if sections count is non-zero if (rva >= this->hdrsSize()) { // the address is in the cave between the headers and the first section: cannot be mapped return INVALID_ADDR; } } // at this point we are sure that the address is within the raw size: return rva; } DataDirEntryWrapper* PEFile::getDataDirEntry(pe::dir_entry eType) { if (eType >= pe::DIR_ENTRIES_COUNT) return NULL; return dataDirEntries[eType]; } BufferView* PEFile::createSectionView(size_t secId) { WatchedLocker lock(&m_peMutex, PE_SHOW_LOCK, __FUNCTION__); SectionHdrWrapper *sec = this->_getSecHdr(secId); if (!sec) { Logger::append(Logger::D_WARNING, "No such section"); return NULL; } return _createSectionView(sec); } bool PEFile::moveDataDirEntry(pe::dir_entry dirNum, offset_t newOffset, Executable::addr_type addrType) { bool allowExceptions = true; //TODO: configure exception mode outside... DataDirEntryWrapper *entry = getDataDirEntry(dirNum); if (entry == NULL) { if (allowExceptions) throw ExeException("No such Data Directory"); return false; } DataDirWrapper* ddirWrapper = dynamic_cast (this->wrappers[WR_DATADIR]); IMAGE_DATA_DIRECTORY *ddir = this->getDataDirectory(); if (ddirWrapper == NULL || ddir == NULL) { if (allowExceptions) throw ExeException("Cannot fetch DataDirTable"); return false; } Executable::addr_type dataDirAddrType = ddirWrapper->containsAddrType(dirNum, DataDirWrapper::ADDRESS); offset_t dataDirAddr = this->convertAddr(newOffset, addrType, dataDirAddrType); if (dataDirAddr == INVALID_ADDR) { if (allowExceptions) throw ExeException("Invalid new offset"); return false; } offset_t targetRaw = this->toRaw(newOffset, addrType); if (entry->canCopyToOffset(targetRaw) == false) { if (allowExceptions) throw ExeException("Cannot copy: no space at such offset"); return false; } if (entry->copyToOffset(targetRaw) == false) { if (allowExceptions) throw ExeException("Cannot copy: error occured"); return false; } entry->fillContent(0); ddir[dirNum].VirtualAddress = static_cast(dataDirAddr); entry->wrap(); return true; } bool PEFile::_canAddNewSection() { ExeNodeWrapper* sec = dynamic_cast(getWrapper(PEFile::WR_SECTIONS)); if (sec == NULL || sec->canAddEntry() == false) { return false; } const size_t secCount = hdrSectionsNum(); if (secCount == SectHdrsWrapper::SECT_COUNT_MAX) { return false; //limit exceeded } //TODO: some more checks? overlay? return true; } SectionHdrWrapper* PEFile::addNewSection(QString name, bufsize_t r_size, bufsize_t v_size) { if (!r_size && !v_size) return nullptr; //nothing to add bufsize_t newSize = 0; bufsize_t roundedRawEnd = 0; bufsize_t roundedVirtualEnd = 0; SectionHdrWrapper* secHdrWr = nullptr; { //scope0 WatchedLocker lock(&m_peMutex, PE_SHOW_LOCK, __FUNCTION__); if (!_canAddNewSection()) { return nullptr; } if (!v_size) { v_size = r_size; } roundedRawEnd = buf_util::roundupToUnit(getMappedSize(Executable::RAW), getAlignment(Executable::RAW)); roundedVirtualEnd = buf_util::roundupToUnit(getMappedSize(Executable::RVA), getAlignment(Executable::RVA)); newSize = roundedRawEnd + r_size; const bufsize_t newVirtualSize = roundedVirtualEnd + v_size; if (setVirtualSize(newVirtualSize) == false) { Logger::append(Logger::D_ERROR, "Failed to change virtual size to: %X", newVirtualSize); return nullptr; } } //scope0 // the PE file is resized and rewrapped: if (resize(newSize) == false) { Logger::append(Logger::D_ERROR, "Failed to resize"); return nullptr; } { //scope1 WatchedLocker lock(&m_peMutex, PE_SHOW_LOCK, __FUNCTION__); // fetch again after resize: ExeNodeWrapper* sec = dynamic_cast(getWrapper(PEFile::WR_SECTIONS)); if (!sec) { return nullptr; } IMAGE_SECTION_HEADER secHdr; ::memset(&secHdr, 0, sizeof(IMAGE_SECTION_HEADER)); //name copy: const size_t nameLen = name.length(); const size_t bufSize = sizeof(secHdr.Name); const size_t copySize = (nameLen < bufSize) ? nameLen : bufSize; if (copySize) { ::memcpy(secHdr.Name, name.toStdString().c_str(), copySize); } secHdr.PointerToRawData = static_cast(roundedRawEnd); secHdr.VirtualAddress = static_cast(roundedVirtualEnd); secHdr.SizeOfRawData = MASK_TO_DWORD(r_size); secHdr.Misc.VirtualSize = MASK_TO_DWORD(v_size); SectionHdrWrapper wr(this, &secHdr); secHdrWr = dynamic_cast(sec->addEntry(&wr)); } //!scope1 return secHdrWr; } SectionHdrWrapper* PEFile::_getLastSection() { size_t secCount = this->_getSectionsCount(true); return (secCount > 0) ? this->_getSecHdr(secCount - 1) : nullptr; } offset_t PEFile::_getLastMapped(Executable::addr_type aType) { offset_t lastMapped = 0; /* check sections bounds */ const size_t secCounter = this->_getSectionsCount(true); if (!secCounter) { // if PE file has no sections, full file will be mapped return getMappedSize(aType); } for (size_t i = 0; i < secCounter; i++) { SectionHdrWrapper *sec = this->_getSecHdr(i); if (!sec) continue; offset_t secLastMapped= sec->getContentOffset(aType, true); if (secLastMapped == INVALID_ADDR) continue; const size_t size = (aType == Executable::RAW) ? sec->getMappedRawSize() : sec->getMappedVirtualSize(); if (size == 0) continue; // exclude not mapped sections secLastMapped += size; if (secLastMapped > lastMapped) { lastMapped = secLastMapped; } } /* check header bounds */ /* section headers: */ if (lastMapped < this->_secHdrsEndOffset()) { lastMapped = this->_secHdrsEndOffset(); } // PE hdrs ending: const offset_t peHdrsEnd = this->core.peSignatureOffset() + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER) + this->core.peNtHeadersSize(); if (lastMapped < peHdrsEnd) { lastMapped = peHdrsEnd; } // OptionalHdr -> SizeOfHeaders: const offset_t ntHeadersEndOffset = this->core.hdrsSize(); if (lastMapped < ntHeadersEndOffset) { lastMapped = ntHeadersEndOffset; } return lastMapped; } SectionHdrWrapper* PEFile::extendLastSection(bufsize_t addedSize) { bufsize_t newSize = 0; { //scope0 WatchedLocker lock(&m_peMutex, PE_SHOW_LOCK, __FUNCTION__); SectionHdrWrapper* secHdr = _getLastSection(); if (!secHdr) return nullptr; //TODO: check overlay... const bufsize_t fullSize = getContentSize(); newSize = fullSize + addedSize; const offset_t secROffset = secHdr->getContentOffset(Executable::RAW, false); if (secROffset == INVALID_ADDR) { return NULL; } const bufsize_t secNewRSize = newSize - secROffset; //include overlay in section secHdr->setNumValue(SectionHdrWrapper::RSIZE, uint64_t(secNewRSize)); const offset_t secVOffset = secHdr->getContentOffset(Executable::RVA, false); const bufsize_t secVSize = secHdr->getContentSize(Executable::RVA, false); // if the previous virtual size is smaller than the new raw size, then update it: if (secVSize < secNewRSize) { secHdr->setNumValue(SectionHdrWrapper::VSIZE, uint64_t(secNewRSize)); // if the virtual size of section has changed, // update the Size of Image (saved in the header): bufsize_t newVSize = secVOffset + secNewRSize; this->setVirtualSize(newVSize); } } //!scope0 //update raw size: this->resize(newSize); //finally, retrieve the resized section: return getLastSection(); } bool PEFile::dumpSection(SectionHdrWrapper *sec, QString fileName) { WatchedLocker lock(&m_peMutex, PE_SHOW_LOCK, __FUNCTION__); if (this->_getSecIndex(sec) == SectHdrsWrapper::SECT_INVALID_INDEX) { return false; //not my section } BufferView *secView = this->_createSectionView(sec); if (!secView) return false; bufsize_t dumpedSize = FileBuffer::dump(fileName, *secView, false); delete secView; return dumpedSize ? true : false; } bool PEFile::unbindImports() { IMAGE_DATA_DIRECTORY* ddir = this->getDataDirectory(); if (ddir[pe::DIR_BOUND_IMPORT].VirtualAddress == 0 && ddir[pe::DIR_BOUND_IMPORT].Size == 0) { // No bound imports already, nothing to do here! return true; } ddir[pe::DIR_BOUND_IMPORT].VirtualAddress = 0; ddir[pe::DIR_BOUND_IMPORT].Size = 0; DataDirEntryWrapper *bImp = this->getDataDirEntry(pe::DIR_BOUND_IMPORT); if (bImp == NULL) { //printf("No Bound imports wrapper!\n"); return false; // todo: throw error? } bool isOk = bImp->wrap(); //TODO: change timestamp for all library entries from (-1 : BOUND) to 0 : NOT BOUND return isOk; } //protected: BufferView* PEFile::_createSectionView(SectionHdrWrapper *sec) { Executable::addr_type aType = Executable::RAW; offset_t start = sec->getContentOffset(aType, true); bufsize_t size = sec->getContentSize(aType, true); if (start == INVALID_ADDR || size == 0) { return NULL; } return new BufferView(this, start, size); } size_t PEFile::getExportsMap(QMap &entrypoints, Executable::addr_type aType) { size_t initialSize = entrypoints.size(); ExportDirWrapper* exports = dynamic_cast(this->getWrapper(PEFile::WR_DIR_ENTRY + pe::DIR_EXPORT)); if (!exports) return 0; const size_t entriesCnt = exports->getEntriesCount(); if (entriesCnt == 0) return 0; for (int i = 0; i < entriesCnt; i++) { ExportEntryWrapper* entry = dynamic_cast(exports->getEntryAt(i)); if (!entry) continue; QString forwarder = entry->getForwarderStr(); if (forwarder.length()) { continue; } offset_t rva = entry->getFuncRva(); offset_t offset = this->convertAddr(rva, Executable::RVA, aType); if (offset == INVALID_ADDR) { continue; } entrypoints.insert(offset, entry->getName()); } return entrypoints.size() - initialSize; }