Compare commits
19 commits
a94ca1ed7e
...
4896cd5c97
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4896cd5c97 | ||
![]() |
ad35a7e956 | ||
![]() |
3bba2c2f14 | ||
![]() |
2d10da420f | ||
![]() |
437bed4be0 | ||
![]() |
c3d8571ba3 | ||
![]() |
c99af66f5f | ||
![]() |
6dd00be368 | ||
![]() |
1c5e9ea9c2 | ||
![]() |
22b3524206 | ||
![]() |
a893955b03 | ||
![]() |
a1851c5911 | ||
![]() |
4c8d408fc7 | ||
![]() |
9e2a8d5604 | ||
![]() |
5f6e3806b3 | ||
![]() |
47acbe98bb | ||
![]() |
fe4bb30704 | ||
![]() |
79eea09e64 | ||
![]() |
4b6d90366c |
|
@ -15,8 +15,33 @@ applied Ghidra SRE capabilities to a variety of problems that involve analyzing
|
|||
generating deep insights for NSA analysts who seek a better understanding of potential
|
||||
vulnerabilities in networks and systems.
|
||||
|
||||
# What's coming in Ghidra 12.0
|
||||
This is a preview of what is coming in the future Ghidra 12.0 release.
|
||||
# What's New in Ghidra 12.0
|
||||
This release includes new features, enhancements, performance improvements, quite a few bug fixes,
|
||||
and many pull-request contributions. Thanks to all those who have contributed their time, thoughts,
|
||||
and code. The Ghidra user community thanks you too!
|
||||
|
||||
### The not-so-fine print: Please Read!
|
||||
Ghidra 12.0 is fully backward compatible with project data from previous releases. However, programs
|
||||
and data type archives which are created or modified in 12.0 will not be usable by an earlier Ghidra
|
||||
version.
|
||||
|
||||
**IMPORTANT:** Ghidra 12.0 requires at minimum JDK 21 to run.
|
||||
|
||||
**IMPORTANT:** To use the Debugger or do a full source distribution build, you will need Python3
|
||||
(3.9 to 3.13 supported) installed on your system.
|
||||
|
||||
**NOTE:** There have been reports of certain features causing the XWindows server to crash. A fix
|
||||
for `CVE-2024-31083` in X.org software in April 2024 introduced a regression, which has been fixed
|
||||
in xwayland 23.2.6 and xorg-server 21.1.13. If you experience any crashing of Ghidra, most likely
|
||||
causing a full logout, check if your xorg-server has been updated to at least the noted version.
|
||||
|
||||
**NOTE:** Each build distribution will include native components (e.g., decompiler) for at least one
|
||||
platform (e.g., Windows x86-64). If you have another platform that is not included in the build
|
||||
distribution, you can build native components for your platform directly from the distribution.
|
||||
See the *Getting Started* document for additional information. Users running with older shared
|
||||
libraries and operating systems (e.g., CentOS 7.x) may also run into compatibility errors when
|
||||
launching native executables such as the Decompiler and GNU Demangler which may necessitate a
|
||||
rebuild of native components.
|
||||
|
||||
**NOTE:** Ghidra Server: The Ghidra 12.0 server is compatible with Ghidra 9.2 and later Ghidra
|
||||
clients although the presence of any newer link-files within a repository may not be handled properly
|
||||
|
@ -24,18 +49,38 @@ by client versions prior to 12.0 which lack support for the new storage format.
|
|||
which introduce new link-files into a project will not be able to add such files into version
|
||||
control if connected to older Ghidra Server versions.
|
||||
|
||||
**NOTE:** Ghidra Server: The Ghidra 12.x server is compatible with Ghidra 9.2 and later Ghidra
|
||||
clients although the presence of any newer link-files within a repository may not be handled
|
||||
properly by client versions prior to 12.0 which lack support for the new storage format. Ghidra 12.0
|
||||
clients which introduce new link-files into a project will not be able to add such files into
|
||||
version control if connected to older Ghidra Server versions. Ghidra 12.x clients are compatible
|
||||
with all 0.x and 9.x servers. Although, due to potential Java version differences, it is
|
||||
recommended that Ghidra Server installations older than 10.2 be upgraded. Those using 10.2 and newer
|
||||
should not need a server upgrade.
|
||||
|
||||
**NOTE:** Programs imported with a Ghidra beta version or code built directly from source code
|
||||
outside of a release tag may not be compatible, and may have flaws that won't be corrected by using
|
||||
this new release. Any programs analyzed from a beta or other local master source build should be
|
||||
considered experimental and re-imported and analyzed with a release version.
|
||||
|
||||
Programs imported with previous release versions should upgrade correctly through various automatic
|
||||
upgrade mechanisms. However, there may be improvements or bug fixes in the import and analysis
|
||||
process that will provide better results than prior Ghidra versions. You might consider comparing a
|
||||
fresh import of any program you will continue to reverse engineer to see if the latest Ghidra
|
||||
provides better results.
|
||||
|
||||
## Project Link Files
|
||||
|
||||
Support for link-files within a Ghidra Project has been significantly expanded with this release and
|
||||
with it a new file storage type has been introduced which can create some incompatibilities if projects
|
||||
and repositories containing such files are used by older version of Ghidra or the Ghidra Server.
|
||||
with it a new file storage type has been introduced which can create some incompatibilities if
|
||||
projects and repositories containing such files are used by older version of Ghidra or the Ghidra
|
||||
Server.
|
||||
|
||||
Previously only external folder and file links were supported through the use of a Ghidra URL.
|
||||
With 12.0 the ability to establish internal folder and file links has been introduced. The new
|
||||
storage format avoids the use of a database and relies only on a light-weight property file. Internal
|
||||
Previously only external folder and file links were supported through the use of a Ghidra URL. With
|
||||
12.0 the ability to establish internal folder and file links has been introduced. The new storage
|
||||
format avoids the use of a database and relies only on a light-weight property file. Internal
|
||||
project links also allow for either absolute or relative links. Due to the fact that Ghidra allows
|
||||
a folder or file to have the same pathname, some ambiguities can result. It is highly recommended that
|
||||
the use of conflicting folder and file pathnames be avoided.
|
||||
a folder or file to have the same pathname, some ambiguities can result. It is highly recommended
|
||||
that the use of conflicting folder and file pathnames be avoided.
|
||||
|
||||
The use of internally linked folders and files allows batch import processing to more accurately
|
||||
reflect the native file-system and its use of symbolic links which allow for the same content to
|
||||
|
@ -51,124 +96,94 @@ link-files: `DomainFolder`, `DomainFile`, `LinkFile`, `LinkHandler`, `DomainFile
|
|||
|
||||
...TO BE CONTINUED...
|
||||
|
||||
## Filesystem Mirroring
|
||||
An option has been added to mirror the local filesystem when importing programs and their libraries.
|
||||
Programs and libraries that exist on the local filesystem as symbolic links will have both their
|
||||
corresponding link file and resolved program file mirrored in the project. Filesystem mirroring
|
||||
can also be used in headless mode with the new `-mirror` command line option.
|
||||
|
||||
# What's New in Ghidra 11.4
|
||||
This release includes new features, enhancements, performance improvements, quite a few bug fixes,
|
||||
and many pull-request contributions. Thanks to all those who have contributed their time, thoughts,
|
||||
and code. The Ghidra user community thanks you too!
|
||||
## PyGhidra
|
||||
PyGhidra 3.0.0 (compatible with Ghidra 12.0 and later) introduces many new Python-specific API
|
||||
methods with the goal of making the most common Ghidra tasks quick and easy, such as opening a
|
||||
project, getting a program, running a GhidraScript, etc. Legacy API fuctions such as
|
||||
`pyghidra.open_program()` and `pyghidra_run_script()` have been deprecated in favor of the new
|
||||
methods. Below is an example program that showcases some of the new API functionality. See the
|
||||
PyGhidra library README for more information.
|
||||
```python
|
||||
import os, jpype, pyghidra
|
||||
pyghidra.start()
|
||||
|
||||
### The not-so-fine print: Please Read!
|
||||
Ghidra 11.4 is fully backward compatible with project data from previous releases. However, programs
|
||||
and data type archives which are created or modified in 11.4 will not be usable by an earlier Ghidra
|
||||
version.
|
||||
# Open/create a project
|
||||
with pyghidra.open_project(os.environ["GHIDRA_PROJECT_DIR"], "ExampleProject", create=True) as project:
|
||||
|
||||
**IMPORTANT:** Ghidra 11.4 requires at minimum JDK 21 to run.
|
||||
# Walk a Ghidra release zip file, load every decompiler binary, and save them to the project
|
||||
with pyghidra.open_filesystem(f"{os.environ['DOWNLOADS_DIR']}/ghidra_11.4_PUBLIC_20250620.zip") as fs:
|
||||
loader = pyghidra.program_loader().project(project)
|
||||
for f in fs.files(lambda f: "os/" in f.path and f.name.startswith("decompile")):
|
||||
loader = loader.source(f.getFSRL()).projectFolderPath("/" + f.parentFile.name)
|
||||
with loader.load() as load_results:
|
||||
load_results.save(pyghidra.monitor())
|
||||
|
||||
**IMPORTANT:** To use the Debugger or do a full source distribution build, you will need Python3
|
||||
(3.9 to 3.13 supported) installed on your system.
|
||||
# Analyze the windows decompiler program for a maximum of 10 seconds
|
||||
with pyghidra.program_context(project, "/win_x86_64/decompile.exe") as program:
|
||||
analysis_props = pyghidra.analysis_properties(program)
|
||||
with pyghidra.transaction(program):
|
||||
analysis_props.setBoolean("Non-Returning Functions - Discovered", False)
|
||||
analysis_log = pyghidra.analyze(program, pyghidra.monitor(10))
|
||||
program.save("Analyzed", pyghidra.monitor())
|
||||
|
||||
**NOTE:** There have been reports of certain features causing the XWindows server to crash. A fix
|
||||
for `CVE-2024-31083` in X.org software in April 2024 introduced a regression, which has been fixed
|
||||
in xwayland 23.2.6 and xorg-server 21.1.13. If you experience any crashing of Ghidra, most likely
|
||||
causing a full logout, check if your xorg-server has been updated to at least the noted version.
|
||||
# Walk the project and set a property in each decompiler program
|
||||
def set_property(domain_file, program):
|
||||
with pyghidra.transaction(program):
|
||||
program_info = pyghidra.program_info(program)
|
||||
program_info.setString("PyGhidra Property", "Set by PyGhidra!")
|
||||
program.save("Setting property", pyghidra.monitor())
|
||||
pyghidra.walk_programs(project, set_property, program_filter=lambda f, p: p.name.startswith("decompile"))
|
||||
|
||||
**NOTE:** Each build distribution will include native components (e.g., decompiler) for at least one
|
||||
platform (e.g., Windows x86-64). If you have another platform that is not included in the build
|
||||
distribution, you can build native components for your platform directly from the distribution.
|
||||
See the *Getting Started* document for additional information. Users running with older shared libraries
|
||||
and operating systems (e.g., CentOS 7.x) may also run into compatibility errors when launching
|
||||
native executables such as the Decompiler and GNU Demangler which may necessitate a rebuild of
|
||||
native components.
|
||||
# Load some bytes as a new program
|
||||
ByteArrayCls = jpype.JArray(jpype.JByte)
|
||||
my_bytes = ByteArrayCls(b"\xaa\xbb\xcc\xdd\xee\xff")
|
||||
loader = pyghidra.program_loader().project(project).source(my_bytes).name("my_bytes")
|
||||
loader = loader.loaders("BinaryLoader").language("DATA:LE:64:default")
|
||||
with loader.load() as load_results:
|
||||
load_results.save(pyghidra.monitor())
|
||||
|
||||
**NOTE:** Ghidra Server: The Ghidra 11.x server is compatible with Ghidra 9.2 and later Ghidra
|
||||
clients. Ghidra 11.x clients are compatible with all 10.x and 9.x servers. Although, due to
|
||||
potential Java version differences, it is recommended that Ghidra Server installations older than
|
||||
10.2 be upgraded. Those using 10.2 and newer should not need a server upgrade.
|
||||
# Run a GhidraScript
|
||||
pyghidra.ghidra_script(f"{os.environ['GHIDRA_SCRIPTS_DIR']}/HelloWorldScript.java", project)
|
||||
```
|
||||
|
||||
**NOTE:** Programs imported with a Ghidra beta version or code built directly from source code
|
||||
outside of a release tag may not be compatible, and may have flaws that won't be corrected by using
|
||||
this new release. Any programs analyzed from a beta or other local master source build should be
|
||||
considered experimental and re-imported and analyzed with a release version.
|
||||
## Z3 Concolic Emulation and Symbolic Summary
|
||||
We've added an experimental Z3-based symbolic emulator, which runs as a "auxilliary" domain to the
|
||||
concrete emulator, effectively constructing what is commonly called a "concolic" emulator. The
|
||||
symbolic emulator creates Z3 expressions and branching constraints, but it only follows the path
|
||||
determined by concrete emulation. This is most easily accessed by installing the "SymbolicSummaryZ3"
|
||||
extension (**File** → **Install Extensions**) and then enabling the `Z3SummaryPlugin` in the
|
||||
Debugger or Emulator tool, which includes a GUI for viewing and sorting through the results. Before
|
||||
using the Z3 emulator, you must download and install z3-4.13.0 from https://github.com/Z3Prover/z3.
|
||||
Depending on your platform, you may need to build it from source. Other versions may work, but our
|
||||
current test configuration uses 4.13.0.
|
||||
|
||||
Programs imported with previous release versions should upgrade correctly through various automatic
|
||||
upgrade mechanisms. However, there may be improvements or bug fixes in the import and analysis
|
||||
process that will provide better results than prior Ghidra versions. You might consider comparing a
|
||||
fresh import of any program you will continue to reverse engineer to see if the latest Ghidra
|
||||
provides better results.
|
||||
## Emulation API
|
||||
The `PcodeEmulator` and related API has undergone substantial changes in preparation for integrating
|
||||
our JIT-accelerated emulator into the GUI. Please see the **Notable API Changes** section of our
|
||||
[Change History](ChangeHistory.md). The goal is to facilitate integration by composition; whereas,
|
||||
it had previously required inheritance, which is now considered poor design. Essentially, we've
|
||||
introduced a set of callbacks that integrators can use to detect when certain things have happened
|
||||
in emulation, as well as offer some control of machine-state behavior, e.g., to facilitate lazily
|
||||
loading from a snapshot.
|
||||
|
||||
|
||||
## Search
|
||||
|
||||
A new "Search and Replace" feature allows searching for string patterns in a wide variety
|
||||
of Ghidra elements and replacing that text with a different text sequence. Using this feature, many different
|
||||
Ghidra elements can be renamed all at once including labels, functions, name-spaces, parameters, data-types,
|
||||
field names, and enum values. This feature also supports regular expressions (including capture groups).
|
||||
After initiating a search and replace, a results table is displayed with a list of items that match the
|
||||
search. From this table, the replace actions can be applied in bulk or individually, one item at a time
|
||||
as they are reviewed.
|
||||
|
||||
## Taint Engine Support
|
||||
|
||||
Extended support for using taint engines, particularly CTADL (https://github.com/sandialabs/ctadl)
|
||||
and AngryGhidra (https://github.com/Nalen98/AngryGhidra), from the decompiler. Allows users to mark
|
||||
pcode varnodes as sources and sinks, displaying paths from sources to sinks as both address selections
|
||||
in the disassembly and token selections in the decompiler.
|
||||
|
||||
## Dockerized Ghidra
|
||||
|
||||
A new capability to build a docker image that demonstrates Ghidra's various entrypoint executions for `headless`,
|
||||
`ghidra-server`, `bsim-server`, `bsim`, `pyghidra`, and `gui` within the docker container has been included. The Docker
|
||||
image can be used as is, or can be tailored to your workflow needs. Configuration such as the base
|
||||
image (linux distro), additional packages, and more is possible using Docker.
|
||||
|
||||
See the `docker/README.md` for information about building a docker image for Ghidra and running within the Ghidra container.
|
||||
|
||||
|
||||
## Binary Formats
|
||||
|
||||
+ New loaders for the a.out and OMF-51 binary file formats.
|
||||
+ Support for Mach-O "re-exports".
|
||||
+ New ability to load Mach-O binaries directly from a Universal Binary without needing to open the File System Browser.
|
||||
+ DWARF will now load external debug files during analysis as is done for PDB files.
|
||||
|
||||
## Debugger
|
||||
|
||||
There have been numerous improvements, extensions for new targets, better launching and configuration, and bug fixes to the debugger.
|
||||
|
||||
## Analysis Speed
|
||||
|
||||
Constant and Stack analysis time has been greatly decreased through algorithm improvements and better threading. There has been additional
|
||||
work to loosen locking of the program database where possible. By locking only when necessary, multiple threads can better analyze the program
|
||||
and interaction with the GUI during analysis should be more responsive.
|
||||
|
||||
## Golang
|
||||
|
||||
Golang binary analysis analysis has been improved.
|
||||
+ Analysis has been improved to model closures, interface methods, and generic functions more accurately.
|
||||
+ Function signatures for core golang library functions are automatically applied.
|
||||
+ Decompilation results are improved by filtering some verbose golang garbage collection function logic.
|
||||
+ Addressed finding the Golang bootstrap information in stripped PE binaries.
|
||||
|
||||
## BSim
|
||||
|
||||
PostgreSQL for BSim has been updated to version 15.13 and the JDBC driver to 42.7.6. This resolves issues with building PostgreSQL
|
||||
server on newer releases of Linux and compiler toolchains which compile with -std=c23 option by default. In addition,
|
||||
building of PostgreSQL for linux_arm_64 and mac_arm_64 based platforms is supported.
|
||||
|
||||
+ BSim is now installed in the default Codebrowser tool.
|
||||
+ Function names now update in BSim search results overview if the name is changed elsewhere in Ghidra.
|
||||
|
||||
## Processors
|
||||
|
||||
+ Enhanced support for the x86 AVX-512 processor extension with additional instruction support - including the BF16, FP16 and VNNI extensions.
|
||||
+ Implemented many AARCH64 Neon instruction semantics to improve decompilation.
|
||||
+ Upgraded pcodetest framework scripts to python3 and improved command-line options.
|
||||
Extensions that currently integrate via inheritance can continue to do so, but will still need to
|
||||
apply some minimal changes to satisfy interface and constructor changes. The developers of such
|
||||
extensions ought to consider porting their integrations to the compositional/callback-based
|
||||
mechanism. A careful assessment may be required depending on the nature of the extension. Extensions
|
||||
that merely integrate with emulation should consider the compositional/callback-based mechanism.
|
||||
Extensions that incorporate new domains (e.g. Z3) or novel behaviors (e.g. JIT) should continue
|
||||
using inheritance.
|
||||
|
||||
## Other Improvements
|
||||
+ Many calling conventions for various processors/compilers have been improved using the more flexible decompiler rules
|
||||
when the data types for parameters and return values are known.
|
||||
+ Upgraded many 3rd party dependencies to address potential bugs and CVE's, including jars for Bouncy Castle,
|
||||
Apache Commons Compress, Apache Commons Lang3, Apache Commons IO, protobuf, and JUnit.
|
||||
+ Added the ability to toggle the displaying of function variables (parameters and locals) that are
|
||||
normally displayed just below the function signature. The variables display can be turned on/off
|
||||
globally or individually per function.
|
||||
|
||||
## Additional Bug Fixes and Enhancements
|
||||
Numerous other new features, improvements, and bug fixes are fully listed in the
|
||||
|
|
|
@ -18,3 +18,11 @@ longjmp
|
|||
quick_exit
|
||||
RpcRaiseException
|
||||
terminate
|
||||
___raise_securityfailure
|
||||
___report_rangecheckfailure
|
||||
?_Xregex_error@std@@YAXW4error_type@regex_constant@1@@Z
|
||||
?_Xbad_alloc@std@@YAXXZ
|
||||
?_Xlength_error@std@@YAXPBD@Z
|
||||
?_Xout_of_range@std@@YAXPBD@Z
|
||||
?_Xbad_function_call@std@@YAXXZ
|
||||
?terminate@@YAXXZ
|
||||
|
|
|
@ -41,6 +41,7 @@ import ghidra.app.script.GhidraScript;
|
|||
import ghidra.app.tablechooser.*;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.block.*;
|
||||
import ghidra.program.model.lang.Processor;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
@ -49,10 +50,15 @@ public class FixupNoReturnFunctionsScript extends GhidraScript {
|
|||
|
||||
IssueEntries entryList = null;
|
||||
|
||||
private final static String X86_NAME = "x86";
|
||||
boolean isX86;
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
Program cp = currentProgram;
|
||||
|
||||
isX86 = checkForX86(cp);
|
||||
|
||||
TableChooserExecutor executor = createTableExecutor();
|
||||
|
||||
if (this.isRunningHeadless()) {
|
||||
|
@ -89,6 +95,11 @@ public class FixupNoReturnFunctionsScript extends GhidraScript {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean checkForX86(Program cp) {
|
||||
return cp.getLanguage().getProcessor().equals(
|
||||
Processor.findOrPossiblyCreateProcessor(X86_NAME));
|
||||
}
|
||||
|
||||
private void configureTableColumns(TableChooserDialog dialog) {
|
||||
StringColumnDisplay explanationColumn = new StringColumnDisplay() {
|
||||
@Override
|
||||
|
@ -373,10 +384,22 @@ public class FixupNoReturnFunctionsScript extends GhidraScript {
|
|||
//
|
||||
//
|
||||
FunctionManager functionManager = currentProgram.getFunctionManager();
|
||||
FunctionIterator functionIter = functionManager.getFunctions(true);
|
||||
|
||||
AddressSet set = new AddressSet();
|
||||
HashSet<Function> suspectNoReturnFunctions = new HashSet<Function>();
|
||||
|
||||
FunctionIterator functionIter = functionManager.getFunctions(true);
|
||||
checkFunctions(cp, functionIter, noReturnEntries, set, suspectNoReturnFunctions);
|
||||
|
||||
FunctionIterator externalFunctionIter = functionManager.getExternalFunctions();
|
||||
checkFunctions(cp, externalFunctionIter, noReturnEntries, set, suspectNoReturnFunctions);
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public void checkFunctions(Program cp, FunctionIterator functionIter,
|
||||
IssueEntries noReturnEntries, AddressSet set,
|
||||
HashSet<Function> suspectNoReturnFunctions) throws CancelledException {
|
||||
while (functionIter.hasNext()) {
|
||||
Function candidateNoReturnfunction = functionIter.next();
|
||||
noReturnEntries.setMessage("Checking function: " + candidateNoReturnfunction.getName());
|
||||
|
@ -428,8 +451,6 @@ public class FixupNoReturnFunctionsScript extends GhidraScript {
|
|||
suspectNoReturnFunctions.add(candidateNoReturnfunction);
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private boolean testCalledFunctionsNonReturning(Function candidateNonReturningFunction,
|
||||
|
@ -462,8 +483,8 @@ public class FixupNoReturnFunctionsScript extends GhidraScript {
|
|||
FunctionManager funcManager = currentProgram.getFunctionManager();
|
||||
Listing listing = currentProgram.getListing();
|
||||
while (fallThru != null) {
|
||||
if (funcManager.getFunctionAt(fallThru) != null) {
|
||||
|
||||
Function fallThruFunction = funcManager.getFunctionAt(fallThru);
|
||||
if (fallThruFunction != null) {
|
||||
NoReturnLocations location = new NoReturnLocations(currentProgram,
|
||||
ref.getToAddress(), ref.getFromAddress(), "Function defined after call");
|
||||
dialog.add(location);
|
||||
|
@ -490,11 +511,15 @@ public class FixupNoReturnFunctionsScript extends GhidraScript {
|
|||
// or references. This is especially true if there is only one
|
||||
// example for a calling reference.
|
||||
if (callingFunc != null) {
|
||||
Address fromAddress = reference.getFromAddress();
|
||||
Function function =
|
||||
funcManager.getFunctionContaining(reference.getFromAddress());
|
||||
if (callingFunc.equals(function)) {
|
||||
funcManager.getFunctionContaining(fromAddress);
|
||||
// The reference must come from an address within this function
|
||||
// before this function call (reference fromAddress)
|
||||
// this should get rid of spurious data references from other functions
|
||||
if ((fromAddress.compareTo(fallThru) < 0) && callingFunc.equals(function)) {
|
||||
NoReturnLocations location = new NoReturnLocations(currentProgram,
|
||||
ref.getToAddress(), ref.getFromAddress(),
|
||||
ref.getToAddress(), fromAddress,
|
||||
"Data Reference from same function after call");
|
||||
dialog.add(location);
|
||||
return true;
|
||||
|
@ -517,6 +542,15 @@ public class FixupNoReturnFunctionsScript extends GhidraScript {
|
|||
dialog.add(location);
|
||||
return true;
|
||||
}
|
||||
if (isX86) {
|
||||
Instruction fallInstr = listing.getInstructionContaining(fallThru);
|
||||
if (fallInstr != null && fallInstr.getMnemonicString().equals("INT3")) {
|
||||
NoReturnLocations location = new NoReturnLocations(currentProgram,
|
||||
ref.getToAddress(), ref.getFromAddress(), "INT3 interrupt after call");
|
||||
dialog.add(location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
fallThru = null;
|
||||
if (block.getFlowType().isFallthrough()) {
|
||||
CodeBlockReferenceIterator dests = block.getDestinations(monitor);
|
||||
|
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 18 KiB |
|
@ -25,8 +25,7 @@ import ghidra.app.util.importer.MessageLog;
|
|||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.block.*;
|
||||
import ghidra.program.model.lang.GhidraLanguagePropertyKeys;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
|
@ -80,6 +79,9 @@ public class FindNoReturnFunctionsAnalyzer extends AbstractAnalyzer {
|
|||
private Address lastGetNextFuncAddress = null; // last addr used for getNextFunction()
|
||||
private Address nextFunction = null; // last return nextFunction
|
||||
|
||||
private final static String X86_NAME = "x86";
|
||||
boolean isX86;
|
||||
|
||||
public FindNoReturnFunctionsAnalyzer() {
|
||||
this(NAME, DESCRIPTION, AnalyzerType.INSTRUCTION_ANALYZER);
|
||||
}
|
||||
|
@ -106,6 +108,8 @@ public class FindNoReturnFunctionsAnalyzer extends AbstractAnalyzer {
|
|||
this.reasonList = new ArrayList<>();
|
||||
lastGetNextFuncAddress = null;
|
||||
|
||||
isX86 = checkForX86(program);
|
||||
|
||||
monitor.setMessage("NoReturn - Finding non-returning functions");
|
||||
|
||||
AddressSet noReturnSet = new AddressSet();
|
||||
|
@ -150,6 +154,11 @@ public class FindNoReturnFunctionsAnalyzer extends AbstractAnalyzer {
|
|||
return true;
|
||||
}
|
||||
|
||||
private boolean checkForX86(Program cp) {
|
||||
return cp.getLanguage().getProcessor().equals(
|
||||
Processor.findOrPossiblyCreateProcessor(X86_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* repair any damaged locations
|
||||
*
|
||||
|
@ -358,7 +367,10 @@ public class FindNoReturnFunctionsAnalyzer extends AbstractAnalyzer {
|
|||
}
|
||||
|
||||
// detected a calling issue, check other instructions calling the same place
|
||||
Address[] flows = inst.getFlows();
|
||||
Address[] flows = getAllFlows(inst);
|
||||
if (flows == null) {
|
||||
continue;
|
||||
}
|
||||
for (Address target : flows) {
|
||||
|
||||
int count = 1;
|
||||
|
@ -558,6 +570,17 @@ public class FindNoReturnFunctionsAnalyzer extends AbstractAnalyzer {
|
|||
return true;
|
||||
}
|
||||
|
||||
// on x86 INT3 after a call indicates a non-returning call from alignment padding
|
||||
if (isX86) {
|
||||
Instruction fallInstr = listing.getInstructionContaining(fallThru);
|
||||
if (fallInstr != null && fallInstr.getMnemonicString().equals("INT3")) {
|
||||
NoReturnLocations location =
|
||||
new NoReturnLocations(target, fallThru, "INT3 interrupt after call");
|
||||
reasonList.add(location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// get the next instruction in fallthru chain
|
||||
fallThru = null;
|
||||
if (instr.getFlowType().isFallthrough()) {
|
||||
|
@ -567,6 +590,38 @@ public class FindNoReturnFunctionsAnalyzer extends AbstractAnalyzer {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all flows that have already been added to instruction.
|
||||
* If there are none and this is an indirect, get the function at
|
||||
* the end of the read.
|
||||
* @param callInst
|
||||
* @return all flows
|
||||
*/
|
||||
private Address[] getAllFlows(Instruction callInst) {
|
||||
Address[] flows = callInst.getFlows();
|
||||
if (flows != null && flows.length > 0) {
|
||||
return flows;
|
||||
}
|
||||
FlowType flowType = callInst.getFlowType();
|
||||
if (!flowType.isCall() || !flowType.isIndirect()) {
|
||||
return flows;
|
||||
}
|
||||
// if haven't found any flows yet, check for a read of a location that refers
|
||||
// to a function.
|
||||
Reference[] referencesFrom = callInst.getReferencesFrom();
|
||||
for (Reference reference : referencesFrom) {
|
||||
if (reference.getReferenceType().isRead()) {
|
||||
Function functionAt = program.getFunctionManager().getFunctionAt(reference.getToAddress());
|
||||
if (functionAt != null) {
|
||||
flows = new Address[1];
|
||||
flows[0] = reference.getToAddress();
|
||||
return flows;
|
||||
}
|
||||
}
|
||||
}
|
||||
return flows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if fallThru address has inconsistent (data/call) references to it.
|
||||
* Adds the reason for non-returning reason to no return locations list.
|
||||
|
@ -585,8 +640,8 @@ public class FindNoReturnFunctionsAnalyzer extends AbstractAnalyzer {
|
|||
Reference reference = refIterTo.next();
|
||||
RefType refType = reference.getReferenceType();
|
||||
if (refType.isRead() || refType.isWrite()) {
|
||||
// look at function the reference is coming from
|
||||
// is the function the same as the call is in
|
||||
// Check function the reference is coming from
|
||||
// is the same function as the call is in
|
||||
// This is a better indicator of non-returning
|
||||
// Random references from another function could be bad disassembly
|
||||
// or references. This is especially true if there is only one
|
||||
|
@ -595,9 +650,13 @@ public class FindNoReturnFunctionsAnalyzer extends AbstractAnalyzer {
|
|||
// TODO: if this is done before functions are created from calls
|
||||
// then this check will do nothing
|
||||
if (callingFunc != null) {
|
||||
Address fromAddress = reference.getFromAddress();
|
||||
Function function =
|
||||
funcManager.getFunctionContaining(reference.getFromAddress());
|
||||
if (callingFunc.equals(function)) {
|
||||
funcManager.getFunctionContaining(fromAddress);
|
||||
// The reference must come from an address within this function
|
||||
// before this the function call (reference fromAddress)
|
||||
// this should get rid of considering spurious/bad data references from other functions
|
||||
if ((fromAddress.compareTo(addr) < 0) && callingFunc.equals(function)) {
|
||||
NoReturnLocations location =
|
||||
new NoReturnLocations(calledAddr, reference.getToAddress(),
|
||||
"Data Reference from same function after call");
|
||||
|
|
|
@ -541,13 +541,26 @@ public class PeLoader extends AbstractPeDebugLoader {
|
|||
return;
|
||||
}
|
||||
|
||||
AddressFactory af = program.getAddressFactory();
|
||||
AddressSpace space = af.getDefaultAddressSpace();
|
||||
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
|
||||
SymbolTable symTable = program.getSymbolTable();
|
||||
Listing listing = program.getListing();
|
||||
ReferenceManager refManager = program.getReferenceManager();
|
||||
ExternalManager extManager = program.getExternalManager();
|
||||
FunctionManager funcManager = program.getFunctionManager();
|
||||
|
||||
// If we have any forwarders, set up the EXTERNAL block
|
||||
ExportInfo[] exports = edd.getExports();
|
||||
Address extAddr = null;
|
||||
long forwardedCount = Arrays.stream(exports).filter(ExportInfo::isForwarded).count();
|
||||
if (forwardedCount > 0) {
|
||||
try {
|
||||
extAddr = AbstractProgramLoader.addExternalBlock(program,
|
||||
forwardedCount * program.getDefaultPointerSize(), log);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.appendException(e);
|
||||
}
|
||||
}
|
||||
|
||||
for (ExportInfo export : exports) {
|
||||
if (monitor.isCancelled()) {
|
||||
return;
|
||||
|
@ -555,65 +568,51 @@ public class PeLoader extends AbstractPeDebugLoader {
|
|||
|
||||
Address address = space.getAddress(export.getAddress());
|
||||
setComment(CommentType.PRE, address, export.getComment());
|
||||
symTable.addExternalEntryPoint(address);
|
||||
|
||||
String name = export.getName();
|
||||
try {
|
||||
symTable.createLabel(address, name, SourceType.IMPORTED);
|
||||
}
|
||||
catch (InvalidInputException e) {
|
||||
// Don't create invalid symbol
|
||||
}
|
||||
|
||||
try {
|
||||
symTable.createLabel(address, SymbolUtilities.ORDINAL_PREFIX + export.getOrdinal(),
|
||||
SourceType.IMPORTED);
|
||||
}
|
||||
catch (InvalidInputException e) {
|
||||
// Don't create invalid symbol
|
||||
}
|
||||
|
||||
// When exported symbol is a forwarder,
|
||||
// a string exists at the address of the export
|
||||
// Therefore, create a string data object to prevent
|
||||
// disassembler from attempting to create
|
||||
// code here. If code was created, it would be incorrect
|
||||
// and offcut.
|
||||
if (export.isForwarded()) {
|
||||
try {
|
||||
listing.createData(address, TerminatedStringDataType.dataType, -1);
|
||||
Data data = listing.getDataAt(address);
|
||||
if (data != null) {
|
||||
Object obj = data.getValue();
|
||||
if (obj instanceof String) {
|
||||
String str = (String) obj;
|
||||
Data data =
|
||||
PeUtils.createData(program, address, TerminatedStringDataType.dataType, log);
|
||||
if (extAddr != null && data != null && data.getValue() instanceof String str) {
|
||||
int dotpos = str.indexOf('.');
|
||||
|
||||
if (dotpos < 0) {
|
||||
dotpos = 0; // TODO
|
||||
}
|
||||
|
||||
// get the name of the dll
|
||||
String dllName = str.substring(0, dotpos) + ".dll";
|
||||
|
||||
// get the name of the symbol
|
||||
String expName = str.substring(dotpos + 1);
|
||||
String libName = str.substring(0, dotpos) + ".dll";
|
||||
String extSymbolName = str.substring(dotpos + 1);
|
||||
|
||||
try {
|
||||
refManager.addExternalReference(address, dllName.toUpperCase(),
|
||||
expName, null, SourceType.IMPORTED, 0, RefType.DATA);
|
||||
symTable.addExternalEntryPoint(extAddr);
|
||||
Function function = funcManager.createFunction(export.getName(), extAddr,
|
||||
new AddressSet(extAddr), SourceType.IMPORTED);
|
||||
ExternalLocation loc = extManager.addExtLocation(libName.toUpperCase(),
|
||||
extSymbolName, null, SourceType.IMPORTED);
|
||||
function.setThunkedFunction(loc.createFunction());
|
||||
symTable.createLabel(extAddr,
|
||||
SymbolUtilities.ORDINAL_PREFIX + export.getOrdinal(),
|
||||
SourceType.IMPORTED);
|
||||
refManager.addMemoryReference(address, extAddr, RefType.DATA,
|
||||
SourceType.IMPORTED, 0);
|
||||
setComment(CommentType.PLATE, extAddr, export.getComment());
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
catch (InvalidInputException | DuplicateNameException
|
||||
| OverlappingFunctionException e) {
|
||||
log.appendMsg("External location not created: " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
extAddr = extAddr.add(program.getDefaultPointerSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
symTable.addExternalEntryPoint(address);
|
||||
|
||||
try {
|
||||
symTable.createLabel(address, export.getName(), SourceType.IMPORTED);
|
||||
symTable.createLabel(address,
|
||||
SymbolUtilities.ORDINAL_PREFIX + export.getOrdinal(), SourceType.IMPORTED);
|
||||
}
|
||||
catch (InvalidInputException e) {
|
||||
log.appendMsg("External location not created: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (CodeUnitInsertionException e) {
|
||||
// Nothing to do...just continue on
|
||||
// Don't create invalid symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.FlowLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
|
@ -38,20 +37,30 @@ public class MemoryScanControlPanel extends JPanel {
|
|||
private JButton scanButton;
|
||||
|
||||
MemoryScanControlPanel(MemorySearchProvider provider) {
|
||||
super(new BorderLayout());
|
||||
super();
|
||||
|
||||
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
|
||||
|
||||
setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
|
||||
add(buildButtonPanel(), BorderLayout.CENTER);
|
||||
|
||||
scanButton = new JButton("Scan Values");
|
||||
scanButton.setMnemonic('V');
|
||||
scanButton.setEnabled(false);
|
||||
scanButton.setToolTipText("Refreshes byte values of current results and eliminates " +
|
||||
"those that don't meet the selected change criteria");
|
||||
|
||||
add(scanButton);
|
||||
add(Box.createHorizontalStrut(20));
|
||||
add(buildButtonPanel());
|
||||
|
||||
HelpService helpService = Help.getHelpService();
|
||||
helpService.registerHelp(this, new HelpLocation(HelpTopics.SEARCH, "Scan_Controls"));
|
||||
add(scanButton, BorderLayout.WEST);
|
||||
|
||||
scanButton.addActionListener(e -> provider.scan(selectedScanner));
|
||||
}
|
||||
|
||||
private JComponent buildButtonPanel() {
|
||||
JPanel panel = new JPanel(new FlowLayout());
|
||||
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));
|
||||
ButtonGroup buttonGroup = new ButtonGroup();
|
||||
for (Scanner scanner : Scanner.values()) {
|
||||
GRadioButton button = new GRadioButton(scanner.getName());
|
||||
|
|
|
@ -24,14 +24,17 @@ import java.util.List;
|
|||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.text.*;
|
||||
|
||||
import docking.DockingUtils;
|
||||
import docking.menu.ButtonState;
|
||||
import docking.menu.MultiStateButton;
|
||||
import docking.widgets.PopupWindow;
|
||||
import docking.widgets.combobox.GComboBox;
|
||||
import docking.widgets.combobox.GhidraComboBox;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
import docking.widgets.list.GComboBoxCellRenderer;
|
||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||
import ghidra.features.base.memsearch.combiner.Combiner;
|
||||
|
@ -89,9 +92,11 @@ class MemorySearchControlPanel extends JPanel {
|
|||
searchButton = new MultiStateButton<Combiner>(initialSearchButtonStates);
|
||||
searchButton
|
||||
.setStateChangedListener(state -> model.setMatchCombiner(state.getClientData()));
|
||||
searchButton.setMnemonic('S');
|
||||
searchButton.addActionListener(e -> search());
|
||||
panel.add(searchButton, BorderLayout.WEST);
|
||||
selectionCheckbox = new JCheckBox("Selection Only");
|
||||
selectionCheckbox.setMnemonic('O');
|
||||
selectionCheckbox.setSelected(model.isSearchSelectionOnly());
|
||||
selectionCheckbox.setEnabled(model.hasSelection());
|
||||
selectionCheckbox
|
||||
|
@ -132,9 +137,10 @@ class MemorySearchControlPanel extends JPanel {
|
|||
if (!formatComboBox.getSelectedItem().equals(searchFormat)) {
|
||||
formatComboBox.setSelectedItem(searchFormat);
|
||||
}
|
||||
|
||||
selectionCheckbox.setSelected(model.isSearchSelectionOnly());
|
||||
selectionCheckbox.setEnabled(model.hasSelection());
|
||||
searchInputField.setToolTipText(searchFormat.getToolTip());
|
||||
searchInputField.setToolTipText("Search Text: " + searchFormat.getToolTip());
|
||||
|
||||
String text = searchInputField.getText();
|
||||
String convertedText = searchFormat.convertText(text, oldSettings, model.getSettings());
|
||||
|
@ -144,7 +150,31 @@ class MemorySearchControlPanel extends JPanel {
|
|||
}
|
||||
|
||||
private JComponent buildLeftSearchInputPanel() {
|
||||
createSearchInputField();
|
||||
|
||||
JPanel searchInputPanel = createSearchInputPanel();
|
||||
GLabel searchLabel = new GLabel("Search Text:");
|
||||
searchLabel.setDisplayedMnemonic('T');
|
||||
searchLabel.setLabelFor(searchInputField);
|
||||
|
||||
JLabel bytesLabel = new GLabel("Byte Sequence:", SwingConstants.RIGHT);
|
||||
bytesLabel.setToolTipText("The byte sequence that will be searched (if applicable)");
|
||||
JPanel bytesPanel = createBytesPanel();
|
||||
|
||||
JPanel panel = new JPanel(new PairLayout(2, 10));
|
||||
|
||||
// row 1
|
||||
panel.add(searchLabel);
|
||||
panel.add(searchInputPanel);
|
||||
|
||||
// row 2
|
||||
panel.add(bytesLabel);
|
||||
panel.add(bytesPanel);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createBytesPanel() {
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
hexSearchSequenceField = new GDLabel();
|
||||
hexSearchSequenceField.setName("Hex Sequence Field");
|
||||
Border outerBorder = BorderFactory.createLoweredBevelBorder();
|
||||
|
@ -152,15 +182,21 @@ class MemorySearchControlPanel extends JPanel {
|
|||
Border border = BorderFactory.createCompoundBorder(outerBorder, innerBorder);
|
||||
hexSearchSequenceField.setBorder(border);
|
||||
|
||||
JPanel panel = new JPanel(new PairLayout(2, 10));
|
||||
panel.add(buildSearchFormatCombo());
|
||||
panel.add(searchInputField);
|
||||
JLabel byteSequenceLabel = new JLabel("Byte Sequence:", SwingConstants.RIGHT);
|
||||
byteSequenceLabel.setToolTipText(
|
||||
"This field shows the byte sequence that will be search (if applicable)");
|
||||
panel.add(hexSearchSequenceField, BorderLayout.CENTER);
|
||||
int spaceWidth = formatComboBox.getPreferredSize().width;
|
||||
panel.add(Box.createHorizontalStrut(spaceWidth), BorderLayout.EAST);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createSearchInputPanel() {
|
||||
|
||||
createSearchInputField();
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.add(searchInputField, BorderLayout.CENTER);
|
||||
panel.add(buildSearchFormatCombo(), BorderLayout.EAST);
|
||||
|
||||
panel.add(byteSequenceLabel);
|
||||
panel.add(hexSearchSequenceField);
|
||||
return panel;
|
||||
}
|
||||
|
||||
|
@ -181,7 +217,7 @@ class MemorySearchControlPanel extends JPanel {
|
|||
updateCombo();
|
||||
searchInputField.setAutoCompleteEnabled(false); // this interferes with validation
|
||||
searchInputField.setEditable(true);
|
||||
searchInputField.setToolTipText(model.getSearchFormat().getToolTip());
|
||||
searchInputField.setToolTipText("Search Text: " + model.getSearchFormat().getToolTip());
|
||||
searchInputField.setDocument(new RestrictedInputDocument());
|
||||
searchInputField.addActionListener(ev -> search());
|
||||
JTextField searchTextField = searchInputField.getTextField();
|
||||
|
@ -220,11 +256,14 @@ class MemorySearchControlPanel extends JPanel {
|
|||
}
|
||||
|
||||
private JComponent buildSearchFormatCombo() {
|
||||
formatComboBox = new JComboBox<>(SearchFormat.ALL);
|
||||
formatComboBox = new GComboBox<>(SearchFormat.ALL);
|
||||
formatComboBox.setSelectedItem(model.getSearchFormat());
|
||||
formatComboBox.addItemListener(this::formatComboChanged);
|
||||
formatComboBox.setToolTipText("The selected format will determine how to " +
|
||||
"interpret text typed into the input field");
|
||||
formatComboBox.setToolTipText("Search Format: how to interpret search text");
|
||||
Border inside = formatComboBox.getBorder();
|
||||
CompoundBorder paddingBorder =
|
||||
BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0), inside);
|
||||
formatComboBox.setBorder(paddingBorder);
|
||||
|
||||
return formatComboBox;
|
||||
}
|
||||
|
@ -252,7 +291,7 @@ class MemorySearchControlPanel extends JPanel {
|
|||
currentMatcher = byteMatcher;
|
||||
String text = currentMatcher.getDescription();
|
||||
hexSearchSequenceField.setText(text);
|
||||
hexSearchSequenceField.setToolTipText(currentMatcher.getToolTip());
|
||||
hexSearchSequenceField.setToolTipText("Search as hex: " + currentMatcher.getToolTip());
|
||||
updateSearchButton();
|
||||
provider.setByteMatcher(byteMatcher);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import javax.swing.text.*;
|
|||
|
||||
import docking.widgets.checkbox.GCheckBox;
|
||||
import docking.widgets.combobox.GComboBox;
|
||||
import docking.widgets.label.GLabel;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.docking.util.LookAndFeelUtils;
|
||||
import ghidra.features.base.memsearch.bytesource.SearchRegion;
|
||||
|
@ -57,7 +58,7 @@ class MemorySearchOptionsPanel extends JPanel {
|
|||
super(new BorderLayout());
|
||||
this.model = model;
|
||||
|
||||
// if the look and feel is Nimbus, the spaceing it too big, so we use less spacing
|
||||
// if the look and feel is Nimbus, the spacing it too big, so we use less spacing
|
||||
// between elements.
|
||||
isNimbus = LookAndFeelUtils.isUsingNimbusUI();
|
||||
|
||||
|
@ -93,9 +94,22 @@ class MemorySearchOptionsPanel extends JPanel {
|
|||
JPanel panel = new JPanel(new VerticalLayout(3));
|
||||
panel.setBorder(createBorder("Search Region Filter"));
|
||||
|
||||
boolean accelerator = true;
|
||||
List<SearchRegion> choices = model.getMemoryRegionChoices();
|
||||
for (SearchRegion region : choices) {
|
||||
GCheckBox checkbox = new GCheckBox(region.getName());
|
||||
|
||||
if (accelerator) {
|
||||
// The text for the checkbox is dynamic. If the first letter is taken by a menu,
|
||||
// then the accelerator may not work. At the time of writing, the first letter of
|
||||
// the first option seems not to conflict with the other accelerators in the parent
|
||||
// dialog.
|
||||
String name = region.getName();
|
||||
char c = name.charAt(0);
|
||||
checkbox.setMnemonic(c);
|
||||
accelerator = false;
|
||||
}
|
||||
|
||||
checkbox.setToolTipText(region.getDescription());
|
||||
checkbox.setSelected(model.isSelectedRegion(region));
|
||||
checkbox.addItemListener(e -> model.selectRegion(region, checkbox.isSelected()));
|
||||
|
@ -109,13 +123,15 @@ class MemorySearchOptionsPanel extends JPanel {
|
|||
panel.setBorder(createBorder("Decimal Options"));
|
||||
|
||||
JPanel innerPanel = new JPanel(new PairLayout(5, 5));
|
||||
JLabel label = new JLabel("Size:");
|
||||
GLabel label = new GLabel("Size:");
|
||||
label.setDisplayedMnemonic('z');
|
||||
label.setToolTipText("Size of decimal values in bytes");
|
||||
innerPanel.add(label);
|
||||
|
||||
Integer[] decimalSizes = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 16 };
|
||||
int decimalByteSize = model.getDecimalByteSize();
|
||||
decimalByteSizeCombo = new GComboBox<>(decimalSizes);
|
||||
label.setLabelFor(decimalByteSizeCombo);
|
||||
decimalByteSizeCombo.setSelectedItem(decimalByteSize);
|
||||
decimalByteSizeCombo.addItemListener(this::byteSizeComboChanged);
|
||||
decimalByteSizeCombo.setToolTipText("Size of decimal values in bytes");
|
||||
|
@ -123,6 +139,7 @@ class MemorySearchOptionsPanel extends JPanel {
|
|||
panel.add(innerPanel);
|
||||
|
||||
decimalUnsignedCheckbox = new GCheckBox("Unsigned");
|
||||
decimalUnsignedCheckbox.setMnemonic('U');
|
||||
decimalUnsignedCheckbox.setToolTipText(
|
||||
"Sets whether decimal values should be interpreted as unsigned values");
|
||||
decimalUnsignedCheckbox.addActionListener(
|
||||
|
@ -145,8 +162,11 @@ class MemorySearchOptionsPanel extends JPanel {
|
|||
JPanel panel = new JPanel(new VerticalLayout(5));
|
||||
panel.setBorder(createBorder("Code Type Filter"));
|
||||
GCheckBox instructionsCheckBox = new GCheckBox("Instructions");
|
||||
instructionsCheckBox.setMnemonic('I');
|
||||
GCheckBox definedDataCheckBox = new GCheckBox("Defined Data");
|
||||
definedDataCheckBox.setMnemonic('D');
|
||||
GCheckBox undefinedDataCheckBox = new GCheckBox("Undefined Data");
|
||||
undefinedDataCheckBox.setMnemonic('U');
|
||||
instructionsCheckBox.setToolTipText(
|
||||
"If selected, include matches found in instructions");
|
||||
definedDataCheckBox.setToolTipText(
|
||||
|
@ -185,9 +205,15 @@ class MemorySearchOptionsPanel extends JPanel {
|
|||
alignField.setToolTipText(
|
||||
"Filters out matches whose address is not divisible by the alignment value");
|
||||
|
||||
panel.add(new JLabel("Endianess:"));
|
||||
GLabel endianessLabel = new GLabel("Endianess:");
|
||||
endianessLabel.setLabelFor(endianessCombo);
|
||||
endianessLabel.setDisplayedMnemonic('n');
|
||||
GLabel alignmentLabel = new GLabel("Alignment:");
|
||||
alignmentLabel.setDisplayedMnemonic('A');
|
||||
alignmentLabel.setLabelFor(alignField);
|
||||
panel.add(endianessLabel);
|
||||
panel.add(endianessCombo);
|
||||
panel.add(new JLabel("Alignment:"));
|
||||
panel.add(alignmentLabel);
|
||||
panel.add(alignField);
|
||||
|
||||
return panel;
|
||||
|
@ -215,19 +241,23 @@ class MemorySearchOptionsPanel extends JPanel {
|
|||
charsetCombo.setToolTipText("Character encoding for translating strings to bytes");
|
||||
|
||||
JPanel innerPanel = new JPanel(new PairLayout(5, 5));
|
||||
JLabel label = new JLabel("Encoding:");
|
||||
GLabel label = new GLabel("Encoding:");
|
||||
label.setDisplayedMnemonic('c');
|
||||
label.setLabelFor(charsetCombo);
|
||||
label.setToolTipText("Character encoding for translating strings to bytes");
|
||||
innerPanel.add(label);
|
||||
innerPanel.add(charsetCombo);
|
||||
panel.add(innerPanel);
|
||||
|
||||
caseSensitiveCheckbox = new GCheckBox("Case Sensitive");
|
||||
caseSensitiveCheckbox.setMnemonic('n');
|
||||
caseSensitiveCheckbox.setSelected(model.isCaseSensitive());
|
||||
caseSensitiveCheckbox.setToolTipText("Allows for case sensitive searching.");
|
||||
caseSensitiveCheckbox.addActionListener(
|
||||
e -> model.setCaseSensitive(caseSensitiveCheckbox.isSelected()));
|
||||
|
||||
escapeSequencesCheckbox = new GCheckBox("Escape Sequences");
|
||||
escapeSequencesCheckbox.setMnemonic('c');
|
||||
escapeSequencesCheckbox.setSelected(model.useEscapeSequences());
|
||||
escapeSequencesCheckbox.setToolTipText(
|
||||
"Allows specifying control characters using escape sequences " +
|
||||
|
|
|
@ -698,10 +698,18 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ActionContext createContext(Component sourceComponent, Object contextObject) {
|
||||
protected ActionContext createContext(Component focusedComponent, Object contextObject) {
|
||||
ActionContext context = new NavigatableActionContext(this, navigatable);
|
||||
context.setContextObject(contextObject);
|
||||
context.setSourceComponent(sourceComponent);
|
||||
|
||||
// the 'sourceComponent' will be the focused item if the focus owner is in our provider,
|
||||
// otherwise it will be the main component
|
||||
context.setSourceObject(focusedComponent);
|
||||
|
||||
// we make the source component be the table so that the 'activate filter' action works
|
||||
// from anywhere in this provider
|
||||
GhidraTable table = resultsPanel.getTable();
|
||||
context.setSourceComponent(table);
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
|
@ -339,11 +339,7 @@ public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest
|
|||
sharedFS.createFolder("/", "b");
|
||||
flushFileSystemEvents(); // wait for FileSystemListener callback to update folder
|
||||
assertEquals(3, root.getFolders().length);
|
||||
assertEventsSize(3);
|
||||
|
||||
checkEvent(events.get(0), "Folder Added", null, "/a", null, null, null);
|
||||
checkEvent(events.get(1), "Folder Added", null, "/b", null, null, null);
|
||||
checkEvent(events.get(2), "Folder Added", null, "/c", null, null, null);
|
||||
assertEventsSize(0);
|
||||
|
||||
sharedFS.createFolder("/b", "subB");
|
||||
flushFileSystemEvents(); // wait for FileSystemListener callback to update folder
|
||||
|
@ -487,12 +483,9 @@ public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest
|
|||
root.getFolders(); // visit root folder to receive change events for it
|
||||
sharedFS.renameFolder("/", "a", "bigA");
|
||||
flushFileSystemEvents(); // wait for FileSystemListener callback to update folder
|
||||
assertEventsSize(4);
|
||||
assertEventsSize(1);
|
||||
|
||||
checkEvent(events.get(0), "Folder Added", null, "/a", null, null, null);
|
||||
checkEvent(events.get(1), "Folder Added", null, "/b", null, null, null);
|
||||
checkEvent(events.get(2), "Folder Added", null, "/c", null, null, null);
|
||||
checkEvent(events.get(3), "Folder Added", null, "/bigA", null, null, null);
|
||||
checkEvent(events.get(0), "Folder Added", null, "/bigA", null, null, null);
|
||||
|
||||
// versioned folder was renamed to /bigA, but private folder /a should still exist
|
||||
|
||||
|
@ -516,11 +509,9 @@ public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest
|
|||
sharedFS.renameFolder("/a", "y", "bigY");
|
||||
flushFileSystemEvents(); // wait for FileSystemListener callback to update folder
|
||||
|
||||
assertEventsSize(4);
|
||||
checkEvent(events.get(0), "Folder Added", null, "/a/x", null, null, null);
|
||||
checkEvent(events.get(1), "Folder Added", null, "/a/y", null, null, null);
|
||||
checkEvent(events.get(2), "Folder Removed", "/a", null, null, null, "y");
|
||||
checkEvent(events.get(3), "Folder Added", null, "/a/bigY", null, null, null);
|
||||
assertEventsSize(2);
|
||||
checkEvent(events.get(0), "Folder Removed", "/a", null, null, null, "y");
|
||||
checkEvent(events.get(1), "Folder Added", null, "/a/bigY", null, null, null);
|
||||
|
||||
}
|
||||
|
||||
|
@ -532,12 +523,9 @@ public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest
|
|||
assertNull(root.getFolder("c"));
|
||||
assertNotNull(root.getFolder("bigC"));
|
||||
|
||||
assertEventsSize(5);
|
||||
checkEvent(events.get(0), "Folder Added", null, "/a", null, null, null);
|
||||
checkEvent(events.get(1), "Folder Added", null, "/b", null, null, null);
|
||||
checkEvent(events.get(2), "Folder Added", null, "/c", null, null, null);
|
||||
checkEvent(events.get(3), "Folder Removed", "/", null, null, null, "c");
|
||||
checkEvent(events.get(4), "Folder Added", null, "/bigC", null, null, null);
|
||||
assertEventsSize(2);
|
||||
checkEvent(events.get(0), "Folder Removed", "/", null, null, null, "c");
|
||||
checkEvent(events.get(1), "Folder Added", null, "/bigC", null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -614,12 +602,9 @@ public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest
|
|||
|
||||
sharedFS.moveFolder("/", "a", "/c");
|
||||
flushFileSystemEvents(); // wait for FileSystemListener callback to update folder
|
||||
assertEventsSize(4);
|
||||
assertEventsSize(1);
|
||||
|
||||
checkEvent(events.get(0), "Folder Added", null, "/a", null, null, null);
|
||||
checkEvent(events.get(1), "Folder Added", null, "/b", null, null, null);
|
||||
checkEvent(events.get(2), "Folder Added", null, "/c", null, null, null);
|
||||
checkEvent(events.get(3), "Folder Added", null, "/c/a", null, null, null);
|
||||
checkEvent(events.get(0), "Folder Added", null, "/c/a", null, null, null);
|
||||
|
||||
// versioned folder was moved to /c/a, but private folder /a should still exist
|
||||
|
||||
|
|
|
@ -209,6 +209,7 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
|||
private String label = null;
|
||||
private boolean isThunk = false; // true if this function should be turned into a thunk
|
||||
private boolean noreturn = false; // true to set function non-returning
|
||||
private String sectionName = null; // required section name
|
||||
boolean validFunction = false; // must be defined at a function
|
||||
private boolean contiguous = true; // require validcode instructions be contiguous
|
||||
|
||||
|
@ -225,6 +226,14 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
|||
}
|
||||
|
||||
protected boolean checkPreRequisites(Program program, Address addr) {
|
||||
// check required section name
|
||||
if (sectionName != null) {
|
||||
MemoryBlock block = program.getMemory().getBlock(addr);
|
||||
if (block == null || !block.getName().matches(sectionName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the match's mark point occurs in undefined data, schedule disassembly
|
||||
* and a function start at that address. If the match's mark point occurs at an instruction, but that
|
||||
|
@ -641,6 +650,10 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
|||
isThunk = true;
|
||||
break;
|
||||
|
||||
case "section":
|
||||
sectionName = attrValue;
|
||||
break;
|
||||
|
||||
case "noreturn":
|
||||
noreturn = true;
|
||||
break;
|
||||
|
@ -816,7 +829,14 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
|||
|
||||
AutoAnalysisManager analysisManager = AutoAnalysisManager.getAnalysisManager(program);
|
||||
if (!disassemResult.isEmpty()) {
|
||||
analysisManager.disassemble(disassemResult, AnalysisPriority.DISASSEMBLY);
|
||||
// disassemble known function starts now
|
||||
AddressSet doNowDisassembly = disassemResult.intersect(funcResult);
|
||||
// this will disassemble at this analyzers priority
|
||||
analysisManager.disassemble(doNowDisassembly);
|
||||
|
||||
// delay disassemble of possible function starts
|
||||
AddressSet delayedDisassembly = disassemResult.subtract(funcResult);
|
||||
analysisManager.disassemble(delayedDisassembly, AnalysisPriority.DISASSEMBLY);
|
||||
}
|
||||
analysisManager.setProtectedLocations(codeLocations);
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ public class FunctionStartPreFuncAnalyzer extends FunctionStartAnalyzer {
|
|||
public FunctionStartPreFuncAnalyzer() {
|
||||
super(FUNCTION_START_PRE_SEARCH, DESCRIPTION, AnalyzerType.BYTE_ANALYZER);
|
||||
|
||||
setPriority(AnalysisPriority.BLOCK_ANALYSIS.after());
|
||||
setPriority(AnalysisPriority.BLOCK_ANALYSIS.before());
|
||||
setDefaultEnablement(true);
|
||||
setSupportsOneTimeAnalysis();
|
||||
}
|
||||
|
|
|
@ -83,10 +83,26 @@ class DetachedWindowNode extends WindowNode {
|
|||
child = processChildElement(childElement, mgr, this, list);
|
||||
}
|
||||
|
||||
void setInitialBounds(Rectangle r) {
|
||||
// Set the bounds for the component that is being placed in this window when the window is first
|
||||
// created. This is useful when dragging a component provider out of an existing window into
|
||||
// its own window.
|
||||
void setWindowContentsBounds(Rectangle r) {
|
||||
// The rectangle will be empty when there is no size information for the component being
|
||||
// added to this window node.
|
||||
if (r == null) {
|
||||
r = new Rectangle();
|
||||
}
|
||||
|
||||
if (!r.isEmpty()) {
|
||||
// We need to create window bounds from the given component bounds. The window has
|
||||
// extra size for the toolbar and menus.
|
||||
int nonComponentWidth = 12;
|
||||
int nonComponentHeight = 120;
|
||||
|
||||
r.width += nonComponentWidth;
|
||||
r.height += nonComponentHeight;
|
||||
}
|
||||
|
||||
restoreBounds = r;
|
||||
}
|
||||
|
||||
|
@ -372,6 +388,7 @@ class DetachedWindowNode extends WindowNode {
|
|||
private Rectangle getNewBounds(Window newWindow) {
|
||||
|
||||
Rectangle updatedBounds = new Rectangle(restoreBounds);
|
||||
restoreBounds = null;
|
||||
if (updatedBounds.isEmpty()) {
|
||||
// No bounds to restore; pick something reasonable
|
||||
window.pack();
|
||||
|
@ -379,20 +396,8 @@ class DetachedWindowNode extends WindowNode {
|
|||
updatedBounds.height = d.height;
|
||||
updatedBounds.width = d.width;
|
||||
}
|
||||
else {
|
||||
|
||||
// Update the desired window bounds for the size of the component. The window size
|
||||
// has to account for things like the menu and toolbars. These value were picked
|
||||
// through trial-and-error.
|
||||
int nonComponentWidth = 12;
|
||||
int nonComponentHeight = 120;
|
||||
|
||||
updatedBounds.width += nonComponentWidth;
|
||||
updatedBounds.height += nonComponentHeight;
|
||||
}
|
||||
|
||||
ensureValidLocation(updatedBounds);
|
||||
|
||||
WindowUtilities.ensureEntirelyOnScreen(newWindow, updatedBounds);
|
||||
|
||||
return updatedBounds;
|
||||
|
|
|
@ -241,7 +241,7 @@ class RootNode extends WindowNode {
|
|||
|
||||
Point location = loc == null ? new Point() : loc;
|
||||
Dimension size = placeholderSize == null ? new Dimension() : placeholderSize;
|
||||
windowNode.setInitialBounds(new Rectangle(location, size));
|
||||
windowNode.setWindowContentsBounds(new Rectangle(location, size));
|
||||
|
||||
detachedWindows.add(windowNode);
|
||||
placeholder.getNode().add(placeholder);
|
||||
|
|
|
@ -558,12 +558,14 @@ class GhidraFolderData {
|
|||
}
|
||||
|
||||
/**
|
||||
* Refresh set of sub-folder names and identify added/removed folders.
|
||||
* Refresh set of sub-folder names and notify about adds/removes if appropriate
|
||||
* @param recursive recurse into visited subfolders if true
|
||||
* @param notifyAdd true if listener should be notified about newly discovered subfolders
|
||||
* @param monitor recursion task monitor - break from recursion if cancelled
|
||||
* @throws IOException if an IO error occurs during the refresh
|
||||
*/
|
||||
private void refreshFolders(boolean recursive, TaskMonitor monitor) throws IOException {
|
||||
private void refreshFolders(boolean recursive, boolean notifyAdd, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
|
||||
// FIXME: inconsistent use of forced-recursive refresh and cached folderList
|
||||
|
||||
|
@ -630,7 +632,7 @@ class GhidraFolderData {
|
|||
GhidraFolderData folderData = addFolderData(folderName);
|
||||
if (folderData != null) {
|
||||
folderList.add(folderName);
|
||||
if (visited) {
|
||||
if (notifyAdd) {
|
||||
listener.domainFolderAdded(folderData.getDomainFolder());
|
||||
}
|
||||
}
|
||||
|
@ -678,7 +680,13 @@ class GhidraFolderData {
|
|||
return map;
|
||||
}
|
||||
|
||||
private void refreshFiles(TaskMonitor monitor) throws IOException {
|
||||
/**
|
||||
* Refresh set of files and notify about adds/removes if appropriate
|
||||
* @param notifyAdd true if listener should be notified about newly discovered files
|
||||
* @param monitor return immediately if cancelled
|
||||
* @throws IOException if an IO error occurs during the refresh
|
||||
*/
|
||||
private void refreshFiles(boolean notifyAdd, TaskMonitor monitor) throws IOException {
|
||||
|
||||
String path = getPathname();
|
||||
|
||||
|
@ -744,7 +752,7 @@ class GhidraFolderData {
|
|||
FolderItem versionedFolderItem = versionedItemMap.get(fileName);
|
||||
|
||||
GhidraFileData fileData = addFileData(fileName, localFolderItem, versionedFolderItem);
|
||||
if (visited) {
|
||||
if (notifyAdd) {
|
||||
listener.domainFileAdded(fileData.getDomainFile());
|
||||
}
|
||||
}
|
||||
|
@ -791,6 +799,7 @@ class GhidraFolderData {
|
|||
return;
|
||||
}
|
||||
|
||||
boolean notifyAdd = visited;
|
||||
visited = true;
|
||||
|
||||
try {
|
||||
|
@ -812,13 +821,13 @@ class GhidraFolderData {
|
|||
|
||||
// FIXME: If forced we should be refreshing folder/file lists
|
||||
|
||||
refreshFiles(monitor);
|
||||
refreshFiles(notifyAdd, monitor);
|
||||
|
||||
if (monitor != null && monitor.isCancelled()) {
|
||||
return; // break-out from recursion on cancel
|
||||
}
|
||||
|
||||
refreshFolders(recursive, monitor);
|
||||
refreshFolders(recursive, notifyAdd, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,5 +92,6 @@ data/patterns/x86-64gcc_patterns.xml||GHIDRA||||END|
|
|||
data/patterns/x86-64win_patterns.xml||GHIDRA||||END|
|
||||
data/patterns/x86delphi_patterns.xml||GHIDRA||||END|
|
||||
data/patterns/x86gcc_patterns.xml||GHIDRA||||END|
|
||||
data/patterns/x86gcc_prepatterns.xml||GHIDRA||||END|
|
||||
data/patterns/x86win_patterns.xml||GHIDRA||||END|
|
||||
data/patterns/x86win_prepatterns.xml||GHIDRA||||END|
|
||||
|
|
|
@ -7,6 +7,15 @@
|
|||
<compiler id="borlandcpp">
|
||||
<patternfile>x86win_prepatterns.xml</patternfile>
|
||||
</compiler>
|
||||
<compiler id="gcc">
|
||||
<patternfile>x86gcc_prepatterns.xml</patternfile>
|
||||
</compiler>
|
||||
</language>
|
||||
|
||||
<language id="x86:LE:64:default">
|
||||
<compiler id="gcc">
|
||||
<patternfile>x86gcc_prepatterns.xml</patternfile>
|
||||
</compiler>
|
||||
</language>
|
||||
|
||||
</patternconstraints>
|
||||
|
|
20
Ghidra/Processors/x86/data/patterns/x86gcc_prepatterns.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<patternlist>
|
||||
|
||||
<pattern>
|
||||
<data>
|
||||
0xff25........ <!-- jmp -->
|
||||
0x68......00 <!-- push -->
|
||||
0xe9......ff <!-- jmp -addr -->
|
||||
</data> <!-- .plt thunk -->
|
||||
<funcstart thunk="true" section=".plt"/>
|
||||
</pattern>
|
||||
|
||||
<pattern>
|
||||
<data>
|
||||
0xf3 0x0f 0x1e 0x1a <!-- ENDBR64 -->
|
||||
0xf2 0xff 0x25 .. .. .. .. <!-- jmp -->
|
||||
</data> <!-- .plt thunk -->
|
||||
<funcstart thunk="true" section=".plt"/>
|
||||
</pattern>
|
||||
|
||||
</patternlist>
|
|
@ -1,5 +1,5 @@
|
|||
application.name=Ghidra
|
||||
application.version=12.0
|
||||
application.version=12.1
|
||||
application.release.name=DEV
|
||||
application.layout.version=3
|
||||
application.gradle.min=8.5
|
||||
|
|