├── LICENSE ├── README.md ├── call_graph.py ├── export2neo4j.py ├── find_device_name.py ├── images ├── call_graph.png ├── mem_complex.PNG └── ref_count_list.PNG ├── mem_complexity.py ├── most_refs.py └── neo4ida ├── README.md ├── images ├── neo4ida_configure.PNG ├── neo4ida_menu.png └── neo_delete.PNG └── neo4ida.py /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ida-scripts 2 | Dumping ground for whatever IDA Pro scripts I write. 3 | 4 | ##most_refs.py 5 | Prints a list of the ten functions which are called by other functions the most. 6 | ![Screenshot](images/ref_count_list.PNG?raw=true) 7 | ##mem_complexity.py 8 | Highlights functions which include a lot of control flow and calls to functions that are on Microsofts banned list (https://msdn.microsoft.com/en-us/library/bb288454.aspx), 9 | this is designed as a very rough way of highlighting interesting functions - colors go Red to Blue for least to most interesting. 10 | ![Screenshot](images/mem_complex.PNG?raw=true) 11 | ##control_flow.py 12 | Renders a .png from a dot graph of the Control Flow Graph of a binary - works by building a full graph of the binaries function calls and then walking the graph from the entry point, 13 | in order to find all reachable function calls. Requires pydot and Grapviz to be installed. 14 | ![Screenshot](images/call_graph.png?raw=true) 15 | ##export2neo4j.py 16 | Exports a binaries function graph to a neo4j instance. 17 | Note: super alpha - slow and still missing a lot of data I want, debating what to do with indirect calls etc. -------------------------------------------------------------------------------- /call_graph.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idautils 3 | import idaapi 4 | 5 | import time 6 | from sets import Set 7 | import pydot 8 | 9 | out_file = "call_graph.png" 10 | 11 | def generate_graph(): 12 | callees = dict() 13 | 14 | # Loop through all the functions in the binary 15 | for function_ea in idautils.Functions(): 16 | 17 | f_name = GetFunctionName(function_ea) 18 | # For each of the incoming references 19 | for ref_ea in CodeRefsTo(function_ea, 0): 20 | 21 | # Get the name of the referring function 22 | caller_name = GetFunctionName(ref_ea) 23 | 24 | # Add the current function to the list of functions 25 | # called by the referring function 26 | callees[caller_name] = callees.get(caller_name, Set()) 27 | 28 | callees[caller_name].add(f_name) 29 | return callees 30 | 31 | #Visit functions called by our starting point recursively 32 | def walk_graph(g,seen,callees,start): 33 | if start in callees.keys() and start not in seen: #Who needs recursion? 34 | seen.add(start) 35 | next = callees[start] 36 | for i in next: 37 | g.add_edge(pydot.Edge(start, i)) 38 | walk_graph(g,seen,callees,i) 39 | 40 | start_time = time.time() 41 | print "---- Generating Callgraph ----" 42 | # Create graph 43 | g = pydot.Dot(type='"digraph"') 44 | 45 | # Set some defaults 46 | g.set_rankdir('LR') 47 | g.set_size('100,100') 48 | g.add_node(pydot.Node('node', shape='ellipse', color='lightblue', style='filled')) 49 | g.add_node(pydot.Node('edge', color='lightgrey')) 50 | 51 | #Generate full control flow graph 52 | callees = generate_graph() 53 | seen = Set() 54 | #walk the graph from start/main/_main/whatever so that only functions which are actually reachable are included 55 | walk_graph(g,seen,callees,'start') 56 | #write_ps to write postscript, write to write a dot file etc 57 | g.write_png(out_file) 58 | print("---- Callgraph complete - saved as: " + out_file +" ----") 59 | print("---- Ran in: %s seconds ----" % (time.time() - start_time)) -------------------------------------------------------------------------------- /export2neo4j.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idautils 3 | import idaapi 4 | from py2neo import authenticate, Graph, Node, Relationship 5 | 6 | neo_instance = "192.168.1.4:7474" 7 | neo_username = "neo4j" 8 | neo_password = "password" 9 | 10 | authenticate(neo_instance,neo_username,neo_password) 11 | neo = Graph("http://192.168.1.4:7474/db/data") 12 | try: 13 | neo.schema.create_uniqueness_constraint("Function", "name") 14 | except: 15 | pass 16 | 17 | target = idaapi.get_root_filename() 18 | for f in Functions(): 19 | callee_name = GetFunctionName(f) 20 | callee = neo.merge_one("Function","name",callee_name) 21 | if target not in callee.labels: 22 | callee.labels.add(target) 23 | callee.push() 24 | for xref in XrefsTo(f): 25 | caller_name = GetFunctionName(xref.frm) 26 | if caller_name == '': 27 | print "Indirect call to " + callee_name + " ignored." 28 | continue 29 | caller = neo.merge_one("Function","name",caller_name) 30 | if target not in callee.labels: 31 | callee.labels.add(target) 32 | callee.push() 33 | caller_callee = Relationship(caller, "CALLS", callee) 34 | neo.get_or_create(caller_callee) 35 | print "Export finished" -------------------------------------------------------------------------------- /find_device_name.py: -------------------------------------------------------------------------------- 1 | import idautils 2 | import idc 3 | import mmap 4 | import re 5 | import sys 6 | from collections import namedtuple 7 | 8 | # 9 | 10 | ASCII_BYTE = " !\"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}\\\~\t" 11 | UNICODE_RE_4 = re.compile(b"((?:[%s]\x00){%d,})" % (ASCII_BYTE, 4)) 12 | REPEATS = ["A", "\x00", "\xfe", "\xff"] 13 | SLICE_SIZE = 4096 14 | 15 | String = namedtuple("String", ["s", "offset"]) 16 | 17 | def buf_filled_with(buf, character): 18 | dupe_chunk = character * SLICE_SIZE 19 | for offset in xrange(0, len(buf), SLICE_SIZE): 20 | new_chunk = buf[offset: offset + SLICE_SIZE] 21 | if dupe_chunk[:len(new_chunk)] != new_chunk: 22 | return False 23 | return True 24 | 25 | def extract_unicode_strings(buf, n=4): 26 | ''' 27 | Extract naive UTF-16 strings from the given binary data. 28 | :param buf: A bytestring. 29 | :type buf: str 30 | :param n: The minimum length of strings to extract. 31 | :type n: int 32 | :rtype: Sequence[String] 33 | ''' 34 | 35 | if not buf: 36 | return 37 | 38 | if (buf[0] in REPEATS) and buf_filled_with(buf, buf[0]): 39 | return 40 | 41 | if n == 4: 42 | r = UNICODE_RE_4 43 | else: 44 | reg = b"((?:[%s]\x00){%d,})" % (ASCII_BYTE, n) 45 | r = re.compile(reg) 46 | for match in r.finditer(buf): 47 | try: 48 | yield String(match.group().decode("utf-16"), match.start()) 49 | except UnicodeDecodeError: 50 | pass 51 | 52 | def get_unicode_device_names(): 53 | path = idc.GetInputFile() 54 | min_length = 4 55 | possible_names = set() 56 | with open(path, "rb") as f: 57 | b = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) 58 | 59 | for s in extract_unicode_strings(b, n=min_length): 60 | if str(s.s).startswith('\\Device\\'): 61 | possible_names.add(str(s.s)) 62 | return possible_names 63 | 64 | def find_unicode_device_name(): 65 | possible_names = get_unicode_device_names() 66 | if len(possible_names) == 1: 67 | if possible_names.pop() == '\\Device\\': 68 | print "The Device prefix was found but no full device paths, the device name is likely obsfucated or created on the stack." 69 | return False 70 | elif len(possible_names) > 1: 71 | print "Possible devices names found:" 72 | for i in possible_names: 73 | print "\t" + i 74 | return True 75 | else: 76 | print "No potential device names found - it may be obsfucated or created on the stack in some way." 77 | return False 78 | 79 | if not find_unicode_device_name(): 80 | print "Device name not found, attempting to find obsfucated and stack based strings." 81 | try: 82 | import floss 83 | import floss.identification_manager 84 | import floss.main 85 | import floss.stackstrings 86 | import viv_utils 87 | except: 88 | print "Please install FLOSS to continue, see: https://github.com/fireeye/flare-floss/" 89 | 90 | sample_file_path = idc.GetInputFile() 91 | 92 | try: 93 | vw = viv_utils.getWorkspace(sample_file_path, should_save=False) 94 | except Exception, e: 95 | print("Vivisect failed to load the input file: {0}".format(e.message)) 96 | sys.exit(1) 97 | 98 | functions = set(vw.getFunctions()) 99 | plugins = floss.main.get_all_plugins() 100 | device_names = set() 101 | 102 | stack_strings = floss.stackstrings.extract_stackstrings(vw, functions) 103 | for i in stack_strings: 104 | device_names.add(i) 105 | decoding_functions_candidates = floss.identification_manager.identify_decoding_functions(vw, plugins, functions) 106 | function_index = viv_utils.InstructionFunctionIndex(vw) 107 | decoded_strings = floss.main.decode_strings(vw, function_index, decoding_functions_candidates) 108 | if len(decoded_strings) > 0: 109 | for i in decoded_strings: 110 | device_names.add(str(i.s)) 111 | print "Potential devices names from obsfucated or stack strings:" 112 | for i in device_names: 113 | if i.startswith('\\Device\\'): 114 | print i 115 | else: 116 | print '\\Device\\' + i 117 | else: 118 | print "No obsfucated or stack strings found :(" -------------------------------------------------------------------------------- /images/call_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/ida-scripts/e40cf6394c6e81ede9aad7e9830d0936f0b49e0f/images/call_graph.png -------------------------------------------------------------------------------- /images/mem_complex.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/ida-scripts/e40cf6394c6e81ede9aad7e9830d0936f0b49e0f/images/mem_complex.PNG -------------------------------------------------------------------------------- /images/ref_count_list.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/ida-scripts/e40cf6394c6e81ede9aad7e9830d0936f0b49e0f/images/ref_count_list.PNG -------------------------------------------------------------------------------- /mem_complexity.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idautils 3 | import idaapi 4 | import colorsys 5 | 6 | def pseudocolor(val, minval, maxval): 7 | # convert val in range minval..maxval to the range 0..120 degrees which 8 | # correspond to the colors red..green in the HSV colorspace 9 | h = (float(val-minval) / (maxval-minval)) * 120 10 | # convert hsv color (h,1,1) to its rgb equivalent 11 | # note: the hsv_to_rgb() function expects h to be in the range 0..1 not 0..360 12 | r, g, b = colorsys.hsv_to_rgb(h/360, 1., 1.) 13 | return int("%02X%02X%02X" % (int(round(r*200)), int(round(b*200)), int(round(g*200))),16) 14 | 15 | start_time = time.time() 16 | #Taken from https://msdn.microsoft.com/en-us/library/bb288454.aspx 17 | interesting_funcs = ['_memset','_free','_strcpy', '_strcpyA', '_strcpyW', '_wcscpy', '_tcscpy', '_mbscpy', '_StrCpy', '_StrCpyA', '_StrCpyW', '_lstrcpy', '_lstrcpyA', '_lstrcpyW', '_tccpy', '_mbccpy', '_ftcscpy', '_strncpy', '_wcsncpy', '_tcsncpy', '_mbsncpy', '_mbsnbcpy', '_StrCpyN', '_StrCpyNA', '_StrCpyNW', '_StrNCpy', '_strcpynA', '_StrNCpyA', '_StrNCpyW', '_lstrcpyn', '_lstrcpynA', '_lstrcpynW,strcat', '_strcatA', '_strcatW', '_wcscat', '_tcscat', '_mbscat', '_StrCat', '_StrCatA', '_StrCatW', '_lstrcat', '_lstrcatA', '_lstrcatW', '_StrCatBuff', '_StrCatBuffA', '_StrCatBuffW', '_StrCatChainW', '_tccat', '_mbccat', '_ftcscat', '_strncat', '_wcsncat', '_tcsncat', '_mbsncat', '_mbsnbcat', '_StrCatN', '_StrCatNA', '_StrCatNW', '_StrNCat', '_StrNCatA', '_StrNCatW', '_lstrncat', '_lstrcatnA', '_lstrcatnW', '_lstrcatn,sprintfW', '_sprintfA', '_wsprintf', '_wsprintfW', '_wsprintfA', '_sprintf', '_swprintf', '_stprintf', '_wvsprintf', '_wvsprintfA', '_wvsprintfW','_vsprintf', '_vstprintf', '_vswprintf', '_wnsprintf', '_wnsprintfA', '_wnsprintfW','_snwprintf', '_snprintf', '_sntprintf _vsnprintf', '_vsnprintf', '_vsnwprintf', '_vsntprintf', '_wvnsprintf', '_wvnsprintfA', '_wvnsprintfW', '_snwprintf', '_snprintf', '_sntprintf', '_nsprintf', '_wvsprintf', '_wvsprintfA', '_wvsprintfW', '_vsprintf', '_vstprintf', '_vswprintf', '_vsnprintf', '_vsnwprintf', '_vsntprintf', '_wvnsprintf', '_wvnsprintfA', '_wvnsprintfW', '_strncpy', '_wcsncpy', '_tcsncpy', '_mbsncpy', '_mbsnbcpy', '_StrCpyN', '_StrCpyNA', '_StrCpyNW', '_StrNCpy', '_strcpynA', '_StrNCpyA', '_StrNCpyW', '_lstrcpyn', '_lstrcpynA', '_lstrcpynW', '_fstrncpy', '_strncat', '_wcsncat', '_tcsncat', '_mbsncat', '_mbsnbcat', '_StrCatN', '_StrCatNA', '_StrCatNW', '_StrNCat', '_StrNCatA', '_StrNCatW', '_lstrncat', '_lstrcatnA', '_lstrcatnW', '_lstrcatn', '_fstrncat', '_strtok', '_tcstok', '_wcstok', '_mbstok', '_makepath', '_tmakepath', '_makepath', '_wmakepath', '_splitpath', '_tsplitpath', '_wsplitpath', '_scanf', '_wscanf', '_tscanf', '_sscanf', '_swscanf', '_stscanf', '_snscanf', '_snwscanf', '_sntscanf', '_itoa', '_itow', '_i64toa', '_i64tow', '_ui64toa', '_ui64tot', '_ui64tow', '_ultoa', '_ultot', '_ultow', '_CharToOem', '_CharToOemA', '_CharToOemW', '_OemToChar', '_OemToCharA', '_OemToCharW', '_CharToOemBuffA', '_CharToOemBuffW', '_IsBadWritePtr', '_IsBadHugeWritePtr', '_IsBadReadPtr', '_IsBadHugeReadPtr', '_IsBadCodePtr', '_IsBadStringPtr', '_gets', '_getts', '_gettws', '_CharToOem', '_CharToOemA', '_CharToOemW', '_OemToChar', '_OemToCharA', '_OemToCharW', '_CharToOemBuffA', '_CharToOemBuffW', '_alloca', '_alloca', '_ strlen', '_wcslen', '_mbslen', '_mbstrlen', '_StrLen', '_lstrlen', '_RtlCopyMemory', '_CopyMemory', '_wmemcpy', '_ChangeWindowMessageFilter'] 18 | jmps_x86 = ['jo', 'jno', 'js', 'jns', 'je', 'jz', 'jne', 'jnz','jb', 'jnae', 'jc', 'jnb', 'jae', 'jnc', 'jbe', 'jna', 'ja', 'jnbe', 'jl', 'jnge', 'jge', 'jnl', 'jle', 'jng', 'jg', 'jnle', 'jp', 'jpe', 'jnp', 'jpo', 'jcxz', 'jecxz'] 19 | 20 | targets = [] 21 | for func in idautils.Functions(): 22 | jmp_count = 0 23 | int_count = 0 24 | func_obj = idaapi.get_func(func) 25 | flags = func_obj.flags 26 | if flags & FUNC_LIB or flags & FUNC_THUNK: #skip library functions and stubs 27 | continue 28 | dism_addr = list(idautils.FuncItems(func)) 29 | line_count = len(dism_addr) 30 | for line in dism_addr: 31 | m = idc.GetMnem(line) 32 | if m == 'call': 33 | opnd = idc.GetOpnd(line, 0) 34 | if opnd in interesting_funcs: 35 | int_count += 1 36 | elif m in jmps_x86: 37 | jmp_count += 1 38 | if jmp_count > 0 and int_count > 0: 39 | print "?" 40 | complex = ((jmp_count + int_count) / float(line_count)) * 100 41 | targets.append((func,idc.GetFunctionName(func), line_count, jmp_count,int_count,complex)) 42 | 43 | targets = sorted(targets, key=lambda targets: targets[5],reverse=True) 44 | if len(targets) == 0: 45 | print "Nothing obviously of interest." 46 | else: 47 | min = targets[0][5] 48 | max = targets[len(targets) - 1][5] 49 | 50 | print("--- Ran in: %s seconds ---" % (time.time() - start_time)) 51 | print "------------------------------------------------------------------------------" 52 | print "| Addr | Name | Line Count | JMP Count | Interesting Calls | Jmp/Int % |" 53 | print "------------------------------------------------------------------------------" 54 | for i in targets: 55 | print "|%8x|%10s|%12s|%11s|%19s|%11.2f|" % i 56 | start_ea = idaapi.get_func(i[0]).startEA 57 | color = pseudocolor(i[5],min,max) 58 | idc.SetColor(start_ea, idc.CIC_FUNC, color) 59 | print "------------------------------------------------------------------------------" -------------------------------------------------------------------------------- /most_refs.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idautils 3 | import idaapi 4 | saved_funcs = [] 5 | #iterate overall functions in the binary 6 | for func in idautils.Functions(): 7 | func_obj = idaapi.get_func(func) 8 | flags = func_obj.flags 9 | if flags & FUNC_LIB or flags & FUNC_THUNK: #skip library functions and stubs 10 | continue 11 | refs = idautils.CodeRefsTo(func, 0) 12 | ref_count = len(list(refs)) #get size of a generator expression 13 | if ref_count > 1: #if its only called once don't bother storing it 14 | saved_funcs.append((hex(func), idc.GetFunctionName(func), ref_count,func_obj.size())) 15 | 16 | sorted_by_ref_count = sorted(saved_funcs, key=lambda saved_funcs: saved_funcs[2],reverse=True) 17 | print "Most ref'd functions - names are clickable :)" 18 | print "|Address |Name |Ref Count |Length |" 19 | width = 69 #Genuine coincidence... 20 | print "|" + "-" * width +"|" 21 | for i in range(10): 22 | print "|%s\t|%s\t|%d\t\t|%d\t|"% sorted_by_ref_count[i] 23 | print "|" + "-" * width +"|" -------------------------------------------------------------------------------- /neo4ida/README.md: -------------------------------------------------------------------------------- 1 | #NEO4IDA 2 | 3 | ###Overview 4 | Neo4IDA is an IDA Pro plugin designed to provide a neo4j interface for IDA. 5 | 6 | ###Requirements 7 | pip install py2neo -------------------------------------------------------------------------------- /neo4ida/images/neo4ida_configure.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/ida-scripts/e40cf6394c6e81ede9aad7e9830d0936f0b49e0f/neo4ida/images/neo4ida_configure.PNG -------------------------------------------------------------------------------- /neo4ida/images/neo4ida_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/ida-scripts/e40cf6394c6e81ede9aad7e9830d0936f0b49e0f/neo4ida/images/neo4ida_menu.png -------------------------------------------------------------------------------- /neo4ida/images/neo_delete.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/ida-scripts/e40cf6394c6e81ede9aad7e9830d0936f0b49e0f/neo4ida/images/neo_delete.PNG -------------------------------------------------------------------------------- /neo4ida/neo4ida.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import time 4 | import hashlib 5 | import inspect 6 | 7 | import idc 8 | import idautils 9 | import idaapi 10 | 11 | from idaapi import Form 12 | 13 | from py2neo import authenticate, Graph, Node, Relationship 14 | 15 | class ConnectionManagementForm(Form): 16 | def __init__(self,manager): 17 | self.manager = manager 18 | self.conf = manager.get_config() 19 | self.changed = False 20 | Form.__init__(self, 21 | """Neo4IDA - Manage Neo4j Connection 22 | {form_change} 23 | <#Host#~H~ost:{host}> <#Port#~P~ort:{port}> 24 | <#Username#~U~sername:{username}> 25 | <#Password#~P~assword:{password}> 26 | """ 27 | , { 28 | "form_change": Form.FormChangeCb(self.form_change), 29 | "host":Form.StringInput(swidth=20), 30 | "port":Form.StringInput(swidth=10), 31 | "username":Form.StringInput(swidth=40), 32 | "password":Form.StringInput(swidth=40) 33 | } 34 | ) 35 | 36 | self.Compile() 37 | self.host.value = self.conf["host"] 38 | self.port.value = self.conf["port"] 39 | self.username.value = self.conf["username"] 40 | self.password.value = self.conf["password"] 41 | self.Execute() 42 | 43 | def form_change(self,fid): 44 | if fid == self.host.id: 45 | tmp = self.GetControlValue(self.host) 46 | self.host.value = tmp 47 | self.changed = True 48 | if fid == self.port.id: 49 | tmp = self.GetControlValue(self.port) 50 | self.port.value = tmp 51 | self.changed = True 52 | if fid == self.username.id: 53 | tmp = self.GetControlValue(self.username) 54 | self.username.value = tmp 55 | self.changed = True 56 | if fid == self.password.id: 57 | tmp = self.GetControlValue(self.password) 58 | self.password.value = tmp 59 | self.changed = True 60 | if fid == -2: 61 | if self.changed: 62 | new_conf = {} 63 | new_conf['host'] = self.host.value 64 | new_conf['port'] = self.port.value 65 | new_conf['username'] = self.username.value 66 | new_conf['password'] = self.password.value 67 | self.manager.update_config(new_conf) 68 | self.conf = new_conf 69 | print "Config updated" 70 | self.manager.connect() 71 | self.Close(-1) 72 | 73 | class CypherQueryForm(Form): 74 | def __init__(self,manager): 75 | self.manager = manager 76 | self.conf = manager.get_config() 77 | self.changed = False 78 | Form.__init__(self, 79 | """Neo4IDA - Execute Cypher Query 80 | {form_change} 81 | <#Query#~Q~uery:{query}> 82 | <#Execute Query#~E~xecute:{executeButton}> 83 | """ 84 | , { 85 | "form_change": Form.FormChangeCb(self.form_change), 86 | "query":Form.StringInput(swidth=80), 87 | "executeButton":Form.ButtonInput(self.button_press) 88 | } 89 | ) 90 | 91 | self.Compile() 92 | self.query.value = "START n=node(*) return n;" 93 | self.Execute() 94 | 95 | def form_change(self,fid): 96 | if fid == self.query.id: 97 | query = self.GetControlValue(self.query) 98 | self.query.value = query 99 | if fid == -2: 100 | self.Close(-1) 101 | 102 | def button_press(self,fid): 103 | print self.query.value 104 | for i in self.manager.neo.cypher.execute(self.query.value): 105 | print i 106 | 107 | class UiAction(idaapi.action_handler_t): 108 | def __init__(self, id, name, tooltip, menuPath, callback, icon): 109 | idaapi.action_handler_t.__init__(self) 110 | self.id = id 111 | self.name = name 112 | self.tooltip = tooltip 113 | self.menuPath = menuPath 114 | self.callback = callback 115 | scriptPath = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 116 | self.icon = idaapi.load_custom_icon( 117 | scriptPath + "/" + "icon" + ".png" 118 | ) 119 | 120 | def registerAction(self): 121 | action_desc = idaapi.action_desc_t( 122 | self.id, 123 | self.name, 124 | self, 125 | "", 126 | self.tooltip, 127 | self.icon 128 | ) 129 | if not idaapi.register_action(action_desc): 130 | return False 131 | if not idaapi.attach_action_to_menu(self.menuPath, self.id, 0): 132 | return False 133 | if not idaapi.attach_action_to_toolbar("AnalysisToolBar", self.id): 134 | return False 135 | return True 136 | 137 | def unregisterAction(self): 138 | idaapi.detach_action_from_menu(self.menuPath, self.id) 139 | idaapi.unregister_action(self.id) 140 | 141 | def activate(self, ctx): 142 | self.callback(ctx) 143 | return 1 144 | 145 | def update(self, ctx): 146 | return idaapi.AST_ENABLE_ALWAYS 147 | 148 | class neo4ida_t(idaapi.plugin_t): 149 | flags = 0 150 | comment = "Neo4j graph export and query interface" 151 | help = "Neo4j graph export and query interface" 152 | wanted_name = "Neo4IDA" 153 | wanted_hotkey = "" 154 | 155 | def init(self): 156 | self.conf_file = os.path.expanduser("~") + os.path.sep + "neo4ida.json" 157 | config = self.get_config() 158 | if not config: 159 | config = self.create_default_config() 160 | self.connect() 161 | action = UiAction( 162 | id="neo4ida:upload", 163 | name="Upload", 164 | tooltip="Upload to neo4j", 165 | menuPath="Edit/neo4ida/", 166 | callback=self.upload, 167 | icon="" 168 | ) 169 | if not action.registerAction(): 170 | return 1 171 | action = UiAction( 172 | id="neo4ida:dropdb", 173 | name="Drop Database", 174 | tooltip="Delete all entries in database instance.", 175 | menuPath="Edit/neo4ida/", 176 | callback=self.drop_db, 177 | icon="" 178 | ) 179 | if not action.registerAction(): 180 | return 1 181 | action = UiAction( 182 | id="neo4ida:config", 183 | name="Configure", 184 | tooltip="Configure neo4j connection details.", 185 | menuPath="Edit/neo4ida/", 186 | callback=self.config_form, 187 | icon="" 188 | ) 189 | if not action.registerAction(): 190 | return 1 191 | action = UiAction( 192 | id="neo4ida:query", 193 | name="Cypher Query", 194 | tooltip="Execute a Cypher query.", 195 | menuPath="Edit/neo4ida/", 196 | callback=self.query_form, 197 | icon="" 198 | ) 199 | if not action.registerAction(): 200 | return 1 201 | action = UiAction( 202 | id="neo4ida:browser", 203 | name="Neo4j Browser", 204 | tooltip="Open Neo4j browser.", 205 | menuPath="Edit/neo4ida/", 206 | callback=self.open_browser, 207 | icon="" 208 | ) 209 | if not action.registerAction(): 210 | return 1 211 | action = UiAction( 212 | id="neo4ida:diff", 213 | name="Binary Diff", 214 | tooltip="Open binary diffing interface.", 215 | menuPath="Edit/neo4ida/", 216 | callback=self.binary_diff, 217 | icon="" 218 | ) 219 | if not action.registerAction(): 220 | return 1 221 | return idaapi.PLUGIN_KEEP 222 | 223 | 224 | def connect(self): 225 | conf = self.get_config() 226 | authenticate(conf['host'] + ":" + conf['port'],conf['username'],conf["password"]) 227 | try: 228 | self.neo = Graph("http://" + conf['host'] + ":" + conf["port"] + "/db/data") 229 | except: 230 | print "Failed to connect!" 231 | 232 | def term(self): 233 | return None 234 | 235 | def binary_diff(self,ctf): 236 | print "Open binary diffing interface" 237 | 238 | def drop_db(self,ctx): 239 | self.neo.cypher.execute("START n=node(*) detach delete n;") 240 | print "All database nodes and relationships deleted." 241 | 242 | def open_browser(self,ctx): 243 | self.neo.open_browser() 244 | 245 | def config_form(self,ctx): 246 | ConnectionManagementForm(self) 247 | 248 | def query_form(self,ctf): 249 | CypherQueryForm(self) 250 | 251 | def upload(self,ctx): 252 | start = time.time() 253 | func_count = 0 254 | bb_count = 0 255 | call_count = 0 256 | target = idaapi.get_root_filename() 257 | hash = idc.GetInputMD5() 258 | tx = self.neo.cypher.begin() 259 | insert_binary = "MERGE (n:Binary {name:{N},hash:{H}}) RETURN n" 260 | insert_func = "MERGE (n:Function {name:{N},start:{S},flags:{F}}) RETURN n" 261 | insert_bb = "MERGE (n:BasicBlock {start:{S}, end:{E}}) RETURN n" 262 | create_relationship = "MATCH (u:Function {name:{N}}), (r:Function {start:{S}}) CREATE (u)-[:CALLS]->(r)" 263 | create_contains = "MATCH (u:BasicBlock {start:{S}}), (f:Function {name:{N}}) CREATE (f)-[:CONTAINS]->(u)" 264 | create_inside = "MATCH (u:Function {start:{S}}), (b:Binary {hash:{H}}) CREATE (f)-[:INSIDE]->(b)" 265 | self.neo.cypher.execute(insert_binary, {"N":target, "H":hash}) 266 | self.neo.cypher.execute("CREATE INDEX ON :Function(start)") 267 | #self.neo.cypher.execute("CREATE INDEX ON :Function(name)") 268 | self.neo.cypher.execute("CREATE INDEX ON :BasicBlock(start)") 269 | for f in Functions(): 270 | tx.append(create_inside, {"S":f, "H":hash}) 271 | callee_name = GetFunctionName(f) 272 | flags = get_flags(f) 273 | type = GetType(f) 274 | if type: 275 | return_type = type.split()[0] 276 | print type 277 | end_return = type.find(' ') 278 | start_args = type.find('(') 279 | print type[end_return +1:start_args] 280 | print type[start_args+1:].split(',') 281 | else: 282 | print GuessType(f) 283 | tx.append(insert_func, {"N": callee_name, "S":f, "F":flags}) 284 | func_count += 1 285 | fc = idaapi.FlowChart(idaapi.get_func(f)) 286 | for block in fc: 287 | tx.append(insert_bb, {"S":block.startEA,"E":block.endEA}) 288 | tx.append(create_contains,{"S":block.startEA,"N":f}) 289 | bb_count += 1 290 | tx.process() 291 | tx.commit() 292 | tx = self.neo.cypher.begin() 293 | for f in Functions(): 294 | for xref in CodeRefsTo(f,0): 295 | caller_name = GetFunctionName(xref) 296 | if caller_name != '': 297 | tx.append(create_relationship,{"N":caller_name,"S":f}) 298 | call_count += 1 299 | tx.process() 300 | tx.commit() 301 | print "Upload ran in: " + str(time.time() - start) 302 | print "Uploaded " + str(func_count) + " functions, " + str(call_count) +" function calls and " + str(bb_count) + " basic blocks." 303 | 304 | def run(self): 305 | pass 306 | 307 | def update_config(self,new_config): 308 | print "updating config to be: " 309 | print json.dumps(new_config) 310 | os.remove(self.conf_file) 311 | with open(self.conf_file,"w+") as f: 312 | f.write(json.dumps(new_config)) 313 | 314 | def create_default_config(self): 315 | default_conf = { 316 | "host": "localhost", 317 | "port": "7474", 318 | "username":"neo4j", 319 | "password":"neo4j" 320 | } 321 | with open(self.conf_file,"w+") as f: 322 | f.write(json.dumps(default_conf)) 323 | return default_conf 324 | 325 | def get_config(self): 326 | try: 327 | with open(self.conf_file,"r") as f: 328 | return json.loads(f.read()) 329 | except: 330 | return None 331 | 332 | def find_path(self,startFunc, endFunc): 333 | all_paths = "" 334 | print "Finding all paths from " + startFunc + " to " + endFunc 335 | self.neo.cypher.execute(all_paths,{}) 336 | 337 | def help(): 338 | print "Upload: Upload graph to neo instance." 339 | print "Drop Database: Delete all nodes and relationships in the neo4j instance." 340 | print "Configure: Update your connection configuration to the neo4j instance." 341 | print "Cypher Query: Execute arbitary cypher queries." 342 | print "Neo4j Browser: Open the Neo4j web interface in your systems default browser." 343 | print "Binary Diff: placeholer menu item." 344 | 345 | def get_args(f): 346 | local_variables = [ ] 347 | arguments = [ ] 348 | current = local_variables 349 | 350 | frame = idc.GetFrame(f) 351 | arg_string = "" 352 | if frame == None: 353 | return None 354 | 355 | start = idc.GetFirstMember(frame) 356 | end = idc.GetLastMember(frame) 357 | count = 0 358 | max_count = 10000 359 | args_str = "" 360 | while start <= end and count <= max_count: 361 | size = idc.GetMemberSize(frame, start) 362 | count = count + 1 363 | if size == None: 364 | start = start + 1 365 | continue 366 | 367 | name = idc.GetMemberName(frame, start) 368 | start += size 369 | 370 | if name in [" r", " s"]: 371 | # Skip return address and base pointer 372 | current = arguments 373 | continue 374 | arg_string += " " + name 375 | current.append(name) 376 | if len(arguments) == 0: 377 | arguments.append("void") 378 | return arguments 379 | 380 | def get_flags(f): 381 | out = [] 382 | flags = idc.GetFunctionFlags(f) 383 | if flags & FUNC_NORET: 384 | out.append("FUNC_NORET") 385 | if flags & FUNC_FAR: 386 | out.append("FUNC_FAR") 387 | if flags & FUNC_LIB: 388 | out.append("FUNC_LIB") 389 | if flags & FUNC_STATIC: 390 | out.append("FUNC_STATIC") 391 | if flags & FUNC_FRAME: 392 | out.append("FUNC_FRAME") 393 | if flags & FUNC_USERFAR: 394 | out.append("FUNC_USERFAR") 395 | if flags & FUNC_HIDDEN: 396 | out.append("FUNC_HIDDEN") 397 | if flags & FUNC_THUNK: 398 | out.append("FUNC_THUNK") 399 | if flags & FUNC_LIB: 400 | out.append("FUNC_BOTTOMBP") 401 | return out 402 | 403 | def PLUGIN_ENTRY(): 404 | return neo4ida_t() --------------------------------------------------------------------------------