mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00:00
Merge branch 'GP-2774_ghidraffe_GhidraGo_final'
This commit is contained in:
commit
32d3fc4c70
0
Ghidra/Features/GhidraGo/Module.manifest
Normal file
0
Ghidra/Features/GhidraGo/Module.manifest
Normal file
30
Ghidra/Features/GhidraGo/build.gradle
Normal file
30
Ghidra/Features/GhidraGo/build.gradle
Normal file
@ -0,0 +1,30 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
|
||||
apply plugin: 'eclipse'
|
||||
|
||||
eclipse.project.name = 'Features GhidraGo'
|
||||
|
||||
dependencies {
|
||||
api project(':Base')
|
||||
api project(':Generic')
|
||||
api project(':Project')
|
||||
}
|
||||
|
4
Ghidra/Features/GhidraGo/certification.manifest
Normal file
4
Ghidra/Features/GhidraGo/certification.manifest
Normal file
@ -0,0 +1,4 @@
|
||||
##VERSION: 2.0
|
||||
Module.manifest||GHIDRA||||END|
|
||||
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
|
||||
src/main/help/help/topics/GhidraGo/GhidraGo.html||GHIDRA||||END|
|
60
Ghidra/Features/GhidraGo/src/main/help/help/TOC_Source.xml
Executable file
60
Ghidra/Features/GhidraGo/src/main/help/help/TOC_Source.xml
Executable file
@ -0,0 +1,60 @@
|
||||
<?xml version='1.0' encoding='ISO-8859-1' ?>
|
||||
<!--
|
||||
|
||||
This is an XML file intended to be parsed by the Ghidra help system. It is loosely based
|
||||
upon the JavaHelp table of contents document format. The Ghidra help system uses a
|
||||
TOC_Source.xml file to allow a module with help to define how its contents appear in the
|
||||
Ghidra help viewer's table of contents. The main document (in the Base module)
|
||||
defines a basic structure for the
|
||||
Ghidra table of contents system. Other TOC_Source.xml files may use this structure to insert
|
||||
their files directly into this structure (and optionally define a substructure).
|
||||
|
||||
|
||||
In this document, a tag can be either a <tocdef> or a <tocref>. The former is a definition
|
||||
of an XML item that may have a link and may contain other <tocdef> and <tocref> children.
|
||||
<tocdef> items may be referred to in other documents by using a <tocref> tag with the
|
||||
appropriate id attribute value. Using these two tags allows any module to define a place
|
||||
in the table of contents system (<tocdef>), which also provides a place for
|
||||
other TOC_Source.xml files to insert content (<tocref>).
|
||||
|
||||
During the help build time, all TOC_Source.xml files will be parsed and validated to ensure
|
||||
that all <tocref> tags point to valid <tocdef> tags. From these files will be generated
|
||||
<module name>_TOC.xml files, which are table of contents files written in the format
|
||||
desired by the JavaHelp system. Additionally, the genated files will be merged together
|
||||
as they are loaded by the JavaHelp system. In the end, when displaying help in the Ghidra
|
||||
help GUI, there will be on table of contents that has been created from the definitions in
|
||||
all of the modules' TOC_Source.xml files.
|
||||
|
||||
|
||||
Tags and Attributes
|
||||
|
||||
<tocdef>
|
||||
-id - the name of the definition (this must be unique across all TOC_Source.xml files)
|
||||
-text - the display text of the node, as seen in the help GUI
|
||||
-target** - the file to display when the node is clicked in the GUI
|
||||
-sortgroup - this is a string that defines where a given node should appear under a given
|
||||
parent. The string values will be sorted by the JavaHelp system using
|
||||
a javax.text.RulesBasedCollator. If this attribute is not specified, then
|
||||
the text of attribute will be used.
|
||||
|
||||
<tocref>
|
||||
-id - The id of the <tocdef> that this reference points to
|
||||
|
||||
**The URL for the target is relative and should start with 'help/topics'. This text is
|
||||
used by the Ghidra help system to provide a universal starting point for all links so that
|
||||
they can be resolved at runtime, across modules.
|
||||
|
||||
|
||||
-->
|
||||
|
||||
<tocroot>
|
||||
|
||||
<tocref id="Ghidra Support">
|
||||
|
||||
|
||||
<tocdef id="GhidraGo"
|
||||
text="GhidraGo"
|
||||
target="help/topics/GhidraGo/GhidraGo.html" />
|
||||
|
||||
</tocref> <!-- End Ghidra Support -->
|
||||
</tocroot>
|
@ -0,0 +1,233 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<!-- Keep pre text from wrapping so that it is formatted exactly as we have it -->
|
||||
<style>
|
||||
pre {
|
||||
white-space: no-wrap;
|
||||
font-family: 'Courier New', 'Courier';
|
||||
}
|
||||
|
||||
typewriter {
|
||||
font-family: 'Courier New', 'Courier';
|
||||
}
|
||||
|
||||
/* Make the general text a bit more readable */
|
||||
body {
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>GhidraGo README</title>
|
||||
<link rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
|
||||
<link rel="stylesheet" type="text/css" href="../../shared/languages.css">
|
||||
<meta name="generator" content="DocBook XSL Stylesheets V1.79.1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 align="center"><a name="top">GhidraGo README</a></h1>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
<UL>
|
||||
<LI><a href="#general">Introduction</a></LI>
|
||||
<ul><li><a href="#example">Example</a></li></ul>
|
||||
<LI><a href="#plugin">Configure GhidraGo Plugin</a></LI>
|
||||
<li>
|
||||
<a href="#configure">Configure Protocol Handler (Platform Specific)</a>
|
||||
<ul>
|
||||
<LI><a href="#windows">Windows</a></LI>
|
||||
<LI><a href="#linux">Linux</a></LI>
|
||||
<LI><a href="#mac">Mac</a></LI>
|
||||
</ul>
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||
|
||||
<h2><a name="general">GhidraGo Introduction</a></h2>
|
||||
<div>
|
||||
<p>
|
||||
GhidraGo is a mechanism to cause Ghidra to display a previously imported program within a
|
||||
local or multi-user project using a ghidraURL hyperlink similar to an http reference. In
|
||||
practice ghidraURL's work very similarly to selecting a URL reference which displays a PDF.
|
||||
Once setup correctly, GhidraURL links can be placed in web pages, external project
|
||||
documentation files, or any other place a URL hyperlink can be placed.
|
||||
</p>
|
||||
<p>
|
||||
When a GhidraURL is selected, GhidraGo will startup Ghidra if it isn't already running as
|
||||
well as prompt to login to the multi-user project if necessary. The program is displayed in
|
||||
the default tool, usually the codebrowser, and can be configured to re-use an open default
|
||||
tool or to use a new default tool. The GhidraURL must currently be locating a DomainFile
|
||||
that is either in a Remote, Shared project, or a local project.
|
||||
</p>
|
||||
<p>
|
||||
GhidraGo is a combination of a command line program to send a link, a plugin running within
|
||||
the Ghidra project manager, and the configuration of the default handling for the ghidraURL
|
||||
within the user environment. The ghidraURL is sent as the first and only parameter to the
|
||||
ghidraGo command line interface.
|
||||
</p>
|
||||
<p>
|
||||
GhidraGo passes information through a simple filesystem mechanism vice an open port for
|
||||
security and simplicity. GhidraGo works on Windows, Linux, and MacOS.
|
||||
</p>
|
||||
<p>
|
||||
<p>GhidraURL's have the format:</p>
|
||||
<div style="margin-left: 25px">
|
||||
<p>
|
||||
Remote Ghidra Server File:
|
||||
ghidra://<host>[:<port>]/<repository-name>/<program-path>
|
||||
[#<address-or-symbol-ref>]
|
||||
</p>
|
||||
<p style="margin-left: 25px">Example: ghidra://hostname/Repo/notepad.exe#main</p>
|
||||
|
||||
<p>
|
||||
Local Ghidra Project File:
|
||||
ghidra:/[<project-path>/]<project-name>?/<program-path>
|
||||
[#<address-or-symbol-ref>]
|
||||
</p>
|
||||
<p style="margin-left: 25px">Example: ghidra:/share/MyProject?/notepad.exe#main</p>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3><a name="example">Example of Using ghidraGo CLI</a></h3>
|
||||
<div>
|
||||
<p><code>ghidraGo ghidra://ghidra-server/project/myProgram#symbol</code></p>
|
||||
<p>Executing this command will result in the program called <code>myProgram</code> being
|
||||
opened in Ghidra's default tool with the cursor at <code>symbol</code>.</p>
|
||||
</div>
|
||||
|
||||
<h2><a name="plugin">Configure GhidraGo Plugin</a></h2>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Start Ghidra</li>
|
||||
<li>Choose File > Configuration in the Project Window (not the Codebrowser Window)</li>
|
||||
<li>Click the Plug Icon in the upper right to display all plugins</li>
|
||||
<li>Search for GhidraGoPlugin and select it</li>
|
||||
<li>Press OK</li>
|
||||
</ol>
|
||||
<p>Ghidra is now configured to listen to GhidraGo Requests. You can execute a GhidraGo request
|
||||
using the "ghidraGo" shell/batch script in
|
||||
<code>/path/to/ghidra/support/GhidraGo/ghidraGo</code></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2><a name="configure">Configure Protocol Handler (Platform Specific)</a></h2>
|
||||
<div>
|
||||
<p>
|
||||
Configuring your platform to handle the <script>ghidra</script> protocol is what
|
||||
enables the ghidraGo command line interface to be associated with a ghidraURL. Once
|
||||
configured, clicking hyperlinks that start with the <script>ghidra</script> protocol
|
||||
will execute the ghidraGo CLI with that hyperlink as the first argument. The
|
||||
configuration is platform specific.
|
||||
</p>
|
||||
<p>
|
||||
*NOTE: changes to your path to ghidra, such as upgrading ghidra to a new version,
|
||||
will require the path you set in this configuration to be updated.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3><a name="windows">Windows Protocol Handler Configuration</a></h3>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Go to Start > Find and Type <code>regedit</code></li>
|
||||
<li>Right click HKEY_CLASSES_ROOT then New > Key</li>
|
||||
<li>Name the key "ghidra"</li>
|
||||
<li>Right Click ghidra > New > String Value and add "URL Protocol" without a value</li>
|
||||
<li>Right Click ghidra > New > Key and create the heiarchy ghidra/shell/open/command</li>
|
||||
<li>Inside command change (Default) to the path where ghidraGo is located followed by
|
||||
a "%1". For Example:</li>
|
||||
<br />
|
||||
<code>C:\Path\To\Ghidra\support\GhidraGo\ghidraGo "%1"</code>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h3><a name="linux">Linux Protocol Handler Configuration</a></h3>
|
||||
<div>
|
||||
<p>In Linux, when you click a browser link with an <code>href</code> value to a GhidraURL,
|
||||
you'll be prompted to use xdg-open.</p>
|
||||
<ol>
|
||||
<li>Edit the file <code>ghidra.desktop</code> in <code>~/.local/share/applications</code></li>
|
||||
<br />
|
||||
<code>
|
||||
[Desktop Entry]<br />
|
||||
Name=ghidra Client<br />
|
||||
Exec=/path/to/ghidra/support/GhidraGo/ghidraGo "%u"<br />
|
||||
Type=Application<br />
|
||||
Terminal=false<br />
|
||||
MimeType=x-scheme-handler/ghidra;<br />
|
||||
</code>
|
||||
<br />
|
||||
<li>Edit the file mimeapps.list in <code>~/.local/share/applications</code></li>
|
||||
<br />
|
||||
<code>
|
||||
[Default Applications]<br />
|
||||
x-scheme-handler/ghidra=ghidra.desktop<br />
|
||||
...<br />
|
||||
</code>
|
||||
</ol>
|
||||
<p>After the steps above, you should be able to click a GhidraURL href, get the same
|
||||
xdg-open prompt, and upon clicking "Open xdg-open" GhidraGo should execute and open
|
||||
Ghidra to the given GhidraURL.</p>
|
||||
</div>
|
||||
|
||||
<h3><a name="mac">Mac Protocol Handler Configuration</a></h3>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open <code>Script Editor</code> and past the following into the editor.</li>
|
||||
<br />
|
||||
<code>
|
||||
on open location schemeUrl<br />
|
||||
 set ghidraUrl to quoted form of schemeUrl<br />
|
||||
 do shell script "/path/to/ghidraGo " & ghidraUrl<br />
|
||||
end open location<br />
|
||||
</code>
|
||||
<br />
|
||||
<li>Save the script as an Application named GhidraGo in either
|
||||
<code>/Applications</code> or <code>~/Applications</code></li>
|
||||
<li>Right click on the saved Application and click Show Package Contents</li>
|
||||
<li>Open Contents > Info.plist and under
|
||||
<code><string>com.apple.ScriptEditor.id.GhidraGo</string></code>
|
||||
paste the following:</li>
|
||||
<br />
|
||||
<code>
|
||||
<key>CFBundleURLTypes</key><br />
|
||||
<array><br />
|
||||
 <dict><br />
|
||||
  <key>CFBundleURLName</key><br />
|
||||
  <string>Ghidra Scheme</string><br />
|
||||
  <key>CFBundleURLSchemes</key><br />
|
||||
  <array><br />
|
||||
   <string>ghidra</string><br />
|
||||
  </array><br />
|
||||
 </dict><br />
|
||||
</array>
|
||||
</code>
|
||||
<br />
|
||||
<li>Go to the Applications folder where you saved the GhidraGo, and Open
|
||||
GhidraGo (run it once).</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
(<a href="#top">Back to Top</a>)
|
||||
|
||||
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||
<address></address>
|
||||
Last modified: Oct 26 2023
|
||||
</body> </html>
|
||||
|
||||
<style>
|
||||
|
||||
table, td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
168
Ghidra/Features/GhidraGo/src/main/java/ghidra/GhidraGo.java
Normal file
168
Ghidra/Features/GhidraGo/src/main/java/ghidra/GhidraGo.java
Normal file
@ -0,0 +1,168 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import docking.framework.DockingApplicationConfiguration;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.go.GhidraGoSender;
|
||||
import ghidra.app.plugin.core.go.exception.*;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.util.*;
|
||||
|
||||
/**
|
||||
* <h1>GhidraGo Client</h1>
|
||||
* <p>The first argument is expected to be non-null and a valid {@link GhidraURL}</p>
|
||||
* <p>If the {@link GhidraURL} is valid, the URL is processed in an existing Ghidra, or
|
||||
* a new Ghidra is started and used to process the URL.</p>
|
||||
* <p>A valid {@link GhidraURL} in this case must be pointing to a remote (shared project)
|
||||
* Program.</p>
|
||||
* <p>In the event that a Ghidra is running and does not have an active project, the URL cannot be
|
||||
* processed.</p>
|
||||
*/
|
||||
public class GhidraGo implements GhidraLaunchable {
|
||||
|
||||
private GhidraGoSender sender;
|
||||
|
||||
/**
|
||||
* Initializes a new GhidraGoSender and processes the {@link GhidraURL}
|
||||
* @param layout the layout passed from main.Ghidra
|
||||
* @param args the CLI args passed to GhidraGo. args should contain a single {@link GhidraURL}.
|
||||
* @throws Exception in the event of an error
|
||||
*/
|
||||
@Override
|
||||
public void launch(GhidraApplicationLayout layout, String[] args) throws Exception {
|
||||
try {
|
||||
ApplicationConfiguration configuration = null;
|
||||
if (!Application.isInitialized()) {
|
||||
System.setProperty(ApplicationProperties.APPLICATION_NAME_PROPERTY, "GhidraGo");
|
||||
configuration = new DockingApplicationConfiguration();
|
||||
Application.initializeApplication(layout, configuration);
|
||||
}
|
||||
if (args != null && args.length > 0) {
|
||||
ghidra.framework.protocol.ghidra.Handler.registerHandler();
|
||||
sender = new GhidraGoSender();
|
||||
|
||||
startGhidraIfNeeded(layout);
|
||||
|
||||
sender.send(args[0]);
|
||||
// if configuration is null, probably running inside a test
|
||||
if (configuration != null) {
|
||||
// calling System.exit explicitly is necessary, otherwise the Loading... screen
|
||||
// persists instead of closing when complete.
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"A valid GhidraURL locating a program, program name, or path to a program name " +
|
||||
"must be specified as the first command line argument.");
|
||||
}
|
||||
}
|
||||
catch (FailedToStartGhidraException e) {
|
||||
logOrShowError("GhidraGo Start Ghidra Exception",
|
||||
"Failed to start Ghidra from GhidraGo", e);
|
||||
System.exit(-1);
|
||||
}
|
||||
catch (StopWaitingException e) {
|
||||
System.exit(-1);
|
||||
}
|
||||
catch (Exception e) {
|
||||
logOrShowError("GhidraGo Exception", "An unexpected exception occurred in GhidraGo", e);
|
||||
// calling System.exit explicitly is necessary, otherwise the Loading... screen
|
||||
// persists instead of closing when complete.
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
private void logOrShowError(String errorTitle, String errorMessage, Exception e) {
|
||||
if (SystemUtilities.isInHeadlessMode()) {
|
||||
Msg.error(this, errorMessage, e);
|
||||
}
|
||||
else {
|
||||
Swing.runNow(() -> Msg.showError(this, null, errorTitle, errorMessage, e));
|
||||
}
|
||||
}
|
||||
|
||||
private void startGhidraIfNeeded(GhidraApplicationLayout layout)
|
||||
throws StopWaitingException, FailedToStartGhidraException {
|
||||
// if there is no listening Ghidra
|
||||
if (!sender.isGhidraListening()) {
|
||||
|
||||
// attempt to start a Ghidra within a locked action
|
||||
// do not wait for the lock if another GhidraGo has been started.
|
||||
try {
|
||||
boolean success = sender.doLockedAction(false, () -> {
|
||||
try {
|
||||
Process ghidraProcess = startGhidra(layout);
|
||||
sender.waitForListener(ghidraProcess);
|
||||
return true;
|
||||
}
|
||||
catch (StopWaitingException e) {
|
||||
return true;
|
||||
}
|
||||
catch (StartedGhidraProcessExitedException | IOException e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (!success) {
|
||||
// GhidraGo attempted to start ghidra and failed
|
||||
throw new FailedToStartGhidraException();
|
||||
}
|
||||
}
|
||||
catch (UnableToGetLockException e) {
|
||||
// When another GhidraGo has the lock,
|
||||
// wait for there to be a listener without starting the process
|
||||
sender.waitForListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the execution platform and executes the appropriate shell/bash script to start
|
||||
* Ghidra.
|
||||
* @throws IOException in the event that the execution failed
|
||||
*/
|
||||
private Process startGhidra(GhidraApplicationLayout layout) throws IOException {
|
||||
ResourceFile file = layout.getApplicationInstallationDir();
|
||||
Path ghidraRunPath;
|
||||
|
||||
if (SystemUtilities.isInDevelopmentMode()) {
|
||||
if (Platform.CURRENT_PLATFORM.getOperatingSystem() == OperatingSystem.WINDOWS) {
|
||||
ghidraRunPath = Path.of(file.getAbsolutePath(),
|
||||
"/ghidra/Ghidra/RuntimeScripts/Windows/ghidraRun.bat");
|
||||
}
|
||||
else {
|
||||
ghidraRunPath = Path.of(file.getAbsolutePath(),
|
||||
"/ghidra/Ghidra/RuntimeScripts/Linux/ghidraRun");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (Platform.CURRENT_PLATFORM.getOperatingSystem() == OperatingSystem.WINDOWS) {
|
||||
ghidraRunPath = Path.of(file.getAbsolutePath(), "/ghidraRun.bat");
|
||||
}
|
||||
else {
|
||||
ghidraRunPath = Path.of(file.getAbsolutePath(), "/ghidraRun");
|
||||
}
|
||||
}
|
||||
|
||||
Msg.info(this, "Starting new Ghidra using ghidraRun script at " + ghidraRunPath);
|
||||
return Runtime.getRuntime().exec(ghidraRunPath.toString());
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.go.ipc.GhidraGoListener;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
|
||||
import ghidra.framework.model.ToolServices;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
category = PluginCategoryNames.COMMON,
|
||||
status = PluginStatus.UNSTABLE,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
shortDescription = "Listens for new GhidraURL's to launch using ToolServices",
|
||||
description = "Polls the ghidraGo directory for any url files written by the GhidraGoClient and " +
|
||||
"processes them in Ghidra",
|
||||
eventsConsumed = {ProjectPluginEvent.class})
|
||||
//@formatter:on
|
||||
public class GhidraGoPlugin extends Plugin implements ApplicationLevelOnlyPlugin {
|
||||
private GhidraGoListener listener;
|
||||
|
||||
public GhidraGoPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
if (this.listener != null) {
|
||||
listener.dispose();
|
||||
listener = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
if (event instanceof ProjectPluginEvent) {
|
||||
if (((ProjectPluginEvent) event).getProject() == null) {
|
||||
dispose();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
listener = new GhidraGoListener((url) -> {
|
||||
processGhidraURL(url);
|
||||
});
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "GhidraGoPlugin Exception",
|
||||
"Unable to create Listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the active project is null, do nothing.
|
||||
* Otherwise, try and open the url using {@link ToolServices} launchDefaultToolWithURL function.
|
||||
* @param ghidraURL the GhidraURL to open.
|
||||
*/
|
||||
private void processGhidraURL(URL ghidraURL) {
|
||||
|
||||
Msg.info(GhidraGoPlugin.class, "GhidraGo processing " + ghidraURL);
|
||||
|
||||
try {
|
||||
Msg.info(GhidraGoPlugin.class,
|
||||
"Accepting the resource at " + GhidraURL.getProjectURL(ghidraURL));
|
||||
SystemUtilities.runSwingNow(() -> {
|
||||
AppInfo.getFrontEndTool().toFront();
|
||||
AppInfo.getFrontEndTool().getToolServices().launchDefaultToolWithURL(ghidraURL);
|
||||
});
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.showError(GhidraGoPlugin.class, null, "GhidraGo Unable to process GhidraURL",
|
||||
"GhidraGo could not process " + ghidraURL, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.app.plugin.core.go.exception.*;
|
||||
import ghidra.app.plugin.core.go.ipc.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class GhidraGoSender extends GhidraGoIPC {
|
||||
|
||||
public GhidraGoSender() throws IOException {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// empty
|
||||
}
|
||||
|
||||
/**
|
||||
* performs the given action once the sender lock has been acquired. Using this method ensures
|
||||
* only one sender will perform the given action.
|
||||
* @param waitForLock whether to block until the lock is available
|
||||
* @param action the action to be performed once a lock is acquired. Returns true if successful.
|
||||
* @return true if action was successfully performed; false otherwise.
|
||||
* @throws UnableToGetLockException if the lock was unobtainable
|
||||
*/
|
||||
public boolean doLockedAction(boolean waitForLock, Supplier<Boolean> action)
|
||||
throws UnableToGetLockException {
|
||||
return GhidraGoIPC.doLockedAction(senderLockPath, waitForLock, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the url to an existing, listening Ghidra
|
||||
* @param url a valid {@link GhidraURL} in string form for a remote Ghidra program. An error is
|
||||
* displayed if the url is null.
|
||||
* @throws StopWaitingException in the event the stop waiting dialog is shown and answered No.
|
||||
*/
|
||||
public void send(String url) throws StopWaitingException {
|
||||
if (StringUtils.isEmpty(url)) {
|
||||
Swing.runNow(() -> Msg.showError(this, null, "GhidraGo Empty URL Error",
|
||||
"An empty GhidraURL cannot be sent."));
|
||||
return;
|
||||
}
|
||||
// create a random file and write the url in it
|
||||
String fileName = UUID.randomUUID().toString();
|
||||
Path randomFilePath = channelPath.resolve(fileName);
|
||||
Path writtenFilePath = urlFilesPath.resolve(fileName);
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(randomFilePath.toFile());) {
|
||||
fos.write(url.getBytes());
|
||||
// need to close the file so that it can be moved on window's host
|
||||
fos.close();
|
||||
Files.move(randomFilePath, writtenFilePath);
|
||||
}
|
||||
catch (IOException e) {
|
||||
randomFilePath.toFile().delete();
|
||||
Swing.runNow(() -> Msg.showError(this, null, "GhidraGo Error Sending URL",
|
||||
"There was a file system error preventing the url from being sent.", e));
|
||||
}
|
||||
|
||||
Msg.info(this, "Wrote " + url + " to random file " + writtenFilePath);
|
||||
if (writtenFilePath.toFile().exists()) {
|
||||
waitForFileToBeProcessed(writtenFilePath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* waits for the file located at the given file path to be deleted.
|
||||
* @param filePath the path to the file to wait for deletion of
|
||||
* @throws StopWaitingException in the event the stop waiting dialog is shown and answered No.
|
||||
*/
|
||||
private void waitForFileToBeProcessed(Path filePath) throws StopWaitingException {
|
||||
// check without dialogs every 100 milliseconds
|
||||
if (filePath.toFile().exists()) {
|
||||
|
||||
// set up periodic check for file
|
||||
CheckForFileProcessedRunnable checkForFile =
|
||||
new CheckForFileProcessedRunnable(filePath, 100, TimeUnit.MILLISECONDS);
|
||||
|
||||
// start checking for file
|
||||
checkForFile.startChecking(100, TimeUnit.MILLISECONDS);
|
||||
|
||||
// block until file has been processed or user answers dialog with No.
|
||||
checkForFile.awaitTermination();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wait for a Ghidra to be listening and ready.
|
||||
* @throws StopWaitingException in the event waiting for a listener was stopped
|
||||
*/
|
||||
public void waitForListener() throws StopWaitingException {
|
||||
try {
|
||||
waitForListener(null);
|
||||
}
|
||||
catch (StartedGhidraProcessExitedException e) {
|
||||
// this will never happen when the process sent is null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wait for a Ghidra to be listening and ready.
|
||||
* @param p ghidraRun process that is being waited for in the event that GhidraGo
|
||||
* started Ghidra
|
||||
* @throws StopWaitingException in the event waiting for a listener was stopped
|
||||
* @throws StartedGhidraProcessExitedException in the event a Ghidra was started and exited
|
||||
* unexpectedly.
|
||||
*/
|
||||
public void waitForListener(Process p)
|
||||
throws StopWaitingException, StartedGhidraProcessExitedException {
|
||||
if (!isGhidraListening()) {
|
||||
// set up periodic check for listener
|
||||
CheckForListenerRunnable checkForListener = new CheckForListenerRunnable(p, 100,
|
||||
TimeUnit.MILLISECONDS,
|
||||
() -> !isGhidraListening());
|
||||
|
||||
// start checking for listener
|
||||
checkForListener.startChecking(100, TimeUnit.MILLISECONDS);
|
||||
|
||||
// block until listener has been processed or user answers dialog with No.
|
||||
checkForListener.awaitTermination();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.dialog;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.MultiLineLabel;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.label.GIconLabel;
|
||||
import ghidra.app.plugin.core.go.exception.StopWaitingException;
|
||||
|
||||
public abstract class GhidraGoWaitDialog extends DialogComponentProvider {
|
||||
|
||||
public static final int WAIT = 0;
|
||||
public static final int DO_NOT_WAIT = 1;
|
||||
|
||||
protected int actionID = DO_NOT_WAIT;
|
||||
protected boolean answered = false;
|
||||
|
||||
public GhidraGoWaitDialog(String title, String msgText, boolean modal) {
|
||||
super(title, modal);
|
||||
|
||||
addWorkPanel(buildMainPanel(msgText));
|
||||
|
||||
JButton waitButton = new JButton("Wait");
|
||||
waitButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
actionID = WAIT;
|
||||
answered = true;
|
||||
close();
|
||||
}
|
||||
});
|
||||
addButton(waitButton);
|
||||
|
||||
JButton noWaitButton = new JButton("No");
|
||||
noWaitButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
actionID = DO_NOT_WAIT;
|
||||
answered = true;
|
||||
close();
|
||||
}
|
||||
});
|
||||
addButton(noWaitButton);
|
||||
}
|
||||
|
||||
public void showDialog() throws StopWaitingException {
|
||||
answered = false;
|
||||
if (!isShowing()) {
|
||||
DockingWindowManager.showDialog(null, this);
|
||||
}
|
||||
|
||||
if (answered && actionID == DO_NOT_WAIT) {
|
||||
throw new StopWaitingException();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAnsweredNo() {
|
||||
return answered && actionID == DO_NOT_WAIT;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
answered = false;
|
||||
actionID = WAIT;
|
||||
close();
|
||||
}
|
||||
|
||||
protected JPanel buildMainPanel(String msgTextString) {
|
||||
JPanel innerPanel = new JPanel();
|
||||
innerPanel.setLayout(new BorderLayout());
|
||||
innerPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
|
||||
|
||||
JPanel msgPanel = new JPanel(new BorderLayout());
|
||||
msgPanel.add(
|
||||
new GIconLabel(OptionDialog.getIconForMessageType(OptionDialog.WARNING_MESSAGE)),
|
||||
BorderLayout.WEST);
|
||||
|
||||
MultiLineLabel msgText = new MultiLineLabel(msgTextString);
|
||||
msgText.setMaximumSize(msgText.getPreferredSize());
|
||||
msgPanel.add(msgText, BorderLayout.CENTER);
|
||||
|
||||
innerPanel.add(msgPanel, BorderLayout.CENTER);
|
||||
return innerPanel;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.dialog;
|
||||
|
||||
public class GhidraGoWaitForListenerDialog extends GhidraGoWaitDialog {
|
||||
|
||||
public GhidraGoWaitForListenerDialog() {
|
||||
super("GhidraGo Taking Longer Than Expected to Listen",
|
||||
"If Ghidra has started, please confirm the GhidraGoPlugin has been added in " +
|
||||
"File->Configure in the Ghidra project manager.\n" +
|
||||
"If GhidraGoPlugin has been configured, make sure Ghidra has an active project.\n" +
|
||||
"Would you like to keep waiting?",
|
||||
true);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.exception;
|
||||
|
||||
public class FailedToStartGhidraException extends Exception {
|
||||
// empty
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.exception;
|
||||
|
||||
public class StartedGhidraProcessExitedException extends Exception {
|
||||
|
||||
public StartedGhidraProcessExitedException(int exitValue) {
|
||||
super("Started Ghidra process exited early with exit-value: " + exitValue);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.exception;
|
||||
|
||||
public class StopWaitingException extends Exception {
|
||||
// empty
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.exception;
|
||||
|
||||
public class UnableToGetLockException extends Exception {
|
||||
// empty
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.ipc;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import ghidra.app.plugin.core.go.exception.StopWaitingException;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class CheckForFileProcessedRunnable extends CheckPeriodicallyRunnable {
|
||||
|
||||
/**
|
||||
* How long to wait before asking to continue waiting after the url has been sent to a listening
|
||||
* Ghidra
|
||||
*/
|
||||
public static int WAIT_FOR_PROCESSING_DELAY_MS = 500;
|
||||
|
||||
/**
|
||||
* How frequently to ask to continue waiting after Wait is selected
|
||||
*/
|
||||
public static int WAIT_FOR_PROCESSING_PERIOD_MS = 60_000;
|
||||
|
||||
/**
|
||||
* Maximum amount of time to wait for the file to be processed
|
||||
*/
|
||||
public static int MAX_WAIT_FOR_PROCESSING_MIN = 1;
|
||||
|
||||
private Path filePath;
|
||||
private StopWaitingException stopWaitingException;
|
||||
|
||||
public CheckForFileProcessedRunnable(Path filePath, int period, TimeUnit timeUnit) {
|
||||
super(false, period, timeUnit, () -> filePath.toFile().exists());
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor is used to create the thread that will show the wait dialog
|
||||
* @param executor the internal executor that should have 2 threads
|
||||
* @param filePath the path to the file to check
|
||||
* @param period the interval to show the dialog
|
||||
* @param timeUnit the units for the period
|
||||
*/
|
||||
private CheckForFileProcessedRunnable(ScheduledExecutorService executor, Path filePath,
|
||||
int period, TimeUnit timeUnit) {
|
||||
super(executor, true, period, timeUnit, () -> filePath.toFile().exists());
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
if (checkCondition.call()) {
|
||||
try {
|
||||
if (showDialog) {
|
||||
// show the dialog in a blocking action. This will throw a StopWaitingException
|
||||
// if they answer No. Otherwise, they want to keep waiting.
|
||||
dialog.showDialog();
|
||||
}
|
||||
|
||||
// if their response was WAIT, reset the timer
|
||||
executor.schedule(this, period, timeUnit);
|
||||
}
|
||||
catch (StopWaitingException e) {
|
||||
this.stopWaitingException = e;
|
||||
dispose();
|
||||
}
|
||||
catch (RejectedExecutionException e) {
|
||||
// this is okay, executor has been shutdown
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
else {
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Swing.runNow(() -> Msg.showError(this, null, "GhidraGo Unable to Check File",
|
||||
"GhidraGo could not check existence of file at " + filePath, e));
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startChecking(int delay, TimeUnit delayTimeUnit) throws StopWaitingException {
|
||||
dialog.reset();
|
||||
try {
|
||||
// start thread to check frequently
|
||||
super.startChecking(delay, delayTimeUnit);
|
||||
|
||||
// start thread for showing dialog
|
||||
executor.schedule(new CheckForFileProcessedRunnable(executor, filePath,
|
||||
WAIT_FOR_PROCESSING_PERIOD_MS, TimeUnit.MILLISECONDS), WAIT_FOR_PROCESSING_DELAY_MS,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (RejectedExecutionException e) {
|
||||
if (dialog.isAnsweredNo()) {
|
||||
throw new StopWaitingException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void awaitTermination() throws StopWaitingException {
|
||||
try {
|
||||
executor.awaitTermination(MAX_WAIT_FOR_PROCESSING_MIN, TimeUnit.MINUTES);
|
||||
if (dialog.isAnsweredNo()) {
|
||||
throw new StopWaitingException();
|
||||
}
|
||||
|
||||
if (this.stopWaitingException != null) {
|
||||
throw this.stopWaitingException;
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// this is okay
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.ipc;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import ghidra.app.plugin.core.go.exception.StartedGhidraProcessExitedException;
|
||||
import ghidra.app.plugin.core.go.exception.StopWaitingException;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class CheckForListenerRunnable extends CheckPeriodicallyRunnable {
|
||||
/**
|
||||
* If a listening Ghidra needs to be started, how long to wait before asking to continue
|
||||
* waiting
|
||||
*/
|
||||
public static int WAIT_FOR_LISTENER_DELAY_MS = 30_000;
|
||||
|
||||
/**
|
||||
* How frequently to ask to continue waiting after Wait is selected
|
||||
*/
|
||||
public static int WAIT_FOR_LISTENER_PERIOD_MS = 60_000;
|
||||
|
||||
/**
|
||||
* Maximum amount of time to wait for a listening Ghidra
|
||||
*/
|
||||
public static int MAX_WAIT_FOR_LISTENER_MIN = 5;
|
||||
|
||||
private Process process;
|
||||
private StopWaitingException stopWaitingException;
|
||||
private StartedGhidraProcessExitedException startedGhidraProcessExitedException;
|
||||
|
||||
public CheckForListenerRunnable(Process p, int period, TimeUnit timeUnit,
|
||||
Callable<Boolean> checkCondition) {
|
||||
super(false, period, timeUnit, checkCondition);
|
||||
this.process = p;
|
||||
}
|
||||
|
||||
private CheckForListenerRunnable(ScheduledExecutorService executor, Process p, int period,
|
||||
TimeUnit timeUnit, Callable<Boolean> checkCondition) {
|
||||
super(executor, true, period, timeUnit, checkCondition);
|
||||
this.process = p;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
if (checkCondition.call()) {
|
||||
try {
|
||||
checkProcessDidNotExit(process);
|
||||
if (showDialog) {
|
||||
Msg.info(this, "Waiting for GhidraGo to listen for new files...");
|
||||
// show the dialog in a blocking action. This will throw a StopWaitingException
|
||||
// if they answer No. Otherwise, they want to keep waiting.
|
||||
dialog.showDialog();
|
||||
}
|
||||
executor.schedule(this, period, timeUnit);
|
||||
}
|
||||
catch (StopWaitingException e) {
|
||||
this.stopWaitingException = e;
|
||||
dispose();
|
||||
}
|
||||
catch (StartedGhidraProcessExitedException e) {
|
||||
this.startedGhidraProcessExitedException = e;
|
||||
dispose();
|
||||
}
|
||||
catch (RejectedExecutionException e) {
|
||||
// this is okay, executor has been shutdown
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
else {
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Swing.runNow(() -> Msg.showError(this, null, "GhidraGo Unable to Check For Listener",
|
||||
"GhidraGo could not check for a listener.", e));
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks to see if Ghidra process exited early, In the event Ghidra exits early,
|
||||
* a runtime exception is thrown
|
||||
* @param p Ghidra process
|
||||
* @throws StartedGhidraProcessExitedException if the Ghidra process has an exit value
|
||||
*/
|
||||
private void checkProcessDidNotExit(Process p) throws StartedGhidraProcessExitedException {
|
||||
if (p != null) {
|
||||
try {
|
||||
int exitValue = p.exitValue();
|
||||
if (exitValue != 0)
|
||||
throw new StartedGhidraProcessExitedException(exitValue);
|
||||
}
|
||||
catch (IllegalThreadStateException e) {
|
||||
// this is okay, ghidraRun hasn't exited
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startChecking(int delay, TimeUnit delayTimeUnit) throws StopWaitingException {
|
||||
dialog.reset();
|
||||
try {
|
||||
// start thread to check frequently
|
||||
super.startChecking(delay, delayTimeUnit);
|
||||
|
||||
// start thread for showing dialog
|
||||
executor.schedule(
|
||||
new CheckForListenerRunnable(executor, process, WAIT_FOR_LISTENER_PERIOD_MS,
|
||||
TimeUnit.MILLISECONDS, checkCondition),
|
||||
WAIT_FOR_LISTENER_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (RejectedExecutionException e) {
|
||||
if (dialog.isAnsweredNo()) {
|
||||
throw new StopWaitingException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void awaitTermination()
|
||||
throws StopWaitingException, StartedGhidraProcessExitedException {
|
||||
try {
|
||||
executor.awaitTermination(MAX_WAIT_FOR_LISTENER_MIN, TimeUnit.MINUTES);
|
||||
|
||||
if (dialog.isAnsweredNo()) {
|
||||
throw new StopWaitingException();
|
||||
}
|
||||
|
||||
if (this.stopWaitingException != null) {
|
||||
throw this.stopWaitingException;
|
||||
}
|
||||
if (this.startedGhidraProcessExitedException != null) {
|
||||
throw this.startedGhidraProcessExitedException;
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// this is okay
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.ipc;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import ghidra.app.plugin.core.go.dialog.GhidraGoWaitForListenerDialog;
|
||||
import ghidra.app.plugin.core.go.exception.StartedGhidraProcessExitedException;
|
||||
import ghidra.app.plugin.core.go.exception.StopWaitingException;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public abstract class CheckPeriodicallyRunnable implements Runnable {
|
||||
|
||||
protected static GhidraGoWaitForListenerDialog dialog = new GhidraGoWaitForListenerDialog();
|
||||
|
||||
protected boolean showDialog;
|
||||
protected int period;
|
||||
protected TimeUnit timeUnit;
|
||||
protected ScheduledExecutorService executor;
|
||||
protected Callable<Boolean> checkCondition;
|
||||
|
||||
public CheckPeriodicallyRunnable(boolean showDialog,
|
||||
int period, TimeUnit timeUnit, Callable<Boolean> checkCondition) {
|
||||
this.showDialog = showDialog;
|
||||
this.period = period;
|
||||
this.timeUnit = timeUnit;
|
||||
this.checkCondition = checkCondition;
|
||||
|
||||
// two threads; one for checking quickly without showing dialog, another for showing dialog
|
||||
this.executor = Executors.newScheduledThreadPool(2);
|
||||
}
|
||||
|
||||
protected CheckPeriodicallyRunnable(ScheduledExecutorService executor, boolean showDialog,
|
||||
int period,
|
||||
TimeUnit timeUnit, Callable<Boolean> checkCondition) {
|
||||
this(showDialog, period, timeUnit, checkCondition);
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins checking the check condition in a thread
|
||||
* @param delay the amount of time to wait to being checking
|
||||
* @param delayTimeUnit the units for the amount of time
|
||||
* @throws StopWaitingException in the event a dialog is answered to stop waiting
|
||||
*/
|
||||
public void startChecking(int delay,
|
||||
TimeUnit delayTimeUnit) throws StopWaitingException {
|
||||
executor.schedule(this, delay, delayTimeUnit);
|
||||
}
|
||||
|
||||
public abstract void awaitTermination()
|
||||
throws StopWaitingException, StartedGhidraProcessExitedException;
|
||||
|
||||
public void dispose() {
|
||||
executor.shutdownNow();
|
||||
Swing.runNow(dialog::close);
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.ipc;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import ghidra.app.plugin.core.go.exception.UnableToGetLockException;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Ghidra Go Inter-Process Communication
|
||||
*/
|
||||
public abstract class GhidraGoIPC {
|
||||
|
||||
protected final Path channelPath =
|
||||
Path.of(Application.getUserTempDirectory().getPath(), "ghidraGo");
|
||||
protected final Path urlFilesPath = channelPath.resolve("urls");
|
||||
|
||||
protected final Path listenerLockPath = channelPath.resolve("listenerLock");
|
||||
protected final Path listenerReadyLockPath = channelPath.resolve("listenerReadyLock");
|
||||
protected final Path senderLockPath = channelPath.resolve("senderLock");
|
||||
|
||||
protected GhidraGoIPC() throws IOException {
|
||||
// make the directories that will be needed
|
||||
try {
|
||||
FileUtilities.checkedMkdir(channelPath.toFile());
|
||||
FileUtilities.checkedMkdir(urlFilesPath.toFile());
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Unable to create IPC directories.");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void dispose();
|
||||
|
||||
/**
|
||||
* @return true if a Ghidra is listening and ready. false otherwise
|
||||
*/
|
||||
public boolean isGhidraListening() {
|
||||
if (listenerLockPath.toFile().exists() && listenerReadyLockPath.toFile().exists()) {
|
||||
return isFileLocked(listenerLockPath) && isFileLocked(listenerReadyLockPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isFileLocked(Path lockPath) {
|
||||
try {
|
||||
return !doLockedAction(lockPath, false, () -> true);
|
||||
}
|
||||
catch (OverlappingFileLockException | UnableToGetLockException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* perform the given action after acquiring the client lock successfully. This method is used
|
||||
* to ensure that only one actor for the given lock path is performing the action.
|
||||
* @param lockPath the path of the file to lock
|
||||
* @param action the action taken after acquiring the lock.
|
||||
* @param waitForLock if true blocks until the lock is acquired. otherwise, if the lock can't be
|
||||
* acquired, the method returns false and does not do any blocking actions
|
||||
* @return true if the action succeeded. false otherwise.
|
||||
* @throws OverlappingFileLockException if another process currently controls the lock
|
||||
* @throws UnableToGetLockException if the lock was unobtainable
|
||||
*/
|
||||
public static boolean doLockedAction(Path lockPath, boolean waitForLock,
|
||||
Supplier<Boolean> action)
|
||||
throws OverlappingFileLockException, UnableToGetLockException {
|
||||
try (FileOutputStream fos = new FileOutputStream(lockPath.toFile());
|
||||
FileChannel channel = fos.getChannel();
|
||||
FileLock lock = waitForLock ? channel.lock() : channel.tryLock();) {
|
||||
if (lock == null) {
|
||||
throw new UnableToGetLockException();
|
||||
}
|
||||
return action.get();
|
||||
}
|
||||
catch (FileLockInterruptionException e) {
|
||||
// this is okay, user interrupted locking action
|
||||
}
|
||||
catch (IOException e) {
|
||||
Swing.runNow(
|
||||
() -> Msg.showError(GhidraGoIPC.class, null, "Could not perform exclusive action",
|
||||
"Another process is currently holding the lock at " + lockPath, e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.ipc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.FileLockInterruptionException;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.nio.file.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.app.plugin.core.go.exception.UnableToGetLockException;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class GhidraGoListener extends GhidraGoIPC implements Runnable {
|
||||
public static int WAIT_FOR_ACTIVE_PROJECT_TIMEOUT_S = 30;
|
||||
|
||||
private Thread t;
|
||||
private Consumer<URL> onNewUrl;
|
||||
|
||||
/**
|
||||
* Begin listening for urls in a non-blocking thread. If a listener already exists, the thread
|
||||
* will wait until no listener exists and attempt to get the lock. Once the lock has been acquired
|
||||
* the listener will start watching for new urls and create a ready lock. Upon a new url being found,
|
||||
* the onNewUrl Consumer will be executed.
|
||||
* @param onNewUrl consumer method to execute upon finding a new url
|
||||
* @throws IOException if the Runnable cannot be created
|
||||
*/
|
||||
public GhidraGoListener(Consumer<URL> onNewUrl) throws IOException {
|
||||
super();
|
||||
this.onNewUrl = onNewUrl;
|
||||
t = new Thread(this, "GhidraGo Handler");
|
||||
t.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
doLockedAction(listenerLockPath, true, () -> {
|
||||
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
|
||||
urlFilesPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
|
||||
Msg.info(this, "Listening for GhidraGo Requests.");
|
||||
doLockedAction(listenerReadyLockPath, true, () -> {
|
||||
try {
|
||||
WatchKey key;
|
||||
while ((key = watchService.take()) != null) {
|
||||
for (WatchEvent<?> event : key.pollEvents()) {
|
||||
// only process events that are not null. null events could happen
|
||||
// when event.kind() is OVERFLOW.
|
||||
if (event.context() != null) {
|
||||
Msg.trace(this, event.context() + " is a new file!");
|
||||
// get the url from the new url
|
||||
Path urlFilePath =
|
||||
urlFilesPath.resolve(event.context().toString());
|
||||
URL url = getGhidraURL(urlFilePath);
|
||||
urlFilePath.toFile().delete();
|
||||
if (url != null) {
|
||||
onNewUrl.accept(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
key.reset();
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// watch service interrupted
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
catch (FileLockInterruptionException | InterruptedIOException e) {
|
||||
return false;
|
||||
}
|
||||
catch (IOException | UnableToGetLockException e) {
|
||||
Swing.runNow(() -> Msg.showError(this, null,
|
||||
"GhidraGo Unable to Watch for New GhidraURL's", e));
|
||||
return false;
|
||||
}
|
||||
catch (ClosedWatchServiceException e) {
|
||||
// do nothing
|
||||
}
|
||||
finally {
|
||||
Msg.info(this, "No longer listening for GhidraGo Requests.");
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
catch (OverlappingFileLockException | UnableToGetLockException e) {
|
||||
Swing.runNow(
|
||||
() -> Msg.showError(this, null, "GhidraGo Unable to Watch for New GhidraURL's", e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL given the first argument from GhidraGo.
|
||||
* @param ghidraGoArgument could be a GhidraURL or a projectFilePath.
|
||||
* @return the GhidraURL to a program
|
||||
* @throws IllegalArgumentException in the event the given GhidraGo argument is invalid
|
||||
*/
|
||||
private URL toURL(String ghidraGoArgument) throws IllegalArgumentException {
|
||||
try {
|
||||
|
||||
if (ghidraGoArgument.startsWith(GhidraURL.PROTOCOL + ":?")) {
|
||||
String projectFilePath =
|
||||
ghidraGoArgument.substring(ghidraGoArgument.indexOf("?") + 1);
|
||||
if (!projectFilePath.startsWith("/")) {
|
||||
projectFilePath = "/" + projectFilePath;
|
||||
}
|
||||
return GhidraURL.makeURL(AppInfo.getActiveProject().getProjectLocator(),
|
||||
projectFilePath, null);
|
||||
}
|
||||
return GhidraURL.toURL(ghidraGoArgument);
|
||||
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
if (ghidraGoArgument.startsWith(GhidraURL.PROTOCOL + "://") ||
|
||||
AppInfo.getActiveProject() == null)
|
||||
throw e;
|
||||
if (!ghidraGoArgument.startsWith("/")) {
|
||||
ghidraGoArgument = "/" + ghidraGoArgument;
|
||||
}
|
||||
return GhidraURL.makeURL(AppInfo.getActiveProject().getProjectLocator(),
|
||||
ghidraGoArgument, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the url file for the url string and returns it.
|
||||
* @param urlFilePath the path for the url file
|
||||
* @return the url string, or null if the file cannot be read.
|
||||
*/
|
||||
private URL getGhidraURL(Path urlFilePath) {
|
||||
try {
|
||||
String urlContents = new String(Files.readAllBytes(urlFilePath));
|
||||
if (StringUtils.isEmpty(urlContents)) {
|
||||
Swing.runNow(() -> Msg.showError(GhidraGoIPC.class, null,
|
||||
"GhidraGo Empty GhidraURL Read",
|
||||
"The GhidraURL read from url file was null or empty. This should not happen, " +
|
||||
"ensure ghidraGo is being used properly."));
|
||||
return null;
|
||||
}
|
||||
|
||||
return toURL(urlContents);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Swing.runNow(() -> Msg.showError(GhidraGoIPC.class, null, "GhidraGo Error",
|
||||
"Failed to read the url from " + urlFilePath, e));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Swing.runNow(
|
||||
() -> Msg.showError(GhidraGoIPC.class, null, "GhidraGo Illegal Argument Given", e));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (t != null) {
|
||||
t.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.AbstractErrDialog;
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraGo;
|
||||
import ghidra.app.plugin.core.go.ipc.CheckForFileProcessedRunnable;
|
||||
import ghidra.app.plugin.core.go.ipc.CheckForListenerRunnable;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.*;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class GhidraGoPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private GhidraGo ghidraGo;
|
||||
private URL url;
|
||||
|
||||
private GhidraApplicationLayout layout;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
env = new TestEnv();
|
||||
tool = env.getFrontEndTool();
|
||||
tool.addPlugin(GhidraGoPlugin.class.getName());
|
||||
showTool(tool);
|
||||
|
||||
DomainFolder rootFolder = env.getProject().getProjectData().getRootFolder();
|
||||
|
||||
Program p = createNotepadProgram();
|
||||
|
||||
rootFolder.createFile("notepad", p, TaskMonitor.DUMMY);
|
||||
|
||||
env.release(p);
|
||||
|
||||
url = GhidraURL.makeURL(env.getProjectManager().getActiveProject().getProjectLocator(),
|
||||
"/notepad", null);
|
||||
|
||||
layout = (GhidraApplicationLayout) createApplicationLayout();
|
||||
ghidraGo = new GhidraGo();
|
||||
|
||||
|
||||
CheckForFileProcessedRunnable.WAIT_FOR_PROCESSING_DELAY_MS = 1000;
|
||||
CheckForFileProcessedRunnable.MAX_WAIT_FOR_PROCESSING_MIN = 1;
|
||||
CheckForFileProcessedRunnable.WAIT_FOR_PROCESSING_PERIOD_MS = 10;
|
||||
|
||||
CheckForListenerRunnable.WAIT_FOR_LISTENER_DELAY_MS = 1000;
|
||||
CheckForListenerRunnable.MAX_WAIT_FOR_LISTENER_MIN = 1;
|
||||
CheckForListenerRunnable.WAIT_FOR_LISTENER_PERIOD_MS = 10;
|
||||
}
|
||||
|
||||
private Program createNotepadProgram() throws Exception {
|
||||
ClassicSampleX86ProgramBuilder builder =
|
||||
new ClassicSampleX86ProgramBuilder("notepad", false, this);
|
||||
|
||||
return builder.getProgram();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessingUrl() throws Exception {
|
||||
Swing.runLater(() -> {
|
||||
try {
|
||||
ghidraGo.launch(layout, new String[] { url.toString() });
|
||||
}
|
||||
catch (Exception e) {
|
||||
// empty
|
||||
}
|
||||
});
|
||||
waitForSwing();
|
||||
waitFor(() -> Arrays.asList(tool.getToolServices().getRunningTools())
|
||||
.stream()
|
||||
.map(PluginTool::getName)
|
||||
.anyMatch(Predicate.isEqual("CodeBrowser")));
|
||||
Optional<PluginTool> cb = Arrays.asList(tool.getToolServices().getRunningTools())
|
||||
.stream()
|
||||
.filter(p -> p.getName().equals("CodeBrowser"))
|
||||
.findFirst();
|
||||
|
||||
assertTrue(cb.isPresent());
|
||||
assertTrue(Arrays.asList(cb.get().getDomainFiles())
|
||||
.stream()
|
||||
.map(DomainFile::getName)
|
||||
.anyMatch(Predicate.isEqual("notepad")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchingWithInvalidUrl() throws Exception {
|
||||
Swing.runLater(() -> {
|
||||
try {
|
||||
ghidraGo.launch(layout, new String[] { "ghidra:/test" });
|
||||
}
|
||||
catch (Exception e) {
|
||||
// empty
|
||||
}
|
||||
});
|
||||
AbstractErrDialog err = waitForErrorDialog();
|
||||
assertEquals("Unsupported Content", err.getTitle());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.app.plugin.core.go.ipc;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import ghidra.app.plugin.core.go.GhidraGoSender;
|
||||
import ghidra.app.plugin.core.go.dialog.GhidraGoWaitForListenerDialog;
|
||||
import ghidra.app.plugin.core.go.exception.StopWaitingException;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class GhidraGoIPCTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
private GhidraGoSender sender;
|
||||
private GhidraGoListener listener;
|
||||
private String url = "ghidra://testing/testProject";
|
||||
private TestEnv env;
|
||||
|
||||
public GhidraGoIPCTest() throws IOException {
|
||||
sender = new GhidraGoSender();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
env = new TestEnv(); // need this so that Application is initialized
|
||||
|
||||
CheckForFileProcessedRunnable.WAIT_FOR_PROCESSING_DELAY_MS = 1000;
|
||||
CheckForFileProcessedRunnable.MAX_WAIT_FOR_PROCESSING_MIN = 1;
|
||||
CheckForFileProcessedRunnable.WAIT_FOR_PROCESSING_PERIOD_MS = 10;
|
||||
|
||||
CheckForListenerRunnable.WAIT_FOR_LISTENER_DELAY_MS = 1000;
|
||||
CheckForListenerRunnable.MAX_WAIT_FOR_LISTENER_MIN = 1;
|
||||
CheckForListenerRunnable.WAIT_FOR_LISTENER_PERIOD_MS = 10;
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (env != null) {
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
sender.dispose();
|
||||
if (listener != null) {
|
||||
listener.dispose();
|
||||
}
|
||||
|
||||
waitFor(() -> !sender.isGhidraListening());
|
||||
}
|
||||
|
||||
public Thread sendExpectingStopWaitingException() {
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
sender.send(url);
|
||||
assertFalse(true); // fail
|
||||
}
|
||||
catch (StopWaitingException e) {
|
||||
// passed
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
return t;
|
||||
}
|
||||
|
||||
public Thread sendExpectingSuccess() {
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
sender.send(url);
|
||||
// passed
|
||||
}
|
||||
catch (StopWaitingException e) {
|
||||
assertFalse(true); // fail
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
return t;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendingWithNoListener() throws InterruptedException {
|
||||
// given no listener is listening and the timeout is 0
|
||||
waitFor(() -> !sender.isGhidraListening());
|
||||
CheckForListenerRunnable.WAIT_FOR_LISTENER_DELAY_MS = 0;
|
||||
CheckForFileProcessedRunnable.WAIT_FOR_PROCESSING_DELAY_MS = 0;
|
||||
|
||||
// then a the wait for listener dialog should appear on send
|
||||
Thread t = sendExpectingStopWaitingException();
|
||||
|
||||
DialogComponentProvider dialog =
|
||||
waitForDialogComponent(GhidraGoWaitForListenerDialog.class);
|
||||
|
||||
// when Wait is pressed, the dialog should reappear with no timeout
|
||||
pressButtonByText(dialog, "Wait");
|
||||
|
||||
// then pressing No when the dialog appears again should stop waiting
|
||||
dialog = waitForDialogComponent(GhidraGoWaitForListenerDialog.class);
|
||||
pressButtonByText(dialog, "No");
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendingWithListener() throws IOException, InterruptedException {
|
||||
// given a listener is listening and processing new urls
|
||||
listener = new GhidraGoListener((passedURL) -> {
|
||||
Msg.info(this, "Found " + passedURL + " in test");
|
||||
});
|
||||
waitFor(sender::isGhidraListening);
|
||||
|
||||
// then the sender should not throw an exception when sending a url
|
||||
Thread t = sendExpectingSuccess();
|
||||
t.join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterruptingListener() throws IOException, InterruptedException {
|
||||
// given a listener is listening and processing new urls
|
||||
listener = new GhidraGoListener((passedURL) -> {
|
||||
Msg.info(this, "Found " + passedURL + " in test");
|
||||
});
|
||||
waitFor(sender::isGhidraListening);
|
||||
|
||||
// then sending a url before disposing the listener should succeed
|
||||
Thread t = sendExpectingSuccess();
|
||||
t.join();
|
||||
|
||||
// when the listener is disposed
|
||||
listener.dispose();
|
||||
|
||||
// given no listener is listening and the timeout is 0
|
||||
waitFor(() -> !sender.isGhidraListening());
|
||||
CheckForListenerRunnable.WAIT_FOR_LISTENER_DELAY_MS = 0;
|
||||
CheckForFileProcessedRunnable.WAIT_FOR_PROCESSING_DELAY_MS = 0;
|
||||
|
||||
// then sending a url should fail
|
||||
t = sendExpectingStopWaitingException();
|
||||
DialogComponentProvider dialog =
|
||||
waitForDialogComponent(GhidraGoWaitForListenerDialog.class);
|
||||
pressButtonByText(dialog, "No");
|
||||
t.join();
|
||||
}
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<!-- Keep pre text from wrapping so that it is formatted exactly as we have it -->
|
||||
<style>
|
||||
pre {
|
||||
white-space: no-wrap;
|
||||
font-family: 'Courier New', 'Courier';
|
||||
}
|
||||
|
||||
typewriter {
|
||||
font-family: 'Courier New', 'Courier';
|
||||
}
|
||||
|
||||
/* Make the general text a bit more readable */
|
||||
body {
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>GhidraGo README</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 align="center"><a name="top">GhidraGo README</a></h1>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
<UL>
|
||||
<LI><a href="#general">Introduction</a></LI>
|
||||
<ul><li><a href="#example">Example</a></li></ul>
|
||||
<LI><a href="#plugin">Configure GhidraGo Plugin</a></LI>
|
||||
<li>
|
||||
<a href="#configure">Configure Protocol Handler (Platform Specific)</a>
|
||||
<ul>
|
||||
<LI><a href="#windows">Windows</a></LI>
|
||||
<LI><a href="#linux">Linux</a></LI>
|
||||
<LI><a href="#mac">Mac</a></LI>
|
||||
</ul>
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||
|
||||
<h2><a name="general">GhidraGo Introduction</a></h2>
|
||||
<div>
|
||||
<p>
|
||||
GhidraGo is a mechanism to cause Ghidra to display a previously imported program within a
|
||||
local or multi-user project using a ghidraURL hyperlink similar to an http reference. In
|
||||
practice ghidraURL's work very similarly to selecting a URL reference which displays a PDF.
|
||||
Once setup correctly, GhidraURL links can be placed in web pages, external project
|
||||
documentation files, or any other place a URL hyperlink can be placed.
|
||||
</p>
|
||||
<p>
|
||||
When a GhidraURL is selected, GhidraGo will startup Ghidra if it isn't already running as
|
||||
well as prompt to login to the multi-user project if necessary. The program is displayed in
|
||||
the default tool, usually the codebrowser, and can be configured to re-use an open default
|
||||
tool or to use a new default tool. The GhidraURL must currently be locating a DomainFile
|
||||
that is either in a Remote, Shared project, or a local project.
|
||||
</p>
|
||||
<p>
|
||||
GhidraGo is a combination of a command line program to send a link, a plugin running within
|
||||
the Ghidra project manager, and the configuration of the default handling for the ghidraURL
|
||||
within the user environment. The ghidraURL is sent as the first and only parameter to the
|
||||
ghidraGo command line interface.
|
||||
</p>
|
||||
<p>
|
||||
GhidraGo passes information through a simple filesystem mechanism vice an open port for
|
||||
security and simplicity. GhidraGo works on Windows, Linux, and MacOS.
|
||||
</p>
|
||||
<p>
|
||||
<p>GhidraURL's have the format:</p>
|
||||
<div style="margin-left: 25px">
|
||||
<p>
|
||||
Remote Ghidra Server File:
|
||||
ghidra://<host>[:<port>]/<repository-name>/<program-path>
|
||||
[#<address-or-symbol-ref>]
|
||||
</p>
|
||||
<p style="margin-left: 25px">Example: ghidra://hostname/Repo/notepad.exe#main</p>
|
||||
|
||||
<p>
|
||||
Local Ghidra Project File:
|
||||
ghidra:/[<project-path>/]<project-name>?/<program-path>
|
||||
[#<address-or-symbol-ref>]
|
||||
</p>
|
||||
<p style="margin-left: 25px">Example: ghidra:/share/MyProject?/notepad.exe#main</p>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3><a name="example">Example of Using ghidraGo CLI</a></h3>
|
||||
<div>
|
||||
<p><code>ghidraGo ghidra://ghidra-server/project/myProgram#symbol</code></p>
|
||||
<p>Executing this command will result in the program called <code>myProgram</code> being
|
||||
opened in Ghidra's default tool with the cursor at <code>symbol</code>.</p>
|
||||
</div>
|
||||
|
||||
<h2><a name="plugin">Configure GhidraGo Plugin</a></h2>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Start Ghidra</li>
|
||||
<li>Choose File > Configuration in the Project Window (not the Codebrowser Window)</li>
|
||||
<li>Click the Plug Icon in the upper right to display all plugins</li>
|
||||
<li>Search for GhidraGoPlugin and select it</li>
|
||||
<li>Press OK</li>
|
||||
</ol>
|
||||
<p>Ghidra is now configured to listen to GhidraGo Requests. You can execute a GhidraGo request
|
||||
using the "ghidraGo" shell/batch script in
|
||||
<code>/path/to/ghidra/support/GhidraGo/ghidraGo</code></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2><a name="configure">Configure Protocol Handler (Platform Specific)</a></h2>
|
||||
<div>
|
||||
<p>
|
||||
Configuring your platform to handle the <script>ghidra</script> protocol is what
|
||||
enables the ghidraGo command line interface to be associated with a ghidraURL. Once
|
||||
configured, clicking hyperlinks that start with the <script>ghidra</script> protocol
|
||||
will execute the ghidraGo CLI with that hyperlink as the first argument. The
|
||||
configuration is platform specific.
|
||||
</p>
|
||||
<p>
|
||||
*NOTE: changes to your path to ghidra, such as upgrading ghidra to a new version,
|
||||
will require the path you set in this configuration to be updated.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3><a name="windows">Windows Protocol Handler Configuration</a></h3>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Go to Start > Find and Type <code>regedit</code></li>
|
||||
<li>Right click HKEY_CLASSES_ROOT then New > Key</li>
|
||||
<li>Name the key "ghidra"</li>
|
||||
<li>Right Click ghidra > New > String Value and add "URL Protocol" without a value</li>
|
||||
<li>Right Click ghidra > New > Key and create the heiarchy ghidra/shell/open/command</li>
|
||||
<li>Inside command change (Default) to the path where ghidraGo is located followed by
|
||||
a "%1". For Example:</li>
|
||||
<br />
|
||||
<code>C:\Path\To\Ghidra\support\GhidraGo\ghidraGo "%1"</code>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h3><a name="linux">Linux Protocol Handler Configuration</a></h3>
|
||||
<div>
|
||||
<p>In Linux, when you click a browser link with an <code>href</code> value to a GhidraURL,
|
||||
you'll be prompted to use xdg-open.</p>
|
||||
<ol>
|
||||
<li>Edit the file <code>ghidra.desktop</code> in <code>~/.local/share/applications</code></li>
|
||||
<br />
|
||||
<code>
|
||||
[Desktop Entry]<br />
|
||||
Name=ghidra Client<br />
|
||||
Exec=/path/to/ghidra/support/GhidraGo/ghidraGo "%u"<br />
|
||||
Type=Application<br />
|
||||
Terminal=false<br />
|
||||
MimeType=x-scheme-handler/ghidra;<br />
|
||||
</code>
|
||||
<br />
|
||||
<li>Edit the file mimeapps.list in <code>~/.local/share/applications</code></li>
|
||||
<br />
|
||||
<code>
|
||||
[Default Applications]<br />
|
||||
x-scheme-handler/ghidra=ghidra.desktop<br />
|
||||
...<br />
|
||||
</code>
|
||||
</ol>
|
||||
<p>After the steps above, you should be able to click a GhidraURL href, get the same
|
||||
xdg-open prompt, and upon clicking "Open xdg-open" GhidraGo should execute and open
|
||||
Ghidra to the given GhidraURL.</p>
|
||||
</div>
|
||||
|
||||
<h3><a name="mac">Mac Protocol Handler Configuration</a></h3>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open <code>Script Editor</code> and past the following into the editor.</li>
|
||||
<br />
|
||||
<code>
|
||||
on open location schemeUrl<br />
|
||||
 set ghidraUrl to quoted form of schemeUrl<br />
|
||||
 do shell script "/path/to/ghidraGo " & ghidraUrl<br />
|
||||
end open location<br />
|
||||
</code>
|
||||
<br />
|
||||
<li>Save the script as an Application named GhidraGo in either
|
||||
<code>/Applications</code> or <code>~/Applications</code></li>
|
||||
<li>Right click on the saved Application and click Show Package Contents</li>
|
||||
<li>Open Contents > Info.plist and under
|
||||
<code><string>com.apple.ScriptEditor.id.GhidraGo</string></code>
|
||||
paste the following:</li>
|
||||
<br />
|
||||
<code>
|
||||
<key>CFBundleURLTypes</key><br />
|
||||
<array><br />
|
||||
 <dict><br />
|
||||
  <key>CFBundleURLName</key><br />
|
||||
  <string>Ghidra Scheme</string><br />
|
||||
  <key>CFBundleURLSchemes</key><br />
|
||||
  <array><br />
|
||||
   <string>ghidra</string><br />
|
||||
  </array><br />
|
||||
 </dict><br />
|
||||
</array>
|
||||
</code>
|
||||
<br />
|
||||
<li>Go to the Applications folder where you saved the GhidraGo, and Open
|
||||
GhidraGo (run it once).</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
(<a href="#top">Back to Top</a>)
|
||||
|
||||
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||
<address></address>
|
||||
Last modified: Oct 26 2023
|
||||
</body> </html>
|
||||
|
||||
<style>
|
||||
|
||||
table, td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
16
Ghidra/RuntimeScripts/Linux/support/GhidraGo/ghidraGo
Executable file
16
Ghidra/RuntimeScripts/Linux/support/GhidraGo/ghidraGo
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Command-line script for starting GhidraGo
|
||||
|
||||
# launch mode (fg, bg, debug, debug-suspend, debug-suspend-launcher)
|
||||
LAUNCH_MODE=fg
|
||||
|
||||
# Resolve symbolic link if present and get the directory this script lives in.
|
||||
# NOTE: "readlink -f" is best but works on Linux only, "readlink" will only work if your PWD
|
||||
# contains the link you are calling (which is the best we can do on macOS), and the "echo" is the
|
||||
# fallback, which doesn't attempt to do anything with links.
|
||||
SCRIPT_FILE="$(readlink -f "$0" 2>/dev/null || readlink "$0" 2>/dev/null || echo "$0")"
|
||||
SCRIPT_DIR="${SCRIPT_FILE%/*}"
|
||||
|
||||
# Launch Filesystem Conversion
|
||||
"${SCRIPT_DIR}"/../launch.sh $LAUNCH_MODE jdk GhidraGo "" "" ghidra.GhidraGo "$@"
|
10
Ghidra/RuntimeScripts/Windows/support/GhidraGo/ghidraGo.bat
Normal file
10
Ghidra/RuntimeScripts/Windows/support/GhidraGo/ghidraGo.bat
Normal file
@ -0,0 +1,10 @@
|
||||
:: GhidraGo launch
|
||||
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
:: Launch mode can be changed to one of the following:
|
||||
:: fg, debug, debug-suspend
|
||||
set LAUNCH_MODE=fg
|
||||
|
||||
call "%~dp0..\launch.bat" %LAUNCH_MODE% jdk GhidraGo "" "" ghidra.GhidraGo "%*"
|
@ -3,6 +3,7 @@
|
||||
Common/server/jaas.conf||GHIDRA||||END|
|
||||
Common/server/server.conf||GHIDRA||||END|
|
||||
Common/server/svrREADME.html||GHIDRA||||END|
|
||||
Common/support/GhidraGo/ghidraGoREADME.html||GHIDRA||||END|
|
||||
Common/support/analyzeHeadlessREADME.html||GHIDRA||||END|
|
||||
Common/support/buildGhidraJarREADME.txt||GHIDRA||||END|
|
||||
Common/support/debug.log4j.xml||GHIDRA||||END|
|
||||
@ -12,6 +13,7 @@ Linux/server/ghidraSvr||GHIDRA||||END|
|
||||
Linux/server/svrAdmin||GHIDRA||||END|
|
||||
Linux/server/svrInstall||GHIDRA||||END|
|
||||
Linux/server/svrUninstall||GHIDRA||||END|
|
||||
Linux/support/GhidraGo/ghidraGo||GHIDRA||||END|
|
||||
Linux/support/analyzeHeadless||GHIDRA||||END|
|
||||
Linux/support/buildGhidraJar||GHIDRA||||END|
|
||||
Linux/support/buildNatives||GHIDRA||||END|
|
||||
@ -25,6 +27,7 @@ Windows/server/ghidraSvr.bat||GHIDRA||||END|
|
||||
Windows/server/svrAdmin.bat||GHIDRA||||END|
|
||||
Windows/server/svrInstall.bat||GHIDRA||||END|
|
||||
Windows/server/svrUninstall.bat||GHIDRA||||END|
|
||||
Windows/support/GhidraGo/ghidraGo.bat||GHIDRA||||END|
|
||||
Windows/support/README_createPdbXmlFiles.txt||GHIDRA||||END|
|
||||
Windows/support/analyzeHeadless.bat||GHIDRA||||END|
|
||||
Windows/support/buildGhidraJar.bat||GHIDRA||||END|
|
||||
|
Loading…
Reference in New Issue
Block a user