#!/usr/bin/python3 # # This file is part of asadbg. # Copyright (c) 2017, Aaron Adams <aaron.adams(at)nccgroup(dot)trust> # Copyright (c) 2017, Cedric Halbronn <cedric.halbronn(at)nccgroup(dot)trust> # # IDA Python script used to save addresses in /asa/bin/lina into an external # database to be used by asadbg. Also for /asa/bin/lina_monitor. # # Assume you previously used asadbg_rename.py. import argparse import filelock import json import os import re import sys import time # ida import idc import idautils # asadbg imports import helper #from helper import * # Note that the current way of importing an external script such as # ida_helper.py in IDA makes it impossible to modify it and then reload the # calling script from IDA without closing IDA and restarting it (due to some # caching problem or Python namespaces that I don't understand yet :|) ida_helper_path = os.path.abspath(os.path.join(sys.path[-1], "..", "idahunt")) sys.path.insert(0, ida_helper_path) import ida_helper #from ida_helper import * def logmsg(s, debug=True): if not debug: return if type(s) == str: print("[asadbg_hunt] " + s) else: print(s) # merge = if you want to merge results in existing ones # such as adding symbols to existing elements # replace = useful if we want to remove old names before adding real symbols def hunt(symbols, dbname, merge=True, replace=False, bin_name="lina"): if bin_name == "lina": base_name = "lina_imagebase" addr_name = "addresses" elif bin_name == "lina_monitor": base_name = "lm_imagebase" addr_name = "lm_addresses" elif bin_name == "libc.so": base_name = "libc_imagebase" addr_name = "libc_addresses" else: logmsg("ERROR: bad elf name in hunt()") return None # parse version/fw from directory name idbdir = idautils.GetIdbDir() version = helper.build_version(idbdir) if not version: logmsg("Can't parse version in %s" % idbdir) sys.exit() fw = helper.build_bin_name(idbdir) if not fw: logmsg("Can't parse fw in %s" % idbdir) sys.exit() new_target = {} new_target["fw"] = fw new_target["arch"] = ida_helper.ARCHITECTURE # by default we don't know the imagebase so we will save # absolute addresses in new_target[addr_name] new_target[base_name] = 0 # XXX - add fw md5 to db? prevtime = time.time() lock = filelock.FileLock("asadb_json") with lock.acquire(): newtime = time.time() logmsg("Acquired lock after %d seconds" % int(newtime-prevtime)) # load old targets targets = [] if os.path.isfile(dbname): targets = helper.load_targets(dbname) else: logmsg("Creating new db: %s" % dbname) #logmsg("Existing targets:") #logmsg(targets) # Building new entry new_target["version"] = version addresses = {} for s,func in symbols.items(): if not s: continue name = s if name.startswith("instruction_"): name = s[len("instruction_"):] # addr can actually be an address but also an offset we need # (e.g. tls->default_channel)... logmsg("Looking up %s" % s) addr = func(s) # we check both as we never want to add a -1 symbol and sometimes # the architecture detected is wrong and we ended up saving -1 :| if addr == 0xffffffffffffffff or addr == 0xffffffff or addr == None: logmsg("[x] Impossible to get '%s' symbol" % name) continue #logmsg("%s = 0x%x (%s)" % (name, addr, type(addr))) addresses[name] = addr #logmsg(addresses) new_target[addr_name] = addresses if helper.is_new(targets, new_target): logmsg("New target: %s (%s)" % (version, fw)) logmsg(addresses) targets.append(new_target) elif merge == True: logmsg("Merging target: %s (%s)" % (version, fw)) i = helper.merge_target(new_target, targets, bin_name=bin_name) if i != None: print(json.dumps(targets[i], indent=2)) # print(targets[i]) else: logmsg("Skipping target: %s (%s) as helper.merge_target() failed" % (version, fw)) elif replace == True: logmsg("Replacing target: %s (%s)" % (version, fw)) helper.replace_target(new_target, targets) logmsg(new_target) else: logmsg("Skipping target: %s (%s)" % (version, fw)) # sort targets by version. Drawback: index changes each time we add # a new firmware but it should not anymore once we have them all targets = sorted(targets, key=lambda k: map(int, k["version"].split("."))) logmsg("Writing to %s" % dbname) open(dbname, "wb").write(json.dumps(targets, indent=4)) def main_lina(dbname): symbols = { "clock_interval":idc.LocByName, "mempool_array":idc.LocByName, "mempool_list_":idc.LocByName, "socks_proxy_server_start":idc.LocByName, "aaa_admin_authenticate":idc.LocByName, "mempool_list_":idc.LocByName, } symbols32 = {} symbols64 = {} if ida_helper.ARCHITECTURE == 32: symbols.update(symbols32) elif ida_helper.ARCHITECTURE == 64: symbols.update(symbols64) else: logmsg("Invalid architecture") sys.exit() hunt(symbols, dbname, bin_name="lina") def main_lina_monitor(dbname): symbols = { "jz_after_code_sign_verify_signature_image":idc.LocByName, } if ida_helper.ARCHITECTURE == 32: logmsg("WARNING: not supported/tested yet") elif ida_helper.ARCHITECTURE == 64: pass else: logmsg("Invalid architecture") sys.exit() hunt(symbols, dbname, bin_name="lina_monitor") def main_libc(dbname): symbols = { "free":ida_helper.MyLocByName, } if ida_helper.ARCHITECTURE == 32: logmsg("WARNING: not supported/tested yet") elif ida_helper.ARCHITECTURE == 64: pass else: logmsg("Invalid architecture") sys.exit() hunt(symbols, dbname, bin_name="libc.so") def main(): try: # e.g. /path/to/asadbg/asadb.json dbname = os.environ["ASADBG_DB"] except: logmsg("You need to define ASADBG_DB first") sys.exit() if ida_helper.get_idb_name() == "lina": logmsg("Hunting lina...") main_lina(dbname) elif ida_helper.get_idb_name() == "lina_monitor": logmsg("Hunting lina_monitor...") main_lina_monitor(dbname) elif ida_helper.get_idb_name() == "libc.so": logmsg("Hunting libc...") main_libc(dbname) else: logmsg("ERROR: Unsupported filename") # This allows us to cleanly exit IDA upon completion if "DO_EXIT" in os.environ: # XXX - Was Exit(1) idc.qexit(1) if __name__ == '__main__': main()