PEParser.java

/*******************************************************************************
 * This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 *
 * Contributors:
 *     Peter Smith
 *******************************************************************************/
package org.owasp.dependencycheck.utils;

import com.kichik.pecoff4j.COFFHeader;
import com.kichik.pecoff4j.DOSHeader;
import com.kichik.pecoff4j.DOSStub;
import com.kichik.pecoff4j.DebugDirectory;
import com.kichik.pecoff4j.ImageData;
import com.kichik.pecoff4j.OptionalHeader;
import com.kichik.pecoff4j.PE;
import com.kichik.pecoff4j.PESignature;
import com.kichik.pecoff4j.SectionData;
import com.kichik.pecoff4j.SectionTable;
import com.kichik.pecoff4j.io.DataEntry;
import com.kichik.pecoff4j.io.DataReader;
import com.kichik.pecoff4j.io.IDataReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * This includes a copy of {@link PE#read(IDataReader)} and a couple of private methods
 * with some added error handling to swallow EOFExceptions when reading certain sections of the file
 * to be a bit more lenient on some "corrupt" (or not fully handled) dlls, per
 * <a href="https://github.com/dependency-check/DependencyCheck/issues/2601">...</a>
 *
 * @see com.kichik.pecoff4j.io.PEParser#parse(File) 
 * @see PE#read(IDataReader)
 */
public class PEParser {
    private static final Logger LOGGER = LoggerFactory.getLogger(PEParser.class);

    public static PE parse(File file) throws IOException {
        try (FileInputStream is = new FileInputStream(file); DataReader dr = new DataReader(is)) {
            return read(dr, file.getPath());
        }
    }

    /**
     * Duplicates {@link PE#read(IDataReader)} with added error handling to swallow EOFExceptions to be more lenient
     * for certain file sections.
     * @see PE#read(IDataReader)
     */
    private static PE read(IDataReader dr, String context) throws IOException {
        PE pe = new PE();
        pe.setDosHeader(DOSHeader.read(dr));

        // Check if we have an old file type
        if (pe.getDosHeader().getAddressOfNewExeHeader() == 0
                || pe.getDosHeader().getAddressOfNewExeHeader() > 8192) {
            return pe;
        }

        pe.setStub(DOSStub.read(pe.getDosHeader(), dr));
        pe.setSignature(PESignature.read(dr));

        // Check signature to ensure we have a pe/coff file
        if (!pe.getSignature().isValid()) {
            return pe;
        }

        pe.setCoffHeader(COFFHeader.read(dr));
        pe.setOptionalHeader(OptionalHeader.read(dr));
        pe.setSectionTable(SectionTable.read(pe, dr));

        pe.set64(pe.getOptionalHeader().isPE32plus());

        // Now read the rest of the file
        DataEntry entry;
        while ((entry = pe.findNextEntry(dr.getPosition())) != null) {
            DataEntry finalEntry = entry;
            if (finalEntry.isSection) {
                SectionData.read(pe, finalEntry, dr);
            } else if (entry.isDebugRawData) {
                withEofSwallowing(() -> readDebugRawData(pe, finalEntry, dr), "debug raw data: " + context);
            } else {
                withEofSwallowing(() -> pe.getImageData().read(pe, finalEntry, dr), "image data: " + context);
            }
        }

        // Read any trailing data
        withEofSwallowing(() -> {
            byte[] tb = dr.readAll();
            if (tb.length > 0) {
                pe.getImageData().setTrailingData(tb);
            }
        }, "trailing data: " + context);

        return pe;
    }

    /**
     * Duplicates {@link PE#readDebugRawData(PE, DataEntry, IDataReader)} since it is private.
     * @see PE#readDebugRawData(PE, DataEntry, IDataReader)
     */
    private static void readDebugRawData(PE pe, DataEntry entry, IDataReader dr) throws IOException {
        // Read any preamble data
        ImageData id = pe.getImageData();
        byte[] pa = dr.readNonZeroOrNull(entry.pointer);
        if (pa != null) {
            id.setDebugRawDataPreamble(pa);
        }
        DebugDirectory dd = id.getDebug();
        byte[] b = new byte[dd.getSizeOfData()];
        dr.read(b);
        id.setDebugRawData(b);
    }

    private static void withEofSwallowing(IOExceptionThrower throwingRunnable, String errorContext) throws IOException {
        try {
            throwingRunnable.read();
        } catch (EOFException e) {
            LOGGER.debug("Error reading {}. Trying to continue...", errorContext, e);
        }
    }

    private interface IOExceptionThrower {
        void read() throws IOException;
    }
}