Skip to content

Commit

Permalink
GhIDA Python 2 updates
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrea Marcelli committed Dec 16, 2020
1 parent cb3ffc8 commit a396916
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 87 deletions.
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,21 @@ More information are provided in the **Features description** section.

## Requirements

* IDA Pro 7.x and Python2 or IDA Pro 7.4 and Python3
* Not compatible with IDA Home

* `requests` and `pygments` installed.
* GhIDA has a Python 2 and Python 3 version:
* For Python 2 requires IDA Pro 7.x
* For Python 3 requires IDA Pro >= 7.4
* GhIDA is not compatible with *IDA Home*

* `requests` and `pygments` Python (2 or 3) packages

* A local installation of [Ghidra](https://ghidra-sre.org/InstallationGuide.html#Install) or [Ghidraaas](https://github.com/Cisco-Talos/Ghidraaas).
* Use Ghidra version 9.1.2


## Installation

![Decompiler settings image](img/ghida_config.png)

* IDA Pro 7.x

* Install `requests` and `pygments` in Python 2 or Python 3.
```
pip install requests
Expand All @@ -67,11 +68,12 @@ pip install pygments

* The first time GhIDA is launched (Ctrl+Alt+D or *Edit* > *Plugins* > *GhIDA Decompiler*), a settings form is displayed, as shown in the previous image.
* If you want to use GhIDA with a local installation of Ghidra:
* [Install Ghidra](https://ghidra-sre.org/InstallationGuide.html#Install)
* Fill the *installation path* (path of the `ghidra_9.x.x` folder)
* [Download](https://ghidra-sre.org/releaseNotes_9.2.html) and [install Ghidra 9.1.2](https://ghidra-sre.org/InstallationGuide.html#Install)
* Fill the *installation path* (path of the `ghidra_9.1.2_PUBLIC` folder)

* Otherwise, if you want to use Ghidraaas:
* Launch a local instance of the server using the [Ghidraaas](https://github.com/Cisco-Talos/Ghidraaas) docker container, and insert `http://0.0.0.0:8080/ghidra/api`.
* Launch a local instance of the server using the [Ghidraaas](https://github.com/Cisco-Talos/Ghidraaas) docker container
* Check the *Use Ghidraaas server* box, and insert `http://0.0.0.0:8080/ghidra/api`.

* To run GhIDA:
* Ctrl+Alt+D
Expand All @@ -97,7 +99,7 @@ By default, the disassembler view is synchronized with the decompiler view. By c
**To disable the synchronization** (in the disassembler view) *right-click > Disable decompiler view synchronization*.

### Code syntax highlight
Decompiled code is syntax-highlighted using the [pygments](http://pygments.org/) python library.
Decompiled code is syntax-highlighted using the [pygments](http://pygments.org/) Python library.

### Code navigation
In the decompiler view, double click (or *right-click > Goto*) over the name of a function to open it in the decompile and disassembler view.
Expand Down Expand Up @@ -137,7 +139,7 @@ To avoid retype GhIDA configuration each time IDA is opened, the configuration i

## Technical details

Under the hood, GhIDA exports the IDA project using [idaxml.py](https://github.com/NationalSecurityAgency/ghidra/blob/master/GhidraBuild/IDAPro/Python/7xx/python/idaxml.py), a python library shipped with Ghidra, then it directly invokes Ghidra in headless mode without requiring any additional analysis. When GhIDA is called the first time, it uses `idaxml` to create two files: a XML file which embeds a program description according to the IDA analysis (including functions, data, symbols, comments, etc) and a `.bytes` file that contains the binary code of the program under analysis. While the binary file does not change during the time, the XML file is recreated each time the user invalidates the GhIDA cache, in order to take into account the updates the user did in the program analysis. To obtain the decompiled code, GhIDA uses [`FunctionDecompile.py`](ghida_plugin/ghidra_plugin/FunctionDecompile.py), a Ghidra plugin in python that exports to a JSON file the decompiled code of a selected function.
Under the hood, GhIDA exports the IDA project using [idaxml.py](https://github.com/NationalSecurityAgency/ghidra/blob/master/GhidraBuild/IDAPro/Python/7xx/python/idaxml.py), a Python library shipped with Ghidra, then it directly invokes Ghidra in headless mode without requiring any additional analysis. When GhIDA is called the first time, it uses `idaxml` to create two files: a XML file which embeds a program description according to the IDA analysis (including functions, data, symbols, comments, etc) and a `.bytes` file that contains the binary code of the program under analysis. While the binary file does not change during the time, the XML file is recreated each time the user invalidates the GhIDA cache, in order to take into account the updates the user did in the program analysis. To obtain the decompiled code, GhIDA uses [`FunctionDecompile.py`](ghida_plugin/ghidra_plugin/FunctionDecompile.py), a Ghidra plugin in Python that exports to a JSON file the decompiled code of a selected function.

Exporting the IDA's IDB and calling Ghidra in headless mode add a small overhead to the decompilation process, but it allows to abstract the low-level communication with the Ghidra decompiler.

Expand Down
39 changes: 20 additions & 19 deletions ghida.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def activate(self, ctx):
if DECOMP_VIEW:
DECOMP_VIEW.Show()
else:
print("GhIDA:: [DEBUG] DECOMP_VIEW non existing")
# print("GhIDA:: [DEBUG] DECOMP_VIEW non existing")
pass
return 1

# This action is always available.
Expand All @@ -92,7 +93,7 @@ def __init__(self, view):

# Say hello when invoked.
def activate(self, ctx):
print("GhIDA:: [DEBUG] GoToCustViewerHandler HELLO")
# print("GhIDA:: [DEBUG] GoToCustViewerHandler HELLO")
goto()
return 1

Expand All @@ -109,7 +110,7 @@ def __init__(self, view):

# Say hello when invoked.
def activate(self, ctx):
print("GhIDA:: [DEBUG] AddCommentCustViewerHandler HELLO")
# print("GhIDA:: [DEBUG] AddCommentCustViewerHandler HELLO")
DECOMP_VIEW.add_comment()
return 1

Expand All @@ -126,7 +127,7 @@ def __init__(self, view):

# Say hello when invoked.
def activate(self, ctx):
print("GhIDA:: [DEBUG] RenameCustViewerHandler HELLO")
# print("GhIDA:: [DEBUG] RenameCustViewerHandler HELLO")
DECOMP_VIEW.rename_symbol()
return 1

Expand Down Expand Up @@ -194,7 +195,7 @@ def view_loc_changed(self, widget, curloc, prevloc):
# ------------------------------------------------------------

def goto(shift=False):
print("GhIDA:: [DEBUG] goto called")
# print("GhIDA:: [DEBUG] goto called")

symbol = None
ret = ida_kernwin.get_highlight(ida_kernwin.get_current_viewer())
Expand All @@ -216,7 +217,7 @@ def goto(shift=False):

# Update IDA DECOMP view
ea = gl.convert_address(address)
print("GhIDA:: [DEBUG] update view to %s" % ea)
# print("GhIDA:: [DEBUG] update view to %s" % ea)
DECOMP_VIEW.switch_to_address(ea)

return True
Expand Down Expand Up @@ -341,7 +342,7 @@ def update(self, ea, decompiled, do_show=True):

# Update the cache
DECOMPILED_CACHE.update_decompiled_cache(ea, decompiled)
print("GhIDA:: [DEBUG] GhIDA DECOM view updated to %s" % ea)
# print("GhIDA:: [DEBUG] GhIDA DECOM view updated to %s" % ea)
return

def switch_to_address(self, ea):
Expand All @@ -357,7 +358,7 @@ def add_comment(self):
"""
Add a commment to the selected line
"""
print("GhIDA:: [DEBUG] add_comment called")
# print("GhIDA:: [DEBUG] add_comment called")
colored_line = self.GetCurrentLine(notags=1)
if not colored_line:
idaapi.warning("Select a line")
Expand Down Expand Up @@ -397,8 +398,8 @@ def add_comment(self):
# Add comment to cache
COMMENTS_CACHE.add_comment_to_cache(self.__ea, num_line, full_comment)

print("GhIDA:: [DEBUG] Added comment to #line: %d (%s)" %
(num_line, new_text))
# print("GhIDA:: [DEBUG] Added comment to #line: %d (%s)" %
# (num_line, new_text))
return

def add_comments(self, comment_list):
Expand All @@ -423,7 +424,7 @@ def add_comments(self, comment_list):
self.EditLine(lineno, new_line)

self.Refresh()
print("GhIDA:: [DEBUG] updated comments terminated")
# print("GhIDA:: [DEBUG] updated comments terminated")
return

def rename_symbol(self):
Expand Down Expand Up @@ -467,7 +468,7 @@ def rename_symbol(self):
gl.updated_symbol_name_for_address(symbol, address, new_name)

# Update symbol name in IDA DISASM view.
print("GhIDA:: [DEBUG] New symbol name: %s" % new_name)
# print("GhIDA:: [DEBUG] New symbol name: %s" % new_name)

# Update symbol name in the decompiled view
new_code = gl.rename_variable_in_text(
Expand Down Expand Up @@ -553,10 +554,10 @@ def __init__(self):

# Say hello when invoked.
def activate(self, ctx):
print("GhIDA:: [DEBUG] InvalidateCache HELLO")
# print("GhIDA:: [DEBUG] InvalidateCache HELLO")
address = gl.get_current_address()
if not address:
print("GhIDA:: [DEBUG] address not found")
# print("GhIDA:: [DEBUG] address not found")
return

DECOMPILED_CACHE.invalidate_cache(address)
Expand All @@ -576,7 +577,7 @@ def __init__(self):

# Say hello when invoked.
def activate(self, ctx):
print("GhIDA:: [DEBUG] DisasmTracker HELLO")
# print("GhIDA:: [DEBUG] DisasmTracker HELLO")

if GHIDA_CONF.disasm_tracker:
GHIDA_CONF.disasm_tracker = False
Expand Down Expand Up @@ -612,7 +613,7 @@ def __init__(self):

# Say hello when invoked.
def activate(self, ctx):
print("GhIDA:: [DEBUG] DisasmsHandler HELLO")
# print("GhIDA:: [DEBUG] DisasmsHandler HELLO")
decompile_function_wrapper()
return 1

Expand Down Expand Up @@ -643,7 +644,7 @@ def register_handlers():
"""
Register the handlers for the pop-up menu to interact with the UI
"""
print("GhIDA:: [DEBUG] Registering handlers")
# print("GhIDA:: [DEBUG] Registering handlers")

# Load a custom icon
icon_path = gl.plugin_resource("ghida.png")
Expand Down Expand Up @@ -720,7 +721,7 @@ def load_configuration():
global COMMENTS_CACHE

# Loading the plugin configuration
print("GhIDA:: [DEBUG] Reading GhIDA configuration")
# print("GhIDA:: [DEBUG] Reading GhIDA configuration")
GHIDA_CONF = gl.GhidaConfiguration()

print("GHIDA_CONF.load_save_cached_code",
Expand Down Expand Up @@ -929,7 +930,7 @@ def decompile_function_wrapper(cache_only=False, do_show=True):
msg += "Press Ctrl-Alt-D or Right click GhIDA decompiler "
msg += "to decompile the function."
DECOMP_VIEW.clear(msg=msg, do_show=do_show)
print("GhIDA:: [DEBUG] Function code not available in cache.")
# print("GhIDA:: [DEBUG] Function code not available in cache.")
return

# Cache miss - opt2: Use Ghidra to decompile the function
Expand Down
28 changes: 14 additions & 14 deletions ghida_plugin/comments_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,23 @@ def set_cache_path(self, file_id):
file_id = "test"
self.__cache_path = os.path.join(
tempfile.gettempdir(), COMMENTS_CACHE_FILE % file_id)
print("GhIDA:: [DEBUG] comments_cache_path: %s" %
self.__cache_path)
# print("GhIDA:: [DEBUG] comments_cache_path: %s" %
# self.__cache_path)

except Exception:
print("GhIDA:: [!] error while setting the comments cache")
return

def invalidate_cache(self):
self.__comments_cache = dict()
print("GhIDA:: [DEBUG] comments cache is empty")
# print("GhIDA:: [DEBUG] comments cache is empty")
return

def add_comments_to_cache(self, address, comments_list):
for c in comments_list:
self.__comment_cache[address] = c
ll = len(comments_list)
print("GhIDA:: [DEBUG] addedd %d comments to cache (%s)" % ll)
# ll = len(comments_list)
# print("GhIDA:: [DEBUG] addedd %d comments to cache (%s)" % ll)
return

def add_comment_to_cache(self, address, line_num, comment):
Expand All @@ -70,17 +70,17 @@ def add_comment_to_cache(self, address, line_num, comment):
if t[0] == line_num:
self.__comments_cache[address].remove(t)
self.__comments_cache[address].append((line_num, comment))
print("GhIDA:: [DEBUG] addedd comments (%s, %d) to cache" %
(address, line_num))
print("GhIDA:: [DEBUG] %d elements in cache" %
len(self.__comments_cache))
# print("GhIDA:: [DEBUG] addedd comments (%s, %d) to cache" %
# (address, line_num))
# print("GhIDA:: [DEBUG] %d elements in cache" %
# len(self.__comments_cache))
return

def get_comments_cache(self, address):
if address in self.__comments_cache:
print("GhIDA:: [DEBUG] comments cache hit (%s)" % address)
# print("GhIDA:: [DEBUG] comments cache hit (%s)" % address)
return self.__comments_cache[address]
print("GhIDA:: [DEBUG] comments cache miss (%s)" % address)
# print("GhIDA:: [DEBUG] comments cache miss (%s)" % address)
return None

def dump_cache_to_json(self):
Expand All @@ -92,11 +92,11 @@ def dump_cache_to_json(self):

def load_cache_from_json(self):
try:
print("GhIDA:: [DEBUG] loading comments cache from json")
# print("GhIDA:: [DEBUG] loading comments cache from json")
with open(self.__cache_path) as f_in:
self.__comments_cache = json.load(f_in)
print("GhIDA:: [DEBUG] loaded %d comments from cache" % len(
self.__comments_cache))
# print("GhIDA:: [DEBUG] loaded %d comments from cache" % len(
# self.__comments_cache))
except Exception:
print("GhIDA:: [!] error while loading comments from %s" %
self.__cache_path)
Expand Down
6 changes: 3 additions & 3 deletions ghida_plugin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def set_default_values(self):
"""
(plugin_path, _) = os.path.split(os.path.realpath(__file__))
self.__ghidra_plugins_path = os.path.join(plugin_path, "ghidra_plugin")

if _is_unix():
self.__ghidra_install_path = LP
self.__ghidra_headless_path = os.path.join(
Expand Down Expand Up @@ -91,8 +91,8 @@ def read_from_json(self):
Avoid the user to always insert the information.
"""
if not os.path.isfile(self.__config_path):
print("GhIDA:: [DEBUG] Configuration not found." +
"Using default values.")
# print("GhIDA:: [DEBUG] Configuration not found." +
# "Using default values.")
return

# Read configuration from the file
Expand Down
27 changes: 14 additions & 13 deletions ghida_plugin/decompiled_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def set_cache_path(self, file_id):
file_id = "test"
self.__cache_path = os.path.join(
tempfile.gettempdir(), CODE_CACHE_FILE % file_id)
print("GhIDA:: [DEBUG] code_cache_path: %s" % self.__cache_path)
# print("GhIDA:: [DEBUG] code_cache_path: %s" % self.__cache_path)

except Exception:
print("GhIDA:: [!] error while setting the comments cache")
Expand All @@ -53,33 +53,34 @@ def set_cache_path(self, file_id):
def invalidate_cache(self, address=None):
if not address:
self.__decompiled_cache = dict()
print("GhIDA:: [DEBUG] decompile cache is empty")
# print("GhIDA:: [DEBUG] decompile cache is empty")
else:
if address in self.__decompiled_cache:
del self.__decompiled_cache[address]
print("GhIDA:: [DEBUG] removed item (%s) from cache" % address)
# print("GhIDA:: [DEBUG] removed item (%s) from cache" % address)
else:
print("GhIDA:: [DEBUG] item (%s) not found" % address)
# print("GhIDA:: [DEBUG] item (%s) not found" % address)
pass
return

def add_decompiled_to_cache(self, address, code):
self.__decompiled_cache[address] = code
print("GhIDA:: [DEBUG] addedd code to cache (%s)" % address)
print("GhIDA:: [DEBUG] %d elements in cache" %
len(self.__decompiled_cache))
# print("GhIDA:: [DEBUG] addedd code to cache (%s)" % address)
# print("GhIDA:: [DEBUG] %d elements in cache" %
# len(self.__decompiled_cache))
return

def update_decompiled_cache(self, address, code):
if address in self.__decompiled_cache:
self.__decompiled_cache[address] = code
print("GhIDA:: [DEBUG] cache updated (%s)" % address)
# print("GhIDA:: [DEBUG] cache updated (%s)" % address)
return

def get_decompiled_cache(self, address):
if address in self.__decompiled_cache:
print("GhIDA:: [DEBUG] decompiled cache hit (%s)" % address)
# print("GhIDA:: [DEBUG] decompiled cache hit (%s)" % address)
return self.__decompiled_cache[address]
print("GhIDA:: [DEBUG] decompiled cache miss (%s)" % address)
# print("GhIDA:: [DEBUG] decompiled cache miss (%s)" % address)
return None

def dump_cache_to_json(self):
Expand All @@ -91,11 +92,11 @@ def dump_cache_to_json(self):

def load_cache_from_json(self):
try:
print("GhIDA:: [DEBUG] loading decomp cache from json")
# print("GhIDA:: [DEBUG] loading decomp cache from json")
with open(self.__cache_path) as f_in:
self.__decompiled_cache = json.load(f_in)
print("GhIDA:: [DEBUG] loaded %d items from cache" % len(
self.__decompiled_cache))
# print("GhIDA:: [DEBUG] loaded %d items from cache" % len(
# self.__decompiled_cache))
except Exception:
print("GhIDA:: [!] error while loading code from %s" %
self.__cache_path)
Expand Down
Loading

0 comments on commit a396916

Please sign in to comment.