Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ni0n committed Mar 21, 2017
0 parents commit b7f1242
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 0 deletions.
49 changes: 49 additions & 0 deletions README.md
@@ -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
97 changes: 97 additions & 0 deletions gdb_ida_bridge_client.py
@@ -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()
Binary file added gdbida.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
189 changes: 189 additions & 0 deletions ida_gdb_bridge.py
@@ -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()

0 comments on commit b7f1242

Please sign in to comment.