PEAnalyzer.java

  1. /*
  2.  * This file is part of dependency-check-core.
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *     http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  *
  16.  * Copyright (c) 2020 Jeremy Long. All Rights Reserved.
  17.  */
  18. package org.owasp.dependencycheck.analyzer;

  19. import com.github.packageurl.MalformedPackageURLException;
  20. import java.io.File;
  21. import java.io.FileFilter;
  22. import java.io.IOException;

  23. import org.boris.pecoff4j.PE;
  24. import org.boris.pecoff4j.ResourceDirectory;
  25. import org.boris.pecoff4j.ResourceEntry;
  26. import org.boris.pecoff4j.constant.ResourceType;
  27. import org.owasp.dependencycheck.utils.PEParser;
  28. import org.boris.pecoff4j.io.ResourceParser;
  29. import org.boris.pecoff4j.resources.StringFileInfo;
  30. import org.boris.pecoff4j.resources.StringTable;
  31. import org.boris.pecoff4j.resources.VersionInfo;
  32. import org.boris.pecoff4j.util.ResourceHelper;

  33. import javax.annotation.concurrent.ThreadSafe;
  34. import org.apache.commons.lang3.StringUtils;

  35. import org.owasp.dependencycheck.Engine;
  36. import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
  37. import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
  38. import org.owasp.dependencycheck.dependency.Confidence;
  39. import org.owasp.dependencycheck.dependency.Dependency;
  40. import org.owasp.dependencycheck.dependency.Evidence;
  41. import org.owasp.dependencycheck.dependency.EvidenceType;
  42. import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
  43. import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
  44. import org.owasp.dependencycheck.exception.InitializationException;
  45. import org.owasp.dependencycheck.utils.DependencyVersion;
  46. import org.owasp.dependencycheck.utils.DependencyVersionUtil;
  47. import org.owasp.dependencycheck.utils.FileFilterBuilder;
  48. import org.owasp.dependencycheck.utils.FileUtils;
  49. import org.owasp.dependencycheck.utils.Settings;
  50. import org.slf4j.Logger;
  51. import org.slf4j.LoggerFactory;

  52. /**
  53.  * Takes a dependency and analyze the PE header for meta data that can be used
  54.  * to identify the library.
  55.  *
  56.  * @author Amodio Pesce
  57.  */
  58. @ThreadSafe
  59. @Experimental
  60. public class PEAnalyzer extends AbstractFileTypeAnalyzer {

  61.     //<editor-fold defaultstate="collapsed" desc="All standard implementation details of Analyzer">
  62.     /**
  63.      * Logger
  64.      */
  65.     private static final Logger LOGGER = LoggerFactory.getLogger(AssemblyAnalyzer.class);
  66.     /**
  67.      * The name of the analyzer.
  68.      */
  69.     private static final String ANALYZER_NAME = "PE Analyzer";

  70.     /**
  71.      * The phase that this analyzer is intended to run in.
  72.      */
  73.     private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION2;
  74.     /**
  75.      * The set of file extensions supported by this analyzer.
  76.      */
  77.     private static final String[] EXTENSIONS = {"exe", "dll"};

  78.     /**
  79.      * The file filter used to determine which files this analyzer supports.
  80.      */
  81.     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(EXTENSIONS).build();
  82.     /**
  83.      * A descriptor for the type of dependencies processed or added by this
  84.      * analyzer.
  85.      */
  86.     public static final String DEPENDENCY_ECOSYSTEM = Ecosystem.NATIVE;

  87.     /**
  88.      * Returns the name of the analyzer.
  89.      *
  90.      * @return the name of the analyzer.
  91.      */
  92.     @Override
  93.     public String getName() {
  94.         return ANALYZER_NAME;
  95.     }

  96.     /**
  97.      * Returns the phase that the analyzer is intended to run in.
  98.      *
  99.      * @return the phase that the analyzer is intended to run in.
  100.      */
  101.     @Override
  102.     public AnalysisPhase getAnalysisPhase() {
  103.         return ANALYSIS_PHASE;
  104.     }

  105.     /**
  106.      * <p>
  107.      * Returns the setting key to determine if the analyzer is enabled.</p>
  108.      *
  109.      * @return the key for the analyzer's enabled property
  110.      */
  111.     @Override
  112.     protected String getAnalyzerEnabledSettingKey() {
  113.         return Settings.KEYS.ANALYZER_PE_ENABLED;
  114.     }

  115.     /**
  116.      * Returns the FileFilter.
  117.      *
  118.      * @return the FileFilter
  119.      */
  120.     @Override
  121.     protected FileFilter getFileFilter() {
  122.         return FILTER;
  123.     }

  124.     @Override
  125.     protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
  126.         //nothing to prepare
  127.     }

  128.     /**
  129.      * Collects information about the file name.
  130.      *
  131.      * @param dependency the dependency to analyze.
  132.      * @param engine the engine that is scanning the dependencies
  133.      * @throws AnalysisException is thrown if there is an error analyzing the PE
  134.      * file.
  135.      */
  136.     @Override
  137.     protected void analyzeDependency(final Dependency dependency, final Engine engine) throws AnalysisException {
  138.         for (Evidence e : dependency.getEvidence()) {
  139.             if ("grokassembly".equals(e.getSource())) {
  140.                 LOGGER.debug("Skipping {} because it was already analyzed by the Assembly Analyzer", dependency.getFileName());
  141.                 return;
  142.             }
  143.         }
  144.         try {
  145.             final File fileToCheck = dependency.getActualFile();
  146.             final PE pe = PEParser.parse(fileToCheck.getPath());
  147.             final ResourceDirectory rd = pe.getImageData().getResourceTable();
  148.             final ResourceEntry[] entries = ResourceHelper.findResources(rd, ResourceType.VERSION_INFO);
  149.             for (ResourceEntry entrie : entries) {
  150.                 final byte[] data = entrie.getData();
  151.                 final VersionInfo version = ResourceParser.readVersionInfo(data);
  152.                 final StringFileInfo strings = version.getStringFileInfo();
  153.                 final StringTable table = strings.getTable(0);
  154.                 String pVersion = null;
  155.                 String fVersion = null;

  156.                 for (int j = 0; j < table.getCount(); j++) {
  157.                     final String key = table.getString(j).getKey();
  158.                     final String value = table.getString(j).getValue();
  159.                     switch (key) {
  160.                         case "ProductVersion":
  161.                             dependency.addEvidence(EvidenceType.VERSION, "PE Header", "ProductVersion", value, Confidence.HIGHEST);
  162.                             pVersion = value;
  163.                             break;
  164.                         case "CompanyName":
  165.                             dependency.addEvidence(EvidenceType.VENDOR, "PE Header", "CompanyName", value, Confidence.HIGHEST);
  166.                             break;
  167.                         case "FileVersion":
  168.                             dependency.addEvidence(EvidenceType.VERSION, "PE Header", "FileVersion", value, Confidence.HIGH);
  169.                             fVersion = value;
  170.                             break;
  171.                         case "InternalName":
  172.                             dependency.addEvidence(EvidenceType.PRODUCT, "PE Header", "InternalName", value, Confidence.MEDIUM);
  173.                             dependency.addEvidence(EvidenceType.VENDOR, "PE Header", "InternalName", value, Confidence.LOW);
  174.                             determineDependencyName(dependency, value);
  175.                             break;
  176.                         case "LegalCopyright":
  177.                             dependency.addEvidence(EvidenceType.VENDOR, "PE Header", "LegalCopyright", value, Confidence.HIGHEST);
  178.                             if (dependency.getLicense() != null && dependency.getLicense().length() > 0) {
  179.                                 dependency.setLicense(dependency.getLicense() + "/n/nLegal Copyright: " + value);
  180.                             } else {
  181.                                 dependency.setLicense("Legal Copyright: " + value);
  182.                             }
  183.                             break;
  184.                         case "OriginalFilename":
  185.                             dependency.addEvidence(EvidenceType.VERSION, "PE Header", "OriginalFilename", value, Confidence.MEDIUM);
  186.                             determineDependencyName(dependency, value);
  187.                             break;
  188.                         case "ProductName":
  189.                             dependency.addEvidence(EvidenceType.PRODUCT, "PE Header", "ProductName", value, Confidence.HIGHEST);
  190.                             dependency.addEvidence(EvidenceType.VENDOR, "PE Header", "ProductName", value, Confidence.MEDIUM);
  191.                             determineDependencyName(dependency, value);
  192.                             break;
  193.                         default:
  194.                             LOGGER.debug("PE Analyzer found `" + key + "` with a value:" + value);
  195.                     }
  196.                     if (fVersion != null && pVersion != null) {
  197.                         final int max = Math.min(fVersion.length(), pVersion.length());
  198.                         int pos;
  199.                         for (pos = 0; pos < max; pos++) {
  200.                             if (fVersion.charAt(pos) != pVersion.charAt(pos)) {
  201.                                 break;
  202.                             }
  203.                         }
  204.                         final DependencyVersion fileVersion = DependencyVersionUtil.parseVersion(fVersion, true);
  205.                         final DependencyVersion productVersion = DependencyVersionUtil.parseVersion(pVersion, true);
  206.                         if (pos > 0) {
  207.                             final DependencyVersion matchingVersion = DependencyVersionUtil.parseVersion(fVersion.substring(0, pos), true);
  208.                             if (fileVersion != null && fileVersion.toString().length() == fVersion.length()) {
  209.                                 if (matchingVersion != null && matchingVersion.getVersionParts().size() > 2) {
  210.                                     dependency.addEvidence(EvidenceType.VERSION, "PE Header", "FilteredVersion",
  211.                                             matchingVersion.toString(), Confidence.HIGHEST);
  212.                                     dependency.setVersion(matchingVersion.toString());
  213.                                 }
  214.                             }
  215.                         }
  216.                         if (dependency.getVersion() == null) {
  217.                             if (fVersion.length() >= pVersion.length()) {
  218.                                 if (fileVersion != null && fileVersion.toString().length() == fVersion.length()) {
  219.                                     dependency.setVersion(fileVersion.toString());
  220.                                 } else if (productVersion != null && productVersion.toString().length() == pVersion.length()) {
  221.                                     dependency.setVersion(productVersion.toString());
  222.                                 }
  223.                             } else {
  224.                                 if (productVersion != null && productVersion.toString().length() == pVersion.length()) {
  225.                                     dependency.setVersion(productVersion.toString());
  226.                                 } else if (fileVersion != null && fileVersion.toString().length() == fVersion.length()) {
  227.                                     dependency.setVersion(fileVersion.toString());
  228.                                 }
  229.                             }
  230.                         }
  231.                     } else if (pVersion != null) {
  232.                         final DependencyVersion productVersion = DependencyVersionUtil.parseVersion(pVersion, true);
  233.                         if (productVersion != null && dependency.getActualFile().getName().contains(productVersion.toString())) {
  234.                             dependency.setVersion(productVersion.toString());
  235.                         }
  236.                     } else if (fVersion != null) {
  237.                         final DependencyVersion fileVersion = DependencyVersionUtil.parseVersion(fVersion, true);
  238.                         if (fileVersion != null && dependency.getActualFile().getName().contains(fileVersion.toString())) {
  239.                             dependency.setVersion(fileVersion.toString());
  240.                         }
  241.                     }
  242.                     if (dependency.getName() != null && dependency.getVersion() != null) {
  243.                         try {
  244.                             dependency.addSoftwareIdentifier(new PurlIdentifier("generic", dependency.getName(),
  245.                                     dependency.getVersion(), Confidence.MEDIUM));
  246.                         } catch (MalformedPackageURLException ex) {
  247.                             LOGGER.debug("Unable to create Package URL Identifier for " + dependency.getName(), ex);
  248.                             dependency.addSoftwareIdentifier(new GenericIdentifier(
  249.                                     String.format("%s@%s", dependency.getName(), dependency.getVersion()),
  250.                                     Confidence.MEDIUM));
  251.                         }
  252.                     }
  253.                     if (dependency.getEcosystem() == null) {
  254.                         //this could be an assembly
  255.                         dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
  256.                     }
  257.                 }
  258.             }
  259.         } catch (IOException ex) {
  260.             throw new AnalysisException(ex);
  261.         }
  262.     }

  263.     private void determineDependencyName(final Dependency dependency, final String value) {
  264.         if (dependency.getName() == null && StringUtils.containsIgnoreCase(dependency.getActualFile().getName(), value)) {
  265.             final String ext = FileUtils.getFileExtension(value);
  266.             if (ext != null) {
  267.                 dependency.setName(value.substring(0, value.length() - ext.length() - 1));
  268.             } else {
  269.                 dependency.setName(value);
  270.             }
  271.         }
  272.     }
  273. }