chore(ghidra): modularize ghidra functions into utils and commands

Splitting the logic into ghidra related functionality, context
processing and plain command invocation makes the code better structured
and the individual files smaller.
pull/901/head
anthraxx 5 years ago committed by Disconnect3d
parent b036575589
commit e8b51243c8

@ -23,6 +23,7 @@ import pwndbg.commands.dt
import pwndbg.commands.dumpargs import pwndbg.commands.dumpargs
import pwndbg.commands.elf import pwndbg.commands.elf
import pwndbg.commands.gdbinit import pwndbg.commands.gdbinit
import pwndbg.commands.ghidra
import pwndbg.commands.got import pwndbg.commands.got
import pwndbg.commands.heap import pwndbg.commands.heap
import pwndbg.commands.hexdump import pwndbg.commands.hexdump

@ -26,6 +26,7 @@ import pwndbg.commands.telescope
import pwndbg.config import pwndbg.config
import pwndbg.disasm import pwndbg.disasm
import pwndbg.events import pwndbg.events
import pwndbg.ghidra
import pwndbg.ida import pwndbg.ida
import pwndbg.regs import pwndbg.regs
import pwndbg.symbol import pwndbg.symbol
@ -206,96 +207,31 @@ def context_expressions(target=sys.stdout, with_banner=True, width=None):
output.extend(lines) output.extend(lines)
return banner + output if with_banner else output return banner + output if with_banner else output
# Ghidra integration through radare2
# This is not (sorry couldn't help it) "the yellow of the egg"
# Using radare2 is suboptimal and will only be an intermediate step to have
# ghidra decompiler within pwndbg.
# But having ghidra in pwndgb is a lot more work as it requires quite some code and c/c++
radare2 = {}
config_context_ghidra = pwndbg.config.Parameter('context-ghidra', config_context_ghidra = pwndbg.config.Parameter('context-ghidra',
'never', 'never',
'if or/when to try to decompile with ' 'when to try to decompile the current function with ghidra (slow and requires radare2/r2pipe) (valid values: always, never, if-no-source)')
'ghidra (slow and requires radare2/r2pipe) '
'(valid values: always, never, if-no-source)')
def init_radare2(filename):
r2 = radare2.get(filename)
if r2:
return r2
import r2pipe
r2 = r2pipe.open(filename)
radare2[filename] = r2
r2.cmd("aaaa")
return r2
parser = argparse.ArgumentParser()
parser.description = """Show current function decompiled by ghidra"""
parser.add_argument("func", type=str, default=None, nargs="?",
help="Function to be shown. Defaults to current")
@pwndbg.commands.ArgparsedCommand(parser, aliases=['ctx-ghidra'])
def contextghidra(func):
print("\n".join(context_ghidra(func, with_banner=False, force_show=True)))
def context_ghidra(func=None, target=sys.stdout, with_banner=True, width=None, force_show=False): def context_ghidra(target=sys.stdout, with_banner=True, width=None):
banner = [pwndbg.ui.banner("ghidra decompile", target=target, width=width)] """
Print out the source of the current function decompiled by ghidra.
The context-ghidra config parameter is used to configure whether to always,
never or only show the context if no source is available.
"""
banner = [pwndbg.ui.banner("ghidra decompile", target=target, width=width)] if with_banner else []
if config_context_ghidra == "never" and not force_show: if config_context_ghidra == "never":
return [] return []
elif config_context_ghidra == "if-no-source" and not force_show: elif config_context_ghidra == "if-no-source":
try: try:
with open(gdb.selected_frame().find_sal().symtab.fullname()) as _: with open(gdb.selected_frame().find_sal().symtab.fullname()) as _:
pass pass
except: # a lot can go wrong in search of source code. except: # a lot can go wrong in search of source code.
return [] # we don't care what, just that it did not work out well... return [] # we don't care what, just that it did not work out well...
filename = gdb.current_progspace().filename return banner + pwndbg.ghidra.decompile()
try:
r2 = init_radare2(filename)
# LD list supported decompilers (e cmd.pdc=?)
# Outputs for example:: pdc\npdg
if not "pdg" in r2.cmd("LD").split("\n"):
return banner + ["radare2 plugin r2ghidra-dec must be installed and available from r2"]
except ImportError: # no r2pipe present
return banner + ["r2pipe not available, but required for r2->ghidra-bridge"]
if func is None:
try:
func = hex(pwndbg.regs[pwndbg.regs.current.pc])
except:
func = "main"
src = r2.cmdj("pdgj @" + func)
source = src.get("code", "")
curline = None
try:
cur = pwndbg.regs[pwndbg.regs.current.pc]
except AttributeError:
cur = None # If not running there is no current.pc
if cur is not None:
closest = 0
for off in (a.get("offset", 0) for a in src.get("annotations", [])):
if abs(cur - closest) > abs(cur - off):
closest = off
pos_annotations = sorted([a for a in src.get("annotations", []) if a.get("offset") == closest],
key=lambda a: a["start"])
if pos_annotations:
curline = source.count("\n", 0, pos_annotations[0]["start"])
source = source.split("\n")
# Append --> for the current line if possible
if curline is not None:
line = source[curline]
if line.startswith(' '):
line = line[4:]
source[curline] = '--> ' + line
# Join the source for highlighting
source = "\n".join(source)
if pwndbg.config.syntax_highlight:
# highlighting depends on the file extension to guess the language, so try to get one...
try: # try to read the source filename from debug information
src_filename = gdb.selected_frame().find_sal().symtab.fullname()
except: # if non, take the original filename and maybe append .c (just assuming is was c)
src_filename = filename+".c" if os.path.basename(filename).find(".") < 0 else filename
source = H.syntax_highlight(source, src_filename)
source = source.split("\n")
return banner + source if with_banner else source
@ -303,14 +239,14 @@ def context_ghidra(func=None, target=sys.stdout, with_banner=True, width=None, f
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.description = "Print out the current register, instruction, and stack context." parser.description = "Print out the current register, instruction, and stack context."
parser.add_argument("subcontext", nargs="*", type=str, default=None, help="Submenu to display: 'reg', 'disasm', 'code', 'stack', 'backtrace', and/or 'args'") parser.add_argument("subcontext", nargs="*", type=str, default=None, help="Submenu to display: 'reg', 'disasm', 'code', 'stack', 'backtrace', 'ghidra', and/or 'args'")
@pwndbg.commands.ArgparsedCommand(parser, aliases=['ctx']) @pwndbg.commands.ArgparsedCommand(parser, aliases=['ctx'])
@pwndbg.commands.OnlyWhenRunning @pwndbg.commands.OnlyWhenRunning
def context(subcontext=None): def context(subcontext=None):
""" """
Print out the current register, instruction, and stack context. Print out the current register, instruction, and stack context.
Accepts subcommands 'reg', 'disasm', 'code', 'stack', 'backtrace', and 'args'. Accepts subcommands 'reg', 'disasm', 'code', 'stack', 'backtrace', 'ghidra' and 'args'.
""" """
if subcontext is None: if subcontext is None:
subcontext = [] subcontext = []

@ -0,0 +1,15 @@
import argparse
import pwndbg.color.message as message
import pwndbg.commands
import pwndbg.ghidra
parser = argparse.ArgumentParser()
parser.description = """Decompile a given function using ghidra"""
parser.add_argument("func", type=str, default=None, nargs="?", help="Function to be decompiled. Defaults to the current function.")
@pwndbg.commands.OnlyWithFile
@pwndbg.commands.ArgparsedCommand(parser)
def ghidra(func):
print("\n".join(pwndbg.ghidra.decompile(func)))

@ -0,0 +1,66 @@
import os
import gdb
import pwndbg.color.syntax_highlight as H
import pwndbg.radare2
import pwndbg.regs
def decompile(func=None):
"""
Return the source of the given function decompiled by ghidra.
If no function is given, decompile the function within the current pc.
This function requires radare2, r2pipe and r2ghidra.
Raises Exception if any fatal error occures.
"""
filename = gdb.current_progspace().filename
try:
r2 = pwndbg.radare2.r2pipe(filename)
# LD list supported decompilers (e cmd.pdc=?)
# Outputs for example:: pdc\npdg
if not "pdg" in r2.cmd("LD").split("\n"):
return ["radare2 plugin r2ghidra-dec must be installed and available from r2"]
except ImportError: # no r2pipe present
return ["r2pipe not available, but required for r2->ghidra-bridge"]
if func is None:
try:
func = hex(pwndbg.regs[pwndbg.regs.current.pc])
except:
func = "main"
src = r2.cmdj("pdgj @" + func)
source = src.get("code", "")
curline = None
try:
cur = pwndbg.regs[pwndbg.regs.current.pc]
except AttributeError:
cur = None # If not running there is no current.pc
if cur is not None:
closest = 0
for off in (a.get("offset", 0) for a in src.get("annotations", [])):
if abs(cur - closest) > abs(cur - off):
closest = off
pos_annotations = sorted([a for a in src.get("annotations", []) if a.get("offset") == closest],
key=lambda a: a["start"])
if pos_annotations:
curline = source.count("\n", 0, pos_annotations[0]["start"])
source = source.split("\n")
# Append --> for the current line if possible
if curline is not None:
line = source[curline]
if line.startswith(' '):
line = line[4:]
source[curline] = '--> ' + line
# Join the source for highlighting
source = "\n".join(source)
if pwndbg.config.syntax_highlight:
# highlighting depends on the file extension to guess the language, so try to get one...
try: # try to read the source filename from debug information
src_filename = gdb.selected_frame().find_sal().symtab.fullname()
except: # if non, take the original filename and maybe append .c (just assuming is was c)
src_filename = filename+".c" if os.path.basename(filename).find(".") < 0 else filename
source = H.syntax_highlight(source, src_filename)
source = source.split("\n")
return source

@ -0,0 +1,12 @@
radare2 = {}
def r2pipe(filename):
r2 = radare2.get(filename)
if r2:
return r2
import r2pipe
r2 = r2pipe.open(filename)
radare2[filename] = r2
r2.cmd("aaaa")
return r2
Loading…
Cancel
Save