diff --git a/.gitignore b/.gitignore index 0af92464ed..9672ad8957 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,13 @@ hs_err_pid*.log *.img *.vhd *.E01 + +/thirdparty/yara/yarabridge/yarabridge/x64/ +/thirdparty/yara/yarabridge/yarabridge.VC.db +/thirdparty/yara/yarabridge/yarabridge.VC.VC.opendb +/thirdparty/yara/yarabridge/x64/ +/thirdparty/yara/YaraWrapperTest/nbproject/private/ +/thirdparty/yara/YaraWrapperTest/build/ +/thirdparty/yara/YaraJNIWrapper/dist/ +/thirdparty/yara/YaraJNIWrapper/build/ +/thirdparty/yara/YaraJNIWrapper/nbproject/private/ diff --git a/Core/build.xml b/Core/build.xml index bbf612c3d9..1ec2a3b481 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -98,6 +98,10 @@ + + + + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 0c83dc776e..2b8155b7b2 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -118,6 +118,7 @@ file.reference.StixLib.jar=release\\modules\\ext\\StixLib.jar file.reference.threetenbp-1.3.3.jar=release\\modules\\ext\\threetenbp-1.3.3.jar file.reference.webp-imageio-sejda-0.1.0.jar=release\\modules\\ext\\webp-imageio-sejda-0.1.0.jar file.reference.xmpcore-5.1.3.jar=release\\modules\\ext\\xmpcore-5.1.3.jar +file.reference.YaraJNIWrapper.jar=release/modules/ext/YaraJNIWrapper.jar file.reference.zookeeper-3.4.6.jar=release\\modules\\ext\\zookeeper-3.4.6.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 165c56ea2f..1cddbaa638 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -547,18 +547,10 @@ ext/checker-compat-qual-2.5.3.jar release\modules\ext\checker-compat-qual-2.5.3.jar - - ext/sleuthkit-4.10.1.jar - release/modules/ext/sleuthkit-4.10.1.jar - ext/animal-sniffer-annotations-1.17.jar release\modules\ext\animal-sniffer-annotations-1.17.jar - - ext/sleuthkit-caseuco-4.10.1.jar - release/modules/ext/sleuthkit-caseuco-4.10.1.jar - ext/gax-1.44.0.jar release\modules\ext\gax-1.44.0.jar @@ -567,6 +559,10 @@ ext/jsoup-1.10.3.jar release\modules\ext\jsoup-1.10.3.jar + + ext/YaraJNIWrapper.jar + release/modules/ext/YaraJNIWrapper.jar + ext/grpc-context-1.19.0.jar release\modules\ext\grpc-context-1.19.0.jar @@ -671,6 +667,10 @@ ext/grpc-alts-1.19.0.jar release\modules\ext\grpc-alts-1.19.0.jar + + ext/sleuthkit-caseuco-4.10.1.jar + release/modules/ext/sleuthkit-caseuco-4.10.1.jar + ext/jdom-2.0.5.jar release\modules\ext\jdom-2.0.5.jar @@ -795,6 +795,10 @@ ext/sevenzipjbinding-AllPlatforms.jar release\modules\ext\sevenzipjbinding-AllPlatforms.jar + + ext/sleuthkit-4.10.1.jar + release/modules/ext/sleuthkit-4.10.1.jar + ext/jutf7-1.0.0.jar release\modules\ext\jutf7-1.0.0.jar diff --git a/Core/src/org/sleuthkit/autopsy/guicomponeontutils/AbstractCheckboxListItem.java b/Core/src/org/sleuthkit/autopsy/guicomponeontutils/AbstractCheckboxListItem.java new file mode 100755 index 0000000000..df0a00888f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/guicomponeontutils/AbstractCheckboxListItem.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * 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 org.sleuthkit.autopsy.guicomponeontutils; + +import javax.swing.Icon; +import org.sleuthkit.autopsy.guiutils.CheckBoxJList; + +/** + * An abstract implementation of CheckBoxJList.CheckboxListItem so that + * implementing classes have default implementation. + */ +public abstract class AbstractCheckboxListItem implements CheckBoxJList.CheckboxListItem { + + private boolean checked = false; + + @Override + public boolean isChecked() { + return checked; + } + + @Override + public void setChecked(boolean checked) { + this.checked = checked; + } + + @Override + public boolean hasIcon() { + return false; + } + + @Override + public Icon getIcon() { + return null; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/CheckBoxJList.java b/Core/src/org/sleuthkit/autopsy/guiutils/CheckBoxJList.java index ab17187c4b..f59db1259f 100755 --- a/Core/src/org/sleuthkit/autopsy/guiutils/CheckBoxJList.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/CheckBoxJList.java @@ -32,8 +32,10 @@ import javax.swing.ListSelectionModel; /** * A JList that renders the list items as check boxes. + * + * @param An object that implements CheckboxListItem */ -final class CheckBoxJList extends JList { +public final class CheckBoxJList extends JList { private static final long serialVersionUID = 1L; @@ -42,7 +44,7 @@ final class CheckBoxJList extends JLis * a checkbox in CheckBoxJList. * */ - interface CheckboxListItem { + public interface CheckboxListItem { /** * Returns the checkbox state. @@ -83,7 +85,7 @@ final class CheckBoxJList extends JLis /** * Construct a new JCheckBoxList. */ - CheckBoxJList() { + public CheckBoxJList() { initalize(); } @@ -134,12 +136,15 @@ final class CheckBoxJList extends JLis checkbox.setSelected(value.isChecked()); checkbox.setBackground(list.getBackground()); checkbox.setEnabled(list.isEnabled()); + checkbox.setOpaque(list.isOpaque()); label.setText(value.getDisplayName()); label.setEnabled(list.isEnabled()); + label.setOpaque(list.isOpaque()); if (value.hasIcon()) { label.setIcon(value.getIcon()); } + setOpaque(list.isOpaque()); setEnabled(list.isEnabled()); return this; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/yara/Bundle.properties-MERGED new file mode 100755 index 0000000000..8f898ba127 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/Bundle.properties-MERGED @@ -0,0 +1,5 @@ +Yara_Module_Description=With the YARA ingest module you use YARA rule files to search files for textual or binary patterns. +Yara_Module_Name=YARA +YaraIngestModule_no_ruleSets=Unable to run YARA ingest, list of YARA rule sets was empty. +YaraIngestModule_windows_error_msg=The YARA ingest module is only available on 64bit Windows. +YaraIngestModule_yarac_not_found=Unable to compile YARA rules files. Unable to find executable at. diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java new file mode 100755 index 0000000000..9afcc9facd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestHelper.java @@ -0,0 +1,207 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.modules.yara; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.openide.modules.InstalledFileLocator; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ExecUtil; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSetManager; +import org.sleuthkit.autopsy.yara.YaraJNIWrapper; +import org.sleuthkit.autopsy.yara.YaraWrapperException; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_YARA_HIT; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Methods for scanning files for yara rule matches. + */ +final class YaraIngestHelper { + + private static final String YARA_DIR = "yara"; + private static final String YARA_C_EXE = "yarac64.exe"; + private static final String MODULE_NAME = YaraIngestModuleFactory.getModuleName(); + + private YaraIngestHelper() { + } + + /** + * Uses the yarac tool to compile the rules in the given rule sets. + * + * @param ruleSetNames List of names of the selected rule sets. + * @param tempDir Path of the directory to put the compiled rule files. + * + * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException + */ + static void compileRules(List ruleSetNames, Path outputDir) throws IngestModuleException { + if (ruleSetNames == null || ruleSetNames.isEmpty()) { + throw new IngestModule.IngestModuleException(Bundle.YaraIngestModule_no_ruleSets()); + } + + // Find javac + File exeFile = InstalledFileLocator.getDefault().locate( + Paths.get(YARA_DIR, YARA_C_EXE).toString(), + YaraIngestModule.class.getPackage().getName(), false); + + if (exeFile == null) { + throw new IngestModuleException(Bundle.YaraIngestModule_yarac_not_found()); + } + + for (RuleSet set : getRuleSetsForNames(ruleSetNames)) { + compileRuleSet(set, outputDir, exeFile); + } + } + + /** + * Scan the given AbstractFile for yara rule matches from the rule sets in + * the given directory creating a blackboard artifact for each matching + * rule. + * + * The baseDirectory should contain a series of directories one for each + * rule set. + * + * @param file The file to scan. + * @param baseDirectory Base directory for the compiled rule sets. + * + * @throws TskCoreException + */ + static List scanFileForMatches(AbstractFile file, File baseDirectory) throws TskCoreException, YaraWrapperException { + List artifacts = new ArrayList<>(); + + byte[] fileBytes = new byte[(int) file.getSize()]; + file.read(fileBytes, 0, fileBytes.length); + + File[] ruleSetDirectories = baseDirectory.listFiles(); + for (File ruleSetDirectory : ruleSetDirectories) { + List ruleMatches = YaraIngestHelper.scanFileForMatches(fileBytes, ruleSetDirectory); + if (!ruleMatches.isEmpty()) { + artifacts.addAll(YaraIngestHelper.createArtifact(file, ruleSetDirectory.getName(), ruleMatches)); + } + } + + return artifacts; + } + + /** + * Scan the given file byte array for rule matches using the YaraJNIWrapper + * API. + * + * @param fileBytes + * @param ruleSetDirectory + * + * @return List of rules that match from the given file from the given rule + * set. Empty list is returned if no matches where found. + * + * @throws TskCoreException + */ + private static List scanFileForMatches(byte[] fileBytes, File ruleSetDirectory) throws TskCoreException, YaraWrapperException { + List matchingRules = new ArrayList<>(); + + File[] ruleSetCompiledFileList = ruleSetDirectory.listFiles(); + + for (File ruleFile : ruleSetCompiledFileList) { + matchingRules.addAll(YaraJNIWrapper.findRuleMatch(ruleFile.getAbsolutePath(), fileBytes)); + } + + return matchingRules; + } + + /** + * Create a list of Blackboard Artifacts, one for each matching rule. + * + * @param abstractFile File to add artifact to. + * @param ruleSetName Name rule set with matching rule. + * @param matchingRules Matching rule. + * + * @return List of artifacts or empty list if none were found. + * + * @throws TskCoreException + */ + private static List createArtifact(AbstractFile abstractFile, String ruleSetName, List matchingRules) throws TskCoreException { + List artifacts = new ArrayList<>(); + for (String rule : matchingRules) { + BlackboardArtifact artifact = abstractFile.newArtifact(TSK_YARA_HIT); + List attributes = new ArrayList<>(); + + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, ruleSetName)); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, MODULE_NAME, rule)); + + artifact.addAttributes(attributes); + artifacts.add(artifact); + } + return artifacts; + } + + @NbBundle.Messages({ + "YaraIngestModule_yarac_not_found=Unable to compile YARA rules files. Unable to find executable at.", + "YaraIngestModule_no_ruleSets=Unable to run YARA ingest, list of YARA rule sets was empty." + }) + + /** + * Compiles the rule files in the given rule set. + * + * The compiled rule files are created in outputDir\RuleSetName. + * + * @param set RuleSet for which to compile files. + * @param outputDir Output directory for the compiled rule files. + * @param yarac yarac executeable file. + * + * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException + */ + static private void compileRuleSet(RuleSet set, Path outputDir, File yarac) throws IngestModuleException { + File tempFolder = Paths.get(outputDir.toString(), set.getName()).toFile(); + if (!tempFolder.exists()) { + tempFolder.mkdir(); + } + + List fileList = set.getRuleFiles(); + for (File file : fileList) { + List commandList = new ArrayList<>(); + commandList.add(String.format("\"%s\"", yarac.toString())); + commandList.add(String.format("\"%s\"", file.toString())); + commandList.add(String.format("\"%s\"", Paths.get(tempFolder.getAbsolutePath(), "compiled_" + file.getName()))); + + ProcessBuilder builder = new ProcessBuilder(commandList); + try { + ExecUtil.execute(builder); + } catch (SecurityException | IOException ex) { + throw new IngestModuleException(String.format("Failed to compile Yara rules file", file.toString()), ex); + } + + } + } + + /** + * Returns a list of RuleSet objects for the given list of RuleSet names. + * + * @param names List of RuleSet names. + * + * @return List of RuleSet or empty list if none of the names matched + * existing rules. + */ + private static List getRuleSetsForNames(List names) { + List ruleSetList = new ArrayList<>(); + + RuleSetManager manager = new RuleSetManager(); + for (RuleSet set : manager.getRuleSetList()) { + if (names.contains(set.getName())) { + ruleSetList.add(set); + } + } + + return ruleSetList; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestJobSettings.java new file mode 100755 index 0000000000..b115bba90f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestJobSettings.java @@ -0,0 +1,106 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * 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 org.sleuthkit.autopsy.modules.yara; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; + +/** + * IngestJobSettings for the YARA ingest module. + */ +public final class YaraIngestJobSettings implements IngestModuleIngestJobSettings { + + private static final long serialVersionUID = 1L; + + private List selectedRuleSetNames; + private boolean onlyExecutableFiles; + + // Default constructor. + YaraIngestJobSettings() { + onlyExecutableFiles = true; + selectedRuleSetNames = new ArrayList<>(); + } + + /** + * Constructor. + * + * @param selected List of selected rules. + * @param onlyExecutableFiles Process only executable files. + */ + public YaraIngestJobSettings(List selected, boolean onlyExecutableFiles) { + this.selectedRuleSetNames = new ArrayList<>(); + + for (RuleSet set : selected) { + selectedRuleSetNames.add(set.getName()); + } + + this.onlyExecutableFiles = onlyExecutableFiles; + } + + /** + * Return the list of rule name sets that were selected in the ingest + * settings panel. + * + * @return List of selected RuleSet names. + */ + public List getSelectedRuleSetNames() { + return Collections.unmodifiableList(selectedRuleSetNames); + } + + /** + * Set the list of selected rule names. + * + * @param selected List of selected rule Sets. + */ + void setSelectedRuleSetNames(List selected) { + this.selectedRuleSetNames = new ArrayList<>(); + for (RuleSet set : selected) { + selectedRuleSetNames.add(set.getName()); + } + } + + /** + * Process only executable Files. + * + * @return If true the ingest module should process only executable files, + * if false process all files. + */ + public boolean onlyExecutableFiles() { + return onlyExecutableFiles; + } + + /** + * Set whether to process only executable files or all files. + * + * @param onlyExecutableFiles True if the ingest module should only process + * executable files. + */ + void setOnlyExecuteableFile(boolean onlyExecutableFiles) { + this.onlyExecutableFiles = onlyExecutableFiles; + } + + @Override + public long getVersionNumber() { + return serialVersionUID; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModule.java new file mode 100755 index 0000000000..85a393d346 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModule.java @@ -0,0 +1,168 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * 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 org.sleuthkit.autopsy.modules.yara; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import org.apache.commons.lang3.RandomStringUtils; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.autopsy.yara.YaraWrapperException; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * An ingest module that runs the yara against the given files. + * + */ +public class YaraIngestModule extends FileIngestModuleAdapter { + + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); + private final static Logger logger = Logger.getLogger(YaraIngestModule.class.getName()); + private static final String YARA_DIR = "yara"; + private static final Map pathsByJobId = new ConcurrentHashMap<>(); + + private final YaraIngestJobSettings settings; + + private IngestJobContext context = null; + private Long jobId; + + /** + * Constructor. + * + * @param settings + */ + YaraIngestModule(YaraIngestJobSettings settings) { + this.settings = settings; + } + + @Messages({ + "YaraIngestModule_windows_error_msg=The YARA ingest module is only available on 64bit Windows.",}) + + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + this.context = context; + this.jobId = context.getJobId(); + + if (!PlatformUtil.isWindowsOS() || !PlatformUtil.is64BitOS()) { + throw new IngestModule.IngestModuleException(Bundle.YaraIngestModule_windows_error_msg()); + } + + if (refCounter.incrementAndGet(jobId) == 1) { + // compile the selected rules & put into temp folder based on jobID + Path tempDir = getTempDirectory(jobId); + + YaraIngestHelper.compileRules(settings.getSelectedRuleSetNames(), tempDir); + } + } + + @Override + public void shutDown() { + if (context != null && refCounter.decrementAndGet(jobId) == 0) { + // do some clean up. + Path jobPath = pathsByJobId.get(jobId); + if (jobPath != null) { + jobPath.toFile().delete(); + pathsByJobId.remove(jobId); + } + } + } + + @Override + public ProcessResult process(AbstractFile file) { + + if (settings.onlyExecutableFiles()) { + String extension = file.getNameExtension(); + if (!extension.equals("exe")) { + return ProcessResult.OK; + } + } + + // Skip the file if its 0 in length. + if (file.getSize() == 0) { + return ProcessResult.OK; + } + + try { + List artifacts = YaraIngestHelper.scanFileForMatches(file, getTempDirectory(jobId).toFile()); + + if(!artifacts.isEmpty()) { + Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); + blackboard.postArtifacts(artifacts, YaraIngestModuleFactory.getModuleName()); + } + + } catch (BlackboardException | NoCurrentCaseException | IngestModuleException | TskCoreException | YaraWrapperException ex) { + logger.log(Level.SEVERE, "YARA ingest module failed to process file.", ex); + return ProcessResult.ERROR; + } + return ProcessResult.OK; + } + + /** + * Return the temp directory for this jobId. If the folder does not exit it + * will be created. + * + * @param jobId The current jobId + * + * @return The path of the temporary directory for the given jobId. + * + * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException + */ + private synchronized Path getTempDirectory(long jobId) throws IngestModuleException { + Path jobPath = pathsByJobId.get(jobId); + if (jobPath != null) { + return jobPath; + } + + Path baseDir; + try { + baseDir = Paths.get(Case.getCurrentCaseThrows().getTempDirectory(), YARA_DIR); + } catch (NoCurrentCaseException ex) { + throw new IngestModuleException("Failed to create YARA ingest model temp directory, no open case.", ex); + } + + // Make the base yara directory, as needed + if (!baseDir.toFile().exists()) { + baseDir.toFile().mkdirs(); + } + + String randomDirName = String.format("%s_%d", RandomStringUtils.randomAlphabetic(8), jobId); + jobPath = Paths.get(baseDir.toString(), randomDirName); + jobPath.toFile().mkdir(); + + pathsByJobId.put(jobId, jobPath); + + return jobPath; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java new file mode 100755 index 0000000000..1abf8aaa0f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/YaraIngestModuleFactory.java @@ -0,0 +1,92 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * 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 org.sleuthkit.autopsy.modules.yara; + +import java.util.ArrayList; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; +import org.sleuthkit.autopsy.modules.yara.ui.YaraIngestSettingsPanel; + +/** + * A factory that creates ingest modules that use the Yara rule set definitions + * to identify files that may be of interest to the user. + */ +@ServiceProvider(service = IngestModuleFactory.class) +public class YaraIngestModuleFactory extends IngestModuleFactoryAdapter { + + @Messages({ + "Yara_Module_Name=YARA", + "Yara_Module_Description=With the YARA ingest module you use YARA rule files to search files for textual or binary patterns." + }) + + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + @Override + public String getModuleDescription() { + return Bundle.Yara_Module_Description(); + } + + @Override + public String getModuleVersionNumber() { + return Version.getVersion(); + } + + @Override + public boolean hasIngestJobSettingsPanel() { + return true; + } + + @Override + public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) { + return new YaraIngestSettingsPanel((YaraIngestJobSettings)settings); + } + + @Override + public IngestModuleIngestJobSettings getDefaultIngestJobSettings() { + return new YaraIngestJobSettings(new ArrayList<>(), true); + } + + @Override + public boolean isFileIngestModuleFactory() { + return true; + } + + @Override + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { + return new YaraIngestModule((YaraIngestJobSettings) settings); + } + + /** + * Return the name of the ingest module. + * + * @return Ingest module name. + */ + static String getModuleName() { + return Bundle.Yara_Module_Name(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java index 12b3838944..112cf9206c 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/rules/RuleSet.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.modules.yara.rules; import java.io.File; +import java.io.Serializable; import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -26,7 +27,9 @@ import java.util.List; /** * Represents a yara rule set which is a collection of yara rule files. */ -public class RuleSet implements Comparable { +public class RuleSet implements Comparable, Serializable { + + private static final long serialVersionUID = 1L; private final String name; private final Path path; diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties index 5012f855b4..2ac49a5463 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties @@ -8,3 +8,7 @@ RuleSetDetailsPanel.setDetailsLabel.text=Set Details RuleSetDetailsPanel.openFolderButton.text=Open Folder RuleSetPanel.descriptionField.text=This module allows you to find files the match Yara rules. Each set has a list of Yara rule files. A file need only match one rule in the set to be found. RuleSetDetailsPanel.openLabel.text=Place rule files in the set's folder. They will be compiled before use. +YaraIngestSettingsPanel.border.title=Select YARA rule sets to enable during ingest: +YaraIngestSettingsPanel.allFilesButton.text=All Files +YaraIngestSettingsPanel.allFilesButton.toolTipText= +YaraIngestSettingsPanel.executableFilesButton.text=Only Executable Files diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties-MERGED index a6b64a97c0..3fad865f43 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/Bundle.properties-MERGED @@ -10,6 +10,10 @@ RuleSetDetailsPanel.setDetailsLabel.text=Set Details RuleSetDetailsPanel.openFolderButton.text=Open Folder RuleSetPanel.descriptionField.text=This module allows you to find files the match Yara rules. Each set has a list of Yara rule files. A file need only match one rule in the set to be found. RuleSetDetailsPanel.openLabel.text=Place rule files in the set's folder. They will be compiled before use. +YaraIngestSettingsPanel.border.title=Select YARA rule sets to enable during ingest: +YaraIngestSettingsPanel.allFilesButton.text=All Files +YaraIngestSettingsPanel.allFilesButton.toolTipText= +YaraIngestSettingsPanel.executableFilesButton.text=Only Executable Files # {0} - rule set name YaraRuleSetOptionPanel_badName_msg=Rule set name {0} already exists.\nRule set names must be unique. YaraRuleSetOptionPanel_badName_title=Create Rule Set diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.form new file mode 100755 index 0000000000..d4832c66a1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.form @@ -0,0 +1,87 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java new file mode 100755 index 0000000000..e68b11ccb2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/yara/ui/YaraIngestSettingsPanel.java @@ -0,0 +1,182 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * 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 org.sleuthkit.autopsy.modules.yara.ui; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import javax.swing.DefaultListModel; +import org.sleuthkit.autopsy.guicomponeontutils.AbstractCheckboxListItem; +import org.sleuthkit.autopsy.guiutils.CheckBoxJList; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; +import org.sleuthkit.autopsy.modules.yara.YaraIngestJobSettings; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSet; +import org.sleuthkit.autopsy.modules.yara.rules.RuleSetManager; + +/** + * Yara Ingest settings panel. + */ +public class YaraIngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { + + private static final long serialVersionUID = 1L; + + private final CheckBoxJList checkboxList; + private final DefaultListModel listModel; + + /** + * Creates new form YaraIngestSettingsPanel + */ + YaraIngestSettingsPanel() { + initComponents(); + listModel = new DefaultListModel<>(); + checkboxList = new CheckBoxJList<>(); + scrollPane.setViewportView(checkboxList); + } + + public YaraIngestSettingsPanel(YaraIngestJobSettings settings) { + this(); + + List setNames = settings.getSelectedRuleSetNames(); + + checkboxList.setModel(listModel); + checkboxList.setOpaque(false); + RuleSetManager manager = new RuleSetManager(); + List ruleSetList = manager.getRuleSetList(); + for (RuleSet set : ruleSetList) { + RuleSetListItem item = new RuleSetListItem(set); + item.setChecked(setNames.contains(set.getName())); + listModel.addElement(item); + } + + allFilesButton.setSelected(!settings.onlyExecutableFiles()); + executableFilesButton.setSelected(settings.onlyExecutableFiles()); + } + + @Override + public IngestModuleIngestJobSettings getSettings() { + List selectedRules = new ArrayList<>(); + + Enumeration enumeration = listModel.elements(); + while (enumeration.hasMoreElements()) { + RuleSetListItem item = enumeration.nextElement(); + if (item.isChecked()) { + selectedRules.add(item.getRuleSet()); + } + } + + return new YaraIngestJobSettings(selectedRules, executableFilesButton.isSelected()); + } + + /** + * RuleSet wrapper class for Checkbox JList model. + */ + private final class RuleSetListItem extends AbstractCheckboxListItem { + + private final RuleSet ruleSet; + + /** + * RuleSetListItem constructor. + * + * @param set RuleSet object to display in list. + */ + RuleSetListItem(RuleSet set) { + this.ruleSet = set; + } + + /** + * Returns the RuleSet. + * + * @return + */ + RuleSet getRuleSet() { + return ruleSet; + } + + @Override + public String getDisplayName() { + return ruleSet.getName(); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + buttonGroup = new javax.swing.ButtonGroup(); + scrollPane = new javax.swing.JScrollPane(); + buttonPanel = new javax.swing.JPanel(); + allFilesButton = new javax.swing.JRadioButton(); + executableFilesButton = new javax.swing.JRadioButton(); + + setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(YaraIngestSettingsPanel.class, "YaraIngestSettingsPanel.border.title"))); // NOI18N + setLayout(new java.awt.GridBagLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(scrollPane, gridBagConstraints); + + buttonPanel.setLayout(new java.awt.GridBagLayout()); + + buttonGroup.add(allFilesButton); + org.openide.awt.Mnemonics.setLocalizedText(allFilesButton, org.openide.util.NbBundle.getMessage(YaraIngestSettingsPanel.class, "YaraIngestSettingsPanel.allFilesButton.text")); // NOI18N + allFilesButton.setToolTipText(org.openide.util.NbBundle.getMessage(YaraIngestSettingsPanel.class, "YaraIngestSettingsPanel.allFilesButton.toolTipText")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + buttonPanel.add(allFilesButton, gridBagConstraints); + + buttonGroup.add(executableFilesButton); + executableFilesButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(executableFilesButton, org.openide.util.NbBundle.getMessage(YaraIngestSettingsPanel.class, "YaraIngestSettingsPanel.executableFilesButton.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + buttonPanel.add(executableFilesButton, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.weightx = 1.0; + add(buttonPanel, gridBagConstraints); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton allFilesButton; + private javax.swing.ButtonGroup buttonGroup; + private javax.swing.JPanel buttonPanel; + private javax.swing.JRadioButton executableFilesButton; + private javax.swing.JScrollPane scrollPane; + // End of variables declaration//GEN-END:variables +} diff --git a/thirdparty/yara/YaraJNIWrapper/nbproject/build-impl.xml b/thirdparty/yara/YaraJNIWrapper/nbproject/build-impl.xml index 38dd8d0c87..d5569a48c3 100755 --- a/thirdparty/yara/YaraJNIWrapper/nbproject/build-impl.xml +++ b/thirdparty/yara/YaraJNIWrapper/nbproject/build-impl.xml @@ -179,9 +179,7 @@ is divided into following sections: - - - + @@ -289,7 +287,6 @@ is divided into following sections: Must set src.dir - Must set test.src.dir Must set build.dir Must set dist.dir Must set build.classes.dir @@ -588,9 +585,6 @@ is divided into following sections: - - - @@ -613,11 +607,7 @@ is divided into following sections: - - - - - + @@ -1544,14 +1534,14 @@ is divided into following sections: - - + + - + @@ -1592,17 +1582,15 @@ is divided into following sections: - + - + - - - + @@ -1616,14 +1604,12 @@ is divided into following sections: Must select some files in the IDE or set javac.includes - + - - - + diff --git a/thirdparty/yara/YaraJNIWrapper/nbproject/project.properties b/thirdparty/yara/YaraJNIWrapper/nbproject/project.properties index a0ef4dac37..0af470a2bf 100755 --- a/thirdparty/yara/YaraJNIWrapper/nbproject/project.properties +++ b/thirdparty/yara/YaraJNIWrapper/nbproject/project.properties @@ -1,9 +1,10 @@ annotation.processing.enabled=true annotation.processing.enabled.in.editor=false -annotation.processing.processor.options= annotation.processing.processors.list= annotation.processing.run.all.processors=true annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=YaraJNIWrapper +application.vendor=kelly build.classes.dir=${build.dir}/classes build.classes.excludes=**/*.java,**/*.form # This directory is removed when the project is cleaned: @@ -32,10 +33,13 @@ dist.jar=${dist.dir}/YaraJNIWrapper.jar dist.javadoc.dir=${dist.dir}/javadoc dist.jlink.dir=${dist.dir}/jlink dist.jlink.output=${dist.jlink.dir}/YaraJNIWrapper +endorsed.classpath= excludes= +file.reference.yara-lib=src/org/sleuthkit/autopsy/yara/lib includes=** jar.compress=false -javac.classpath= +javac.classpath=\ + ${file.reference.yara-lib} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false @@ -90,4 +94,3 @@ run.test.modulepath=\ ${javac.test.modulepath} source.encoding=UTF-8 src.dir=src -test.src.dir=test diff --git a/thirdparty/yara/YaraJNIWrapper/nbproject/project.xml b/thirdparty/yara/YaraJNIWrapper/nbproject/project.xml index df43138d7e..89ae97a48b 100755 --- a/thirdparty/yara/YaraJNIWrapper/nbproject/project.xml +++ b/thirdparty/yara/YaraJNIWrapper/nbproject/project.xml @@ -7,9 +7,7 @@ - - - + diff --git a/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/YaraJNIWrapper.java b/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/YaraJNIWrapper.java index 0fc5e8f0f4..1905d690cd 100755 --- a/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/YaraJNIWrapper.java +++ b/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/YaraJNIWrapper.java @@ -18,9 +18,11 @@ */ package org.sleuthkit.autopsy.yara; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,18 +33,12 @@ import java.util.logging.Logger; */ public class YaraJNIWrapper { - // Load the yarabridge.dll which should be located in the same directory as - // the jar file. If we need to use this code for debugging the dll this - // code will need to be modified to add that support. static { - Path directoryPath = null; try { - directoryPath = Paths.get(YaraJNIWrapper.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent().toAbsolutePath(); - } catch (URISyntaxException ex) { + extractAndLoadDll(); + } catch (IOException | YaraWrapperException ex) { Logger.getLogger(YaraJNIWrapper.class.getName()).log(Level.SEVERE, null, ex); } - String libraryPath = Paths.get(directoryPath != null ? directoryPath.toString() : "", "yarabridge.dll").toAbsolutePath().toString(); - System.load(libraryPath); } /** @@ -59,10 +55,40 @@ public class YaraJNIWrapper { */ static public native List findRuleMatch(String compiledRulesPath, byte[] byteBuffer) throws YaraWrapperException; + /** + * Copy yarabridge.dll from inside the jar to a temp file that can be loaded + * with System.load. + * + * To make this work, the dll needs to be in the same folder as this source + * file. The dll needs to be located somewhere in the jar class path. + * + * @throws IOException + * @throws YaraWrapperException + */ + static private void extractAndLoadDll() throws IOException, YaraWrapperException { + File tempFile = File.createTempFile("lib", null); + tempFile.deleteOnExit(); + try (InputStream in = YaraJNIWrapper.class.getResourceAsStream("yarabridge.dll")) { + if (in == null) { + throw new YaraWrapperException("native library was not found in jar file."); + } + try (OutputStream out = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[1024]; + int lengthRead; + while ((lengthRead = in.read(buffer)) > 0) { + out.write(buffer, 0, lengthRead); + out.flush(); + } + } + } + + System.load(tempFile.getAbsolutePath()); + } + /** * private constructor. */ private YaraJNIWrapper() { } - + } diff --git a/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/yarabridge.dll b/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/yarabridge.dll new file mode 100755 index 0000000000..d6d3d75e1b Binary files /dev/null and b/thirdparty/yara/YaraJNIWrapper/src/org/sleuthkit/autopsy/yara/yarabridge.dll differ diff --git a/thirdparty/yara/YaraWrapperTest/nbproject/project.properties b/thirdparty/yara/YaraWrapperTest/nbproject/project.properties index c0126ab42a..b7874aae82 100755 --- a/thirdparty/yara/YaraWrapperTest/nbproject/project.properties +++ b/thirdparty/yara/YaraWrapperTest/nbproject/project.properties @@ -35,7 +35,7 @@ dist.jlink.dir=${dist.dir}/jlink dist.jlink.output=${dist.jlink.dir}/YaraWrapperTest endorsed.classpath= excludes= -file.reference.YaraJNIWrapper.jar=../bin/YaraJNIWrapper.jar +file.reference.YaraJNIWrapper.jar=../YaraJNIWrapper/dist/YaraJNIWrapper.jar includes=** jar.compress=false javac.classpath=\ diff --git a/thirdparty/yara/bin/YaraJNIWrapper.jar b/thirdparty/yara/bin/YaraJNIWrapper.jar index 749d7a6ae7..f10842d973 100755 Binary files a/thirdparty/yara/bin/YaraJNIWrapper.jar and b/thirdparty/yara/bin/YaraJNIWrapper.jar differ diff --git a/thirdparty/yara/bin/yarabridge.dll b/thirdparty/yara/bin/yarabridge.dll deleted file mode 100755 index c74062a626..0000000000 Binary files a/thirdparty/yara/bin/yarabridge.dll and /dev/null differ diff --git a/thirdparty/yara/bin/yarac64.exe b/thirdparty/yara/bin/yarac64.exe new file mode 100755 index 0000000000..bf94cc4462 Binary files /dev/null and b/thirdparty/yara/bin/yarac64.exe differ diff --git a/thirdparty/yara/yarabridge/yarabridge/yarabridge.vcxproj b/thirdparty/yara/yarabridge/yarabridge/yarabridge.vcxproj index ce5dd10c80..c049e81dc9 100755 --- a/thirdparty/yara/yarabridge/yarabridge/yarabridge.vcxproj +++ b/thirdparty/yara/yarabridge/yarabridge/yarabridge.vcxproj @@ -113,7 +113,7 @@ ws2_32.lib;crypt32.lib;libyara64.lib;%(AdditionalDependencies) - copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\bin\$(ProjectName).dll" + copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\YaraJNIWrapper\src\org\sleuthkit\autopsy\yara\$(ProjectName).dll" @@ -153,7 +153,7 @@ ws2_32.lib;crypt32.lib;libyara64.lib;%(AdditionalDependencies) - copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\bin\$(ProjectName).dll" + copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\YaraJNIWrapper\src\org\sleuthkit\autopsy\yara\$(ProjectName).dll"