All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.katjahahn.parser.MemoryMappedPE.scala Maven / Gradle / Ivy

The newest version!
/**
 * *****************************************************************************
 * Copyright 2014 Katja Hahn
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ****************************************************************************
 */
package com.github.katjahahn.parser

import com.github.katjahahn.parser.MemoryMappedPE._
import com.github.katjahahn.parser.ScalaIOUtil._
import com.github.katjahahn.parser.optheader.WindowsEntryKey
import com.github.katjahahn.parser.sections.SectionLoader
import org.apache.logging.log4j.LogManager

import scala.collection.JavaConverters._
import scala.collection.mutable.ListBuffer

/**
 * Represents the PE file content as it is mapped to memory.
 * 

* The bytes are read from file only on request, making it possible to map large files. * Only maps section bytes for now. No file headers. * * @author Karsten Hahn * * @param mappings A list of section mappings, in ascending order of virtual addresses * @param data the PEData instance of the file */ class MemoryMappedPE( private val mappings: List[Mapping], private val data: PEData) { /** * Determines how many bytes are read at once while checking indexWhere and indexOf. */ private val chunkSize = 1024 /** * Returns the virtual offset for the given physical address, if it is within * a mapping. Otherwise -1 is returned. *

* Spits out a warning if several matching virtual addresses are found and * returns the last one. *

* @Beta This method is subject to change * @TODO remove -1 as return value * * @param physAddr physical address * @return virtual address for physAddr, -1 if it does not exist */ def physToVirtAddress(physAddr: Long): Long = { val addresses = _physToVirtAddresses(physAddr) // VA doesn't exist, return -1 if (addresses.isEmpty) -1 else { if (addresses.size > 1) { logger.warn(s"Caution: Several mappings for the file offset (${hex(physAddr)}) found.") } addresses.last } } /** * Returns all virtual addresses that are mapped to this physical address. *

* Scala method. Use physToVirtAddress(Long) for Java. *

* @Beta This method is subject to change * * @param physAddr the physical address * @return list of virtual addresses that are mapped to physAddr */ def _physToVirtAddresses(physAddr: Long): List[Long] = { // find mapping that contains physAddr val matchedMappings = mappings.filter(m => m.physRange.contains(physAddr)) // return virtual addresses matchedMappings.map(m => m.virtRange.start + (physAddr - m.physRange.start)) } /** * Returns the physical offset for the given virtual address, if it is within * a mapping. Otherwise -1 is returned. *

* Spits out a warning if several matching physical addresses are found and * returns the last one. *

* @Beta This method is subject to change * @param va the virtual address * @return file offset for the VA, -1 if it does not exist */ def virtToPhysAddress(va: Long): Long = { val addresses = _virtToPhysAddresses(va) // VA doesn't exist, return -1 if (addresses.isEmpty) -1 else { if (addresses.size > 1) { logger.warn(s"Caution: Several mappings for the VA (${hex(va)}) found, that means the VA is overwritten.") } addresses.last } } /** * Returns all physical addresses that are mapped to the VA. *

* @Beta This method is subject to change * @param va the virtual address * @return list of file offsets */ def virtToPhysAddresses(va: Long): java.util.List[Long] = _virtToPhysAddresses(va).asJava /** * Returns all physical addresses that are mapped to the VA. *

* Scala method. Use virtToPhysAddress(Long) for Java. *

* @Beta This method is subject to change * @param va the virtual address * @return list of file offsets */ def _virtToPhysAddresses(va: Long): List[Long] = { // find mapping that contains VA val matchedMappings = mappings.filter(m => m.virtRange.contains(va)) // return physical addresses matchedMappings.map(m => m.physRange.start + (va - m.virtRange.start)) } /**Array-like methods**/ /** * Returns byte at position i. *

* Scala method. Use get() for Java. * * @param i virtual index/position * @return byte at position i */ def apply(i: Long): Byte = { // find a mapping that contains i val mapping = mappings.find(m => m.virtRange.contains(i)) mapping match { case Some(m) => m(i) // no mapping found, return 0 for unmapped virtual location case None => 0.toByte } } /** * Returns byte at position i. * * @param i virtual index/position * @return byte at position i */ def get(i: Long): Byte = apply(i) /** * Returns the size of the memory mapped information. *

* Bytes above that size are always 0. * * @return size of memory mapped information */ def length(): Long = if (mappings.isEmpty) 0L else mappings.last.virtRange.end /** * Returns a byte array of the specified segment. *

* The distance until-from has to be in Integer range. * * @param from the virtual start offset * @param until the virtual end offset * @return byte array containing the elements greater than or equal to index * from extending up to (but not including) index until */ def slice(from: Long, until: Long): Array[Byte] = { require((until - from) == (until - from).toInt) // fetch all mappings for that range val sliceMappings = mappingsInRange(new VirtRange(from, until)) // create zero filled array val bytes = zeroBytes((until - from).toInt) // fill byte array with actual values sliceMappings.foreach { mapping => // determine range of bytes to be read for this mapping val start = Math.max(mapping.virtRange.start, from) val end = Math.min(mapping.virtRange.end, until) // read bytes val mappedBytes = mapping(start, (end - start).toInt) // write bytes into result-array for (i <- 0 until mappedBytes.length) { // calculate index to write the byte to val index = (start - from).toInt + i bytes(index) = mappedBytes(i) } } bytes } /** * Filters all mappings that are relevant for the range. *

* Relevant means the mapping maps VAs of that range to physical addresses * * @param range the virtual range */ private def mappingsInRange(range: VirtRange): List[Mapping] = { val (from, until) = range.unpack mappings.filter(m => m.virtRange.end >= from && m.virtRange.start <= until) } /** * Returns the index of the first byte that satisfies the condition, returns -1 * if no such byte was found. * * @param p the function that specifies the condition * @param from virtual offset to start searching from * @return index of the first byte that satisfies the condition, * -1 if no byte satisfies the condition */ def indexWhere(p: Byte => Boolean, from: Long): Long = { // no byte found, from exceeds MemoryMappedPE length, return -1 if (from > length) -1 else { // read chunkSize bytes val bytes = slice(from, from + chunkSize) // get index for first byte that satisfies the condition val index = bytes.indexWhere(p) // no such byte found, recursive call to search the rest if (index == -1) { val nextIndex = this.indexWhere(p, from + chunkSize) // if no byte of the rest satisfies the condition, return -1 // otherwise update the found index with chunkSize and return it if (nextIndex == -1) -1 else chunkSize + nextIndex } else { // byte was found in current chunk, update index with the starting VA from + index } } } /** * Returns the index of the first byte that has the value. * * @param value value of the byte searched for * @param from offset to start searching from * @return index of the first byte that has the value */ def indexOf(elem: Byte, from: Long): Long = indexWhere(((b: Byte) => b == elem), from) /**ByteArrayUtil methods**/ /** * Retrieves the integer value of the bytes in the memory mapped PE given by * offset and length. *

* The values are considered little endian. *

* Length must be between 1 and 4 inclusive. * * @param offset * the index to start reading the integer value from * @param length * the number of bytes used to convert to int * @return int value */ def getBytesIntValue(offset: Long, length: Int): Int = { assert(length <= 4 && length > 0) ByteArrayUtil.bytesToInt(this.slice(offset, offset + length)) } /** * Retrieves the integer value of the bytes in the memory mapped PE given by * offset and length. *

* The values are considered little endian. *

* Length must be between 1 and 8 inclusive. * * @param offset * the index to start reading the long value from * @param length * the number of bytes used to convert to long * @return long value */ def getBytesLongValue(offset: Long, length: Int): Long = { assert(length <= 8 && length > 0) ByteArrayUtil.bytesToLong(this.slice(offset, offset + length)) } } /** * Responsible for creating the memory mappings */ object MemoryMappedPE { private val logger = LogManager.getLogger(MemoryMappedPE.getClass().getName()) /** * Creates a representation of the PE content as it is mapped into memory * * @param data the PEData object * @param secLoader the section loader instance * @returns a memory mapped PE */ def newInstance(data: PEData, secLoader: SectionLoader): MemoryMappedPE = apply(data, secLoader) /** * Creates a representation of the PE content as it is mapped into memory * * @param data the PEData object * @param secLoader the section loader instance * @returns a memory mapped PE */ def apply(data: PEData, secLoader: SectionLoader): MemoryMappedPE = { val mappings = readMemoryMappings(data, secLoader) new MemoryMappedPE(mappings, data) } /** * Reads and returns the memory mappings for the sections. * * @param data the PEData object * @param secLoader the section loader instance * @returns a list of mappings */ private def readMemoryMappings(data: PEData, secLoader: SectionLoader): List[Mapping] = { val optHeader = data.getOptionalHeader /* in low alignment mode all virtual addresses equal their physical counterparts thus, the whole file is mapped as is */ if (optHeader.isLowAlignmentMode()) { val filesize = data.getFile.length List(new Mapping(new VirtRange(0, filesize), new PhysRange(0, filesize), data)) } else { /* not low alignment mode, so mappings are applied per section */ val mappings = getHeaderMappings(secLoader, data) ++ getSectionMappings(secLoader, data) // sort mappings to be in ascending order for their virtual start val sorted = mappings.sortBy(m => m.virtRange.start) sorted.toList } } /** * Returns the header mappings of the file. * * @param secLoader the section loader instance * @param data the PEData object * @return list of header mapping(s) */ private def getHeaderMappings(secLoader: SectionLoader, data: PEData): List[Mapping] = { val table = data.getSectionTable val lowAlign = data.getOptionalHeader.isLowAlignmentMode if (table.getNumberOfSections() > 0) { // size of headers is also size of header mapping val sizeOfHeaders = data.getOptionalHeader.get(WindowsEntryKey.SIZE_OF_HEADERS) // virtual end of header mapping marked by VA of first section val vEnd = table.getSectionHeader(1).getAlignedVirtualAddress(lowAlign) - 1 // virtual start of headers val vStart = vEnd - sizeOfHeaders /* create mapping with ranges */ val virtRange = new VirtRange(vStart, vEnd) val pStart = data.getPESignature().getOffset() logger.debug("pesig offset: " + pStart) val pEnd = pStart + sizeOfHeaders val physRange = new PhysRange(pStart, pEnd) logger.debug("header mapping (v --> p): " + virtRange + " --> " + physRange) //TODO remove List(new Mapping(virtRange, physRange, data)) } else Nil //TODO add mapping } /** * Returns the section mappings of the file. * * @param secLoader the section loader instance * @param data the PEData object * @return list of section mapping(s) */ private def getSectionMappings(secLoader: SectionLoader, data: PEData): ListBuffer[Mapping] = { val table = data.getSectionTable val mappings = ListBuffer[Mapping]() val lowAlign = data.getOptionalHeader.isLowAlignmentMode // get all valid section headers for (header <- table.getSectionHeaders().asScala if secLoader.isValidSection(header)) { val readSize = secLoader.getReadSize(header) /* calculate physical range */ val pStart = header.getAlignedPointerToRaw(lowAlign) val pEnd = pStart + readSize val physRange = new PhysRange(pStart, pEnd) /* calculate virtual range */ val vStart = header.getAlignedVirtualAddress(lowAlign) val vEnd = vStart + readSize val virtRange = new VirtRange(vStart, vEnd) logger.debug("section mapping (v --> p): " + virtRange + " --> " + physRange) //TODO remove // add mapping to list mappings += new Mapping(virtRange, physRange, data) } mappings } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy