Skip to content

Commit

Permalink
ext_ida: sync with Hex-Rays pseudocode
Browse files Browse the repository at this point in the history
Syncing is now possible with Hex-Rays pseudocode view. Global shortcuts
and toolbar buttons.
  • Loading branch information
bootleg committed Dec 10, 2018
1 parent a188800 commit 40bcecc
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 29 deletions.
19 changes: 16 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ From IDA and static analysis we got:
* macro view over modules
* code analysis, signatures, types, etc.
* fancy graph view
* Hex-Rays pseudocode view
* persistent storage of knowledge within IDBs


Expand Down Expand Up @@ -77,6 +78,18 @@ included in Python standard libraries for release >= 2.7.




Global shortcuts
----------------

* Ctrl-Shift-S - Toggle global syncing
* Ctrl-H - Toggle Hex-Rays syncing

Two buttons are also available in the Debug toolbar.




WinDbg
------

Expand All @@ -99,7 +112,7 @@ Use it
* ``HOST`` in ``broker.py``, localhost is the default interface.

``broker.py`` and ``sync.dll`` check for a configuration file named ``.sync`` in user's home directory.
(IDA's side broker.py and dispatcher.py actually look for the configuration file in the IDB's
(IDA's side ``broker.py`` and ``dispatcher.py`` actually look for the configuration file in the IDB's
directory first).
Content of this file overwrite default values. It should respect a ``.ini`` file format::

Expand Down Expand Up @@ -282,7 +295,7 @@ Extra commands
.text:00430DB1 push edi ; edi=00000064


* **!bc <||on|off|set 0xBBGGRR>>**
* **!bc <||on|off|set 0xBBGGRR>**

Enable/disable path coloring in IDA. This is NOT a code tracing tool,
there are efficient tools for that. Each manually stepped instruction is
Expand Down Expand Up @@ -617,7 +630,7 @@ TODO
KNOWN BUGS/LIMITATIONS
-----------------------

- Tested with Python 2.7, IDA 6.4 to 7.0 (Windows, Linux and Mac OS X), GNU gdb (GDB) 7.4.1 (Debian), lldb 310.2.37.
- Tested with Python 2.7, IDA 6.4 to 7.2 (Windows, Linux and Mac OS X), GNU gdb (GDB) 7.4.1 (Debian), lldb 310.2.37.
- **THERE IS NO AUTHENTICATION/ENCRYPTION** or whatsoever between the parties; you're on your own.
- Self modifying code is out of scope.

Expand Down
126 changes: 100 additions & 26 deletions ext_ida/SyncPlugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (C) 2016-2017, Alexandre Gazet.
# Copyright (C) 2016-2018, Alexandre Gazet.
#
# Copyright (C) 2012-2015, Quarkslab.
#
Expand Down Expand Up @@ -29,7 +29,6 @@
import struct
import binascii
import base64
import ctypes
import socket
import ConfigParser

Expand All @@ -42,8 +41,11 @@
import idaapi
import idautils
import ida_graph
import ida_hexrays
from idaapi import PluginForm

from syncrays import Syncrays


# Enable/disable logging JSON received in the IDA output window
DEBUG_JSON = False
Expand Down Expand Up @@ -99,7 +101,7 @@
COL_GRAY = 0x808080

COL_CURLINE = COL_YLW
#COL_CURLINE = COL_BLUE_NAVY # renders better with some themes
# COL_CURLINE = COL_BLUE_NAVY # renders better with some themes
COL_CBTRACE = COL_GREEN

NETNODE_STORE = "$ SYNC_STORE"
Expand Down Expand Up @@ -237,13 +239,16 @@ def req_loc(self, hash):
if not ea:
return

if(self.color):
if self.color:
self.cb_color(ea)

idaapi.jumpto(ea)
self.cb_curline(ea)
self.gm.center()

if self.hexsync.enabled:
self.hexsync.cb_loc(ea)

# log command output request at addr
def req_cmd(self, hash):
msg_b64, offset, base = hash['msg'], hash['offset'], hash['base']
Expand Down Expand Up @@ -774,6 +779,7 @@ def __init__(self, parser):
print "[sync] module base 0x%x" % self.base
self.base_remote = None
self.gm = GraphManager()
self.hexsync = Syncrays()
self.parser = parser
self.broker_sock = None
self.is_active = False
Expand Down Expand Up @@ -814,7 +820,7 @@ def cb_broker_on_state_change(self, new_state):
states = ["Not running", "Starting", "Running"]
print "[*] broker new state: ", states[new_state]
if states[new_state] == "Not running":
print "[*] Check dispatcher.py.err if you think this is an error"
print " Check dispatcher.py.err if you think this is an error"

def cb_broker_on_out(self):
# readAllStandardOutput() returns QByteArray
Expand Down Expand Up @@ -916,6 +922,21 @@ def center(self):
# --------------------------------------------------------------------------


class CheckBoxActionHandler(idaapi.action_handler_t):
def __init__(self, cb):
idaapi.action_handler_t.__init__(self)
self.cb = cb

def activate(self, ctx):
self.cb.toggle()
return 1

def update(self, ctx):
return idaapi.AST_ENABLE_ALWAYS


# --------------------------------------------------------------------------

class SyncForm_t(PluginForm):

def cb_broker_started(self):
Expand All @@ -926,9 +947,9 @@ def cb_broker_finished(self):
print "[*] broker finished"
if self.broker:
self.broker.worker.stop()
self.cb.stateChanged.disconnect(self.cb_change_state)
self.cb.toggle()
self.cb.stateChanged.connect(self.cb_change_state)
self.cb_sync.stateChanged.disconnect(self.cb_change_state)
self.cb_sync.toggle()
self.cb_sync.stateChanged.connect(self.cb_change_state)

self.btn.setText("Start")

Expand Down Expand Up @@ -980,6 +1001,7 @@ def init_hotkeys(self):
self.init_single_hotkey("Alt-F5", self.broker.worker.go_notice)
self.init_single_hotkey("F10", self.broker.worker.so_notice)
self.init_single_hotkey("F11", self.broker.worker.si_notice)
self.init_single_hotkey("Ctrl-F1", self.broker.worker.export_bp_notice)

def init_single_hotkey(self, key, fnCb):
ctx = idaapi.add_hotkey(key, fnCb)
Expand All @@ -1001,10 +1023,10 @@ def uninit_hotkeys(self):

def cb_btn_restart(self):
print "[sync] restarting broker."
if self.cb.checkState() == QtCore.Qt.Checked:
self.cb.toggle()
if self.cb_sync.checkState() == QtCore.Qt.Checked:
self.cb_sync.toggle()
time.sleep(0.1)
self.cb.toggle()
self.cb_sync.toggle()

def cb_change_state(self, state):
if state == QtCore.Qt.Checked:
Expand All @@ -1017,22 +1039,41 @@ def cb_change_state(self, state):
self.smooth_kill()
print "[*] sync disabled\n"

def cb_hexrays_sync_state(self, state):
if self.broker:
if state == QtCore.Qt.Checked:
print "[*] hexrays sync enabled\n"
self.broker.worker.hexsync.enable()
else:
print "[*] hexrays sync disabled\n"
self.broker.worker.hexsync.disable()

def cb_hexrays_toggle(self):
print "cb_hexrays_toggle"
self.cb_hexrays.toggle()

def OnCreate(self, form):
print "[sync] form create"

# Get parent widget
parent = self.FormToPyQtWidget(form)

# Create checkbox
self.cb = QtWidgets.QCheckBox("Synchronization enable")
self.cb.move(20, 20)
self.cb.stateChanged.connect(self.cb_change_state)
# Create global sync checkbox
self.cb_sync = QtWidgets.QCheckBox("Synchronization enable")
self.cb_sync.move(20, 20)
self.cb_sync.stateChanged.connect(self.cb_change_state)

# Create hexrays sync checkbox
self.cb_hexrays = QtWidgets.QCheckBox("Hex-Rays Synchronization enable")
self.cb_hexrays.move(20, 20)
self.cb_hexrays.stateChanged.connect(self.cb_hexrays_sync_state)

# Create label
label = QtWidgets.QLabel('Overwrite idb name:')

name = idaapi.get_root_filename()
print "[sync] default idb name: %s" % name

# Check in conf for name overwrite
confpath = os.path.join(os.path.realpath(IDB_PATH), '.sync')
if os.path.exists(confpath):
Expand All @@ -1056,31 +1097,64 @@ def OnCreate(self, form):

# Create layout
layout = QtWidgets.QGridLayout()
layout.addWidget(self.cb)
layout.addWidget(self.cb_sync)
layout.addWidget(self.cb_hexrays)
layout.addWidget(label)
layout.addWidget(self.input)
layout.addWidget(self.btn, 2, 2)
layout.setColumnStretch(3, 1)
layout.setRowStretch(3, 1)
layout.setColumnStretch(4, 1)
layout.setRowStretch(4, 1)
parent.setLayout(layout)

# workaround: crash when instanciated in Broker.__init__
# weird interaction with Qtxxx libraries ?
# File "C:\Python27\Lib\argparse.py", line 1584, in __init__
# self._positionals = add_group(_('positional arguments'))
# File "C:\Python27\Lib\gettext.py", line 566, in gettext
# return dgettext(_current_domain, message)
# TypeError: 'NoneType' object is not callable
self.parser = argparse.ArgumentParser()
self.parser.add_argument("-a", "--address", nargs=1, action='store')
self.parser.add_argument('msg', nargs=argparse.REMAINDER)

# Synchronization is enabled by default
self.cb.toggle()
self.cb_sync.toggle()

# Register action for hexrays sync
action_hex_sync_desc = idaapi.action_desc_t(
'hexrays_sync_toogle:action',
'Toggle Hex-Rays syncing',
CheckBoxActionHandler(self.cb_hexrays),
'Ctrl+H',
'Toggle Hex-Rays syncing',
198)

idaapi.register_action(action_hex_sync_desc)
idaapi.attach_action_to_toolbar(
"DebugToolBar",
'hexrays_sync_toogle:action')

# Register action for global sync
action_g_sync_desc = idaapi.action_desc_t(
'g_sync_toogle:action',
'Toggle syncing',
CheckBoxActionHandler(self.cb_sync),
'Ctrl+Shift+S',
'Toggle syncing',
203)

idaapi.register_action(action_g_sync_desc)
idaapi.attach_action_to_toolbar(
"DebugToolBar",
'g_sync_toogle:action')

def OnClose(self, form):
print "[sync] form close"
self.smooth_kill()

idaapi.unregister_action('hexrays_sync_toogle:action')
idaapi.detach_action_from_toolbar(
"DebugToolBar",
'hexrays_sync_toogle:action')

idaapi.unregister_action('g_sync_toogle:action')
idaapi.detach_action_from_toolbar(
"DebugToolBar",
'g_sync_toogle:action')

global SyncForm
del SyncForm

Expand Down

0 comments on commit 40bcecc

Please sign in to comment.