Files
ida-scripts/plugins/ret_sync_ext_ida/SyncPlugin.py
2018-08-02 06:57:30 -04:00

1125 lines
36 KiB
Python

#
# Copyright (C) 2016-2017, Alexandre Gazet.
#
# Copyright (C) 2012-2015, Quarkslab.
#
# This file is part of ret-sync.
#
# ret-sync is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import time
import traceback
import struct
import binascii
import base64
import ctypes
import socket
import ConfigParser
try:
import argparse
except:
print "[-] please make sure python's argparse module is available\n%s" % repr(sys.exc_info())
sys.exit(0)
import idc
import idaapi
import idautils
import ida_graph
import ida_kernwin
from idaapi import PluginForm
# Enable/disable logging JSON received in the IDA output window
DEBUG_JSON = False
if sys.platform == 'win32':
PYTHON_BIN = 'python.exe'
PYTHON_PATH = os.path.normpath("C:\\Programs\\share\\Python27")
elif sys.platform.startswith('linux') or sys.platform == 'darwin':
PYTHON_BIN = 'python'
PYTHON_PATH = os.path.normpath("/usr/bin")
else:
print "[-] please fix PYTHON_PATH & PYTHON_BIN values, %s platform currently unknown" % sys.platform
sys.exit(0)
if not os.path.exists(os.path.join(PYTHON_PATH, PYTHON_BIN)):
print "[-] please fix PYTHON_PATH value"
sys.exit(0)
site_packages = os.path.join(PYTHON_PATH, "lib", "site-packages")
if site_packages not in sys.path:
sys.path.insert(0, site_packages)
try:
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QProcess, QProcessEnvironment
except:
print "[-] failed to import Qt libs from PyQt5\n%s" % repr(sys.exc_info())
sys.exit(0)
try:
import json
except:
print "[-] failed to import json\n%s" % repr(sys.exc_info())
sys.exit(0)
# default value is current script's path
BROKER_PATH = os.path.join(os.path.normpath(os.path.dirname(__file__)), "broker.py")
if not os.path.exists(BROKER_PATH):
print "[-] broker path is not properly set, current value: <%s>" % BROKER_PATH
sys.exit(0)
IDB_PATH = os.path.dirname(os.path.realpath(idc.GetIdbPath()))
CONNECT_BROKER_MAX_ATTEMPT = 4
COL_GREEN = 0x33ff00
COL_DEEP_PURPLE = 0xff44dd
COL_YLW = 0x23ffff
COL_BLUE_NAVY = 0x000080
COL_GRAY = 0x808080
COL_CURLINE = COL_YLW
#COL_CURLINE = COL_BLUE_NAVY # renders better with some themes
COL_CBTRACE = COL_GREEN
NETNODE_STORE = "$ SYNC_STORE"
NETNODE_INDEX = 0xFFC0DEFF
DBG_DIALECTS = {
'windbg': {'prefix': '!', 'si': 't', 'so': 'p', 'go': 'g', 'bp': 'bp ', 'hbp': 'ba e 1 ', 'bp1': 'bp /1 ', 'hbp1': 'ba e 1 /1 '},
'gdb': {'prefix': '', 'si': 'si', 'so': 'ni', 'go': 'continue', 'bp': 'b *', 'hbp': 'hb *', 'bp1': 'tb *', 'hbp1': 'thb *'},
'ollydbg2': {'prefix': '', 'si': 'si', 'so': 'so', 'go': 'go', 'bp': 'bp ', 'hbp': 'xxx ', 'bp1': 'xxx ', 'hbp1': 'xxx '},
'x64_dbg': {'prefix': '', 'si': 'sti', 'so': 'sto', 'go': 'go', 'bp': 'bp ', 'hbp': 'bph ', 'bp1': 'xxx ', 'hbp1': 'xxx '},
}
# --------------------------------------------------------------------------
class RequestHandler(object):
# color callback
def cb_color(self, ea):
idaapi.set_item_color(ea, COL_CBTRACE)
# instruction step callback
def cb_curline(self, ea):
if self.prev_loc:
prev_ea, prev_color = self.prev_loc
cur_color = idaapi.get_item_color(prev_ea)
# race condition: block/instruction's color may have been modified
# after it was saved
if (cur_color != prev_color) and (cur_color != COL_CURLINE):
prev_color = cur_color
idaapi.set_item_color(prev_ea, prev_color)
self.prev_loc = [ea, idaapi.get_item_color(ea)]
idaapi.set_item_color(ea, COL_CURLINE)
def cb_restore_last_line(self):
if self.prev_loc:
ea, col = self.prev_loc
idaapi.set_item_color(ea, col)
# support -a / --address switch
def addr_switch(self, offset, msg):
if (not msg) or (msg == ''):
return [offset, msg]
try:
args = self.parser.parse_args(msg.split())
except:
print "[*] failed to parse command"
return [None, msg]
# no address switch supplied
if not args.address:
return [offset, msg]
try:
addr = int(''.join(args.address), 16)
except:
print "[*] failed to parse address, should be hex"
return [None, msg]
# make sure the address points to a valid instruction/data
head = idaapi.get_item_head(addr)
if head != addr:
print "[*] ambiguous address, did you mean 0x%x ?" % head
return [None, msg]
return [addr, ' '.join(args.msg)]
# check if address is within a valid segment
def is_safe(self, offset):
return not (idc.SegStart(offset) == idaapi.BADADDR)
# rebase address with respect to local image base
def rebase(self, base, offset):
if base is not None:
# check for non-compliant debugger client
if base > offset:
print "[sync] unsafe addr"
return None
if not (self.base == base):
offset = (offset - base) + self.base
# update base address of remote module
if self.base_remote != base:
self.base_remote = base
if not self.is_safe(offset):
print "[sync] unsafe addr"
return None
return offset
# rebase address with respect to remote image base
def rebase_remote(self, offset):
if not (self.base == self.base_remote):
offset = (offset - self.base) + self.base_remote
return offset
# demangle names
def demangle(self, name):
mask = idc.GetLongPrm(INF_SHORT_DN)
demangled = idc.Demangle(name, mask)
if demangled is None:
return name
else:
return demangled
# prevent flooding debug engine with too much commands
# sync plugin does NOT wait for any sort of ack
# example: "^ Debuggee already running error in 'g'"
def notice_anti_flood(self):
time.sleep(0.1)
# append comment and handle cmt's size limitation (near 1024)
def append_cmt(self, ea, cmt, rptble=False):
if len(cmt) > 1024:
print "[*] warning, comment needs to be splitted (from 0x%x)" % ea
nh = idaapi.next_head(ea, idaapi.BADADDR)
if nh == idaapi.BADADDR:
print "[*] failed to find next instruction candidate"
return
self.append_cmt(nh, cmt[1024:], rptble)
cmt = cmt[:1024]
idaapi.append_cmt(ea, cmt, rptble)
# location request, update disassembly IDA view
def req_loc(self, hash):
offset, base = hash['offset'], hash.get('base')
ea = self.rebase(base, offset)
if not ea:
return
if(self.color):
self.cb_color(ea)
idaapi.jumpto(ea)
self.cb_curline(ea)
self.gm.center()
# log command output request at addr
def req_cmd(self, hash):
msg_b64, offset, base = hash['msg'], hash['offset'], hash['base']
msg = base64.b64decode(msg_b64)
ea = self.rebase(base, offset)
if not ea:
return
print ("[*] cmd output added at 0x%x" % ea)
self.append_cmt(ea, str(msg))
# reset comment at addr
def req_rcmt(self, hash):
msg, offset, base = hash['msg'], hash['offset'], hash['base']
offset, msg = self.addr_switch(offset, msg)
if not offset:
return
ea = self.rebase(base, offset)
if not ea:
return
idaapi.set_cmt(ea, str(''), False)
print ("[*] reset comment at 0x%x" % ea)
# add comment request at addr
def req_cmt(self, hash):
msg, offset, base = hash['msg'], hash['offset'], hash['base']
offset, msg = self.addr_switch(offset, msg)
if not offset:
return
ea = self.rebase(base, offset)
if not ea:
return
self.append_cmt(ea, str(msg))
print ("[*] comment added at 0x%x" % ea)
# add a function comment at addr
def req_fcmt(self, hash):
msg, offset, base = hash['msg'], hash['offset'], hash['base']
offset, msg = self.addr_switch(offset, msg)
if not offset:
return
ea = self.rebase(base, offset)
if not ea:
return
func = idaapi.get_func(ea)
if not func:
print ("[*] could not find func for 0x%x" % ea)
return
idaapi.set_func_cmt(func, str(msg), False)
print ("[*] function comment added at 0x%x" % ea)
# add an address comment request at addr
def req_raddr(self, hash):
raddr, rbase, offset, base = hash['raddr'], hash['rbase'], hash['offset'], hash['base']
ea = self.rebase(base, offset)
if not ea:
return
if self.base_remote != rbase:
print("[*] could not rebase this address, not in module")
return
addr = self.rebase(rbase, raddr)
if not addr:
return
self.append_cmt(ea, "0x%x (rebased from 0x%x)" % (addr, raddr))
print ("[*] comment added at 0x%x" % ea)
# return current cursor in IDA Pro
def req_cursor(self, hash):
print("[*] request IDA Pro cursor position")
addr = idc.ScreenEA()
self.notice_broker("cmd", "\"cmd\":\"%s\"" % addr)
return
# patch memory at specified address using info from debugger
def req_patch(self, hash):
addr, value, length = hash['addr'], hash['value'], hash['len']
if length != 4 and length != 8:
print("[x] unsupported length: %d" % length)
return
if length == 4:
prev_value = Dword(addr)
if MakeDword(addr) != 1:
print("[x] MakeDword failed")
if PatchDword(addr, value) != 1:
print("[x] PatchDword failed")
if not idc.OpOff(addr, 0, 0):
print("[x] OpOff failed")
elif length == 8:
prev_value = Qword(addr)
if MakeQword(addr) != 1:
print("[x] MakeQword failed")
if PatchQword(addr, value) != 1:
print("[x] PatchQword failed")
if not idc.OpOff(addr, 0, 0):
print("[x] OpOff failed")
print ("[*] patched 0x%x = 0x%x (previous was 0x%x)" % (addr, value, prev_value))
# return idb's symbol for a given address
def req_rln(self, hash):
raddr, rbase, offset, base = hash['raddr'], hash['rbase'], hash['offset'], hash['base']
print("[*] 0x%x - 0x%x - 0x%x - 0x%x" % (raddr, rbase, offset, base))
addr = self.rebase(rbase, raddr)
if not addr:
print("[*] could not rebase this address (0x%x)" % raddr)
return
sym = idaapi.get_func_name(addr)
if sym:
sym = self.demangle(sym)
func = idaapi.get_func(addr)
if not func:
print ("[*] could not find func for 0x%x" % addr)
return
lck = idaapi.lock_func(func)
limits = idaapi.area_t()
if idaapi.get_func_limits(func, limits):
if limits.startEA != addr:
if (addr > limits.startEA):
sym = "%s%s0x%x" % (sym, "+", addr - limits.startEA)
else:
sym = "%s%s0x%x" % (sym, "-", limits.startEA - addr)
lck = None
else:
sym = idc.Name(addr)
if sym:
sym = self.demangle(sym)
if sym:
self.notice_broker("cmd", "\"cmd\":\"%s\"" % sym)
print ("[*] resolved symbol: %s" % sym)
else:
print ("[*] could not resolve symbol for address 0x%x" % addr)
# return address for a given idb's symbol
def req_rrln(self, hash):
sym, rbase, offset, base = hash['sym'], hash['rbase'], hash['offset'], hash['base']
print("[*] %s - 0x%x - 0x%x - 0x%x" % (sym, rbase, offset, base))
addr = idc.LocByName(sym)
if addr:
self.notice_broker("cmd", "\"cmd\":\"%s\"" % addr)
print ("[*] resolved address: %s" % addr)
else:
print ("[*] could not resolve address for symbol %s" % sym)
# add label request at addr
def req_lbl(self, hash):
msg, offset, base = hash['msg'], hash['offset'], hash['base']
offset, msg = self.addr_switch(offset, msg)
if not offset:
return
ea = self.rebase(base, offset)
if not ea:
return
flags = False
if str(msg).startswith('@@'):
flags = idaapi.SN_LOCAL
idaapi.set_name(ea, str(msg), flags)
print ("[*] label added at 0x%x" % ea)
# color request at addr
def req_bc(self, hash):
global COL_CBTRACE
msg, offset, base = hash['msg'], hash['offset'], hash['base']
if self.is_active:
ea = self.rebase(base, offset)
if not ea:
return
else:
ea = self.base
if (msg == 'oneshot'):
print ("[*] color oneshot added at 0x%x" % ea)
# mark address as being colored
self.prev_loc = [ea, COL_CBTRACE]
elif (msg == 'on'):
print ("[*] color start from 0x%x" % ea)
self.color = True
self.prev_loc = [ea, COL_CBTRACE]
elif (msg == 'off'):
print ("[*] color end at 0x%x" % ea)
self.color = False
elif (msg == 'set'):
new_col = hash['rgb']
if new_col > 0xffffff:
print ("[*] restoring color")
new_col = COL_GREEN
COL_CBTRACE = new_col
print ("[*] set color to 0x%x" % COL_CBTRACE)
else:
print ("[*] invalid color request (%s)" % msg)
# reload .bpcmds from idb
def req_bps_get(self, hash):
print ("[-] reload .bpcmds")
node = idaapi.netnode(NETNODE_INDEX)
if not node:
print ("[-] failed to open netnode store")
self.notice_broker("cmd", "\"cmd\":\"no blob\"")
return
node.create(NETNODE_STORE)
blob = node.getblob(0, str(chr(1)))
if not blob:
print (" -> no blob")
self.notice_broker("cmd", "\"cmd\":\" -> reloading .bpcmds: no blob\"")
return
self.notice_broker("cmd", "\"cmd\":\"%s\"" % blob)
return
# save .bpcmds to idb
def req_bps_set(self, hash):
blob = hash['msg']
print ("[-] save .bpcmds")
node = idaapi.netnode(NETNODE_INDEX)
if not node:
print ("[-] failed to open netnode store")
self.notice_broker("cmd", "\"cmd\":\" -> failed to save .bpcmds")
return
new = node.create(NETNODE_STORE)
if new == 0:
print (" -> creating new netnode store")
out = node.setblob(str(blob), 0, str(chr(1)))
self.notice_broker("cmd", "\"cmd\":\" -> .bpcmds saved\"")
return
# compare loaded module md5 with idb's input file md5
def req_modcheck(self, hash):
md5, pdb = hash.get('md5'), hash.get('pdb')
remote = None
if md5:
print ("[*] modcheck idb (md5)")
local = idc.GetInputMD5()
remote = (''.join(str(md5).encode("ascii").split())).upper()
elif pdb:
print ("[*] modcheck idb (pdb guid)")
msg = base64.b64decode(pdb)
local = DbgDirHlpr.read_rsds_codeview()
remote = DbgDirHlpr.parse_itoldyouso_output(msg)
print (" -> remote: <%s>" % remote)
print (" -> local : <%s>" % local)
if remote == "0":
res = "[!] warning, no Debug Directory"
elif local == remote:
res = "[+] module successfully matched"
else:
res = "[!] warning, modules mismatch"
print res
self.notice_broker("cmd", "\"cmd\":\"%s\"" % res)
return
# specify debugger dialect used to send commands
def req_set_dbg_dialect(self, hash):
global SyncForm
dialect = hash['dialect']
if dialect in DBG_DIALECTS:
self.dbg_dialect = DBG_DIALECTS[dialect]
print "[sync] set debugger dialect to %s, enabling hotkeys" % dialect
SyncForm.init_hotkeys()
else:
SyncForm.uninit_hotkeys()
# request from broker
def req_broker(self, hash):
subtype = hash['subtype']
if (subtype == 'msg'):
# simple message announcement
print ("[*] << broker << %s" % hash['msg'])
elif(subtype == 'notice'):
# notice from broker
self.broker_port = int(hash['port'])
print ("[*] << broker << listening on port %d" % self.broker_port)
for attempt in range(CONNECT_BROKER_MAX_ATTEMPT):
try:
host = socket.gethostbyname('localhost')
self.broker_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.broker_sock.connect((host, self.broker_port))
break
except:
print "[sync] failed to connect to broker"
print sys.exc_info()
if self.broker_sock:
self.broker_sock.close()
self.broker_sock = None
time.sleep(0.1)
if (attempt == (CONNECT_BROKER_MAX_ATTEMPT - 1)):
self.announcement("[sync] failed to connect to broker (attempt %d)" % attempt)
sys.exit()
# enable/disable idb, if disable it drops most sync requests
elif(subtype == 'enable_idb'):
self.is_active = True
print "[sync] idb is enabled"
elif(subtype == 'disable_idb'):
self.is_active = False
self.cb_restore_last_line()
print "[sync] idb is disabled"
# parse and execute request
# Note that sometimes we don't receive the whole request from the broker.py
# so parsing fails. One way for fixing this would be to fix broker.py to get
# everything until "\n" before proxying it but the way we do here is to read
# everything until "}" is received (end of json)
def parse_exec(self, req):
if self.prev_req:
if self.prev_req != "":
if DEBUG_JSON:
print "[+] JSON merge with request: \"%s\"" % req
req = self.prev_req + req
self.prev_req = ""
if req == '':
return
if DEBUG_JSON:
print("parse_exec -> " + str(req))
if not (req[0:6] == '[sync]'):
print "\[<] bad hdr %s" % repr(req)
print '[-] Request dropped due to bad header'
return
req_ = self.normalize(req, 6)
try:
hash = json.loads(req_)
except:
if DEBUG_JSON:
print "[-] Sync failed to parse json\n '%s'. Caching for next req..." % req_
print "------------------------------------"
self.prev_req = req
return
type = hash['type']
if type not in self.req_handlers:
print ("[*] unknown request: %s" % type)
return
req_handler = self.req_handlers[type]
# few requests are handled even though idb is not enable
if type in ['broker', 'dialect', 'bc']:
req_handler(hash)
else:
if self.is_active:
req_handler(hash)
else:
print "[-] Drop the request because idb is not enabled"
return
idaapi.refresh_idaview_anyway()
def normalize(self, req, taglen):
req = req[taglen:]
req = req.replace("\\", "\\\\")
req = req.replace("\n", "")
return req
# send a kill notice to the broker (then forwarded to the dispatcher)
def kill_notice(self):
self.notice_broker("kill")
# send a bp command (F2) to the debugger (via the broker and dispatcher)
def bp_notice(self, oneshot=False):
if not self.is_active:
print "[sync] idb isn't enabled, bp can't be set"
return
ea = idaapi.get_screen_ea()
offset = self.rebase_remote(ea)
cmd = "%s0x%x" % (self.dbg_dialect['bp1' if oneshot else 'bp'], offset)
self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
print "[sync] >> set %s" % cmd
# send a hardware bp command (Ctrl-F2) to the debugger (via the broker and dispatcher)
def hbp_notice(self, oneshot=False):
if not self.is_active:
print "[sync] idb isn't enabled, hbp can't be set"
return
ea = idaapi.get_screen_ea()
offset = self.rebase_remote(ea)
cmd = "%s0x%x" % (self.dbg_dialect['hbp1' if oneshot else 'hbp'], offset)
self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
print "[sync] >> set %s" % cmd
# send a oneshot bp command (F3) to the debugger (via the broker and dispatcher)
def bp_oneshot_notice(self):
self.bp_notice(True)
# send a oneshot hardware bp command (Ctrl-F3) to the debugger (via the broker and dispatcher)
def hbp_oneshot_notice(self):
self.hbp_notice(True)
# export IDB's breakpoint (Ctrl-F1) to the debugger (via the broker and dispatcher)
def export_bp_notice(self):
if not self.dbg_dialect:
print "[sync] idb isn't synced yet, can't export bp"
return
mod = self.name.split('.')[0].strip()
nbp = idc.GetBptQty()
for i in range(nbp):
ea = idc.GetBptEA(i)
attrs = [idc.BPTATTR_TYPE, idc.BPTATTR_COND, idc.BPTATTR_FLAGS]
btype, cond, flags = [idc.GetBptAttr(ea, x) for x in attrs]
if cond:
print "bp %d: conditional bp not supported" % i
else:
if ((btype in [idc.BPT_EXEC, idc.BPT_SOFT]) and
((flags & idc.BPT_ENABLED) != 0)):
offset = ea - self.base
bp = self.dbg_dialect['hbp' if (btype == idc.BPT_EXEC) else 'bp']
cmd = "%s%s+0x%x" % (bp, mod, offset)
self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
print "bp %d: %s" % (i, cmd)
print "[sync] export done"
# send a translate command (Alt-F2) to the debugger (via the broker and dispatcher)
def translate_notice(self):
if not self.dbg_dialect:
print "[sync] idb isn't synced yet, can't translate"
return
ea = idaapi.get_screen_ea()
mod = self.name.split('.')[0].strip()
cmd = self.dbg_dialect['prefix'] + "translate 0x%x 0x%x %s" % (self.base, ea, mod)
self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
print "[sync] translate address 0x%x" % ea
# send a go command (Alt-F5) to the debugger (via the broker and dispatcher)
def go_notice(self):
if not self.is_active:
print "[sync] idb isn't enabled, can't go"
return
self.notice_broker("cmd", "\"cmd\":\"%s\"" % self.dbg_dialect['go'])
self.notice_anti_flood()
# send a single trace command (F11) to the debugger (via the broker and dispatcher)
def si_notice(self):
if not self.is_active:
print "[sync] idb isn't enabled, can't trace"
return
self.notice_broker("cmd", "\"cmd\":\"%s\"" % self.dbg_dialect['si'])
self.notice_anti_flood()
# send a single step command (F10) to the debugger (via the broker and dispatcher)
def so_notice(self):
if not self.is_active:
print "[sync] idb isn't enabled, can't single step"
return
self.notice_broker("cmd", "\"cmd\":\"%s\"" % self.dbg_dialect['so'])
self.notice_anti_flood()
# send a notice message to the broker process
def notice_broker(self, type, args=None):
if not self.broker_sock:
return
if args:
notice = "[notice]{\"type\":\"%s\",%s}\n" % (type, args)
else:
notice = "[notice]{\"type\":\"%s\"}\n" % (type)
try:
self.broker_sock.sendall(notice)
except:
None
def stop(self):
if self.broker_sock:
self.broker_sock.close()
self.broker_sock = None
self.cb_restore_last_line()
idaapi.refresh_idaview_anyway()
self.is_active = False
print "[sync] idb is disabled"
def __init__(self, parser):
self.color = False
self.prev_loc = None
self.prev_node = None
self.name = idaapi.get_root_filename()
print "[sync] name %s" % self.name
self.base = idaapi.get_imagebase()
print "[sync] module base 0x%x" % self.base
self.base_remote = None
self.gm = GraphManager()
self.parser = parser
self.broker_sock = None
self.is_active = False
self.dbg_dialect = None
self.req_handlers = {
'broker': self.req_broker,
'loc': self.req_loc,
'cmd': self.req_cmd,
'cmt': self.req_cmt,
'rcmt': self.req_rcmt,
'fcmt': self.req_fcmt,
'raddr': self.req_raddr,
'cursor': self.req_cursor,
'patch': self.req_patch,
'rln': self.req_rln,
'rrln': self.req_rrln,
'lbl': self.req_lbl,
'bc': self.req_bc,
'bps_get': self.req_bps_get,
'bps_set': self.req_bps_set,
'modcheck': self.req_modcheck,
'dialect': self.req_set_dbg_dialect
}
self.prev_req = "" # used as a cache if json is not completely received
# --------------------------------------------------------------------------
class Broker(QtCore.QProcess):
def cb_on_error(self, error):
errors = ["Failed to start", "Crashed", "Timedout",
"Read error", "Write Error", "Unknown Error"]
print "[-] broker error: ", errors[error]
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"
def cb_broker_on_out(self):
# readAllStandardOutput() returns QByteArray
buffer = self.readAllStandardOutput().data().encode("ascii")
batch = buffer.split('\n')
for req in batch:
self.worker.parse_exec(req)
def __init__(self, parser):
QtCore.QProcess.__init__(self)
self.error.connect(self.cb_on_error)
self.readyReadStandardOutput.connect(self.cb_broker_on_out)
self.stateChanged.connect(self.cb_broker_on_state_change)
# Create a request handler
self.worker = RequestHandler(parser)
# --------------------------------------------------------------------------
class DbgDirHlpr(object):
@staticmethod
def read_rsds_codeview():
guid = None
penode = idaapi.netnode()
penode.create(peutils_t.PE_NODE)
fpos = penode.altval(peutils_t.PE_ALT_DBG_FPOS)
if (fpos == 0):
print "[*] No debug directory"
return guid
input_file = idc.GetInputFilePath()
if not os.path.exists(input_file):
print "[*] input file not available"
else:
with open(input_file, 'r') as fd:
fd.seek(fpos)
raw = fd.read(0x1C)
"""
typedef struct _IMAGE_DEBUG_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Type;
DWORD SizeOfData;
DWORD AddressOfRawData;
DWORD PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
"""
dbgdir = struct.unpack('LLHHLLLL', raw)
# 2, IMAGE_DEBUG_TYPE_CODEVIEW
if not (dbgdir[4] == 2):
print "[*] not CODEVIEW data"
else:
fd.seek(dbgdir[7])
if not (fd.read(4) == "RSDS"):
print "[*] unsupported CODEVIEW information format (%s)" % sig
else:
d1, d2, d3 = struct.unpack('LHH', fd.read(0x8))
d4 = struct.unpack('>H', fd.read(0x2))[0]
d5 = binascii.hexlify(fd.read(0x6)).upper()
guid = "%08X-%04X-%04X-%04X-%s" % (d1, d2, d3, d4, d5)
return guid
@staticmethod
def parse_itoldyouso_output(res):
for line in res.splitlines(True):
line = line.strip()
if line.startswith('pdb sig: '):
return (line.split(':')[-1]).strip()
return None
# --------------------------------------------------------------------------
class GraphManager():
def __init__(self):
self.prev_node = None
self.graph_viewer = ida_kernwin.get_current_viewer()
def center(self):
curnode = ida_graph.viewer_get_curnode(self.graph_viewer)
if not (self.prev_node == curnode):
ida_graph.viewer_center_on(self.graph_viewer, curnode)
self.prev_node = curnode
return curnode
# --------------------------------------------------------------------------
class SyncForm_t(PluginForm):
def cb_broker_started(self):
print "[*] broker started"
self.btn.setText("Restart")
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.btn.setText("Start")
# send a kill notice to the broker
# wait at most 2sec for him to gently kill itself
def smooth_kill(self):
self.uninit_hotkeys()
if self.broker:
broker = self.broker
self.broker = None
broker.worker.cb_restore_last_line()
broker.worker.kill_notice()
broker.waitForFinished(1500)
def init_broker(self):
print "[*] init_broker"
modname = self.input.text().encode('ascii', 'replace')
cmdline = u"\"%s\" -u \"%s\" --idb \"%s\"" % (
os.path.join(PYTHON_PATH, PYTHON_BIN),
BROKER_PATH, modname)
print "[*] init broker,", cmdline
self.broker = Broker(self.parser)
env = QProcessEnvironment.systemEnvironment()
env.insert("IDB_PATH", IDB_PATH)
env.insert("PYTHON_PATH", os.path.realpath(PYTHON_PATH))
env.insert("PYTHON_BIN", PYTHON_BIN)
try:
self.broker.started.connect(self.cb_broker_started)
self.broker.finished.connect(self.cb_broker_finished)
self.broker.setProcessEnvironment(env)
self.broker.start(cmdline)
except Exception as e:
print "[-] failed to start broker: %s\n%s" % (str(e), traceback.format_exc())
return
self.init_hotkeys()
self.broker.worker.name = modname
def init_hotkeys(self):
if not self.hotkeys_ctx:
self.init_single_hotkey("F2", self.broker.worker.bp_notice)
self.init_single_hotkey("F3", self.broker.worker.bp_oneshot_notice)
self.init_single_hotkey("Ctrl-F2", self.broker.worker.hbp_notice)
self.init_single_hotkey("Ctrl-F3", self.broker.worker.hbp_oneshot_notice)
self.init_single_hotkey("Ctrl-F1", self.broker.worker.export_bp_notice)
self.init_single_hotkey("Alt-F2", self.broker.worker.translate_notice)
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)
def init_single_hotkey(self, key, fnCb):
ctx = idaapi.add_hotkey(key, fnCb)
if ctx is None:
print("[sync] failed to register hotkey %s", key)
del ctx
else:
self.hotkeys_ctx.append(ctx)
def uninit_hotkeys(self):
if not self.hotkeys_ctx:
return
for ctx in self.hotkeys_ctx:
if idaapi.del_hotkey(ctx):
del ctx
self.hotkeys_ctx = []
def cb_btn_restart(self):
print "[sync] restarting broker."
if self.cb.checkState() == QtCore.Qt.Checked:
self.cb.toggle()
time.sleep(0.1)
self.cb.toggle()
def cb_change_state(self, state):
if state == QtCore.Qt.Checked:
print "[*] sync enabled"
# Restart broker
self.hotkeys_ctx = []
self.init_broker()
else:
if self.broker:
self.smooth_kill()
print "[*] sync disabled\n"
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 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):
print "[sync] found config file: %s" % confpath
config = ConfigParser.SafeConfigParser()
config.read(confpath)
if config.has_option(name, 'name'):
name = config.get(name, 'name')
print "[sync] overwrite idb name with %s" % name
# Create input field
self.input = QtWidgets.QLineEdit(parent)
self.input.setText(name)
self.input.setMaxLength = 256
self.input.setFixedWidth(300)
# Create restart button
self.btn = QtWidgets.QPushButton('restart', parent)
self.btn.setToolTip('Restart broker.')
self.btn.clicked.connect(self.cb_btn_restart)
# Create layout
layout = QtWidgets.QGridLayout()
layout.addWidget(self.cb)
layout.addWidget(label)
layout.addWidget(self.input)
layout.addWidget(self.btn, 2, 2)
layout.setColumnStretch(3, 1)
layout.setRowStretch(3, 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()
def OnClose(self, form):
print "[sync] form close"
self.smooth_kill()
global SyncForm
del SyncForm
def Show(self):
return PluginForm.Show(self, "ret-sync", options=PluginForm.FORM_PERSIST)
# --------------------------------------------------------------------------
def main():
if not idaapi.get_root_filename():
print "[sync] please load a file/idb before"
return
global SyncForm
try:
SyncForm
except:
SyncForm = SyncForm_t()
SyncForm.Broker = None
SyncForm.Show()
class SyncPlugin(idaapi.plugin_t):
flags = 0
wanted_hotkey = ""
comment = "ret-sync Plugin"
help = ""
wanted_name = "ret-sync Plugin"
def init(self):
return idaapi.PLUGIN_OK
def term(self):
pass
def run(self, arg):
main()