Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b7f1242
Showing
4 changed files
with
335 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# gdbida - a visual bridge between a GDB session and IDA Pro's disassembler | ||
|
||
The purpose of gdbida is to provide means during interactive debug sessions in | ||
gdb to quickly follow the flow in IDA. gdbida is not meant to be a full | ||
debugger or replace IDA's own debugger. Instead, it merely serves as a small | ||
helper tool to assist during interactive debug sessions that make use of a | ||
mixture of tools. It provides simple means to quickly follow along a gdb debug | ||
session in IDA. gdbida was originally meant to be used with gdb. However, e.g. | ||
extending support to Windbg using pykd should be straight-forward. | ||
|
||
gdbida consists of the following two parts: | ||
* ida\_gdb\_bridge.py : main IDA Pro plugin | ||
* gdb\_ida\_bridge\_client.py : gdb python script | ||
|
||
|
||
![gdbida](gdbida.gif) | ||
|
||
Installation | ||
============ | ||
Make a change the ~/.gdbinit configuration file to include the plugin: | ||
``` | ||
source ~/gdb_ida_bridge_client.py | ||
``` | ||
|
||
Copy the IDA plugin to your plugins directory, e.g.: | ||
``` | ||
cp gdb_ida_bridge_client.py /Applications/IDA\ Pro\ 6.95/idaq.app/Contents/MacOS/plugins/ | ||
``` | ||
|
||
Execute the plugin from IDA and specify a listener address and port. | ||
Next, configure the gdb stub to connect to gdbida's IDA port (either command line or gdbinit): | ||
``` | ||
idabridge 10.0.10.10:2305 | ||
``` | ||
|
||
For debugging a PIE ELF binary,which is still relocated by the kernel, use the | ||
following to translate text addresses at runtime: | ||
``` | ||
idabridge 10.0.10.10:2305 reloc_text | ||
``` | ||
|
||
Notes | ||
===== | ||
Please be aware that this is not considered to be finished. Specifically, the following thoughts are on my mind: | ||
* Network listening input masks untested for errors. | ||
* Individual TCP connections for 4 bytes payload are not ideal from a performance pov. | ||
* The network connection is not authenticated in any way. | ||
* A lot of potential for additional commands. For now, I kept it super simple. | ||
* Color cleanup on exit is untested |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# (C) Copyright 2016 Comsecuris UG | ||
# https://sourceware.org/gdb/onlinedocs/gdb/Events-In-Python.html#Events-In-Python | ||
|
||
import os | ||
import socket | ||
import struct | ||
|
||
IDA_BRIGE_IP = '10.0.10.10' | ||
IDA_BRIDGE_PORT = 2305 | ||
|
||
INIT_BP_WORKAROUND = False | ||
DEBUG = 1 | ||
|
||
TEXT_START = 'Start of text: ' | ||
TEXT_END = 'End of text: ' | ||
|
||
socket.setdefaulttimeout(0.1) | ||
|
||
class IdaBridge(gdb.Command): | ||
def __init__(self): | ||
super (IdaBridge, self).__init__("idabridge", gdb.COMMAND_USER) | ||
self.ida_ip = IDA_BRIGE_IP | ||
self.ida_port = IDA_BRIDGE_PORT | ||
self.init_bps = [] | ||
self.img_base = None | ||
self.img_reloc = False | ||
|
||
def hdl_stop_event(self, event): | ||
# in case we want to ignore all breakpoints that were set before the idabridge was launched. | ||
if isinstance(event, gdb.BreakpointEvent) and event.breakpoint in self.init_bps: | ||
return | ||
|
||
if self.img_base == None and self.img_reloc == True: | ||
self.get_relocation() | ||
pc = self.get_pc() | ||
# TODO: make sure to adjust pc only if within .text | ||
if self.img_reloc: | ||
print("adjusted pc from 0x%x to 0x%x (base: 0x%x)\n" %(pc, pc-self.img_reloc, self.img_base)) | ||
pc = pc - self.img_base | ||
|
||
self.tell_ida(pc) | ||
|
||
def get_relocation(self): | ||
val = gdb.execute('info proc stat', to_string=True) | ||
s_text = val.find(TEXT_START) | ||
e_text = val.find(TEXT_END) | ||
if s_text == -1: | ||
print("could not determine image relocation information\n") | ||
self.img_reloc = False | ||
return | ||
|
||
reloc = val[s_text + len(TEXT_START) : e_text - 1] | ||
self.img_base = int(reloc, 16) | ||
print("using 0x%x as text relocation\n" %(self.img_base)) | ||
|
||
def get_pc(self): | ||
val = gdb.selected_frame().pc() | ||
return val | ||
|
||
# TODO: make this eventually keep the connection | ||
def tell_ida(self, pc): | ||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
try: | ||
s.connect((self.ida_ip, self.ida_port)) | ||
except Exception as e: | ||
print "couldn't connect to IDA bridge", str(e) | ||
return | ||
|
||
s.send(struct.pack("<Q", pc)) | ||
s.close() | ||
|
||
# TODO: we do minimal to no error checking here, fix that | ||
def invoke(self, arg, from_tty): | ||
argv = arg.split(' ') | ||
if len(argv) < 1: | ||
print "idabridge <ip:port> [reloc_text]" | ||
return | ||
|
||
target = argv[0].split(':') | ||
|
||
if not '.' in target[0] or len(target) < 2: | ||
print "please specify ip:port combination" | ||
return | ||
|
||
self.ida_ip = target[0] | ||
self.ida_port = int(target[1]) | ||
print("idabridge: using ip: %s port: %d\n" %(self.ida_ip, self.ida_port)) | ||
|
||
if len(argv) >= 2 and argv[1] == 'reloc_text': | ||
self.img_reloc = True | ||
|
||
if INIT_BP_WORKAROUND: | ||
self.init_bps = gdb.breakpoints() | ||
|
||
gdb.events.stop.connect(self.hdl_stop_event) | ||
|
||
IdaBridge() |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
import idautils | ||
import signal | ||
import idc | ||
import idaapi | ||
|
||
import socket | ||
import threading | ||
import time | ||
import Queue | ||
|
||
TRACE = False | ||
DEBUG = False | ||
COLOR_CUR = 0x68ff90 | ||
|
||
# make sure our operations time out so we can actually quit IDA | ||
DEFAULT_TIMEOUT = 3 | ||
socket.setdefaulttimeout(DEFAULT_TIMEOUT) | ||
|
||
def dprint(s): | ||
if DEBUG == True: | ||
print('IDAbridge: ' + s) | ||
|
||
# use idaapi.execute_ui_requests to ensure that SetColor and Jump | ||
# are executed in the main thread to ensure thread safety. if we | ||
# ignore that, we will run into btree and other errors. | ||
# thanks for hex-rays for helping with this! | ||
class color_req_t(object): | ||
def __init__(self, ea, color): | ||
self.ea = ea | ||
self.color = color | ||
|
||
def __call__(self): | ||
SetColor(self.ea, CIC_ITEM, self.color) | ||
|
||
return False # Don't reschedule | ||
|
||
def safe_setcolor(ea, color): | ||
idaapi.execute_ui_requests((color_req_t(ea, color),)) | ||
|
||
class jump_req_t(object): | ||
def __init__(self, ea): | ||
self.ea = ea | ||
|
||
def __call__(self): | ||
Jump(self.ea) | ||
|
||
return False # Don't reschedule | ||
|
||
def safe_jump(ea): | ||
idaapi.execute_ui_requests((jump_req_t(ea),)) | ||
|
||
# TODO: in case of any exceptions it may be nice to have | ||
# a list of all locations where color was changed so that on | ||
# exiting the plugin, we could restore these. | ||
class ColorThread(threading.Thread): | ||
def __init__(self, ea = -1): | ||
#print "ColorThread()" | ||
threading.Thread.__init__(self) | ||
self.c_queue = Queue.Queue() | ||
self.ea = ea | ||
self.running = False | ||
|
||
def join(self, timeout = None): | ||
self.running = False | ||
threading.Thread.join(self, timeout) | ||
|
||
def run(self): | ||
self.running = True | ||
|
||
while self.running == True: | ||
event_ea = self.ea | ||
try: | ||
ea = self.c_queue.get(block=True, timeout=DEFAULT_TIMEOUT) | ||
except: | ||
continue | ||
|
||
try: | ||
event_ea = int(struct.unpack("<Q", ea)[0]) | ||
except: | ||
continue | ||
|
||
dprint("current position: %x" %(event_ea)) | ||
try: | ||
safe_jump(event_ea) | ||
except: | ||
continue | ||
|
||
# TODO: do some sanity checking on received EAs | ||
# e.g. make sure that the address is within the image space | ||
if TRACE == False: | ||
safe_setcolor(self.ea, 0xffffff) | ||
|
||
safe_setcolor(event_ea, COLOR_CUR) | ||
self.ea = event_ea | ||
|
||
|
||
class BridgeThread(threading.Thread): | ||
def __init__(self): | ||
threading.Thread.__init__(self) | ||
self.server = None | ||
self.running = False | ||
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
self.c_thread = ColorThread() | ||
|
||
def join(self, timeout = None): | ||
self.running = False | ||
self.s.close() | ||
threading.Thread.join(self, timeout) | ||
|
||
def bind(self): | ||
try: | ||
self.s.bind(self.server) | ||
except: | ||
dprint("bind() failed") | ||
self.running = False | ||
return False | ||
|
||
self.running = True | ||
return True | ||
|
||
def run(self): | ||
self.s.listen(1) | ||
dprint("accepting connections") | ||
if not self.c_thread.running: | ||
self.c_thread.start() | ||
|
||
# TODO: make it possible to keep a connection or switch to UDP | ||
while self.running == True: | ||
try: | ||
conn, addr = self.s.accept() | ||
except socket.timeout: | ||
continue | ||
|
||
try: | ||
read_ea = conn.recv(4) | ||
except Exception as e: | ||
dprint("closing client/%s connection: %s" % (addr, str(e))) | ||
conn.close() | ||
continue | ||
|
||
conn.close() | ||
self.c_thread.c_queue.put(read_ea) | ||
|
||
class ida_gdb_debug_bridge_plugin(idaapi.plugin_t): | ||
flags = idaapi.PLUGIN_OK | ||
comment = "" | ||
help = "" | ||
wanted_name = "IDA debug bridge" | ||
wanted_hotkey = "Alt-f" | ||
|
||
def init(self): | ||
self.bridge_thread = BridgeThread() | ||
return idaapi.PLUGIN_OK | ||
|
||
def run(self, arg): | ||
if self.bridge_thread.running == True: | ||
dprint("server already running: %s:%s\n" % (self.bridge_thread.server[0], self.bridge_thread.server[1])) | ||
return | ||
|
||
ask_port = AskLong(2305, "Enter port number for incoming events") | ||
if ask_port < 1 or ask_port <= 1024: | ||
dprint('invalid port number') | ||
return | ||
|
||
ask_addr = AskStr('0.0.0.0', "Enter address to bind to") | ||
|
||
self.bridge_thread.server = (ask_addr, ask_port) | ||
if self.bridge_thread.running == False: | ||
self.bridge_thread.bind() | ||
|
||
if self.bridge_thread.running == False: | ||
dprint("there was an issue starting the server") | ||
else: | ||
self.bridge_thread.start() | ||
|
||
return | ||
|
||
def term(self): | ||
if self.bridge_thread.c_thread.ea != -1: | ||
safe_setcolor(self.bridge_thread.c_thread.ea, 0xffffff) | ||
self.bridge_thread.running = False | ||
self.bridge_thread.c_thread.running = False | ||
self.bridge_thread.s.close() | ||
pass | ||
|
||
|
||
def PLUGIN_ENTRY(): | ||
return ida_gdb_debug_bridge_plugin() | ||
|