ghidra/gradle/support/fetchDependencies.gradle

295 lines
11 KiB
Groovy

/* ###
* IP: GHIDRA
*
* 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.
*/
/*******************************************************************************
* fetchDependencies.gradle *
* *
* Fetches/downloads required dependencies that aren't available in the *
* standard online repositories (eg: maven) and configures a flat *
* directory-style respository that points to them. This should be run *
* immediately after cloning the Ghidra repository before any other gradle *
* tasks are run. *
* *
* usage: from the command line in the main ghidra repository directory, run *
* the following: *
* *
* gradle -I gradle/support/fetchDependencies.gradle init *
* *
* Note: When running the script, files will only be downloaded if *
* necessary (eg: they are not already in the dependencies/downloads/ *
* directory). *
* *
*******************************************************************************/
import java.util.zip.*;
import java.nio.file.*;
import java.security.MessageDigest;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
initscript {
repositories { mavenCentral() }
dependencies { classpath 'commons-io:commons-io:2.11.0' }
}
ext.NUM_RETRIES = 3 // # of times to try to download a file before failing
ext.REPO_DIR = ((Script)this).buildscript.getSourceFile().getParentFile().getParentFile().getParentFile()
ext.DEPS_DIR = file("${REPO_DIR}/dependencies")
ext.DOWNLOADS_DIR = file("${DEPS_DIR}/downloads")
ext.FID_DIR = file("${DEPS_DIR}/fidb")
ext.FLAT_REPO_DIR = file("${DEPS_DIR}/flatRepo")
file("${REPO_DIR}/Ghidra/application.properties").withReader { reader ->
def ghidraProps = new Properties()
ghidraProps.load(reader)
ext.RELEASE_VERSION = ghidraProps.getProperty('application.version')
}
ext.deps = [
[
name: "dex-tools-2.0.zip",
url: "https://github.com/pxb1988/dex2jar/releases/download/2.0/dex-tools-2.0.zip",
sha256: "7907eb4d6e9280b6e17ddce7ee0507eae2ef161ee29f70a10dbc6944fdca75bc",
destination: {
unzip(DOWNLOADS_DIR, DOWNLOADS_DIR, "dex-tools-2.0.zip")
FileUtils.copyDirectory(new File(DOWNLOADS_DIR, "dex2jar-2.0/lib/"), FLAT_REPO_DIR, new WildcardFileFilter("dex-*"));
}
],
[
name: "AXMLPrinter2.jar",
url: "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/android4me/AXMLPrinter2.jar",
sha256: "00ed038eb6abaf6ddec8d202a3ed7a81b521458f4cd459948115cfd02ff59d6d",
destination: FLAT_REPO_DIR
],
[
name: "yajsw-beta-13.01.zip",
url: "https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-beta-13.01/yajsw-beta-13.01.zip",
sha256: "430fb7901bd0fd52a5b90bd0cbd89e9d334077eb72a9b26896f465de1e593a99",
destination: file("${DEPS_DIR}/GhidraServer")
],
[
name: "PyDev 6.3.1.zip",
url: "https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip",
sha256: "4d81fe9d8afe7665b8ea20844d3f5107f446742927c59973eade4f29809b0699",
destination: file("${DEPS_DIR}/GhidraDev")
],
[
name: "cdt-8.6.0.zip",
url: "https://archive.eclipse.org/tools/cdt/releases/8.6/cdt-8.6.0.zip",
sha256: "81b7d19d57c4a3009f4761699a72e8d642b5e1d9251d2bb98df438b1e28f8ba9",
destination: file("${DEPS_DIR}/GhidraDev")
],
[
name: "vs2012_x64.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vs2012_x64.fidb",
sha256: "80d1c31c636b1775dd06001747dfb7e2ff60ff716299a8fcc232b8d2faa53a21",
destination: FID_DIR
],
[
name: "vs2012_x86.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vs2012_x86.fidb",
sha256: "8fc7ea9451b3a201ac3623b4a0924427cbb6bba410bdf9ebba4f2dfe8e77dfad",
destination: FID_DIR
],
[
name: "vs2015_x64.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vs2015_x64.fidb",
sha256: "c3c16d23517c233d6950d5d43e34cdfa1b15c1ee2e066c8e9a53b4f75a907a87",
destination: FID_DIR
],
[
name: "vs2015_x86.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vs2015_x86.fidb",
sha256: "6fc0a61e935a0060ab33bd6406c9ca5f215ac3dff655f83c18f3d144ad389fe5",
destination: FID_DIR
],
[
name: "vs2017_x64.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vs2017_x64.fidb",
sha256: "5b0f8b8dee110d8c54fc27ed808ea28c2c675e95f3809e33eb99f03672741833",
destination: FID_DIR
],
[
name: "vs2017_x86.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vs2017_x86.fidb",
sha256: "2a81615d588aa80043f7bc3a35db04580c990c90398e77399fcffc66f053e5ac",
destination: FID_DIR
],
[
name: "vs2019_x64.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vs2019_x64.fidb",
sha256: "80413b57ae20fc1850d15a401d1cec87ac61b1809a1dafc2fa4403bf2029ec94",
destination: FID_DIR
],
[
name: "vs2019_x86.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vs2019_x86.fidb",
sha256: "68b96a4e13ee2c157517636aa1c7841f750fbfc0026188f6123af017f3fa3117",
destination: FID_DIR
],
[
name: "vsOlder_x64.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vsOlder_x64.fidb",
sha256: "2466595f6e74d9599e16095cc61c6de769461c935c20a442db1d2dfd2d7bde9b",
destination: FID_DIR
],
[
name: "vsOlder_x86.fidb",
url: "https://github.com/NationalSecurityAgency/ghidra-data/raw/Ghidra_${RELEASE_VERSION}/FunctionID/vsOlder_x86.fidb",
sha256: "5b97f786eaebc785aaa365f17eb7cc9a1dd2b5a15b277a5229157ea76945fdc1",
destination: FID_DIR
]
]
// Download dependencies (if necessary) and verify their hashes
DOWNLOADS_DIR.mkdirs()
deps.each {
File file = new File(DOWNLOADS_DIR, it.name)
if (!it.sha256.equals(generateHash(file))) {
download(it.url, file.path)
assert(it.sha256.equals(generateHash(file)));
}
}
// Copies the downloaded dependencies to their required destination.
// Some downloads require pre-processing before their relevant pieces can be copied.
deps.each {
if (it.destination instanceof File) {
println("Copying " + it.name + " to " + it.destination)
it.destination.mkdirs()
FileUtils.copyFile(new File(DOWNLOADS_DIR, it.name), new File(it.destination, it.name));
}
else if (it.destination instanceof Closure) {
println("Processing " + it.name)
it.destination()
}
else {
throw new GradleException("Unexpected destination type: " + it.destination)
}
}
//-------------------------------------Helper methods----------------------------------------------
/**
* Downloads a file from a URL. The download attempt will be tried NUM_RETRIES times before failing.
*
* Progress is shown on the command line in the form of the number of bytes downloaded and a
* percentage of the total.
*
* Note: We do not validate that the number of bytes downloaded matches the expected total here; any
* discrepencies will be caught when checking the SHA-256s later on.
*
* @param url the file to download
* @param filename the local file to create for the download
*/
def download(url, filename) {
println("URL: " + url)
def(InputStream istream, size) = establishConnection(url, NUM_RETRIES);
assert istream != null : " ***CONNECTION FAILURE***\n max attempts exceeded; exiting\n"
FileOutputStream ostream = new FileOutputStream(filename);
def dataBuffer = new byte[1024];
int bytesRead;
int totalRead;
while ((bytesRead = istream.read(dataBuffer, 0, 1024)) != -1) {
ostream.write(dataBuffer, 0, bytesRead);
totalRead += bytesRead
print("\r")
print(" Downloading: " + totalRead + " of " + size)
if (!size.equals("???")) {
int pctComplete = (totalRead / size) * 100
print(" (" + pctComplete + "%)")
}
print(" ") // overwrite gradle timer output
System.out.flush()
}
println()
istream.close();
ostream.close();
}
/**
* Attempts to establish a connection to the given URL
*
* @param url the URL to connect to
* @param retries the number of times to attempt to connect if there are failures
* @return the InputStream for the URL, and the size of the download in bytes as a string
*/
def establishConnection(url, retries) {
for (int i = 0; i < retries; i++) {
try {
if (i == 0) {
println(" Connecting...")
}
else {
println(" Connecting (" + (i+1) + "/" + retries + ")...")
}
URLConnection conn = new URL(url).openConnection();
conn.setRequestMethod("HEAD");
def size = conn.getContentLengthLong();
if (size == -1) {
size = "???"
}
return [new BufferedInputStream(new URL(url).openStream()), size];
}
catch (Exception e) {
println(" Connection error! " + e)
}
}
}
/**
* Unzips a file to a directory
*
* @param sourceDir the directory where the zip file resides
* @param targetDir the directory where the unzipped files should be placed
* @param zipFileName the name of the file to unpack
*/
def unzip(sourceDir, targetDir, zipFileName) {
def zip = new ZipFile(new File(sourceDir, zipFileName))
zip.entries().findAll { !it.directory }.each { e ->
(e.name as File).with { f ->
if (f.parentFile != null) {
File destPath = new File(targetDir.path, f.parentFile.path)
destPath.mkdirs()
File targetFile = new File(destPath.path, f.name)
targetFile.withOutputStream { w ->
w << zip.getInputStream(e)
}
}
}
}
zip.close()
}
/**
* Generates the SHA-256 hash for the given file
*
* @param file the file to generate the SHA-256 hash for
* @return the generated SHA-256 hash, or null if the file does not exist
*/
def generateHash(file) {
if (!file.exists()) {
return null
}
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(Files.readAllBytes(Paths.get(file.path)));
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}