Skip to content

Commit

Permalink
Version 1.4-stable
Browse files Browse the repository at this point in the history
  • Loading branch information
ThisIsLibra committed Sep 29, 2022
1 parent 616c4b2 commit f2583f7
Show file tree
Hide file tree
Showing 19 changed files with 406 additions and 227 deletions.
22 changes: 20 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>malpull</groupId>
<artifactId>MalPull</artifactId>
<version>1.3-stable</version>
<version>1.4-stable</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down Expand Up @@ -93,7 +93,25 @@
<dependency>
<groupId>net.lingala.zip4j</groupId>
<artifactId>zip4j</artifactId>
<version>2.7.0</version>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>triageapi</groupId>
<artifactId>TriageApi</artifactId>
<version>1.6-stable</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>malshareapi</groupId>
<artifactId>MalShareApi</artifactId>
<version>1.2-stable</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>malwarebazaarapi</groupId>
<artifactId>MalwareBazaarApi</artifactId>
<version>1.0-stable</version>
<type>jar</type>
</dependency>
</dependencies>
<name>MalPull</name>
Expand Down
13 changes: 9 additions & 4 deletions src/main/java/malpull/MalPull.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import malpull.endpoints.VirusShare;
import malpull.exceptions.NoArgumentsSetException;
import malpull.model.MalPullResult;

Expand Down Expand Up @@ -123,14 +124,18 @@ private DownloadWorker getDownloadWorker(Arguments arguments, String hash, int c
IEndpoint malShare = new MalShare(arguments.getMalShareKey());
endpoints.add(malShare);
}
if (arguments.getKoodousKey() != null) {
IEndpoint koodous = new Koodous(arguments.getKoodousKey());
endpoints.add(koodous);
if (arguments.getVirusShareKey() != null) {
IEndpoint virusShare = new VirusShare(arguments.getVirusShareKey());
endpoints.add(virusShare);
}
if (arguments.getVirusTotalKey() != null) {
IEndpoint virusTotal = new VirusTotal(arguments.getVirusTotalKey());
endpoints.add(virusTotal);
}
if (arguments.getKoodousKey() != null) {
IEndpoint koodous = new Koodous(arguments.getKoodousKey());
endpoints.add(koodous);
}

//Create a download worker for the hash, with all configured endpoints embedded
DownloadWorker worker = new DownloadWorker(this, endpoints, arguments.getOutputPath(), hash, count, arguments.getHashes().size());
Expand Down Expand Up @@ -255,6 +260,6 @@ protected synchronized void addMissingHash(String missingHash) {
* @return the version information
*/
public String getVersionInformation() {
return "MalPull version 1.3-stable by Max 'Libra' Kersten [@Libranalysis, https://maxkersten.nl]\n";
return "MalPull version 1.4-stable by Max 'Libra' Kersten [@Libranalysis, https://maxkersten.nl]\n";
}
}
87 changes: 61 additions & 26 deletions src/main/java/malpull/cli/ArgumentHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
Expand All @@ -47,43 +48,45 @@ public class ArgumentHandler {
* @throws NoHashesFoundException if no hashes are provided
*/
public static Arguments handle(String[] args) throws NoServicesSetException, NoHashesFoundException {
//Only 4 arguments are accepted:
//java -jar malpull.jar threadCount /path/to/keys.txt /path/to/hashes.txt /path/to/write/samples/to
if (args.length != 4) {
//A minimum of two arguments are required: the destination folder for the downloaded samples, and one or more hashes:
//java -jar malpull.jar /path/to/write/samples/to hash1 hash2 hashN
//Note that the keys file is required to be in the same folder as the JAR
if (args.length < 2) {
//If that is the case, the usage should be printed
printUsage();
//Then the system should exit
System.exit(0);
}
//Test if the thread count is a valid number

String keysPath = null;
//Get each key per line, sanity checks are performed within the loadFile function
try {
Integer.parseInt(args[0]);
} catch (Exception e) {
System.out.println("Please provide a valid number for the thread count: " + args[0]);
System.exit(0);
}
//Get the thread count if its a valid number
int threadCount = Integer.parseInt(args[0]);
if (threadCount <= 0) {
System.out.println("At least 1 thread is required!");
String baseFolder = new File(malpull.MalPull.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParentFile().getAbsolutePath();
keysPath = baseFolder + File.separator + "keys.txt";
} catch (URISyntaxException e) {
System.out.println("The JAR's location cannot be obtained, check your privilege and try again!");
System.exit(0);
}

//Get each key per line, sanity checks are performed within the loadFile function
List<String> keys = loadFile(args[1]);
List<String> keys = loadFile(keysPath);
/**
* Initialise all strings as null, only changing the value if it is
* included in the keys list. Only if the value is not null, it should
* be used later on
*/
String threadsString = null;
String koodousKey = null;
String malwareBazaarKey = null;
String malShareKey = null;
String virusTotalKey = null;
String triageKey = null;
String virusShareKey = null;

//Iterate through all keys, note that the endpoint prefix is case insensitive
for (String key : keys) {
if (key.toLowerCase().startsWith("koodous=".toLowerCase())) {
if (key.toLowerCase().startsWith("threads=".toLowerCase())) {
threadsString = key.substring("threads=".length(), key.length());
} else if (key.toLowerCase().startsWith("koodous=".toLowerCase())) {
koodousKey = key.substring("koodous=".length(), key.length());
} else if (key.toLowerCase().startsWith("malwarebazaar=".toLowerCase())) {
malwareBazaarKey = key.substring("malwarebazaar=".length(), key.length());
Expand All @@ -93,23 +96,52 @@ public static Arguments handle(String[] args) throws NoServicesSetException, NoH
virusTotalKey = key.substring("virustotal=".length(), key.length());
} else if (key.toLowerCase().startsWith("triage=".toLowerCase())) {
triageKey = key.substring("triage=".length(), key.length());
} else if (key.toLowerCase().startsWith("virusshare=".toLowerCase())) {
virusShareKey = key.substring("virusshare=".length(), key.length());
}
}

int threadCount = -1;
try {
threadCount = Integer.parseInt(threadsString);
} catch (Exception e) {
System.out.println("Please provide a valid number for the thread count: " + args[0]);
System.exit(0);
}

if (threadCount <= 0) {
System.out.println("Please provide a non-negative non-zero number for the thread count within the keys file: " + threadCount);
System.exit(0);
}

//Create a new set to store all hashes in, as a set cannot contain duplicate strings
Set<String> hashes = new HashSet<>();
//Add all hashes from the file into the set, sanity checks are performed within the loadFile function
hashes.addAll(loadFile(args[2]));

//Get the output path as a file object. The path is filtered for the home symbol
File path = new File(filterPath(args[3]));
//If it does not exist, any missing folder is created
//Add all hashes from the file into the set
for (int i = 1; i < args.length; i++) {
String hash = args[i];
if (hash.isBlank() == false) {
hashes.add(hash);
}
}

//Get the current working directory of the invoker
File path = null;

try {
path = new File(filterPath(args[0]));
} catch (Exception e) {
System.out.println("Please provide a valid path, the currently provided value is: " + args[0]);
System.exit(0);
}

//If it does not exist, any and all missing folders are created
if (path.exists() == false) {
path.mkdirs();
}

//Return the parsed arguments
return new Arguments(hashes, path.getAbsolutePath(), threadCount, koodousKey, malwareBazaarKey, malShareKey, virusTotalKey, triageKey);
return new Arguments(hashes, path.getAbsolutePath(), threadCount, koodousKey, malwareBazaarKey, malShareKey, virusTotalKey, triageKey, virusShareKey);
}

/**
Expand All @@ -125,7 +157,6 @@ private static List<String> loadFile(String path) {
//Create the file object based on the given path
path = filterPath(path);
File file = new File(path);

//Perform santiy checks
if (file.isDirectory()) {
System.out.println("The file at " + file.getAbsolutePath() + " is a directory!");
Expand All @@ -136,9 +167,12 @@ private static List<String> loadFile(String path) {
}

//Read the file, line by line where one line contains one hash
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
try ( BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while ((line = br.readLine()) != null) {
if (line.isEmpty() || line.isBlank()) {
continue;
}
output.add(line);
}
} catch (IOException ex) {
Expand All @@ -165,12 +199,13 @@ private static String filterPath(String path) {
* Prints the program's usage
*/
private static void printUsage() {
System.out.println("This tool downloads samples from MalShare, MalwareBazaar, Koodous, VirusTotal, and Hatching Triage based on given MD-5, SHA-1, or SHA-256 hashes.");
System.out.println("This tool downloads samples from (and in order) Hatching Triage, Malware Bazaar, MalShare, VirusShare, VirusTotal, and Koodous, based on given MD-5, SHA-1, or SHA-256 hash(es).");
System.out.println("The sample is written to the given output directory. API Keys for any of the used services is required.");
System.out.println("Once all samples are downloaded, the hashes that couldn't be found will be listed.");
System.out.println("For detailed information on the usage of MalPull, please visit https://maxkersten.nl/projects/malpull/#usage");
System.out.println("");
System.out.println("Sample usage of this program:");
System.out.println("\t\tjava -jar /path/toMalPull.jar threadCount /path/to/keys.txt /path/to/hashes.txt /path/to/write/samples/to");
System.out.println("\t\tjava -jar /path/toMalPull.jar /path/to/write/samples/to hash1 hash2 hashN");
System.out.println("Note that the keys.txt file is required to be in the same folder as MalPull's JAR!");
}
}
8 changes: 7 additions & 1 deletion src/main/java/malpull/cli/MalPullCli.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ public static void main(String[] args) {
//Parse the arguments
Arguments arguments = ArgumentHandler.handle(args);

System.out.println("The available platforms are:");
for (String platform : arguments.getAvailablePlatforms()) {
System.out.println("\t" + platform);
}
System.out.println("");

//Show the input to the user, as this helps to avoid mistakes
System.out.println("Read " + arguments.getHashes().size() + " hashes");
System.out.println("Downloading will be done using " + arguments.getThreadCount() + " thread(s)");
Expand All @@ -65,7 +71,7 @@ public static void main(String[] args) {
System.out.println("\nAll downloads finished! The sample number count is not always printed in ascending order, as the threads print the messages.");

//If some hashes could not be found, these are printed
if (result.getMissingHashes().size() > 0) {
if (result.getMissingHashes().isEmpty() == false) {
System.out.println("\n\nMissing " + result.getMissingHashes().size() + " hashes:");
for (String missingHash : result.getMissingHashes()) {
System.out.println(missingHash);
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/malpull/endpoints/Koodous.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,15 @@ public Koodous(String key) {
*
* @param hash the hash to look for
* @return the sample as a byte array
* @throws SampleNotFoundException if the sample cannot be found
* @throws SampleNotFoundException if the sample cannot be found or if the
* hash is invalid
*/
@Override
public byte[] getSample(String hash) throws SampleNotFoundException {
//Checks if the given hash is valid
if (hash == null || hash.isEmpty() || hash.isBlank()) {
throw new SampleNotFoundException("The given hash is null, empty, or consists of only white space characters!");
}
//Get the SHA-256 hash via the search function of the API, as only SHA-256 hashes can be used when downloading a sample
String sha256Hash = getSha256Hash(hash);
//Return the API's response
Expand Down
49 changes: 17 additions & 32 deletions src/main/java/malpull/endpoints/MalShare.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,39 @@
*/
package malpull.endpoints;

import java.io.IOException;
import malpull.exceptions.SampleNotFoundException;
import okhttp3.Request;
import malshareapi.MalShareApi;

/**
* This class is used to get a sample from the MalShare database via its API.
*
* @author Max 'Libra' Kersten [@Libranalysis, https://maxkersten.nl]
*/
public class MalShare extends GenericEndpoint implements IEndpoint {
public class MalShare implements IEndpoint {

/**
* An instance of the MalShare API library
*/
private MalShareApi api;

/**
* Creates an object to interact with the MalShare API
*
* @param key the MalShare API key which is required to use the API
*/
public MalShare(String key) {
//Sets the apiBase variable in the abstract GenericEndpoint class
super("https://malshare.com/api.php?api_key=" + key + "&action=", "MalShare");
api = new MalShareApi(key);
}

/**
* This API call will return a byte array if the sample is found. If it is
* not found, a plain text response that starts with "Sample not found by
* hash" is given.
* Gets the endpoint name
*
* @param hash the sample's hash
* @return the URL to get the sample from
* @return the name of the endpoint
*/
private String getDownloadUrl(String hash) {
return apiBase + "getfile&hash=" + hash;
@Override
public String getName() {
return "MalShare";
}

/**
Expand All @@ -58,28 +61,10 @@ private String getDownloadUrl(String hash) {
*/
@Override
public byte[] getSample(String hash) throws SampleNotFoundException {
//Gets the URL
String url = getDownloadUrl(hash);
//Create the request based on the URL
Request request = new Request.Builder()
.url(url)
.build();
//Get the result from the API
byte[] result = downloader.get(request);
//Convert the result into a new string to check what the result is
String temp = new String(result);
/**
* If the sample is not present in the MalShare API, a plain text string
* is returned. This string contains the "Sample not found by hash"
* text. If string contains this text, the sample cannot be found.
* Otherwise, the returned value is the raw file
*/
if (temp.contains("Sample not found by hash")) {
//If the sample cannot be found, an exception is thrown
try {
return api.getFile(hash);
} catch (IOException ex) {
throw new SampleNotFoundException("Sample " + hash + " not found on MalShare!");
}
//Return the sample
return result;
}

}
Loading

0 comments on commit f2583f7

Please sign in to comment.