From be0cef9cd0192a039aa74b955211c217e1b81efc Mon Sep 17 00:00:00 2001 From: Cristiano Maruti Date: Wed, 24 Oct 2012 12:33:33 +0200 Subject: [PATCH 1/5] Added some functions and minor fixes --- proc_peek_recon.py | 430 ++++++++++++++++++---------- proc_peek_recon_db.py | 641 +++++++++++++++++++++++++----------------- 2 files changed, 657 insertions(+), 414 deletions(-) diff --git a/proc_peek_recon.py b/proc_peek_recon.py index 89efc30..1698bea 100644 --- a/proc_peek_recon.py +++ b/proc_peek_recon.py @@ -32,35 +32,42 @@ ### Support Functions ### -def get_arg (ea, arg_num): - arg_index = 1 - - while True: - ea = PrevNotTail(ea) - - if GetMnem(ea) == "push": - if arg_index == arg_num: - dref = Dfirst(ea) - - if dref == BADADDR: - return dref - - return read_string(dref) - - arg_index += 1 - - +# +# Type define the argument data type; +# S -> string +# I -> num +# +def get_arg(ea, arg_num, type): + arg_index = 1 + + while True: + ea = PrevNotTail(ea) + if GetMnem(ea) == "push": + if arg_index == arg_num: + + # string parameter + if type == 'S': + dref = Dfirst(ea) + if dref == BADADDR: + return dref + return read_string(dref) + + # number parameter + elif type == 'I': + return read_size(ea) + + arg_index += 1 + def instruction_match (ea, mnem=None, op1=None, op2=None, op3=None): if mnem and mnem != GetMnem(ea): - return False - + return False + if op1 and op1 != GetOpnd(ea, 0): return False if op2 and op2 != GetOpnd(ea, 1): return False if op3 and op3 != GetOpnd(ea, 2): return False return True - def disasm_match (ea, needle): disasm_line = GetDisasm(ea) @@ -73,26 +80,51 @@ def disasm_match (ea, needle): return True +def read_size(ea): + s = GetOpnd(ea, 0) + if s.endswith("h"): + return int(s.rstrip('h'), 16) + else: + return BADADDR -def read_string (ea): - s = "" - - while True: - byte = Byte(ea) - - if byte == 0 or byte < 32 or byte > 126: - break - - s += chr(byte) - ea += 1 - - return s +def read_string (ea): + s = "" + string_type = GetStringType(ea) + if string_type == ASCSTR_C: + while True: + byte = Byte(ea) + + if byte == 0: #or byte < 32 or byte > 126: + break + + if byte == 0x0d: s += "" + elif byte == 0x0a: s += "" + else: s += chr(byte) + + ea += 1 + return s + elif string_type == ASCSTR_UNICODE: + #TODO + while True: + word = Word(ea) + if byte == 0: + break + else: s += word + ea += 2 + return "unicode string: " + s + + elif string_type == ASCSTR_PASCAL: + byte = Byte(ea) + for i in range (1, byte): + s += Byte(ea) + return "pascal string: " + s + + def token_count (format_string): return format_string.count("%") - format_string.count("%%") - ######################################################################################################################## ### Meat and Potatoes ### @@ -101,7 +133,7 @@ def token_count (format_string): peek_file = open(peek_filename, "w+") #ida_log = lambda x: None -ida_log = lambda x: sys.stdout.write(x + "\n") +ida_log = lambda x: sys.stdout.write("RECON> " + x + "\n") write_line = lambda x: peek_file.write("%s\n" % x) window = state = found_ea = processed = 0 @@ -109,135 +141,225 @@ def token_count (format_string): ida_log("searching for inline memcpy()'s and sign extended moves (movsx).") for ea in Heads(MinEA(), MaxEA()): - processed += 1 - - # we don't care about instructions within known library routines. - if GetFunctionFlags(ea) and GetFunctionFlags(ea) & FUNC_LIB: - continue - - if disasm_match(ea, "movsx"): - ida_log("%08x: found sign extended move" % ea) - write_line("%08x:3:sign extended move" % ea) - - if state == 0 and instruction_match(ea, "shr", "ecx", "2"): - state = 1 - window = 0 - - elif state == 1 and disasm_match(ea, "rep movsd"): - state = 2 - window = 0 - found_ea = ea - - elif state == 2 and instruction_match(ea, "and", "ecx", "3"): - state = 3 - window = 0 - - elif state == 3 and disasm_match(ea, "rep movsb"): - ida_log("%08x: found memcpy" % found_ea) - set_cmt(found_ea, "inline memcpy()", False) - write_line("%08x:5:inline memcpy" % found_ea) - found_ea = state = window = 0 - - if window > 15: - state = window = 0 - - if state != 0: - window += 1 + processed += 1 + + # rep movsd : rep movsd [edi], [esi] : eax = memcpy(edi, esi, ecx) + # rep stosd : rep stosd [edi], eax : eax = memset(edi, eax, ecx) + # rep scasd : rep scasd [edi] : eax = strchr(edi, eax) + + # we don't care about instructions within known library routines. + # if GetFunctionFlags(ea) and GetFunctionFlags(ea) & FUNC_LIB: + if GetFunctionFlags(ea) & FUNC_LIB: + continue + + if disasm_match(ea, "movsx"): + ida_log("%08x: found sign extended move" % ea) + set_cmt(ea, "sign extended move", False) + write_line("%08x:sign extended move" % ea) + elif state == 0 and instruction_match(ea, "shr", "ecx", "2"): + state = 1 + window = 0 + elif state == 1 and disasm_match(ea, "rep movsd"): + state = 2 + window = 0 + found_ea = ea + elif state == 2 and instruction_match(ea, "and", "ecx", "3"): + state = 3 + window = 0 + elif state == 3 and disasm_match(ea, "rep movsb"): + ida_log("%08x: found memcpy" % found_ea) + set_cmt(ea, "inline memcpy()", False) + write_line("%08x: inline memcpy" % found_ea) + found_ea = state = window = 0 + + if window > 15: + state = window = 0 + + if state != 0: + window += 1 ida_log("done. looked at %d heads." % processed) ida_log("looking for potentially interesting API calls now.") # format of functions dictionary is function name: format string arg number # fill this from google search: +run-time.library +security.note site:msdn.microsoft.com +# [cm] my own google dork: "Security Warning" intitle:function site:msdn.microsoft.com +# "Security note" crt site:msdn.microsoft.com functions = \ { - "fread" : {}, - "gets" : {}, - "lstrcat" : {}, - "lstrcpy" : {}, - "mbscat" : {}, - "mbscpy" : {}, - "mbsncat" : {}, - "memcpy" : {}, - #"snprintf" : {"fs_arg": 3}, - #"snwprintf" : {"fs_arg": 3}, - "sprintf" : {"fs_arg": 2}, - "sscanf" : {"fs_arg": 2}, - "strcpy" : {}, - "strcat" : {}, - "StrCatBuf" : {}, - "strncat" : {}, - "swprintf" : {"fs_arg": 2}, - "swscanf" : {"fs_arg": 2}, - "vfprintf" : {"fs_arg": 2}, - "vfwprintf" : {"fs_arg": 2}, - "vprintf" : {"fs_arg": 1}, - "vwprintf" : {"fs_arg": 1}, - "vsprintf" : {"fs_arg": 2}, - #"vsnprintf" : {"fs_arg": 3}, - #"vsnwprintf" : {"fs_arg": 3}, - "vswprintf" : {"fs_arg": 2}, - "wcscat" : {}, - "wcsncat" : {}, - "wcscpy" : {}, - "wsprintfA" : {"fs_arg": 2}, - "wsprintfW" : {"fs_arg": 2}, - "wvsprintfA" : {"fs_arg": 2}, - "wvsprintfW" : {"fs_arg": 2}, +# insecure by default + "gets" : {}, + "getws" : {}, + +# exec functions + "execl" : {"cmd_name": 1}, + "wexecl" : {"cmd_name": 1}, + "execv" : {"cmd_name": 1}, + "wexecv" : {"cmd_name": 1}, + "WinExec" : {"cmd_name": 1}, + "ShellExecute" : {}, + "ShellExecuteEx" : {}, + "CreateProcess" : {"cmd_name": 2}, + "CreateProcessAsUser": {"cmd_name": 2}, + "CreateProcessWithLogon" : {"cmd_name": 2}, + +# memory copy functions + "memcpy" : {"size": 3}, + "wmemcpy" : {"size": 3}, + "VirtualAllocEx" : {"size": 3}, + "VirtualAlloc" : {"size": 2}, + "VirtualAllocExNuma": {"size": 2}, + "LocalAlloc" : {"size": 2}, + "HeapAlloc" : {"size": 3}, + "CopyMemory" : {"size": 3}, + +# string functions + "lstrcat" : {}, + "lstrcat" : {}, + "lstrcpy" : {}, + "lstrlen" : {}, + "lstrlen" : {}, + "mbscat" : {}, + "mbscpy" : {}, + "mbsncpy" : {"size": 3}, + "mbsnbcpy" : {"size": 3}, + "mbsncat" : {}, + "mbsstr_l" : {}, + "RtlInitString" : {}, + "SHAnsiToAnsi" : {"size": 3}, + "SHAnsiToUnicode" : {"size": 3}, + "SHUnicodeToUnicode": {"size": 3}, + "SHUnicodeToAnsi" : {"size": 3}, + "strcpy" : {}, + "strncpy" : {"size": 3}, + "strcat" : {}, + "StrCatBuf" : {}, + "StrCatChain" : {}, + "StrCpyN" : {}, + "StrCpyN" : {}, + "strcpy" : {}, + "strncat" : {"size": 3}, + "strstr" : {}, + "wcscat" : {}, + "wcsstr" : {}, + "wcsncat" : {}, + "wcscpy" : {}, + "wcsncpy" : {"size": 3}, + "CompareStringWrapW": {}, + +# format strings + "printf" : {"fs_arg": 1}, + "wprintf" : {"fs_arg": 1}, + "snprintf" : {"fs_arg": 3}, + "snwprintf" : {"fs_arg": 3}, + "scanf" : {"fs_arg": 1}, + "sprintf" : {"fs_arg": 2}, + "sscanf" : {"fs_arg": 2}, + "swprintf" : {"fs_arg": 2}, + "swscanf" : {"fs_arg": 2}, + "vfprintf" : {"fs_arg": 2}, + "vfwprintf" : {"fs_arg": 2}, + "vprintf" : {"fs_arg": 1}, + "vwprintf" : {"fs_arg": 1}, + "vsprintf" : {"fs_arg": 2}, + "vsnprintf" : {"fs_arg": 3}, + "vsnwprintf" : {"fs_arg": 3}, + "vswprintf" : {"fs_arg": 2}, + "wsprintf" : {"fs_arg": 2}, + "wsprintf" : {"fs_arg": 2}, + "wvsprintf" : {"fs_arg": 2}, + "wvsprintf" : {"fs_arg": 2}, + "wvnsprintf" : {"fs_arg": 3}, + "wnsprintf" : {"fs_arg": 3}, } -prefixes = ["", "_", "__imp_", "__imp__"] +prefixes = ["", "_", "__imp_", "__imp__"] +suffixes = ["", "A", "W"] -# for every function we are interested in. +# For every function we are interested in. for func in functions: - # enumerate all possible prefixes. - for prefix in prefixes: - full_name = prefix + func - location = LocByName(full_name) - - if location == BADADDR: - continue - - ida_log("enumerating xrefs to %s" % full_name) - - for xref in list(CodeRefsTo(location, True)) + list(DataRefsTo(location)): - if GetMnem(xref) in ("call", "jmp"): - # ensure the xref does not exist within a known library routine. - if GetFunctionFlags(ea) and GetFunctionFlags(xref) & FUNC_LIB: - continue - - ### - ### peek a call with format string arguments - ### - - if functions[func].has_key("fs_arg"): - fs_arg = functions[func]["fs_arg"] - - format_string = get_arg(xref, fs_arg) - - # format string must be resolved at runtime. - if format_string == BADADDR: - ida_log("%08x format string must be resolved at runtime" % xref) - write_line("%08x:10:%s" % (xref, func)) - - # XXX - we have to escape '%' chars here otherwise 'print', which wraps around 'Message()' will - # incorrectly dereference from the stack and potentially crash the script. - else: - format_string = str(format_string).replace("%", "%%") - - # format string found. - if format_string.find("%s") != -1: - format_string = format_string.replace("\n", "") - ida_log("%08x favorable format string found '%s'" % (xref, format_string)) - write_line("%08x:%d:%s %s" % (xref, token_count(format_string)+fs_arg, func, format_string)) - - ### - ### peek a non format string call - ### - - else: - ida_log("%08x found call to '%s'" % (xref, func)) - write_line("%08x:3:%s" % (xref, func)) + + # enumerate all possibile suffixes. + for prefix in prefixes: + + # enumerate all possible prefixes. + for suffix in suffixes: + full_name = prefix + func + suffix + #ida_log("library %s" % full_name) + location = LocByName(full_name) + + if location == BADADDR: + continue + + ida_log("enumerating xrefs to %s" % full_name) + + for xref in list(CodeRefsTo(location, True)) + list(DataRefsTo(location)): + if GetMnem(xref) in ("call", "jmp"): + # Ensure the xref does not exist within a known library routine. + # if GetFunctionFlags(ea) and GetFunctionFlags(xref) & FUNC_LIB: + + #if GetFunctionFlags(xref) & FUNC_LIB: + # continue + + ### + ### peek a call with format string arguments + ### + if functions[func].has_key("fs_arg"): + fs_arg = functions[func]["fs_arg"] + + format_string = get_arg(xref, fs_arg, 'S') + + # format string must be resolved at runtime. + if format_string == BADADDR: + ida_log("%08x format string must be resolved at runtime" % xref) + write_line("%08x: %s" % (xref, full_name)) + + # XXX - we have to escape '%' chars here otherwise 'print', which wraps around 'Message()' will + # incorrectly dereference from the stack and potentially crash the script. + else: + format_string = str(format_string).replace("%", "%%") + + # format string found. + if format_string.find("%s") != -1: + format_string = format_string.replace("\n", "") + ida_log("%08x favorable format string found '%s'" % (xref, format_string)) + write_line("%08x:%d:%s %s" % (xref, token_count(format_string), full_name, format_string)) + # + # TODO: get cmd_name string + # + elif functions[func].has_key("cmd_name"): + cmd_name = functions[func]["cmd_name"] + + cmd = get_arg(xref, cmd_name, 'S') + + if cmd == BADADDR: + ida_log("%08x command must be resolved at runtime" % xref) + write_line("%08x: %s" % (xref, full_name)) + else: + ida_log("%08x found call to '%s' with static command: %d" % (xref, full_name, cmd)) + write_line("%08x: %s command: %s" % (xref, full_name, cmd)) + + # + # get static size value + # + elif functions[func].has_key("size"): + size_arg = functions[func]["size"] + + size = get_arg(xref, size_arg, 'I') + + if size == BADADDR: + ida_log("%08x size must be resolved at runtime" % xref) + write_line("%08x: %s" % (xref, full_name)) + else: + ida_log("%08x found call to '%s' with static size: %d" % (xref, full_name, size)) + write_line("%08x: %s size: %d" % (xref, full_name, size)) + + ### + ### peek a non format string call + ### + else: + ida_log("%08x found call to '%s'" % (xref, full_name)) + write_line("%08x: %s" % (xref, full_name)) peek_file.close() -print "done." \ No newline at end of file +ida_log("done.") \ No newline at end of file diff --git a/proc_peek_recon_db.py b/proc_peek_recon_db.py index 138f818..cf07047 100644 --- a/proc_peek_recon_db.py +++ b/proc_peek_recon_db.py @@ -34,34 +34,42 @@ ### Support Functions ### -def get_arg (ea, arg_num): - arg_index = 1 - - while True: - ea = PrevNotTail(ea) - - if GetMnem(ea) == "push": - if arg_index == arg_num: - dref = Dfirst(ea) - - if dref == BADADDR: - return dref - - return read_string(dref) - - arg_index += 1 +# +# Type define the argument data type; +# S -> string +# I -> num +# +def get_arg(ea, arg_num, type): + arg_index = 1 + + while True: + ea = PrevNotTail(ea) + if GetMnem(ea) == "push": + if arg_index == arg_num: + + # string parameter + if type == 'S': + dref = Dfirst(ea) + if dref == BADADDR: + return dref + return read_string(dref) + + # number parameter + elif type == 'I': + return read_size(ea) + + arg_index += 1 def instruction_match (ea, mnem=None, op1=None, op2=None, op3=None): - if mnem and mnem != GetMnem(ea): - return False + if mnem and mnem != GetMnem(ea): + return False - if op1 and op1 != GetOpnd(ea, 0): return False - if op2 and op2 != GetOpnd(ea, 1): return False - if op3 and op3 != GetOpnd(ea, 2): return False - - return True + if op1 and op1 != GetOpnd(ea, 0): return False + if op2 and op2 != GetOpnd(ea, 1): return False + if op3 and op3 != GetOpnd(ea, 2): return False + return True def disasm_match (ea, needle): disasm_line = GetDisasm(ea) @@ -75,230 +83,342 @@ def disasm_match (ea, needle): return True +def read_size(ea): + s = GetOpnd(ea, 0) + if s.endswith("h"): + return int(s.rstrip('h'), 16) + else: + return BADADDR def read_string (ea): - s = "" - - while True: - byte = Byte(ea) - - if byte == 0 or byte < 32 or byte > 126: - break - - s += chr(byte) - ea += 1 - - return s - + s = "" + string_type = GetStringType(ea) + if string_type == ASCSTR_C: + while True: + byte = Byte(ea) + + if byte == 0: #or byte < 32 or byte > 126: + break + + if byte == 0x0d: s += "" + elif byte == 0x0a: s += "" + else: s += chr(byte) + + ea += 1 + return s + elif string_type == ASCSTR_UNICODE: + #TODO + while True: + word = Word(ea) + if byte == 0: + break + else: s += word + ea += 2 + return "unicode string: " + s + + elif string_type == ASCSTR_PASCAL: + byte = Byte(ea) + for i in range (1, byte): + s += Byte(ea) + return "pascal string: " + s def token_count (format_string): - return format_string.count("%") - format_string.count("%%") + return format_string.count("%") - format_string.count("%%") def ida_log (message): - print "RECON> " + message + print "RECON> " + message def add_recon (mysql, module_id, offset, stack_depth, reason, status): - # escape single quotes and backslashes in fields that might have them. - reason = reason.replace("\\", "\\\\").replace("'", "\\'") - - sql = " INSERT INTO pp_recon" - sql += " SET module_id = '%d'," % module_id - sql += " offset = '%d'," % offset - sql += " stack_depth = '%d'," % stack_depth - sql += " reason = '%s'," % reason - sql += " status = '%s'," % status - sql += " notes = ''" - - cursor = mysql.cursor() - - try: - cursor.execute(sql) - except MySQLdb.Error, e: - ida_log("MySQL error %d: %s" % (e.args[0], e.args[1])) - ida_log(sql) - return False + # escape single quotes and backslashes in fields that might have them. + reason = reason.replace("\\", "\\\\").replace("'", "\\'") - cursor.close() - return True + sql = " INSERT INTO pp_recon" + sql += " SET module_id = '%d'," % module_id + sql += " offset = '%d'," % offset + sql += " stack_depth = '%d'," % stack_depth + sql += " reason = '%s'," % reason + sql += " status = '%s'," % status + sql += " notes = ''" + + cursor = mysql.cursor() + + try: + cursor.execute(sql) + except MySQLdb.Error, e: + ida_log("MySQL error %d: %s" % (e.args[0], e.args[1])) + ida_log(sql) + return False + cursor.close() + return True ######################################################################################################################## ### Meat and Potatoes ### def meat_and_potatoes (mysql): - # init some local vars. - window = state = found_ea = processed = 0 - - # calculate the current modules base address. - # XXX - cheap hack, the subtraction is for the PE header size. - base_address = MinEA() - 0x1000 - - # create a database entry for the current module. - cursor = mysql.cursor() - - try: - cursor.execute("INSERT INTO pp_modules SET name = '%s', base = '%d', notes = ''" % (GetInputFile(), base_address)) - except MySQLdb.Error, e: - ida_log("MySQL error %d: %s" % (e.args[0], e.args[1])) - ida_log(sql) - return - - # save the module ID we just created. - module_id = cursor.lastrowid - - cursor.close() - - ida_log("searching for inline memcpy()'s and sign extended moves (movsx).") - for ea in Heads(MinEA(), MaxEA()): - processed += 1 - - # we don't care about instructions within known library routines. - if GetFunctionFlags(ea) and GetFunctionFlags(ea) & FUNC_LIB: - continue - - if disasm_match(ea, "movsx"): - ida_log("%08x: found sign extended move" % ea) - - if not add_recon(mysql, module_id, ea - base_address, 3, "sign extended mov", "new"): - return - - if state == 0 and instruction_match(ea, "shr", "ecx", "2"): - # this is a good place to watch the inline strcpy since it gets executed only once and we can see the - # original size value prior to division by 4. - state = 1 - window = 0 - found_ea = ea - - elif state == 1 and disasm_match(ea, "rep movsd"): - state = 2 - window = 0 - - elif state == 2 and instruction_match(ea, "and", "ecx", "3"): - state = 3 - window = 0 - - elif state == 3 and disasm_match(ea, "rep movsb"): - ida_log("%08x: found memcpy" % found_ea) - set_cmt(found_ea, "inline memcpy()", False) - - if not add_recon(mysql, module_id, found_ea - base_address, 5, "inline memcpy", "new"): - return - - found_ea = state = window = 0 - - if window > 15: - state = window = 0 - - if state != 0: - window += 1 - - ida_log("done. looked at %d heads." % processed) - ida_log("looking for potentially interesting API calls now.") - - # format of functions dictionary is function name: format string arg number - # XXX - fill this from google search: +run-time.library +security.note site:msdn.microsoft.com - functions = \ - { - "fread" : {}, - "gets" : {}, - "lstrcat" : {}, - "lstrcpy" : {}, - "mbscat" : {}, - "mbscpy" : {}, - "mbsncat" : {}, - "memcpy" : {}, - #"snprintf" : {"fs_arg": 3}, - #"snwprintf" : {"fs_arg": 3}, - "sprintf" : {"fs_arg": 2}, - "sscanf" : {"fs_arg": 2}, - "strcpy" : {}, - "strcat" : {}, - "StrCatBuf" : {}, - "strncat" : {}, - "swprintf" : {"fs_arg": 2}, - "swscanf" : {"fs_arg": 2}, - "vfprintf" : {"fs_arg": 2}, - "vfwprintf" : {"fs_arg": 2}, - "vprintf" : {"fs_arg": 1}, - "vwprintf" : {"fs_arg": 1}, - "vsprintf" : {"fs_arg": 2}, - #"vsnprintf" : {"fs_arg": 3}, - #"vsnwprintf" : {"fs_arg": 3}, - "vswprintf" : {"fs_arg": 2}, - "wcscat" : {}, - "wcsncat" : {}, - "wcscpy" : {}, - "wsprintfA" : {"fs_arg": 2}, - "wsprintfW" : {"fs_arg": 2}, - "wvsprintfA" : {"fs_arg": 2}, - "wvsprintfW" : {"fs_arg": 2}, - } - - prefixes = ["", "_", "__imp_", "__imp__"] - - # for every function we are interested in. - for func in functions: - # enumerate all possible prefixes. - for prefix in prefixes: - full_name = prefix + func - location = LocByName(full_name) - - if location == BADADDR: - continue - - ida_log("enumerating xrefs to %s" % full_name) - - for xref in CodeRefsTo(location, True) + DataRefsTo(location): - if GetMnem(xref) in ("call", "jmp"): - # ensure the xref does not exist within a known library routine. - flags = GetFunctionFlags(xref) - if flags: - if flags & FUNC_LIB: - continue - - ### - ### peek a call with format string arguments - ### - - if functions[func].has_key("fs_arg"): - fs_arg = functions[func]["fs_arg"] - - format_string = get_arg(xref, fs_arg) - - # format string must be resolved at runtime. - if format_string == BADADDR: - ida_log("%08x format string must be resolved at runtime" % xref) - - if not add_recon(mysql, module_id, xref - base_address, 10, func, "new"): - return - - # XXX - we have to escape '%' chars here otherwise 'print', which wraps around 'Message()' will - # incorrectly dereference from the stack and potentially crash the script. - else: - format_string = str(format_string).replace("%", "%%") - - # format string found. - if format_string.find("%s") != -1: - format_string = format_string.replace("\n", "") - ida_log("%08x favorable format string found '%s'" % (xref, format_string)) - - if not add_recon(mysql, module_id, xref - base_address, token_count(format_string)+fs_arg, "%s %s" % (func, format_string), "new"): - return - - ### - ### peek a non format string call - ### - - else: - ida_log("%08x found call to '%s'" % (xref, func)) - - if not add_recon(mysql, module_id, xref - base_address, 3, func, "new"): - return - - ida_log("done.") + # init some local vars. + window = state = found_ea = processed = 0 + + # calculate the current modules base address. + # XXX - cheap hack, the subtraction is for the PE header size. + base_address = MinEA() - 0x1000 + + # create a database entry for the current module. + cursor = mysql.cursor() + + try: + cursor.execute("INSERT INTO pp_modules SET name = '%s', base = '%d', notes = ''" % (GetInputFile(), base_address)) + except MySQLdb.Error, e: + ida_log("MySQL error %d: %s" % (e.args[0], e.args[1])) + ida_log(sql) + cursor.close() + return + + # save the module ID we just created. + module_id = cursor.lastrowid + + cursor.close() + + ida_log("searching for inline memcpy()'s and sign extended moves (movsx).") + for ea in Heads(MinEA(), MaxEA()): + processed += 1 + + # we don't care about instructions within known library routines. + if GetFunctionFlags(ea) & FUNC_LIB: + continue + + if disasm_match(ea, "movsx"): + ida_log("%08x: found sign extended move" % ea) + + if not add_recon(mysql, module_id, ea - base_address, 3, "sign extended mov", "new"): + return + + if state == 0 and instruction_match(ea, "shr", "ecx", "2"): + # this is a good place to watch the inline strcpy since it gets executed only once and we can see the + # original size value prior to division by 4. + state = 1 + window = 0 + found_ea = ea + + elif state == 1 and disasm_match(ea, "rep movsd"): + state = 2 + window = 0 + + elif state == 2 and instruction_match(ea, "and", "ecx", "3"): + state = 3 + window = 0 + + elif state == 3 and disasm_match(ea, "rep movsb"): + ida_log("%08x: found memcpy" % found_ea) + set_cmt(found_ea, "inline memcpy()", False) + + if not add_recon(mysql, module_id, found_ea - base_address, 5, "inline memcpy", "new"): + return + + found_ea = state = window = 0 + + if window > 15: + state = window = 0 + + if state != 0: + window += 1 + + ida_log("done. looked at %d heads." % processed) + ida_log("looking for potentially interesting API calls now.") + + # format of functions dictionary is function name: format string arg number + # fill this from google search: +run-time.library +security.note site:msdn.microsoft.com + # [cm] my own google dork: "Security Warning" intitle:function site:msdn.microsoft.com + # "Security note" crt site:msdn.microsoft.com + functions = \ + { + # insecure by default + "gets" : {}, + "getws" : {}, + + # exec functions + "execl" : {"cmd_name": 1}, + "wexecl" : {"cmd_name": 1}, + "execv" : {"cmd_name": 1}, + "wexecv" : {"cmd_name": 1}, + "WinExec" : {"cmd_name": 1}, + "ShellExecute" : {}, + "ShellExecuteEx" : {}, + "CreateProcess" : {"cmd_name": 2}, + "CreateProcessAsUser": {"cmd_name": 2}, + "CreateProcessWithLogon" : {"cmd_name": 2}, + + # memory copy functions + "memcpy" : {"size": 3}, + "wmemcpy" : {"size": 3}, + "VirtualAllocEx" : {"size": 3}, + "VirtualAlloc" : {"size": 2}, + "VirtualAllocExNuma": {"size": 2}, + "LocalAlloc" : {"size": 2}, + "HeapAlloc" : {"size": 3}, + "CopyMemory" : {"size": 3}, + + # string functions + "lstrcat" : {}, + "lstrcat" : {}, + "lstrcpy" : {}, + "lstrlen" : {}, + "lstrlen" : {}, + "mbscat" : {}, + "mbscpy" : {}, + "mbsncpy" : {"size": 3}, + "mbsnbcpy" : {"size": 3}, + "mbsncat" : {}, + "mbsstr_l" : {}, + "RtlInitString" : {}, + "SHAnsiToAnsi" : {"size": 3}, + "SHAnsiToUnicode" : {"size": 3}, + "SHUnicodeToUnicode": {"size": 3}, + "SHUnicodeToAnsi" : {"size": 3}, + "strcpy" : {}, + "strncpy" : {"size": 3}, + "strcat" : {}, + "StrCatBuf" : {}, + "StrCatChain" : {}, + "StrCpyN" : {}, + "StrCpyN" : {}, + "strcpy" : {}, + "strncat" : {"size": 3}, + "strstr" : {}, + "wcscat" : {}, + "wcsstr" : {}, + "wcsncat" : {}, + "wcscpy" : {}, + "wcsncpy" : {"size": 3}, + "CompareStringWrapW": {}, + + # format strings + "printf" : {"fs_arg": 1}, + "wprintf" : {"fs_arg": 1}, + "snprintf" : {"fs_arg": 3}, + "snwprintf" : {"fs_arg": 3}, + "scanf" : {"fs_arg": 1}, + "sprintf" : {"fs_arg": 2}, + "sscanf" : {"fs_arg": 2}, + "swprintf" : {"fs_arg": 2}, + "swscanf" : {"fs_arg": 2}, + "vfprintf" : {"fs_arg": 2}, + "vfwprintf" : {"fs_arg": 2}, + "vprintf" : {"fs_arg": 1}, + "vwprintf" : {"fs_arg": 1}, + "vsprintf" : {"fs_arg": 2}, + "vsnprintf" : {"fs_arg": 3}, + "vsnwprintf" : {"fs_arg": 3}, + "vswprintf" : {"fs_arg": 2}, + "wsprintf" : {"fs_arg": 2}, + "wsprintf" : {"fs_arg": 2}, + "wvsprintf" : {"fs_arg": 2}, + "wvsprintf" : {"fs_arg": 2}, + "wvnsprintf" : {"fs_arg": 3}, + "wnsprintf" : {"fs_arg": 3}, + } + + prefixes = ["", "_", "__imp_", "__imp__"] + suffixes = ["", "A", "W"] + + # for every function we are interested in. + for func in functions: + + # enumerate all possibile suffixes. + for suffix in suffixes: + + # enumerate all possible prefixes. + for prefix in prefixes: + full_name = prefix + func + suffix + location = LocByName(full_name) + + if location == BADADDR: + continue + + ida_log("enumerating xrefs to %s" % full_name) + + for xref in list(CodeRefsTo(location, True)) + list(DataRefsTo(location)): + if GetMnem(xref) in ("call", "jmp"): + # ensure the xref does not exist within a known library routine. + flags = GetFunctionFlags(xref) + if flags: + if flags & FUNC_LIB: + continue + + ### + ### peek a call with format string arguments + ### + if functions[func].has_key("fs_arg"): + fs_arg = functions[func]["fs_arg"] + + format_string = get_arg(xref, fs_arg, 'S') + + # format string must be resolved at runtime. + if format_string == BADADDR: + ida_log("%08x format string must be resolved at runtime" % xref) + + if not add_recon(mysql, module_id, xref - base_address, 0, full_name, "new"): + return + + # XXX - we have to escape '%' chars here otherwise 'print', which wraps around 'Message()' will + # incorrectly dereference from the stack and potentially crash the script. + else: + format_string = str(format_string).replace("%", "%%") + + # format string found. + if format_string.find("%s") != -1: + format_string = format_string.replace("\n", "") + ida_log("%08x favorable format string found '%s'" % (xref, format_string)) + + if not add_recon(mysql, module_id, xref - base_address, token_count(format_string)+fs_arg, "%s - fs: %s" % (full_name, format_string), "new"): + return + # + # TODO: get cmd_name string + # + elif functions[func].has_key("cmd_name"): + cmd_name = functions[func]["cmd_name"] + + cmd = get_arg(xref, cmd_name, 'S') + + if cmd == BADADDR: + ida_log("%08x command must be resolved at runtime" % xref) + if not add_recon(mysql, module_id, xref - base_address, cmd_name, full_name, "new"): + return + else: + ida_log("%08x found call to '%s' with static command: %d" % (xref, full_name, cmd)) + if not add_recon(mysql, module_id, xref - base_address, cmd_name, "%s - cmd: %s" % (full_name, cmd_name), "new"): + return + # + # get static size value + # + elif functions[func].has_key("size"): + size_arg = functions[func]["size"] + + size = get_arg(xref, size_arg, 'I') + + if size == BADADDR: + ida_log("%08x size must be resolved at runtime" % xref) + if not add_recon(mysql, module_id, xref - base_address, size_arg, full_name, "new"): + return + else: + ida_log("%08x found call to '%s' with static size: %d" % (xref, full_name, size)) + if not add_recon(mysql, module_id, xref - base_address, size_arg, "%s - size: %d" % (full_name, size), "new"): + return + + ### + ### peek a non format string call + ### + + else: + ida_log("%08x found call to '%s'" % (xref, func)) + + if not add_recon(mysql, module_id, xref - base_address, 0, full_name, "new"): + return + + ida_log("done.") ######################################################################################################################## @@ -306,36 +426,36 @@ def meat_and_potatoes (mysql): ### def mysql_connect (): - mysql_host = None - mysql_user = None - mysql_pass = None + mysql_host = None + mysql_user = None + mysql_pass = None - if not mysql_host: - mysql_host = AskStr("localhost", "MySQL IP address or hostname:") + if not mysql_host: + mysql_host = AskStr("localhost", "MySQL IP address or hostname:") - if not mysql_host: - return -1 + if not mysql_host: + return -1 - if not mysql_user: - mysql_user = AskStr("root", "MySQL username:") + if not mysql_user: + mysql_user = AskStr("root", "MySQL username:") - if not mysql_user: - return -1 + if not mysql_user: + return -1 - if not mysql_pass: - mysql_pass = AskStr("", "MySQL password:") + if not mysql_pass: + mysql_pass = AskStr("", "MySQL password:") - if not mysql_pass: - return -1 + if not mysql_pass: + return -1 # connect to mysql - try: - mysql = MySQLdb.connect(host=mysql_host, user=mysql_user, passwd=mysql_pass, db="paimei") - except MySQLdb.OperationalError, err: - ida_log("failed connecting to MySQL server: %s" % err[1]) - mysql = None + try: + mysql = MySQLdb.connect(host=mysql_host, user=mysql_user, passwd=mysql_pass, db="paimei") + except MySQLdb.OperationalError, err: + ida_log("failed connecting to MySQL server: %s" % err[1]) + mysql = None - return mysql + return mysql ######################################################################################################################## @@ -343,15 +463,16 @@ def mysql_connect (): ### def main (): - mysql = mysql_connect() - - if mysql == -1: - ida_log("cancelled by user.") - elif mysql == None: - # error message already printed. - return - else: - meat_and_potatoes(mysql) - mysql.close() + mysql = mysql_connect() + + if mysql == -1: + ida_log("cancelled by user.") + elif mysql == None: + # error message already printed. + return + else: + meat_and_potatoes(mysql) + + mysql.close() main() \ No newline at end of file From b20e0c45b11d2cf1ea5d553722e6b16df439ab39 Mon Sep 17 00:00:00 2001 From: Cristiano Maruti Date: Wed, 24 Oct 2012 15:22:17 +0200 Subject: [PATCH 2/5] Added a disconnect menu item and other minor fixes --- console/PAIMEIconsole.pyw | 963 +++++++++++++++++----------------- console/modules/PAIMEIpeek.py | 2 +- 2 files changed, 489 insertions(+), 476 deletions(-) diff --git a/console/PAIMEIconsole.pyw b/console/PAIMEIconsole.pyw index a45cbbf..3f454c3 100644 --- a/console/PAIMEIconsole.pyw +++ b/console/PAIMEIconsole.pyw @@ -1,5 +1,3 @@ -#!c:\python\python.exe - # # PaiMei Console # Copyright (C) 2006 Pedram Amini @@ -18,9 +16,9 @@ # ''' -@author: Pedram Amini -@license: GNU General Public License 2.0 or later -@contact: pedram.amini@gmail.com +@author: Pedram Amini +@license: GNU General Public License 2.0 or later +@contact: pedram.amini@gmail.com @organization: www.openrce.org ''' @@ -36,605 +34,620 @@ sys.path.append("support") sys.path.append("..") try: - import about + import about - missing_requirement = "MySQLdb" - import mysql_connect_dialog + missing_requirement = "MySQLdb" + import mysql_connect_dialog - missing_requirement = "PaiMei PyDbg" - import pydbg_locale_dialog + missing_requirement = "PaiMei PyDbg" + import pydbg_locale_dialog - missing_requirement = "PaiMei Utilities" - import udraw_connect_dialog + missing_requirement = "PaiMei Utilities" + import udraw_connect_dialog - import pydbg - missing_requirement = False + import pydbg + missing_requirement = False except: - pass + pass # this is the top most handle for accessing the entire console from the shell. class __paimei__: - pass + pass p = paimei = __paimei__() ######################################################################################################################## class persistant_state: - ''' - This shell class is used to serial / unserialize persistant data to / from disk. - ''' + ''' + This shell class is used to serial / unserialize persistant data to / from disk. + ''' - username = None - pydbg_host = None - pydbg_port = None - mysql_host = None - mysql_username = None - mysql_password = None - udraw_host = None - udraw_port = None + username = None + pydbg_host = None + pydbg_port = None + mysql_host = None + mysql_username = None + mysql_password = None + udraw_host = None + udraw_port = None ######################################################################################################################## class PAIMEIapp (wx.App): - ''' - The top level wx class that is instantiated. - ''' + ''' + The top level wx class that is instantiated. + ''' - def OnInit (self): - wx.InitAllImageHandlers() + def OnInit (self): + wx.InitAllImageHandlers() - splash = PAIMEIsplash() - splash.Show() + splash = PAIMEIsplash() + splash.Show() - return True + return True ######################################################################################################################## class PAIMEIsplash (wx.SplashScreen): - ''' - Instantiated from PAIMEIapp, simply displays a splash screen and proceeds with creating the main frame - (PAIMEIframe). - ''' + ''' + Instantiated from PAIMEIapp, simply displays a splash screen and proceeds with creating the main frame + (PAIMEIframe). + ''' - def __init__ (self): - bmp = wx.Image("images/splash.png").ConvertToBitmap() - opt = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT + def __init__ (self): + bmp = wx.Image("images/splash.png").ConvertToBitmap() + opt = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT - wx.SplashScreen.__init__(self, bmp, opt, 2000, None, -1) + wx.SplashScreen.__init__(self, bmp, opt, 2000, None, -1) - self.Bind(wx.EVT_CLOSE, self.splash_finished) + self.Bind(wx.EVT_CLOSE, self.splash_finished) - def splash_finished(self, evt): - ''' - This routine is called once the splash screen has timed out. - ''' + def splash_finished(self, evt): + ''' + This routine is called once the splash screen has timed out. + ''' - self.Hide() + self.Hide() - if missing_requirement: - dlg = wx.MessageDialog(None, - "Required module missing: %s\nRun the __install_requirements.py script." % missing_requirement, - "Initialization Failed", - wx.OK | wx.ICON_ERROR) - dlg.ShowModal() - dlg.Destroy() - sys.exit(1) + if missing_requirement: + dlg = wx.MessageDialog(None, + "Required module missing: %s\nRun the __install_requirements.py script." % missing_requirement, + "Initialization Failed", + wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + sys.exit(1) - frame = PAIMEIframe(parent=None, title="PAIMEIconsole") - frame.Maximize(True) - frame.Show(True) - evt.Skip() + frame = PAIMEIframe(parent=None, title="PAIMEIconsole") + frame.Maximize(True) + frame.Show(True) + evt.Skip() ######################################################################################################################## class PAIMEIframe (wx.Frame): - ''' - Instantiated from PAIMEIsplash, this is the main frame of the PAIMEIconsole. A menu, status bar and custom listbook - (PAIMEIlistbook) are created and compromise the general layout of the application. This class also defines dialog - boxes to handle the menu events. - ''' - - documented_properties = { - "paimei" : "Top most handle under which every other attribute is accessible. A shortcut to this variable is available through 'p'.", - ".main_frame" : "Handle to main frame. Every component in the console is accessible under this variable.", - ".current_module" : "Handle to list book page of the currently activated module.", - ".main_frame.mysql" : "Handle to MySQL connection.", - ".main_frame.pydbg" : "Handle to PyDbg, customized by locale. You should not directly modify this variable, instead use copy.copy() to obtain a local PyDbg object.", - ".main_frame.udraw" : "Handle to uDraw(Graph) connector.", - ".main_frame.modules" : "Dictionary of currently loaded PaiMei modules.", - ".main_frame.cwd" : "Top level working directory of the PaiMei console (the directory containg PAIMEIconsole.pyw).", - ".main_frame.username" : "Current console username.", - } - - username = None - mysql = None - mysql_host = None - mysql_username = None - mysql_password = None - pydbg = None - pydbg_host = None - pydbg_port = None - udraw = None - udraw_host = None - udraw_port = None - modules = {} - cwd = os.getcwd() - - def __init__ (self, *args, **kwds): - global paimei - paimei.main_frame = self - - self.pydbg = pydbg.pydbg() - - # restore stored connection options if available. - try: - fh = open(".options", "rb") - ps = cPickle.loads(fh.read()) - fh.close() - - self.username = ps.username - self.mysql_host = ps.mysql_host - self.mysql_username = ps.mysql_username - self.mysql_password = ps.mysql_password - self.pydbg_host = ps.pydbg_host - self.pydbg_port = ps.pydbg_port - self.udraw_host = ps.udraw_host - self.udraw_port = ps.udraw_port - except: - pass - - # documentation help choices. - self.docs_modules_choices = [] - self.docs_properties_choices = {} - self.selected_module = None - - self.parent = kwds["parent"] - self.title = kwds["title"] - - wx.Frame.__init__(self, self.parent, -1, self.title) - self.CenterOnScreen() - - # set program icon. - self.SetIcon(wx.Icon(self.cwd + "/images/paimei.ico", wx.BITMAP_TYPE_ICO)) - - # instantiate and set the status bar. - self.status_bar = PAIMEIstatusbar(parent=self) - self.SetStatusBar(self.status_bar) - - # if the username was restored, show that in the status bar. - self.status_bar.SetStatusText("User: %s" % self.username, 5) - - connect_menu = wx.Menu() - connect_menu.Append(101, "&MySQL Connect", "Connect to MySQL server.") - connect_menu.Append(102, "&PyDbg Locale", "Set PyDbg locale.") - connect_menu.Append(103, "&Set Username", "Tell PaiMei who you are.") - connect_menu.Append(104, "&uDraw(Graph) Connect", "Connect to uDraw(Graph) server.") - connect_menu.AppendSeparator() - connect_menu.Append(105, "Save &Options", "Save connection settings and automatically restore them on load.") - connect_menu.Append(106, "&Restore Connections", "Restore connections from saved settings.") - - advanced_menu = wx.Menu() - advanced_menu.Append(201, "Clear Log", "Clear the current modules log.") - advanced_menu.Append(202, "Toggle Python &Shell", "Toggle interactive Python shell.") - - help_menu = wx.Menu() - help_menu.Append(901, "&About", "About PAIMEIconsole.") - - self.menu = wx.MenuBar() - self.menu.Append(connect_menu, "&Connections") - self.menu.Append(advanced_menu, "&Advanced") - self.menu.Append(help_menu, "&Help") - - self.SetMenuBar(self.menu) - - # set a handler for when menu items are highlighted. - self.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnMenuHighlight) - - # menu events. - self.Bind(wx.EVT_MENU, self.OnMenuMySQLConnect, id=101) - self.Bind(wx.EVT_MENU, self.OnMenuPyDbgLocale, id=102) - self.Bind(wx.EVT_MENU, self.OnMenuSetUsername, id=103) - self.Bind(wx.EVT_MENU, self.OnMenuUDrawConnect, id=104) - self.Bind(wx.EVT_MENU, self.OnMenuSaveOptions, id=105) - self.Bind(wx.EVT_MENU, self.OnMenuRestoreConnections, id=106) - self.Bind(wx.EVT_MENU, self.OnMenuClearLog, id=201) - self.Bind(wx.EVT_MENU, self.OnMenuPythonShell, id=202) - self.Bind(wx.EVT_MENU, self.OnMenuAbout, id=901) - - # splitter stuff. - self.splitter = wx.SplitterWindow(self, -1, style=wx.SP_3D|wx.SP_BORDER|wx.SP_PERMIT_UNSPLIT) - self.shell_window = wx.Panel(self.splitter, -1) - self.lbook_window = wx.Panel(self.splitter, -1) - - # create the listbook before the rest of the controls. - self.lbook = PAIMEIlistbook(parent=self.lbook_window, top=self) - - # fill the doc controls. - module_keys = self.modules.keys() - module_keys.sort() - - # add the top-level variables to the doc control. - self.docs_modules_choices.append("PaiMei") - self.docs_properties_choices["PaiMei"] = {} - for key, var in self.documented_properties.items(): - self.docs_properties_choices["PaiMei"][key] = var - - # step through the loaded modules, and add the documented variables to the doc control. - for mod in module_keys: - module = self.modules[mod] - if hasattr(module, "documented_properties"): - self.docs_modules_choices.append(mod) - self.docs_properties_choices[mod] = {} - - for key, var in module.documented_properties.items(): - self.docs_properties_choices[mod][key] = var - - # create the rest of the controls. - self.pysh = py.shell.Shell(self.shell_window, -1, introText="") - self.modules_staticbox = wx.StaticBox(self.shell_window, -1, "Modules") - self.variables_staticbox = wx.StaticBox(self.shell_window, -1, "Attributes and Functions") - self.description_staticbox = wx.StaticBox(self.shell_window, -1, "Description") - self.docs_modules = wx.Choice(self.shell_window, -1, choices=self.docs_modules_choices) - self.docs_properties = PShellVariableList(self.shell_window, -1, choices=[], top=self) - self.docs_description = wx.TextCtrl(self.shell_window, -1, "", style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_LINEWRAP) - - # bindings. - self.Bind(wx.EVT_CHOICE, self.OnModuleSelected, self.docs_modules) - - # sizers. - self.overall = wx.BoxSizer(wx.VERTICAL) - self.shell_sizer = wx.BoxSizer(wx.HORIZONTAL) - self.shell_docs = wx.BoxSizer(wx.VERTICAL) - self.lbook_sizer = wx.BoxSizer(wx.HORIZONTAL) - - # layout. - description_border = wx.StaticBoxSizer(self.description_staticbox, wx.HORIZONTAL) - variables_border = wx.StaticBoxSizer(self.variables_staticbox, wx.HORIZONTAL) - modules_border = wx.StaticBoxSizer(self.modules_staticbox, wx.HORIZONTAL) - description_border.Add(self.docs_description, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - variables_border.Add(self.docs_properties, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - modules_border.Add(self.docs_modules, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - self.lbook_sizer.Add(self.lbook, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - self.lbook_window.SetAutoLayout(True) - self.lbook_window.SetSizer(self.lbook_sizer) - self.lbook_sizer.Fit(self.lbook_window) - self.lbook_sizer.SetSizeHints(self.lbook_window) - self.shell_sizer.Add(self.pysh, 3, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - self.shell_docs.Add(modules_border, 0, wx.EXPAND, 0) - self.shell_docs.Add(variables_border, 1, wx.EXPAND, 0) - self.shell_docs.Add(description_border, 1, wx.EXPAND, 0) - self.shell_sizer.Add(self.shell_docs, 1, wx.EXPAND, 0) - self.shell_window.SetAutoLayout(True) - self.shell_window.SetSizer(self.shell_sizer) - self.shell_sizer.Fit(self.shell_window) - self.shell_sizer.SetSizeHints(self.shell_window) - self.splitter.SplitHorizontally(self.lbook_window, self.shell_window) - - # hide the shell by default. - self.splitter.Unsplit(self.shell_window) - - self.overall.Add(self.splitter, 1, wx.EXPAND, 0) - self.SetAutoLayout(True) - self.SetSizer(self.overall) - self.overall.Fit(self) - self.overall.SetSizeHints(self) - self.Layout() - - event = wx.SizeEvent(self.GetSize()) - self.ProcessEvent(event) - - - def OnMenuAbout (self, event): - ''' - Event handler for the help\about menu item. - ''' - - dlg = about.about(parent=self) - dlg.ShowModal() - - - def OnMenuClearLog (self, event): - ''' - Event handler for the advanced\clear log menu item. - ''' - - global paimei - - # ensure a page is selected. - if not paimei.current_module: - return - - # ensure the selected module has a log control. - if not hasattr(paimei.current_module, "log"): - return + ''' + Instantiated from PAIMEIsplash, this is the main frame of the PAIMEIconsole. A menu, status bar and custom listbook + (PAIMEIlistbook) are created and compromise the general layout of the application. This class also defines dialog + boxes to handle the menu events. + ''' + + documented_properties = { + "paimei" : "Top most handle under which every other attribute is accessible. A shortcut to this variable is available through 'p'.", + ".main_frame" : "Handle to main frame. Every component in the console is accessible under this variable.", + ".current_module" : "Handle to list book page of the currently activated module.", + ".main_frame.mysql" : "Handle to MySQL connection.", + ".main_frame.pydbg" : "Handle to PyDbg, customized by locale. You should not directly modify this variable, instead use copy.copy() to obtain a local PyDbg object.", + ".main_frame.udraw" : "Handle to uDraw(Graph) connector.", + ".main_frame.modules" : "Dictionary of currently loaded PaiMei modules.", + ".main_frame.cwd" : "Top level working directory of the PaiMei console (the directory containg PAIMEIconsole.pyw).", + ".main_frame.username" : "Current console username.", + } + + username = None + mysql = None + mysql_host = None + mysql_username = None + mysql_password = None + pydbg = None + pydbg_host = None + pydbg_port = None + udraw = None + udraw_host = None + udraw_port = None + modules = {} + cwd = os.getcwd() + + def __init__ (self, *args, **kwds): + global paimei + paimei.main_frame = self + + self.pydbg = pydbg.pydbg() + + # restore stored connection options if available. + try: + fh = open(".options", "rb") + ps = cPickle.loads(fh.read()) + fh.close() + + self.username = ps.username + self.mysql_host = ps.mysql_host + self.mysql_username = ps.mysql_username + self.mysql_password = ps.mysql_password + self.pydbg_host = ps.pydbg_host + self.pydbg_port = ps.pydbg_port + self.udraw_host = ps.udraw_host + self.udraw_port = ps.udraw_port + except: + pass + + # documentation help choices. + self.docs_modules_choices = [] + self.docs_properties_choices = {} + self.selected_module = None + + self.parent = kwds["parent"] + self.title = kwds["title"] + + wx.Frame.__init__(self, self.parent, -1, self.title) + self.CenterOnScreen() + + # set program icon. + self.SetIcon(wx.Icon(self.cwd + "/images/paimei.ico", wx.BITMAP_TYPE_ICO)) + + # instantiate and set the status bar. + self.status_bar = PAIMEIstatusbar(parent=self) + self.SetStatusBar(self.status_bar) + + # if the username was restored, show that in the status bar. + self.status_bar.SetStatusText("User: %s" % self.username, 5) + + connect_menu = wx.Menu() + connect_menu.Append(101, "&MySQL Connect","Connect to MySQL server.") + connect_menu.Append(107, "MySQL &Disconnect","Disconnect from MySQL server.") + connect_menu.Append(102, "&PyDbg Locale""Set PyDbg locale.") + connect_menu.Append(103, "&Set Username", "Tell PaiMei who you are.") + connect_menu.Append(104, "&uDraw(Graph) Connect", "Connect to uDraw(Graph) server.") + connect_menu.AppendSeparator() + connect_menu.Append(105, "Save &Options","Save connection settings and automatically restore them on load.") + connect_menu.Append(106, "&Restore Connections", "Restore connections from saved settings.") + + advanced_menu = wx.Menu() + advanced_menu.Append(201, "Clear Log", "Clear the current modules log.") + advanced_menu.Append(202, "Toggle Python &Shell", "Toggle interactive Python shell.") + + help_menu = wx.Menu() + help_menu.Append(901, "&About", "About PAIMEIconsole.") + + self.menu = wx.MenuBar() + self.menu.Append(connect_menu, "&Connections") + self.menu.Append(advanced_menu, "&Advanced") + self.menu.Append(help_menu, "&Help") + + self.SetMenuBar(self.menu) + + # set a handler for when menu items are highlighted. + self.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnMenuHighlight) + + # menu events. + self.Bind(wx.EVT_MENU, self.OnMenuMySQLConnect, id=101) + self.Bind(wx.EVT_MENU, self.OnMenuMySQLDisconnect, id=107) + self.Bind(wx.EVT_MENU, self.OnMenuPyDbgLocale, id=102) + self.Bind(wx.EVT_MENU, self.OnMenuSetUsername, id=103) + self.Bind(wx.EVT_MENU, self.OnMenuUDrawConnect, id=104) + self.Bind(wx.EVT_MENU, self.OnMenuSaveOptions, id=105) + self.Bind(wx.EVT_MENU, self.OnMenuRestoreConnections, id=106) + self.Bind(wx.EVT_MENU, self.OnMenuClearLog, id=201) + self.Bind(wx.EVT_MENU, self.OnMenuPythonShell, id=202) + self.Bind(wx.EVT_MENU, self.OnMenuAbout, id=901) + + # splitter stuff. + self.splitter = wx.SplitterWindow(self, -1, style=wx.SP_3D|wx.SP_BORDER|wx.SP_PERMIT_UNSPLIT) + self.shell_window = wx.Panel(self.splitter, -1) + self.lbook_window = wx.Panel(self.splitter, -1) + + # create the listbook before the rest of the controls. + self.lbook = PAIMEIlistbook(parent=self.lbook_window, top=self) + + # fill the doc controls. + module_keys = self.modules.keys() + module_keys.sort() + + # add the top-level variables to the doc control. + self.docs_modules_choices.append("PaiMei") + self.docs_properties_choices["PaiMei"] = {} + for key, var in self.documented_properties.items(): + self.docs_properties_choices["PaiMei"][key] = var + + # step through the loaded modules, and add the documented variables to the doc control. + for mod in module_keys: + module = self.modules[mod] + if hasattr(module, "documented_properties"): + self.docs_modules_choices.append(mod) + self.docs_properties_choices[mod] = {} + + for key, var in module.documented_properties.items(): + self.docs_properties_choices[mod][key] = var + + # create the rest of the controls. + self.pysh = py.shell.Shell(self.shell_window, -1, introText="") + self.modules_staticbox = wx.StaticBox(self.shell_window, -1, "Modules") + self.variables_staticbox = wx.StaticBox(self.shell_window, -1, "Attributes and Functions") + self.description_staticbox = wx.StaticBox(self.shell_window, -1, "Description") + self.docs_modules = wx.Choice(self.shell_window, -1, choices=self.docs_modules_choices) + self.docs_properties = PShellVariableList(self.shell_window, -1, choices=[], top=self) + self.docs_description = wx.TextCtrl(self.shell_window, -1, "", style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_LINEWRAP) + + # bindings. + self.Bind(wx.EVT_CHOICE, self.OnModuleSelected, self.docs_modules) + + # sizers. + self.overall = wx.BoxSizer(wx.VERTICAL) + self.shell_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.shell_docs = wx.BoxSizer(wx.VERTICAL) + self.lbook_sizer = wx.BoxSizer(wx.HORIZONTAL) + + # layout. + description_border = wx.StaticBoxSizer(self.description_staticbox, wx.HORIZONTAL) + variables_border = wx.StaticBoxSizer(self.variables_staticbox, wx.HORIZONTAL) + modules_border = wx.StaticBoxSizer(self.modules_staticbox, wx.HORIZONTAL) + description_border.Add(self.docs_description, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + variables_border.Add(self.docs_properties, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + modules_border.Add(self.docs_modules, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + self.lbook_sizer.Add(self.lbook, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + self.lbook_window.SetAutoLayout(True) + self.lbook_window.SetSizer(self.lbook_sizer) + self.lbook_sizer.Fit(self.lbook_window) + self.lbook_sizer.SetSizeHints(self.lbook_window) + self.shell_sizer.Add(self.pysh, 3, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + self.shell_docs.Add(modules_border, 0, wx.EXPAND, 0) + self.shell_docs.Add(variables_border, 1, wx.EXPAND, 0) + self.shell_docs.Add(description_border, 1, wx.EXPAND, 0) + self.shell_sizer.Add(self.shell_docs, 1, wx.EXPAND, 0) + self.shell_window.SetAutoLayout(True) + self.shell_window.SetSizer(self.shell_sizer) + self.shell_sizer.Fit(self.shell_window) + self.shell_sizer.SetSizeHints(self.shell_window) + self.splitter.SplitHorizontally(self.lbook_window, self.shell_window) + + # hide the shell by default. + self.splitter.Unsplit(self.shell_window) + + self.overall.Add(self.splitter, 1, wx.EXPAND, 0) + self.SetAutoLayout(True) + self.SetSizer(self.overall) + self.overall.Fit(self) + self.overall.SetSizeHints(self) + self.Layout() + + event = wx.SizeEvent(self.GetSize()) + self.ProcessEvent(event) + + + def OnMenuAbout (self, event): + ''' + Event handler for the help\about menu item. + ''' + + dlg = about.about(parent=self) + dlg.ShowModal() + + + def OnMenuClearLog (self, event): + ''' + Event handler for the advanced\clear log menu item. + ''' + + global paimei + + # ensure a page is selected. + if not paimei.current_module: + return + + # ensure the selected module has a log control. + if not hasattr(paimei.current_module, "log"): + return + + paimei.current_module.log.SetValue("") + + + def OnMenuMySQLConnect (self, event): + ''' + Event handler for the connections\mysql menu item. + ''' + + dlg = mysql_connect_dialog.mysql_connect_dialog(parent=self) + dlg.ShowModal() - paimei.current_module.log.SetValue("") - - - def OnMenuMySQLConnect (self, event): - ''' - Event handler for the connections\mysql menu item. - ''' - - dlg = mysql_connect_dialog.mysql_connect_dialog(parent=self) - dlg.ShowModal() + + def OnMenuMySQLDisconnect (self, event): + ''' + Event handler for the connections\mysql menu item. + ''' + if self.mysql is None: + self.status_bar.SetStatusText("Not connected to MySQL server.") + else: + self.mysql.close() + self.mysql = None + self.status_bar.SetStatusText("Connection closed.") - def OnMenuPyDbgLocale (self, event): - ''' - Event handler for the connections\pydbg menu item. - ''' + def OnMenuPyDbgLocale (self, event): + ''' + Event handler for the connections\pydbg menu item. + ''' - dlg = pydbg_locale_dialog.pydbg_locale_dialog(parent=self) - dlg.ShowModal() + dlg = pydbg_locale_dialog.pydbg_locale_dialog(parent=self) + dlg.ShowModal() - def OnMenuSetUsername (self, event): - ''' - Event handler for the connections\set username menu item. - ''' + def OnMenuSetUsername (self, event): + ''' + Event handler for the connections\set username menu item. + ''' - if self.username: default = self.username - else: default = "Daniel-San" + if self.username: default = self.username + else: default = "Daniel-San" - dlg = wx.TextEntryDialog(self, \ - "Tell PaiMei who you are. This username is used to tag various actions as belonging to you.", \ - "PaiMei wants to know your name...", \ - default) + dlg = wx.TextEntryDialog(self, \ + "Tell PaiMei who you are. This username is used to tag various actions as belonging to you.", \ + "PaiMei wants to know your name...", \ + default) - if dlg.ShowModal() == wx.ID_OK: - self.username = dlg.GetValue() - self.status_bar.SetStatusText("Welcome %s. Your training can now begin." % self.username, 0) - self.status_bar.SetStatusText("User: %s" % self.username, 5) + if dlg.ShowModal() == wx.ID_OK: + self.username = dlg.GetValue() + self.status_bar.SetStatusText("Welcome %s. Your training can now begin." % self.username, 0) + self.status_bar.SetStatusText("User: %s" % self.username, 5) - def OnMenuUDrawConnect (self, event): - ''' - Event handler for the connections\udraw menu item. - ''' + def OnMenuUDrawConnect (self, event): + ''' + Event handler for the connections\udraw menu item. + ''' - dlg = udraw_connect_dialog.udraw_connect_dialog(parent=self) - dlg.ShowModal() + dlg = udraw_connect_dialog.udraw_connect_dialog(parent=self) + dlg.ShowModal() - def OnMenuHighlight (self, event): - ''' - Describe the highlighted menu item in the status bar. - ''' + def OnMenuHighlight (self, event): + ''' + Describe the highlighted menu item in the status bar. + ''' - id = event.GetMenuId() - item = self.GetMenuBar().FindItemById(id) + id = event.GetMenuId() + item = self.GetMenuBar().FindItemById(id) - if item: - self.status_bar.SetStatusText(item.GetHelp(), 0) + if item: + self.status_bar.SetStatusText(item.GetHelp(), 0) - def OnMenuPythonShell (self, event): - if self.splitter.IsSplit(): - self.splitter.Unsplit(self.shell_window) - else: - self.splitter.SplitHorizontally(self.lbook_window, self.shell_window) + def OnMenuPythonShell (self, event): + if self.splitter.IsSplit(): + self.splitter.Unsplit(self.shell_window) + else: + self.splitter.SplitHorizontally(self.lbook_window, self.shell_window) - def OnMenuRestoreConnections (self, event): - ''' - Event handler for the connections\restore connections menu item. - ''' + def OnMenuRestoreConnections (self, event): + ''' + Event handler for the connections\restore connections menu item. + ''' - busy = wx.BusyInfo("Restoring connections... please wait.") - wx.Yield() + busy = wx.BusyInfo("Restoring connections... please wait.") + wx.Yield() - # restore mysql connection. - dlg = mysql_connect_dialog.mysql_connect_dialog(parent=self) - dlg.mysql_connect(self.mysql_host, self.mysql_username, self.mysql_password) + # restore mysql connection. + dlg = mysql_connect_dialog.mysql_connect_dialog(parent=self) + dlg.mysql_connect(self.mysql_host, self.mysql_username, self.mysql_password) - # restore pydbg locale - dlg = pydbg_locale_dialog.pydbg_locale_dialog(parent=self) - dlg.pydbg_set_locale(self.pydbg_host, self.pydbg_port) + # restore pydbg locale + dlg = pydbg_locale_dialog.pydbg_locale_dialog(parent=self) + dlg.pydbg_set_locale(self.pydbg_host, self.pydbg_port) - # restore udraw connection. - dlg = udraw_connect_dialog.udraw_connect_dialog(parent=self) - dlg.udraw_connect(self.udraw_host, self.udraw_port) + # restore udraw connection. + dlg = udraw_connect_dialog.udraw_connect_dialog(parent=self) + dlg.udraw_connect(self.udraw_host, self.udraw_port) - def OnMenuSaveOptions (self, event): - ''' - Event handler for the connections\save options menu item. - ''' + def OnMenuSaveOptions (self, event): + ''' + Event handler for the connections\save options menu item. + ''' - fh = open(".options", "wb+") - ps = persistant_state() + fh = open(".options", "wb+") + ps = persistant_state() - ps.username = self.username - ps.mysql_host = self.mysql_host - ps.mysql_username = self.mysql_username - ps.mysql_password = self.mysql_password - ps.pydbg_host = self.pydbg_host - ps.pydbg_port = self.pydbg_port - ps.udraw_host = self.udraw_host - ps.udraw_port = self.udraw_port + ps.username = self.username + ps.mysql_host = self.mysql_host + ps.mysql_username = self.mysql_username + ps.mysql_password = self.mysql_password + ps.pydbg_host = self.pydbg_host + ps.pydbg_port = self.pydbg_port + ps.udraw_host = self.udraw_host + ps.udraw_port = self.udraw_port - fh.write(cPickle.dumps(ps)) - fh.close() + fh.write(cPickle.dumps(ps)) + fh.close() - self.status_bar.SetStatusText("Connection options saved and will be automatically restored.") + self.status_bar.SetStatusText("Connection options saved and will be automatically restored.") - def OnModuleSelected (self, event): - ''' - Event handler for when a module is selected from the shell, module list dropdown. - ''' + def OnModuleSelected (self, event): + ''' + Event handler for when a module is selected from the shell, module list dropdown. + ''' - self.selected_module = event.GetString() + self.selected_module = event.GetString() - variables = self.docs_properties_choices[self.selected_module].keys() - variables.sort() + variables = self.docs_properties_choices[self.selected_module].keys() + variables.sort() - self.docs_properties.Set(variables) + self.docs_properties.Set(variables) ######################################################################################################################## class PAIMEIstatusbar (wx.StatusBar): - ''' - Instantiated from PAIMEIframe, this class creates a custom status bar. + ''' + Instantiated from PAIMEIframe, this class creates a custom status bar. - Field 0 is for general framework use. - Field 1 is for module use. - Fields 2, 3, 4 and 5 display the established connections. - ''' + Field 0 is for general framework use. + Field 1 is for module use. + Fields 2, 3, 4 and 5 display the established connections. + ''' - def __init__ (self, *args, **kwds): - self.parent = kwds["parent"] + def __init__ (self, *args, **kwds): + self.parent = kwds["parent"] - wx.StatusBar.__init__(self, self.parent, -1) + wx.StatusBar.__init__(self, self.parent, -1) - self.SetFieldsCount(6) + self.SetFieldsCount(6) - # set the fields to have relative widths. - self.SetStatusWidths([-3, -4, -1, -1, -1, -1]) + # set the fields to have relative widths. + self.SetStatusWidths([-3, -4, -1, -1, -1, -1]) - # set the default status fields. - self.SetStatusText("PaiMei ... Hayai!", 1) - self.SetStatusText("MySQL: NONE", 2) - self.SetStatusText("PyDbg: localhost", 3) - self.SetStatusText("uDraw: NONE", 4) - self.SetStatusText("User: NONE", 5) + # set the default status fields. + self.SetStatusText("PaiMei ... Hayai!", 1) + self.SetStatusText("MySQL: NONE", 2) + self.SetStatusText("PyDbg: localhost", 3) + self.SetStatusText("uDraw: NONE", 4) + self.SetStatusText("User: NONE", 5) ######################################################################################################################## class PAIMEIlistbook (wx.Listbook): - ''' - Instantiated from PAIMEIframe, this class establishes a chooser list on the left side of the screen and a variable - and dynamically loaded frame on the right. - ''' + ''' + Instantiated from PAIMEIframe, this class establishes a chooser list on the left side of the screen and a variable + and dynamically loaded frame on the right. + ''' - def __init__ (self, *args, **kwds): - global paimei + def __init__ (self, *args, **kwds): + global paimei - self.parent = kwds["parent"] - self.top = kwds["top"] + self.parent = kwds["parent"] + self.top = kwds["top"] - wx.Listbook.__init__(self, self.parent, -1, style=wx.LB_LEFT) + wx.Listbook.__init__(self, self.parent, -1, style=wx.LB_LEFT) - image_list = wx.ImageList(96, 64) + image_list = wx.ImageList(96, 64) - # load all available icons into the image list. - for icon in os.listdir("images/icons"): - if icon.endswith(".png"): - bmp = wx.Image("images/icons/" + icon).ConvertToBitmap() - image_list.Add(bmp) + # load all available icons into the image list. + for icon in os.listdir("images/icons"): + if icon.endswith(".png"): + bmp = wx.Image("images/icons/" + icon).ConvertToBitmap() + image_list.Add(bmp) - self.AssignImageList(image_list) + self.AssignImageList(image_list) - image_id = 0 - for icon in os.listdir("images/icons"): - if icon.endswith(".png"): - try: - module = icon.replace(".png", "") - module_short = module.replace("PAIMEI", "") + image_id = 0 + for icon in os.listdir("images/icons"): + if icon.endswith(".png"): + try: + module = icon.replace(".png", "") + module_short = module.replace("PAIMEI", "") - exec("from %s import *" % module) - exec("panel = %s(parent=self)" % module) + exec("from %s import *" % module) + exec("panel = %s(parent=self)" % module) - self.top.modules[module_short] = panel - exec("paimei.%s = panel" % module_short) + self.top.modules[module_short] = panel + exec("paimei.%s = panel" % module_short) - self.AddPage(panel, "", imageId=image_id) + self.AddPage(panel, "", imageId=image_id) - # set the docs module as the default loaded page. - if module == "PAIMEIdocs": - self.SetSelection(image_id) - paimei.current_module = panel - except: - import traceback - traceback.print_exc(file=sys.stdout) + # set the docs module as the default loaded page. + if module == "PAIMEIdocs": + self.SetSelection(image_id) + paimei.current_module = panel + except: + import traceback + traceback.print_exc(file=sys.stdout) - image_id += 1 + image_id += 1 - self.Bind(wx.EVT_LISTBOOK_PAGE_CHANGED, self.OnPageChanged) + self.Bind(wx.EVT_LISTBOOK_PAGE_CHANGED, self.OnPageChanged) - # update the currently selected listbook page. - def OnPageChanged (self, event): - global paimei + # update the currently selected listbook page. + def OnPageChanged (self, event): + global paimei - paimei.current_module = self.GetPage(event.GetSelection()) + paimei.current_module = self.GetPage(event.GetSelection()) - if hasattr(paimei.current_module, "_get_status"): - self.top.status_bar.SetStatusText(paimei.current_module._get_status(), 1) - else: - self.top.status_bar.SetStatusText("PaiMei ... Hayai!", 1) + if hasattr(paimei.current_module, "_get_status"): + self.top.status_bar.SetStatusText(paimei.current_module._get_status(), 1) + else: + self.top.status_bar.SetStatusText("PaiMei ... Hayai!", 1) ######################################################################################################################## class PShellVariableList (wx.ListBox): - def __init__(self, *args, **kwds): - self.top = kwds["top"] - del kwds["top"] + def __init__(self, *args, **kwds): + self.top = kwds["top"] + del kwds["top"] - wx.ListBox.__init__(self, *args, **kwds) + wx.ListBox.__init__(self, *args, **kwds) - self.typed_text = '' + self.typed_text = '' - self.Bind(wx.EVT_KEY_DOWN, self.OnKey) - self.Bind(wx.EVT_LISTBOX, self.OnEvtListBox) - self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnEvtListBox) + self.Bind(wx.EVT_KEY_DOWN, self.OnKey) + self.Bind(wx.EVT_LISTBOX, self.OnEvtListBox) + self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnEvtListBox) - def FindPrefix (self, prefix): - if prefix: - prefix = prefix.lower() - length = len(prefix) + def FindPrefix (self, prefix): + if prefix: + prefix = prefix.lower() + length = len(prefix) - for x in range(self.GetCount()): - text = self.GetString(x) - text = text.lower() + for x in range(self.GetCount()): + text = self.GetString(x) + text = text.lower() - if text[:length] == prefix: - return x + if text[:length] == prefix: + return x - return -1 + return -1 - def OnEvtListBox (self, event): - description = self.top.docs_properties_choices[self.top.selected_module][event.GetString()] - self.top.docs_description.SetValue(description) + def OnEvtListBox (self, event): + description = self.top.docs_properties_choices[self.top.selected_module][event.GetString()] + self.top.docs_description.SetValue(description) - def OnKey (self, event): - key = event.GetKeyCode() + def OnKey (self, event): + key = event.GetKeyCode() - if key >= 32 and key <= 127: - self.typed_text = self.typed_text + chr(key) - item = self.FindPrefix(self.typed_text) + if key >= 32 and key <= 127: + self.typed_text = self.typed_text + chr(key) + item = self.FindPrefix(self.typed_text) - if item != -1: - self.SetSelection(item) + if item != -1: + self.SetSelection(item) - # backspace removes one character and backs up - elif key == wx.WXK_BACK: - self.typed_text = self.typed_text[:-1] + # backspace removes one character and backs up + elif key == wx.WXK_BACK: + self.typed_text = self.typed_text[:-1] - if not self.typed_text: - self.SetSelection(0) - else: - item = self.FindPrefix(self.typed_text) + if not self.typed_text: + self.SetSelection(0) + else: + item = self.FindPrefix(self.typed_text) - if item != -1: - self.SetSelection(item) - else: - self.typed_text = '' - event.Skip() + if item != -1: + self.SetSelection(item) + else: + self.typed_text = '' + event.Skip() - def OnKeyDown (self, event): - pass + def OnKeyDown (self, event): + pass ######################################################################################################################## if __name__ == '__main__': - wxapp = PAIMEIapp(True) - wxapp.MainLoop() + wxapp = PAIMEIapp(True) + wxapp.MainLoop() + \ No newline at end of file diff --git a/console/modules/PAIMEIpeek.py b/console/modules/PAIMEIpeek.py index be73106..d77e3d0 100644 --- a/console/modules/PAIMEIpeek.py +++ b/console/modules/PAIMEIpeek.py @@ -432,7 +432,7 @@ def on_button_select_module (self, event): mysql = self.main_frame.mysql - if not mysql: + if (not mysql) or (mysql is None): self.err("No available connection to MySQL server.") return From 8c09865990777ea77cd708cc947c693931845112 Mon Sep 17 00:00:00 2001 From: Cristiano Maruti Date: Wed, 24 Oct 2012 15:51:42 +0200 Subject: [PATCH 3/5] accidentally added file belonging to another branch --- proc_peek_recon.py | 365 -------------------------------- proc_peek_recon_db.py | 478 ------------------------------------------ 2 files changed, 843 deletions(-) delete mode 100644 proc_peek_recon.py delete mode 100644 proc_peek_recon_db.py diff --git a/proc_peek_recon.py b/proc_peek_recon.py deleted file mode 100644 index 1698bea..0000000 --- a/proc_peek_recon.py +++ /dev/null @@ -1,365 +0,0 @@ -# -# IDA Python Proc Peek Recon -# Locate all potentially interesting points and dump to file. -# -# Copyright (C) 2006 Pedram Amini -# -# $Id: proc_peek_recon.py 236 2010-03-05 18:16:17Z pedram.amini $ -# -# This program 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 2 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, write to the Free -# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# - -''' -@author: Pedram Amini -@license: GNU General Public License 2.0 or later -@contact: pedram.amini@gmail.com -@organization: www.openrce.org -''' - -from idaapi import * -from idautils import * -from idc import * - -######################################################################################################################## -### Support Functions -### - -# -# Type define the argument data type; -# S -> string -# I -> num -# -def get_arg(ea, arg_num, type): - arg_index = 1 - - while True: - ea = PrevNotTail(ea) - if GetMnem(ea) == "push": - if arg_index == arg_num: - - # string parameter - if type == 'S': - dref = Dfirst(ea) - if dref == BADADDR: - return dref - return read_string(dref) - - # number parameter - elif type == 'I': - return read_size(ea) - - arg_index += 1 - -def instruction_match (ea, mnem=None, op1=None, op2=None, op3=None): - if mnem and mnem != GetMnem(ea): - return False - - if op1 and op1 != GetOpnd(ea, 0): return False - if op2 and op2 != GetOpnd(ea, 1): return False - if op3 and op3 != GetOpnd(ea, 2): return False - - return True - -def disasm_match (ea, needle): - disasm_line = GetDisasm(ea) - - # collapse whitespace - while disasm_line.find(" ") != -1: - disasm_line = disasm_line.replace(" ", " ") - - if disasm_line.find(needle) == -1: - return False - - return True - -def read_size(ea): - s = GetOpnd(ea, 0) - if s.endswith("h"): - return int(s.rstrip('h'), 16) - else: - return BADADDR - - -def read_string (ea): - s = "" - string_type = GetStringType(ea) - if string_type == ASCSTR_C: - while True: - byte = Byte(ea) - - if byte == 0: #or byte < 32 or byte > 126: - break - - if byte == 0x0d: s += "" - elif byte == 0x0a: s += "" - else: s += chr(byte) - - ea += 1 - return s - elif string_type == ASCSTR_UNICODE: - #TODO - while True: - word = Word(ea) - if byte == 0: - break - else: s += word - ea += 2 - return "unicode string: " + s - - elif string_type == ASCSTR_PASCAL: - byte = Byte(ea) - for i in range (1, byte): - s += Byte(ea) - return "pascal string: " + s - - - -def token_count (format_string): - return format_string.count("%") - format_string.count("%%") - -######################################################################################################################## -### Meat and Potatoes -### - -peek_filename = AskFile(1, "*.recon", "Proc Peek Recon Filename?") -peek_file = open(peek_filename, "w+") - -#ida_log = lambda x: None -ida_log = lambda x: sys.stdout.write("RECON> " + x + "\n") -write_line = lambda x: peek_file.write("%s\n" % x) - -window = state = found_ea = processed = 0 - -ida_log("searching for inline memcpy()'s and sign extended moves (movsx).") - -for ea in Heads(MinEA(), MaxEA()): - processed += 1 - - # rep movsd : rep movsd [edi], [esi] : eax = memcpy(edi, esi, ecx) - # rep stosd : rep stosd [edi], eax : eax = memset(edi, eax, ecx) - # rep scasd : rep scasd [edi] : eax = strchr(edi, eax) - - # we don't care about instructions within known library routines. - # if GetFunctionFlags(ea) and GetFunctionFlags(ea) & FUNC_LIB: - if GetFunctionFlags(ea) & FUNC_LIB: - continue - - if disasm_match(ea, "movsx"): - ida_log("%08x: found sign extended move" % ea) - set_cmt(ea, "sign extended move", False) - write_line("%08x:sign extended move" % ea) - elif state == 0 and instruction_match(ea, "shr", "ecx", "2"): - state = 1 - window = 0 - elif state == 1 and disasm_match(ea, "rep movsd"): - state = 2 - window = 0 - found_ea = ea - elif state == 2 and instruction_match(ea, "and", "ecx", "3"): - state = 3 - window = 0 - elif state == 3 and disasm_match(ea, "rep movsb"): - ida_log("%08x: found memcpy" % found_ea) - set_cmt(ea, "inline memcpy()", False) - write_line("%08x: inline memcpy" % found_ea) - found_ea = state = window = 0 - - if window > 15: - state = window = 0 - - if state != 0: - window += 1 - -ida_log("done. looked at %d heads." % processed) -ida_log("looking for potentially interesting API calls now.") - -# format of functions dictionary is function name: format string arg number -# fill this from google search: +run-time.library +security.note site:msdn.microsoft.com -# [cm] my own google dork: "Security Warning" intitle:function site:msdn.microsoft.com -# "Security note" crt site:msdn.microsoft.com -functions = \ -{ -# insecure by default - "gets" : {}, - "getws" : {}, - -# exec functions - "execl" : {"cmd_name": 1}, - "wexecl" : {"cmd_name": 1}, - "execv" : {"cmd_name": 1}, - "wexecv" : {"cmd_name": 1}, - "WinExec" : {"cmd_name": 1}, - "ShellExecute" : {}, - "ShellExecuteEx" : {}, - "CreateProcess" : {"cmd_name": 2}, - "CreateProcessAsUser": {"cmd_name": 2}, - "CreateProcessWithLogon" : {"cmd_name": 2}, - -# memory copy functions - "memcpy" : {"size": 3}, - "wmemcpy" : {"size": 3}, - "VirtualAllocEx" : {"size": 3}, - "VirtualAlloc" : {"size": 2}, - "VirtualAllocExNuma": {"size": 2}, - "LocalAlloc" : {"size": 2}, - "HeapAlloc" : {"size": 3}, - "CopyMemory" : {"size": 3}, - -# string functions - "lstrcat" : {}, - "lstrcat" : {}, - "lstrcpy" : {}, - "lstrlen" : {}, - "lstrlen" : {}, - "mbscat" : {}, - "mbscpy" : {}, - "mbsncpy" : {"size": 3}, - "mbsnbcpy" : {"size": 3}, - "mbsncat" : {}, - "mbsstr_l" : {}, - "RtlInitString" : {}, - "SHAnsiToAnsi" : {"size": 3}, - "SHAnsiToUnicode" : {"size": 3}, - "SHUnicodeToUnicode": {"size": 3}, - "SHUnicodeToAnsi" : {"size": 3}, - "strcpy" : {}, - "strncpy" : {"size": 3}, - "strcat" : {}, - "StrCatBuf" : {}, - "StrCatChain" : {}, - "StrCpyN" : {}, - "StrCpyN" : {}, - "strcpy" : {}, - "strncat" : {"size": 3}, - "strstr" : {}, - "wcscat" : {}, - "wcsstr" : {}, - "wcsncat" : {}, - "wcscpy" : {}, - "wcsncpy" : {"size": 3}, - "CompareStringWrapW": {}, - -# format strings - "printf" : {"fs_arg": 1}, - "wprintf" : {"fs_arg": 1}, - "snprintf" : {"fs_arg": 3}, - "snwprintf" : {"fs_arg": 3}, - "scanf" : {"fs_arg": 1}, - "sprintf" : {"fs_arg": 2}, - "sscanf" : {"fs_arg": 2}, - "swprintf" : {"fs_arg": 2}, - "swscanf" : {"fs_arg": 2}, - "vfprintf" : {"fs_arg": 2}, - "vfwprintf" : {"fs_arg": 2}, - "vprintf" : {"fs_arg": 1}, - "vwprintf" : {"fs_arg": 1}, - "vsprintf" : {"fs_arg": 2}, - "vsnprintf" : {"fs_arg": 3}, - "vsnwprintf" : {"fs_arg": 3}, - "vswprintf" : {"fs_arg": 2}, - "wsprintf" : {"fs_arg": 2}, - "wsprintf" : {"fs_arg": 2}, - "wvsprintf" : {"fs_arg": 2}, - "wvsprintf" : {"fs_arg": 2}, - "wvnsprintf" : {"fs_arg": 3}, - "wnsprintf" : {"fs_arg": 3}, -} - -prefixes = ["", "_", "__imp_", "__imp__"] -suffixes = ["", "A", "W"] - -# For every function we are interested in. -for func in functions: - - # enumerate all possibile suffixes. - for prefix in prefixes: - - # enumerate all possible prefixes. - for suffix in suffixes: - full_name = prefix + func + suffix - #ida_log("library %s" % full_name) - location = LocByName(full_name) - - if location == BADADDR: - continue - - ida_log("enumerating xrefs to %s" % full_name) - - for xref in list(CodeRefsTo(location, True)) + list(DataRefsTo(location)): - if GetMnem(xref) in ("call", "jmp"): - # Ensure the xref does not exist within a known library routine. - # if GetFunctionFlags(ea) and GetFunctionFlags(xref) & FUNC_LIB: - - #if GetFunctionFlags(xref) & FUNC_LIB: - # continue - - ### - ### peek a call with format string arguments - ### - if functions[func].has_key("fs_arg"): - fs_arg = functions[func]["fs_arg"] - - format_string = get_arg(xref, fs_arg, 'S') - - # format string must be resolved at runtime. - if format_string == BADADDR: - ida_log("%08x format string must be resolved at runtime" % xref) - write_line("%08x: %s" % (xref, full_name)) - - # XXX - we have to escape '%' chars here otherwise 'print', which wraps around 'Message()' will - # incorrectly dereference from the stack and potentially crash the script. - else: - format_string = str(format_string).replace("%", "%%") - - # format string found. - if format_string.find("%s") != -1: - format_string = format_string.replace("\n", "") - ida_log("%08x favorable format string found '%s'" % (xref, format_string)) - write_line("%08x:%d:%s %s" % (xref, token_count(format_string), full_name, format_string)) - # - # TODO: get cmd_name string - # - elif functions[func].has_key("cmd_name"): - cmd_name = functions[func]["cmd_name"] - - cmd = get_arg(xref, cmd_name, 'S') - - if cmd == BADADDR: - ida_log("%08x command must be resolved at runtime" % xref) - write_line("%08x: %s" % (xref, full_name)) - else: - ida_log("%08x found call to '%s' with static command: %d" % (xref, full_name, cmd)) - write_line("%08x: %s command: %s" % (xref, full_name, cmd)) - - # - # get static size value - # - elif functions[func].has_key("size"): - size_arg = functions[func]["size"] - - size = get_arg(xref, size_arg, 'I') - - if size == BADADDR: - ida_log("%08x size must be resolved at runtime" % xref) - write_line("%08x: %s" % (xref, full_name)) - else: - ida_log("%08x found call to '%s' with static size: %d" % (xref, full_name, size)) - write_line("%08x: %s size: %d" % (xref, full_name, size)) - - ### - ### peek a non format string call - ### - else: - ida_log("%08x found call to '%s'" % (xref, full_name)) - write_line("%08x: %s" % (xref, full_name)) - -peek_file.close() -ida_log("done.") \ No newline at end of file diff --git a/proc_peek_recon_db.py b/proc_peek_recon_db.py deleted file mode 100644 index cf07047..0000000 --- a/proc_peek_recon_db.py +++ /dev/null @@ -1,478 +0,0 @@ -# -# IDA Python Proc Peek Recon -# Locate all potentially interesting points and dump to file. -# -# Copyright (C) 2006 Pedram Amini -# -# $Id: proc_peek_recon_db.py 231 2008-07-21 22:43:36Z pedram.amini $ -# -# This program 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 2 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, write to the Free -# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# - -''' -@author: Pedram Amini -@license: GNU General Public License 2.0 or later -@contact: pedram.amini@gmail.com -@organization: www.openrce.org -''' - -from idaapi import * -from idautils import * -from idc import * - -import MySQLdb - -######################################################################################################################## -### Support Functions -### - -# -# Type define the argument data type; -# S -> string -# I -> num -# -def get_arg(ea, arg_num, type): - arg_index = 1 - - while True: - ea = PrevNotTail(ea) - if GetMnem(ea) == "push": - if arg_index == arg_num: - - # string parameter - if type == 'S': - dref = Dfirst(ea) - if dref == BADADDR: - return dref - return read_string(dref) - - # number parameter - elif type == 'I': - return read_size(ea) - - arg_index += 1 - - -def instruction_match (ea, mnem=None, op1=None, op2=None, op3=None): - if mnem and mnem != GetMnem(ea): - return False - - if op1 and op1 != GetOpnd(ea, 0): return False - if op2 and op2 != GetOpnd(ea, 1): return False - if op3 and op3 != GetOpnd(ea, 2): return False - - return True - -def disasm_match (ea, needle): - disasm_line = GetDisasm(ea) - - # collapse whitespace - while disasm_line.find(" ") != -1: - disasm_line = disasm_line.replace(" ", " ") - - if disasm_line.find(needle) == -1: - return False - - return True - -def read_size(ea): - s = GetOpnd(ea, 0) - if s.endswith("h"): - return int(s.rstrip('h'), 16) - else: - return BADADDR - -def read_string (ea): - s = "" - string_type = GetStringType(ea) - if string_type == ASCSTR_C: - while True: - byte = Byte(ea) - - if byte == 0: #or byte < 32 or byte > 126: - break - - if byte == 0x0d: s += "" - elif byte == 0x0a: s += "" - else: s += chr(byte) - - ea += 1 - return s - elif string_type == ASCSTR_UNICODE: - #TODO - while True: - word = Word(ea) - if byte == 0: - break - else: s += word - ea += 2 - return "unicode string: " + s - - elif string_type == ASCSTR_PASCAL: - byte = Byte(ea) - for i in range (1, byte): - s += Byte(ea) - return "pascal string: " + s - -def token_count (format_string): - return format_string.count("%") - format_string.count("%%") - - -def ida_log (message): - print "RECON> " + message - - -def add_recon (mysql, module_id, offset, stack_depth, reason, status): - # escape single quotes and backslashes in fields that might have them. - reason = reason.replace("\\", "\\\\").replace("'", "\\'") - - sql = " INSERT INTO pp_recon" - sql += " SET module_id = '%d'," % module_id - sql += " offset = '%d'," % offset - sql += " stack_depth = '%d'," % stack_depth - sql += " reason = '%s'," % reason - sql += " status = '%s'," % status - sql += " notes = ''" - - cursor = mysql.cursor() - - try: - cursor.execute(sql) - except MySQLdb.Error, e: - ida_log("MySQL error %d: %s" % (e.args[0], e.args[1])) - ida_log(sql) - return False - - cursor.close() - return True - -######################################################################################################################## -### Meat and Potatoes -### - -def meat_and_potatoes (mysql): - # init some local vars. - window = state = found_ea = processed = 0 - - # calculate the current modules base address. - # XXX - cheap hack, the subtraction is for the PE header size. - base_address = MinEA() - 0x1000 - - # create a database entry for the current module. - cursor = mysql.cursor() - - try: - cursor.execute("INSERT INTO pp_modules SET name = '%s', base = '%d', notes = ''" % (GetInputFile(), base_address)) - except MySQLdb.Error, e: - ida_log("MySQL error %d: %s" % (e.args[0], e.args[1])) - ida_log(sql) - cursor.close() - return - - # save the module ID we just created. - module_id = cursor.lastrowid - - cursor.close() - - ida_log("searching for inline memcpy()'s and sign extended moves (movsx).") - for ea in Heads(MinEA(), MaxEA()): - processed += 1 - - # we don't care about instructions within known library routines. - if GetFunctionFlags(ea) & FUNC_LIB: - continue - - if disasm_match(ea, "movsx"): - ida_log("%08x: found sign extended move" % ea) - - if not add_recon(mysql, module_id, ea - base_address, 3, "sign extended mov", "new"): - return - - if state == 0 and instruction_match(ea, "shr", "ecx", "2"): - # this is a good place to watch the inline strcpy since it gets executed only once and we can see the - # original size value prior to division by 4. - state = 1 - window = 0 - found_ea = ea - - elif state == 1 and disasm_match(ea, "rep movsd"): - state = 2 - window = 0 - - elif state == 2 and instruction_match(ea, "and", "ecx", "3"): - state = 3 - window = 0 - - elif state == 3 and disasm_match(ea, "rep movsb"): - ida_log("%08x: found memcpy" % found_ea) - set_cmt(found_ea, "inline memcpy()", False) - - if not add_recon(mysql, module_id, found_ea - base_address, 5, "inline memcpy", "new"): - return - - found_ea = state = window = 0 - - if window > 15: - state = window = 0 - - if state != 0: - window += 1 - - ida_log("done. looked at %d heads." % processed) - ida_log("looking for potentially interesting API calls now.") - - # format of functions dictionary is function name: format string arg number - # fill this from google search: +run-time.library +security.note site:msdn.microsoft.com - # [cm] my own google dork: "Security Warning" intitle:function site:msdn.microsoft.com - # "Security note" crt site:msdn.microsoft.com - functions = \ - { - # insecure by default - "gets" : {}, - "getws" : {}, - - # exec functions - "execl" : {"cmd_name": 1}, - "wexecl" : {"cmd_name": 1}, - "execv" : {"cmd_name": 1}, - "wexecv" : {"cmd_name": 1}, - "WinExec" : {"cmd_name": 1}, - "ShellExecute" : {}, - "ShellExecuteEx" : {}, - "CreateProcess" : {"cmd_name": 2}, - "CreateProcessAsUser": {"cmd_name": 2}, - "CreateProcessWithLogon" : {"cmd_name": 2}, - - # memory copy functions - "memcpy" : {"size": 3}, - "wmemcpy" : {"size": 3}, - "VirtualAllocEx" : {"size": 3}, - "VirtualAlloc" : {"size": 2}, - "VirtualAllocExNuma": {"size": 2}, - "LocalAlloc" : {"size": 2}, - "HeapAlloc" : {"size": 3}, - "CopyMemory" : {"size": 3}, - - # string functions - "lstrcat" : {}, - "lstrcat" : {}, - "lstrcpy" : {}, - "lstrlen" : {}, - "lstrlen" : {}, - "mbscat" : {}, - "mbscpy" : {}, - "mbsncpy" : {"size": 3}, - "mbsnbcpy" : {"size": 3}, - "mbsncat" : {}, - "mbsstr_l" : {}, - "RtlInitString" : {}, - "SHAnsiToAnsi" : {"size": 3}, - "SHAnsiToUnicode" : {"size": 3}, - "SHUnicodeToUnicode": {"size": 3}, - "SHUnicodeToAnsi" : {"size": 3}, - "strcpy" : {}, - "strncpy" : {"size": 3}, - "strcat" : {}, - "StrCatBuf" : {}, - "StrCatChain" : {}, - "StrCpyN" : {}, - "StrCpyN" : {}, - "strcpy" : {}, - "strncat" : {"size": 3}, - "strstr" : {}, - "wcscat" : {}, - "wcsstr" : {}, - "wcsncat" : {}, - "wcscpy" : {}, - "wcsncpy" : {"size": 3}, - "CompareStringWrapW": {}, - - # format strings - "printf" : {"fs_arg": 1}, - "wprintf" : {"fs_arg": 1}, - "snprintf" : {"fs_arg": 3}, - "snwprintf" : {"fs_arg": 3}, - "scanf" : {"fs_arg": 1}, - "sprintf" : {"fs_arg": 2}, - "sscanf" : {"fs_arg": 2}, - "swprintf" : {"fs_arg": 2}, - "swscanf" : {"fs_arg": 2}, - "vfprintf" : {"fs_arg": 2}, - "vfwprintf" : {"fs_arg": 2}, - "vprintf" : {"fs_arg": 1}, - "vwprintf" : {"fs_arg": 1}, - "vsprintf" : {"fs_arg": 2}, - "vsnprintf" : {"fs_arg": 3}, - "vsnwprintf" : {"fs_arg": 3}, - "vswprintf" : {"fs_arg": 2}, - "wsprintf" : {"fs_arg": 2}, - "wsprintf" : {"fs_arg": 2}, - "wvsprintf" : {"fs_arg": 2}, - "wvsprintf" : {"fs_arg": 2}, - "wvnsprintf" : {"fs_arg": 3}, - "wnsprintf" : {"fs_arg": 3}, - } - - prefixes = ["", "_", "__imp_", "__imp__"] - suffixes = ["", "A", "W"] - - # for every function we are interested in. - for func in functions: - - # enumerate all possibile suffixes. - for suffix in suffixes: - - # enumerate all possible prefixes. - for prefix in prefixes: - full_name = prefix + func + suffix - location = LocByName(full_name) - - if location == BADADDR: - continue - - ida_log("enumerating xrefs to %s" % full_name) - - for xref in list(CodeRefsTo(location, True)) + list(DataRefsTo(location)): - if GetMnem(xref) in ("call", "jmp"): - # ensure the xref does not exist within a known library routine. - flags = GetFunctionFlags(xref) - if flags: - if flags & FUNC_LIB: - continue - - ### - ### peek a call with format string arguments - ### - if functions[func].has_key("fs_arg"): - fs_arg = functions[func]["fs_arg"] - - format_string = get_arg(xref, fs_arg, 'S') - - # format string must be resolved at runtime. - if format_string == BADADDR: - ida_log("%08x format string must be resolved at runtime" % xref) - - if not add_recon(mysql, module_id, xref - base_address, 0, full_name, "new"): - return - - # XXX - we have to escape '%' chars here otherwise 'print', which wraps around 'Message()' will - # incorrectly dereference from the stack and potentially crash the script. - else: - format_string = str(format_string).replace("%", "%%") - - # format string found. - if format_string.find("%s") != -1: - format_string = format_string.replace("\n", "") - ida_log("%08x favorable format string found '%s'" % (xref, format_string)) - - if not add_recon(mysql, module_id, xref - base_address, token_count(format_string)+fs_arg, "%s - fs: %s" % (full_name, format_string), "new"): - return - # - # TODO: get cmd_name string - # - elif functions[func].has_key("cmd_name"): - cmd_name = functions[func]["cmd_name"] - - cmd = get_arg(xref, cmd_name, 'S') - - if cmd == BADADDR: - ida_log("%08x command must be resolved at runtime" % xref) - if not add_recon(mysql, module_id, xref - base_address, cmd_name, full_name, "new"): - return - else: - ida_log("%08x found call to '%s' with static command: %d" % (xref, full_name, cmd)) - if not add_recon(mysql, module_id, xref - base_address, cmd_name, "%s - cmd: %s" % (full_name, cmd_name), "new"): - return - # - # get static size value - # - elif functions[func].has_key("size"): - size_arg = functions[func]["size"] - - size = get_arg(xref, size_arg, 'I') - - if size == BADADDR: - ida_log("%08x size must be resolved at runtime" % xref) - if not add_recon(mysql, module_id, xref - base_address, size_arg, full_name, "new"): - return - else: - ida_log("%08x found call to '%s' with static size: %d" % (xref, full_name, size)) - if not add_recon(mysql, module_id, xref - base_address, size_arg, "%s - size: %d" % (full_name, size), "new"): - return - - ### - ### peek a non format string call - ### - - else: - ida_log("%08x found call to '%s'" % (xref, func)) - - if not add_recon(mysql, module_id, xref - base_address, 0, full_name, "new"): - return - - ida_log("done.") - - -######################################################################################################################## -### MySQL Connectivity -### - -def mysql_connect (): - mysql_host = None - mysql_user = None - mysql_pass = None - - if not mysql_host: - mysql_host = AskStr("localhost", "MySQL IP address or hostname:") - - if not mysql_host: - return -1 - - if not mysql_user: - mysql_user = AskStr("root", "MySQL username:") - - if not mysql_user: - return -1 - - if not mysql_pass: - mysql_pass = AskStr("", "MySQL password:") - - if not mysql_pass: - return -1 - - # connect to mysql - try: - mysql = MySQLdb.connect(host=mysql_host, user=mysql_user, passwd=mysql_pass, db="paimei") - except MySQLdb.OperationalError, err: - ida_log("failed connecting to MySQL server: %s" % err[1]) - mysql = None - - return mysql - - -######################################################################################################################## -### main() -### - -def main (): - mysql = mysql_connect() - - if mysql == -1: - ida_log("cancelled by user.") - elif mysql == None: - # error message already printed. - return - else: - meat_and_potatoes(mysql) - - mysql.close() - -main() \ No newline at end of file From 52975c7ea97119f18aa523bb98b969d24d8bf42b Mon Sep 17 00:00:00 2001 From: Cristiano Maruti Date: Fri, 26 Oct 2012 15:06:37 +0200 Subject: [PATCH 4/5] add Ports column in Peek! -> Select Target windows add Delete all in the right click popup --- .../modules/_PAIMEIpeek/ProcessListCtrl.py | 101 ++-- console/modules/_PAIMEIpeek/PyDbgDlg.py | 140 +++--- console/modules/_PAIMEIpeek/ReconListCtrl.py | 473 +++++++++--------- 3 files changed, 373 insertions(+), 341 deletions(-) diff --git a/console/modules/_PAIMEIpeek/ProcessListCtrl.py b/console/modules/_PAIMEIpeek/ProcessListCtrl.py index fb40b35..b88e8f0 100644 --- a/console/modules/_PAIMEIpeek/ProcessListCtrl.py +++ b/console/modules/_PAIMEIpeek/ProcessListCtrl.py @@ -16,9 +16,9 @@ # ''' -@author: Pedram Amini -@license: GNU General Public License 2.0 or later -@contact: pedram.amini@gmail.com +@author: Pedram Amini +@license: GNU General Public License 2.0 or later +@contact: pedram.amini@gmail.com @organization: www.openrce.org ''' @@ -32,65 +32,72 @@ import utils class ProcessListCtrl (wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin): - ''' - Our custom list control containing a sortable list of PIDs and process names. - ''' + ''' + Our custom list control containing a sortable list of PIDs and process names. + ''' - FUNCTIONS = utils.process_stalker.FUNCTIONS - BASIC_BLOCKS = utils.process_stalker.BASIC_BLOCKS + FUNCTIONS = utils.process_stalker.FUNCTIONS + BASIC_BLOCKS = utils.process_stalker.BASIC_BLOCKS - def __init__(self, parent, id, pos=None, size=None, style=None, top=None): - wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | wx.SIMPLE_BORDER | wx.LC_HRULES ) - self.top = top - self.selected_pid = 0 - self.selected_proc = None + def __init__(self, parent, id, pos=None, size=None, style=None, top=None): + wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | wx.SIMPLE_BORDER | wx.LC_HRULES ) + self.top = top + self.selected_pid = 0 + self.selected_proc = None - ListCtrlAutoWidthMixin.__init__(self) + ListCtrlAutoWidthMixin.__init__(self) - self.items_sort_map = {} - self.itemDataMap = self.items_sort_map + self.items_sort_map = {} + self.itemDataMap = self.items_sort_map - ColumnSorterMixin.__init__(self, 2) + ColumnSorterMixin.__init__(self, 3) - self.InsertColumn(0, "PID") - self.InsertColumn(1, "Process") + self.InsertColumn(0, "PID") + self.InsertColumn(1, "Process") + self.InsertColumn(2, "Ports") - #################################################################################################################### - def GetListCtrl (self): - ''' - Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py - ''' + #################################################################################################################### + def GetListCtrl (self): + ''' + Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + ''' - return self + return self - #################################################################################################################### - def on_retrieve_list (self, event): - pydbg = self.top.main_frame.pydbg + #################################################################################################################### + def on_retrieve_list (self, event): + pydbg = self.top.main_frame.pydbg - self.DeleteAllItems() + self.DeleteAllItems() - idx = 0 - for (pid, proc) in pydbg.enumerate_processes(): - # ignore system processes. - if pid < 10: - continue + idx = 0 + for (pid, proc) in pydbg.enumerate_processes(): + # ignore system processes. + if pid < 10: + continue - self.InsertStringItem(idx, "") - self.SetStringItem(idx, 0, "%d" % pid) - self.SetStringItem(idx, 1, proc) + self.InsertStringItem(idx, "") + self.SetStringItem(idx, 0, "%d" % pid) + self.SetStringItem(idx, 1, proc) + + portslist = pydbg.pid_to_port(pid) + ports = "" + for (lport, addr, proto) in portslist: + ports += addr + ":" + lport + "/" + proto + " " + + self.SetStringItem(idx, 2, ports) - self.items_sort_map[idx] = (pid, proc) - self.SetItemData(idx, idx) + self.items_sort_map[idx] = (pid, proc, ports) + self.SetItemData(idx, idx) - idx += 1 + idx += 1 + #################################################################################################################### + def on_select (self, event): + ''' + ''' - #################################################################################################################### - def on_select (self, event): - ''' - ''' - - self.selected_pid = int(self.GetItem(event.m_itemIndex, 0).GetText()) - self.selected_proc = self.GetItem(event.m_itemIndex, 1).GetText() \ No newline at end of file + self.selected_pid = int(self.GetItem(event.m_itemIndex, 0).GetText()) + self.selected_proc = self.GetItem(event.m_itemIndex, 1).GetText() \ No newline at end of file diff --git a/console/modules/_PAIMEIpeek/PyDbgDlg.py b/console/modules/_PAIMEIpeek/PyDbgDlg.py index 26c7ba8..8213981 100644 --- a/console/modules/_PAIMEIpeek/PyDbgDlg.py +++ b/console/modules/_PAIMEIpeek/PyDbgDlg.py @@ -16,9 +16,9 @@ # ''' -@author: Pedram Amini -@license: GNU General Public License 2.0 or later -@contact: pedram.amini@gmail.com +@author: Pedram Amini +@license: GNU General Public License 2.0 or later +@contact: pedram.amini@gmail.com @organization: www.openrce.org ''' @@ -29,70 +29,70 @@ ######################################################################################################################## class PyDbgDlg(wx.Dialog): - def __init__(self, *args, **kwds): - self.parent = kwds["parent"] - - # begin wxGlade: PyDbgDialog.__init__ - kwds["style"] = wx.DEFAULT_DIALOG_STYLE - wx.Dialog.__init__(self, *args, **kwds) - - self.retrieve_list = wx.Button(self, -1, "Retrieve List") - self.process_list = _PAIMEIpeek.ProcessListCtrl.ProcessListCtrl(self, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER, top=self.parent) - self.load_target = filebrowse.FileBrowseButton(self, -1, labelText="", fileMask="*.exe", fileMode=wx.OPEN, toolTip="Specify the target executable to load") - self.attach_or_load = wx.Button(self, -1, "Attach / Load") - self.cancel = wx.Button(self, wx.ID_CANCEL) - - self.__set_properties() - self.__do_layout() - # end wxGlade - - # event bindings. - self.Bind(wx.EVT_BUTTON, self.process_list.on_retrieve_list, self.retrieve_list) - self.Bind(wx.EVT_BUTTON, self.on_attach_or_load, self.attach_or_load) - self.process_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.process_list.on_select) - - - #################################################################################################################### - def __set_properties(self): - # begin wxGlade: PyDbgDialog.__set_properties - self.SetTitle("Select Target") - self.SetSize((300, 500)) - self.retrieve_list.SetFont(wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "MS Shell Dlg 2")) - self.process_list.SetFont(wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "MS Shell Dlg 2")) - self.attach_or_load.SetFont(wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "MS Shell Dlg 2")) - # end wxGlade - - - #################################################################################################################### - def __do_layout(self): - # begin wxGlade: PyDbgDialog.__do_layout - overall = wx.BoxSizer(wx.VERTICAL) - overall.Add(self.retrieve_list, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - overall.Add(self.process_list, 1, wx.EXPAND, 0) - overall.Add(self.load_target, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - button_bar = wx.BoxSizer(wx.HORIZONTAL) - button_bar.Add(self.attach_or_load, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - button_bar.Add(self.cancel, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - overall.Add(button_bar, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) - self.SetAutoLayout(True) - self.SetSizer(overall) - self.Layout() - # end wxGlade - - - #################################################################################################################### - def on_attach_or_load (self, event): - ''' - Bubble up the attach or load target to the main PAIMEIpeek module. - ''' - - self.parent.load = self.load_target.GetValue() - self.parent.pid = self.process_list.selected_pid - self.parent.proc = self.process_list.selected_proc - - if not self.parent.load and not self.parent.pid and not self.parent.proc: - dlg = wx.MessageDialog(self, "You haven't selected a process to load or attach to.", "Error", wx.OK | wx.ICON_WARNING) - dlg.ShowModal() - return - - self.Destroy() \ No newline at end of file + def __init__(self, *args, **kwds): + self.parent = kwds["parent"] + + # begin wxGlade: PyDbgDialog.__init__ + kwds["style"] = (wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + wx.Dialog.__init__(self, *args, **kwds) + + self.retrieve_list = wx.Button(self, -1, "Retrieve List") + self.process_list = _PAIMEIpeek.ProcessListCtrl.ProcessListCtrl(self, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER, top=self.parent) + self.load_target = filebrowse.FileBrowseButton(self, -1, labelText="", fileMask="*.exe", fileMode=wx.OPEN, toolTip="Specify the target executable to load") + self.attach_or_load = wx.Button(self, -1, "Attach / Load") + self.cancel = wx.Button(self, wx.ID_CANCEL) + + self.__set_properties() + self.__do_layout() + # end wxGlade + + # event bindings. + self.Bind(wx.EVT_BUTTON, self.process_list.on_retrieve_list, self.retrieve_list) + self.Bind(wx.EVT_BUTTON, self.on_attach_or_load, self.attach_or_load) + self.process_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.process_list.on_select) + + + #################################################################################################################### + def __set_properties(self): + # begin wxGlade: PyDbgDialog.__set_properties + self.SetTitle("Select Target") + self.SetSize((300, 500)) + self.retrieve_list.SetFont(wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "MS Shell Dlg 2")) + self.process_list.SetFont(wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "MS Shell Dlg 2")) + self.attach_or_load.SetFont(wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "MS Shell Dlg 2")) + # end wxGlade + + + #################################################################################################################### + def __do_layout(self): + # begin wxGlade: PyDbgDialog.__do_layout + overall = wx.BoxSizer(wx.VERTICAL) + overall.Add(self.retrieve_list, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + overall.Add(self.process_list, 1, wx.EXPAND, 0) + overall.Add(self.load_target, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + button_bar = wx.BoxSizer(wx.HORIZONTAL) + button_bar.Add(self.attach_or_load, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + button_bar.Add(self.cancel, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + overall.Add(button_bar, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + self.SetAutoLayout(True) + self.SetSizer(overall) + self.Layout() + # end wxGlade + + + #################################################################################################################### + def on_attach_or_load (self, event): + ''' + Bubble up the attach or load target to the main PAIMEIpeek module. + ''' + + self.parent.load = self.load_target.GetValue() + self.parent.pid = self.process_list.selected_pid + self.parent.proc = self.process_list.selected_proc + + if not self.parent.load and not self.parent.pid and not self.parent.proc: + dlg = wx.MessageDialog(self, "You haven't selected a process to load or attach to.", "Error", wx.OK | wx.ICON_WARNING) + dlg.ShowModal() + return + + self.Destroy() \ No newline at end of file diff --git a/console/modules/_PAIMEIpeek/ReconListCtrl.py b/console/modules/_PAIMEIpeek/ReconListCtrl.py index 64c9707..46eb807 100644 --- a/console/modules/_PAIMEIpeek/ReconListCtrl.py +++ b/console/modules/_PAIMEIpeek/ReconListCtrl.py @@ -16,9 +16,9 @@ # ''' -@author: Pedram Amini -@license: GNU General Public License 2.0 or later -@contact: pedram.amini@gmail.com +@author: Pedram Amini +@license: GNU General Public License 2.0 or later +@contact: pedram.amini@gmail.com @organization: www.openrce.org ''' @@ -32,270 +32,295 @@ import _PAIMEIpeek class ReconListCtrl (wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin): - ''' - Our custom list control containing the various recon points and relevant notes. - ''' + ''' + Our custom list control containing the various recon points and relevant notes. + ''' - def __init__(self, parent, id, pos=None, size=None, style=None, top=None): - wx.ListCtrl.__init__(self, parent, id, style=style) - self.parent = parent - self.top = top + def __init__(self, parent, id, pos=None, size=None, style=None, top=None): + wx.ListCtrl.__init__(self, parent, id, style=style) + self.parent = parent + self.top = top + + ListCtrlAutoWidthMixin.__init__(self) - ListCtrlAutoWidthMixin.__init__(self) + self.items_sort_map = {} + self.itemDataMap = self.items_sort_map - self.items_sort_map = {} - self.itemDataMap = self.items_sort_map + ColumnSorterMixin.__init__(self, 8) - ColumnSorterMixin.__init__(self, 8) + self.InsertColumn(0, "ID") + self.InsertColumn(1, "Address") + self.InsertColumn(2, "Depth") + self.InsertColumn(3, "Status") + self.InsertColumn(4, "Username") + self.InsertColumn(5, "# Hits") + self.InsertColumn(6, "Boron Tag") + self.InsertColumn(7, "Reason") - self.InsertColumn(0, "ID") - self.InsertColumn(1, "Address") - self.InsertColumn(2, "Depth") - self.InsertColumn(3, "Status") - self.InsertColumn(4, "Username") - self.InsertColumn(5, "# Hits") - self.InsertColumn(6, "Boron Tag") - self.InsertColumn(7, "Reason") + #################################################################################################################### + def GetListCtrl (self): + ''' + Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + ''' + + return self - #################################################################################################################### - def GetListCtrl (self): - ''' - Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py - ''' - return self + #################################################################################################################### + def load (self, id): + self.DeleteAllItems() + + mysql = self.top.main_frame.mysql + + if not mysql: + self.top.err("No available connection to MySQL server.") + return + + try: + busy = wx.BusyInfo("Loading... please wait.") + wx.Yield() + except: + pass + + # instantiate a mysql cursor. + cursor = mysql.cursor(MySQLdb.cursors.DictCursor) + + # retrieve the module info. + cursor.execute("SELECT * FROM pp_modules WHERE id = '%d'" % id) + module = cursor.fetchone() + + # save the selected module DB entry to the top. + self.top.module = module + + # step through the recon entries for this module id. + cursor.execute("SELECT * FROM pp_recon WHERE module_id = '%d' ORDER BY offset ASC" % id) + + percent = idx = reviewed = 0 + for recon in cursor.fetchall(): + address = module["base"] + recon["offset"] + + # count the number of hits under this recon point. + c = mysql.cursor(MySQLdb.cursors.DictCursor) + c.execute("SELECT COUNT(id) AS count FROM pp_hits WHERE recon_id = '%d'" % recon["id"]) + num_hits = c.fetchone()["count"] + c.close() + + self.InsertStringItem(idx, "") + self.SetStringItem(idx, 0, "%04x" % recon["id"]) + self.SetStringItem(idx, 1, "%08x" % address) + self.SetStringItem(idx, 2, "%d" % recon["stack_depth"]) + self.SetStringItem(idx, 3, recon["status"]) + self.SetStringItem(idx, 4, recon["username"]) + self.SetStringItem(idx, 5, "%d" % num_hits) + self.SetStringItem(idx, 6, recon["boron_tag"]) + self.SetStringItem(idx, 7, recon["reason"]) + + # create an entry for the column sort map. + self.SetItemData(idx, idx) + self.items_sort_map[idx] = (recon["id"], address, recon["stack_depth"], recon["status"], recon["username"], num_hits, recon["boron_tag"], recon["reason"]) + + if recon["status"] in ["uncontrollable", "clear", "vulnerable"]: + reviewed += 1 + idx += 1 + + # update % coverage gauge. + self.top.percent_analyzed_static.SetLabel("%d of %d RECON Points Reviewed:" % (reviewed, idx)) + try: + percent = int((float(reviewed) / float(idx)) * 100) + except: + pass + self.top.percent_analyzed.SetValue(percent) + + cursor.close() + + + #################################################################################################################### + def on_activated (self, event): + ''' + Load the PIDA module into the browser tree ctrl. + ''' + + recon_id = long(self.GetItem(event.m_itemIndex, 0).GetText(), 16) + dlg = _PAIMEIpeek.EditReconDlg.EditReconDlg(parent=self) + + dlg.propagate(recon_id) + dlg.ShowModal() + + + #################################################################################################################### + def on_right_click (self, event): + ''' + When an item in the recon list is right clicked, display a context menu. + ''' - #################################################################################################################### - def load (self, id): - self.DeleteAllItems() + if not self.x or not self.y: + return + + # we only have to do this once, that is what the hasattr() check is for. + if not hasattr(self, "right_click_popup_refresh"): + self.right_click_popup_refresh = wx.NewId() + self.right_click_popup_edit = wx.NewId() + self.right_click_popup_self_assign = wx.NewId() + self.right_click_popup_delete = wx.NewId() + self.right_click_popup_delete_all = wx.NewId() - mysql = self.top.main_frame.mysql + self.Bind(wx.EVT_MENU, self.on_right_click_popup_refresh, id=self.right_click_popup_refresh) + self.Bind(wx.EVT_MENU, self.on_right_click_popup_edit, id=self.right_click_popup_edit) + self.Bind(wx.EVT_MENU, self.on_right_click_popup_self_assign, id=self.right_click_popup_self_assign) + self.Bind(wx.EVT_MENU, self.on_right_click_popup_delete, id=self.right_click_popup_delete) + self.Bind(wx.EVT_MENU, self.on_right_click_popup_delete_all, id=self.right_click_popup_delete_all) + + # make a menu. + menu = wx.Menu() + menu.Append(self.right_click_popup_refresh, "&Refresh List") + menu.AppendSeparator() + menu.Append(self.right_click_popup_edit, "&Edit Recon Point") + menu.Append(self.right_click_popup_self_assign, "Assign to &Self") + menu.AppendSeparator() + menu.Append(self.right_click_popup_delete, "&Delete") + menu.Append(self.right_click_popup_delete_all, "Delete &All") - if not mysql: - self.top.err("No available connection to MySQL server.") - return - try: - busy = wx.BusyInfo("Loading... please wait.") - wx.Yield() - except: - pass + self.PopupMenu(menu, (self.x, self.y)) + menu.Destroy() - # instantiate a mysql cursor. - cursor = mysql.cursor(MySQLdb.cursors.DictCursor) - # retrieve the module info. - cursor.execute("SELECT * FROM pp_modules WHERE id = '%d'" % id) - module = cursor.fetchone() + #################################################################################################################### + def on_right_click_popup_delete (self, event): + ''' + Right click event handler for popup delete menu selection. + ''' + recon_id = self.selected_id - # save the selected module DB entry to the top. - self.top.module = module + # make sure the user is sure about this action. + dlg = wx.MessageDialog(self, 'Delete the selected recon point?', 'Are you sure?', wx.YES_NO | wx.ICON_QUESTION) - # step through the recon entries for this module id. - cursor.execute("SELECT * FROM pp_recon WHERE module_id = '%d' ORDER BY offset ASC" % id) + if dlg.ShowModal() != wx.ID_YES: + return - idx = reviewed = 0 - for recon in cursor.fetchall(): - address = module["base"] + recon["offset"] + cursor = self.top.main_frame.mysql.cursor() + cursor.execute("DELETE FROM pp_recon WHERE id = '%d'" % recon_id) + cursor.close() - # count the number of hits under this recon point. - c = mysql.cursor(MySQLdb.cursors.DictCursor) - c.execute("SELECT COUNT(id) AS count FROM pp_hits WHERE recon_id = '%d'" % recon["id"]) - num_hits = c.fetchone()["count"] - c.close() + # reload the recon list control. we reload instead of updating the control to partially solve + # contention issues when multiple users are hitting the database at the same time. + self.load(self.top.module["id"]) - self.InsertStringItem(idx, "") - self.SetStringItem(idx, 0, "%04x" % recon["id"]) - self.SetStringItem(idx, 1, "%08x" % address) - self.SetStringItem(idx, 2, "%d" % recon["stack_depth"]) - self.SetStringItem(idx, 3, recon["status"]) - self.SetStringItem(idx, 4, recon["username"]) - self.SetStringItem(idx, 5, "%d" % num_hits) - self.SetStringItem(idx, 6, recon["boron_tag"]) - self.SetStringItem(idx, 7, recon["reason"]) + #################################################################################################################### + def on_right_click_popup_delete_all (self, event): + ''' + Right click event handler for popup delete all menu selection. + ''' - # create an entry for the column sort map. - self.SetItemData(idx, idx) - self.items_sort_map[idx] = (recon["id"], address, recon["stack_depth"], recon["status"], recon["username"], num_hits, recon["boron_tag"], recon["reason"]) + recon_id = self.selected_id - if recon["status"] in ["uncontrollable", "clear", "vulnerable"]: - reviewed += 1 + # make sure the user is sure about this action. + dlg = wx.MessageDialog(self, 'Delete all recon points?', 'Are you sure?', wx.YES_NO | wx.ICON_QUESTION) - idx += 1 + if dlg.ShowModal() != wx.ID_YES: + return - # update % coverage gauge. - self.top.percent_analyzed_static.SetLabel("%d of %d RECON Points Reviewed:" % (reviewed, idx)) - percent = int((float(reviewed) / float(idx)) * 100) - self.top.percent_analyzed.SetValue(percent) + cursor = self.top.main_frame.mysql.cursor() + cursor.execute("DELETE FROM pp_hits WHERE module_id = '%d'" % self.top.module["id"]) + cursor.close() - cursor.close() + # empty the recon list control. + self.DeleteAllItems() + #################################################################################################################### + def on_right_click_popup_edit (self, event): + ''' + Right click event handler for popup edit menu selection. + ''' + recon_id = self.selected_id + dlg = _PAIMEIpeek.EditReconDlg.EditReconDlg(parent=self) + + dlg.propagate(recon_id) + dlg.ShowModal() - #################################################################################################################### - def on_activated (self, event): - ''' - Load the PIDA module into the browser tree ctrl. - ''' + #################################################################################################################### + def on_right_click_popup_refresh (self, event): + ''' + Right click event handler for popup refresh list. + ''' - recon_id = long(self.GetItem(event.m_itemIndex, 0).GetText(), 16) - dlg = _PAIMEIpeek.EditReconDlg.EditReconDlg(parent=self) + self.load(self.top.module["id"]) - dlg.propagate(recon_id) - dlg.ShowModal() + #################################################################################################################### + def on_right_click_popup_self_assign (self, event): + ''' + Right click event handler for popup assign item to self selection. + ''' + + if not self.top.main_frame.username: + self.top.err("You must tell PaiMei who you are first.") + return + + cursor = self.top.main_frame.mysql.cursor() + recon_id = self.selected_id - #################################################################################################################### - def on_right_click (self, event): - ''' - When an item in the recon list is right clicked, display a context menu. - ''' + cursor.execute("UPDATE pp_recon SET username = '%s' WHERE id = '%d'" % (self.top.main_frame.username, recon_id)) - if not self.x or not self.y: - return + # reload the recon list control. we reload instead of updating the control to partially solve + # contention issues when multiple users are hitting the database at the same time. + self.load(self.top.module["id"]) - # we only have to do this once, that is what the hasattr() check is for. - if not hasattr(self, "right_click_popup_refresh"): - self.right_click_popup_refresh = wx.NewId() - self.right_click_popup_edit = wx.NewId() - self.right_click_popup_self_assign = wx.NewId() - self.right_click_popup_delete = wx.NewId() - self.Bind(wx.EVT_MENU, self.on_right_click_popup_refresh, id=self.right_click_popup_refresh) - self.Bind(wx.EVT_MENU, self.on_right_click_popup_edit, id=self.right_click_popup_edit) - self.Bind(wx.EVT_MENU, self.on_right_click_popup_self_assign, id=self.right_click_popup_self_assign) - self.Bind(wx.EVT_MENU, self.on_right_click_popup_delete, id=self.right_click_popup_delete) + #################################################################################################################### + def on_right_down (self, event): + ''' + Grab the x/y coordinates when the right mouse button is clicked. + ''' - # make a menu. - menu = wx.Menu() - menu.Append(self.right_click_popup_refresh, "&Refresh List") - menu.AppendSeparator() - menu.Append(self.right_click_popup_edit, "&Edit Recon Point") - menu.Append(self.right_click_popup_self_assign, "Assign to &Self") - menu.AppendSeparator() - menu.Append(self.right_click_popup_delete, "Delete") + self.x = event.GetX() + self.y = event.GetY() - self.PopupMenu(menu, (self.x, self.y)) - menu.Destroy() + item, flags = self.HitTest((self.x, self.y)) + if flags & wx.LIST_HITTEST_ONITEM: + self.Select(item) + else: + self.x = None + self.y = None - #################################################################################################################### - def on_right_click_popup_delete (self, event): - ''' - Right click event handler for popup delete menu selection. - ''' - recon_id = self.selected_id + #################################################################################################################### + def on_select (self, event): + ''' + A line item in the recon list control was selected, load the hits list. + ''' - # make sure the user is sure about this action. - dlg = wx.MessageDialog(self, 'Delete the selected recon point?', 'Are you sure?', wx.YES_NO | wx.ICON_QUESTION) + recon_id = long(self.GetItem(event.m_itemIndex, 0).GetText(), 16) + self.selected_id = recon_id - if dlg.ShowModal() != wx.ID_YES: - return + # clear the hit list control. + self.top.hit_list.Set("") - cursor = self.top.main_frame.mysql.cursor() - cursor.execute("DELETE FROM pp_recon WHERE id = '%d'" % recon_id) - cursor.close() + # load the list of hits for this recon_id. + # select DESC so when we insert it re-sorts to ASC. + try: + cursor = self.top.main_frame.mysql.cursor(MySQLdb.cursors.DictCursor) + cursor.execute("SELECT id, timestamp FROM pp_hits WHERE recon_id = '%d' ORDER BY timestamp, id DESC" % recon_id) + except: + self.top.err("MySQL query failed. Connection dropped?") + return - # reload the recon list control. we reload instead of updating the control to partially solve - # contention issues when multiple users are hitting the database at the same time. - self.load(self.top.module["id"]) + hit = False + for hit in cursor.fetchall(): + timestamp = time.strftime("%m/%d/%Y %H:%M.%S", time.localtime(hit["timestamp"])) - #################################################################################################################### - def on_right_click_popup_edit (self, event): - ''' - Right click event handler for popup edit menu selection. - ''' + # timestamps are returned from the DB in reverse order and placed in ASC order by this command. + self.top.hit_list.Insert(timestamp, 0) - recon_id = self.selected_id - dlg = _PAIMEIpeek.EditReconDlg.EditReconDlg(parent=self) + # associate the needed ID with this inserted item. + self.top.hit_list.SetClientData(0, hit["id"]) - dlg.propagate(recon_id) - dlg.ShowModal() - - - #################################################################################################################### - def on_right_click_popup_refresh (self, event): - ''' - Right click event handler for popup refresh list. - ''' - - self.load(self.top.module["id"]) - - - #################################################################################################################### - def on_right_click_popup_self_assign (self, event): - ''' - Right click event handler for popup assign item to self selection. - ''' - - if not self.top.main_frame.username: - self.top.err("You must tell PaiMei who you are first.") - return - - cursor = self.top.main_frame.mysql.cursor() - recon_id = self.selected_id - - cursor.execute("UPDATE pp_recon SET username = '%s' WHERE id = '%d'" % (self.top.main_frame.username, recon_id)) - - # reload the recon list control. we reload instead of updating the control to partially solve - # contention issues when multiple users are hitting the database at the same time. - self.load(self.top.module["id"]) - - - #################################################################################################################### - def on_right_down (self, event): - ''' - Grab the x/y coordinates when the right mouse button is clicked. - ''' - - self.x = event.GetX() - self.y = event.GetY() - - item, flags = self.HitTest((self.x, self.y)) - - if flags & wx.LIST_HITTEST_ONITEM: - self.Select(item) - else: - self.x = None - self.y = None - - - #################################################################################################################### - def on_select (self, event): - ''' - A line item in the recon list control was selected, load the hits list. - ''' - - recon_id = long(self.GetItem(event.m_itemIndex, 0).GetText(), 16) - self.selected_id = recon_id - - # clear the hit list control. - self.top.hit_list.Set("") - - # load the list of hits for this recon_id. - # select DESC so when we insert it re-sorts to ASC. - try: - cursor = self.top.main_frame.mysql.cursor(MySQLdb.cursors.DictCursor) - cursor.execute("SELECT id, timestamp FROM pp_hits WHERE recon_id = '%d' ORDER BY timestamp, id DESC" % recon_id) - except: - self.top.err("MySQL query failed. Connection dropped?") - return - - hit = False - for hit in cursor.fetchall(): - timestamp = time.strftime("%m/%d/%Y %H:%M.%S", time.localtime(hit["timestamp"])) - - # timestamps are returned from the DB in reverse order and placed in ASC order by this command. - self.top.hit_list.Insert(timestamp, 0) - - # associate the needed ID with this inserted item. - self.top.hit_list.SetClientData(0, hit["id"]) - - # select the first entry in the hit list. - if hit: - self.top.on_hit_list_select(None, hit["id"]) - self.top.hit_list.Select(0) - else: - self.top.peek_data.SetValue("") \ No newline at end of file + # select the first entry in the hit list. + if hit: + self.top.on_hit_list_select(None, hit["id"]) + self.top.hit_list.Select(0) + else: + self.top.peek_data.SetValue("") \ No newline at end of file From 6868f5ae38531e627b13bb048abf3f81b88e2340 Mon Sep 17 00:00:00 2001 From: Cristiano Maruti Date: Sat, 3 Nov 2012 16:28:03 +0100 Subject: [PATCH 5/5] add "Ports" column in PStalker module --- .../_PAIMEIpstalker/ProcessListCtrl.py | 368 +++++++++--------- 1 file changed, 188 insertions(+), 180 deletions(-) diff --git a/console/modules/_PAIMEIpstalker/ProcessListCtrl.py b/console/modules/_PAIMEIpstalker/ProcessListCtrl.py index b37bd01..a04df5b 100644 --- a/console/modules/_PAIMEIpstalker/ProcessListCtrl.py +++ b/console/modules/_PAIMEIpstalker/ProcessListCtrl.py @@ -16,9 +16,9 @@ # ''' -@author: Pedram Amini -@license: GNU General Public License 2.0 or later -@contact: pedram.amini@gmail.com +@author: Pedram Amini +@license: GNU General Public License 2.0 or later +@contact: pedram.amini@gmail.com @organization: www.openrce.org ''' @@ -30,187 +30,195 @@ import utils class ProcessListCtrl (wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin): - ''' - Our custom list control containing a sortable list of PIDs and process names. - ''' + ''' + Our custom list control containing a sortable list of PIDs and process names. + ''' - FUNCTIONS = utils.process_stalker.FUNCTIONS - BASIC_BLOCKS = utils.process_stalker.BASIC_BLOCKS + FUNCTIONS = utils.process_stalker.FUNCTIONS + BASIC_BLOCKS = utils.process_stalker.BASIC_BLOCKS - def __init__(self, parent, id, pos=None, size=None, style=None, top=None): - wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | wx.SIMPLE_BORDER | wx.LC_HRULES ) - self.top = top - self.restore_breakpoints = False - self.selected_pid = 0 - self.selected_proc = None - self.process_stalker = None + def __init__(self, parent, id, pos=None, size=None, style=None, top=None): + wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | wx.SIMPLE_BORDER | wx.LC_HRULES ) + self.top = top + self.restore_breakpoints = False + self.selected_pid = 0 + self.selected_proc = None + self.process_stalker = None - ListCtrlAutoWidthMixin.__init__(self) + ListCtrlAutoWidthMixin.__init__(self) - self.items_sort_map = {} - self.itemDataMap = self.items_sort_map + + self.items_sort_map = {} + self.itemDataMap = self.items_sort_map - ColumnSorterMixin.__init__(self, 2) + ColumnSorterMixin.__init__(self, 2) - self.InsertColumn(0, "PID") - self.InsertColumn(1, "Process") + self.InsertColumn(0, "PID") + self.InsertColumn(1, "Process") + self.InsertColumn(2, "Ports") - - #################################################################################################################### - def GetListCtrl (self): - ''' - Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py - ''' - - return self - - - #################################################################################################################### - def on_attach_detach (self, event): - ''' - This is the meat and potatoes. Grab the coverage depth, attach to the selected process, load the appropriate - modules ... etc etc. - ''' - - ### - ### detaching ... - ### - - if self.process_stalker: - self.process_stalker.detach = True - self.process_stalker = None - - self.top.attach_detach.SetLabel("Start Stalking") - return - - ### - ### attaching / loading ... - ### - - # sanity checking. - if not len(self.top.pida_modules): - self.top.err("You must load at least one PIDA file.") - return - - if not self.top.stalk_tag: - self.top.err("You must select a tag for code coverage data storage.") - return - - # pull the value from the load target control, if anything is specified. - load_value = self.top.load_target.GetValue().rstrip(" ").lstrip(" ") - - if not self.selected_pid and not load_value and not self.top.watch: - self.top.err("You must select a target process or executable to stalk.") - return - - for module in self.top.pida_modules: - self.top.msg("Stalking module %s" % module) - - # create a new debugger instance for this stalk. - if hasattr(self.top.main_frame.pydbg, "port"): - dbg = pydbg_client(self.top.main_frame.pydbg.host, self.top.main_frame.pydbg.port) - else: - dbg = pydbg() - - # we are loading a target to stalk. (filled load control takes precedence over selected process) - if load_value: - # look for a quotation mark, any quotation mark will do but if there is one present we also need to know - # the location of the second one, so just start off by looking for that now. - second_quote = load_value.find('"', 1) - - if second_quote != -1: - load = load_value[1:second_quote] - args = load_value[second_quote+1:] - attach = None - else: - load = load_value - args = None - attach = None - - main = load - - if main.rfind("\\"): - main = main[main.rfind("\\")+1:] - - elif self.top.watch: - process_found = False - - self.top.msg("Watching for process: %s" % self.top.watch) - - while not process_found: - for (pid, proc_name) in dbg.enumerate_processes(): - wx.Yield() - if proc_name.lower() == self.top.watch.lower(): - process_found = True - break - - self.top.msg("Found target process at %d (0x04x)" % (pid, pid)) - - attach = pid - main = proc_name.lower() - load = None - args = None - - # we are attaching a target to stalk. - else: - attach = self.selected_pid - main = self.selected_proc.lower() - load = None - args = None - - self.process_stalker = utils.process_stalker( \ - attach = attach, \ - load = load, \ - args = args, \ - filter_list = self.top.filter_list, \ - heavy = self.top.heavy.GetValue(), \ - ignore_first_chance = self.top.ignore_first_chance.GetValue(), \ - log = self.top.msg, \ - main = main, \ - mysql = self.top.main_frame.mysql, \ - pida_modules = self.top.pida_modules, \ - pydbg = dbg, \ - print_bps = self.top.print_bps, \ - restore = self.top.restore_breakpoints.GetValue(), \ - tag_id = self.top.stalk_tag["id"], \ - target_id = self.top.stalk_tag["target_id"], \ - depth = self.top.coverage_depth.GetSelection() \ - ) - - self.top.attach_detach.SetLabel("Stop Stalking") - self.process_stalker.stalk() - - # reset state after stalking is finished. - self.top.attach_detach.SetLabel("Start Stalking") - self.process_stalker = None - - - #################################################################################################################### - def on_retrieve_list (self, event): - pydbg = self.top.main_frame.pydbg - - self.DeleteAllItems() - - idx = 0 - for (pid, proc) in pydbg.enumerate_processes(): - # ignore system processes. - if pid < 10: - continue - - self.InsertStringItem(idx, "") - self.SetStringItem(idx, 0, "%d" % pid) - self.SetStringItem(idx, 1, proc) - - self.items_sort_map[idx] = (pid, proc) - self.SetItemData(idx, idx) - - idx += 1 - - - #################################################################################################################### - def on_select (self, event): - ''' - ''' - - self.selected_pid = int(self.GetItem(event.m_itemIndex, 0).GetText()) - self.selected_proc = self.GetItem(event.m_itemIndex, 1).GetText() \ No newline at end of file + + #################################################################################################################### + def GetListCtrl (self): + ''' + Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + ''' + + return self + + + #################################################################################################################### + def on_attach_detach (self, event): + ''' + This is the meat and potatoes. Grab the coverage depth, attach to the selected process, load the appropriate + modules ... etc etc. + ''' + + ### + ### detaching ... + ### + + if self.process_stalker: + self.process_stalker.detach = True + self.process_stalker = None + + self.top.attach_detach.SetLabel("Start Stalking") + return + + ### + ### attaching / loading ... + ### + + # sanity checking. + if not len(self.top.pida_modules): + self.top.err("You must load at least one PIDA file.") + return + + if not self.top.stalk_tag: + self.top.err("You must select a tag for code coverage data storage.") + return + + # pull the value from the load target control, if anything is specified. + load_value = self.top.load_target.GetValue().rstrip(" ").lstrip(" ") + + if not self.selected_pid and not load_value and not self.top.watch: + self.top.err("You must select a target process or executable to stalk.") + return + + for module in self.top.pida_modules: + self.top.msg("Stalking module %s" % module) + + # create a new debugger instance for this stalk. + if hasattr(self.top.main_frame.pydbg, "port"): + dbg = pydbg_client(self.top.main_frame.pydbg.host, self.top.main_frame.pydbg.port) + else: + dbg = pydbg() + + # we are loading a target to stalk. (filled load control takes precedence over selected process) + if load_value: + # look for a quotation mark, any quotation mark will do but if there is one present we also need to know + # the location of the second one, so just start off by looking for that now. + second_quote = load_value.find('"', 1) + + if second_quote != -1: + load = load_value[1:second_quote] + args = load_value[second_quote+1:] + attach = None + else: + load = load_value + args = None + attach = None + + main = load + + if main.rfind("\\"): + main = main[main.rfind("\\")+1:] + + elif self.top.watch: + process_found = False + + self.top.msg("Watching for process: %s" % self.top.watch) + + while not process_found: + for (pid, proc_name) in dbg.enumerate_processes(): + wx.Yield() + if proc_name.lower() == self.top.watch.lower(): + process_found = True + break + + self.top.msg("Found target process at %d (0x04x)" % (pid, pid)) + + attach = pid + main = proc_name.lower() + load = None + args = None + + # we are attaching a target to stalk. + else: + attach = self.selected_pid + main = self.selected_proc.lower() + load = None + args = None + + self.process_stalker = utils.process_stalker( \ + attach = attach, \ + load = load, \ + args = args, \ + filter_list = self.top.filter_list, \ + heavy = self.top.heavy.GetValue(), \ + ignore_first_chance = self.top.ignore_first_chance.GetValue(), \ + log = self.top.msg, \ + main = main, \ + mysql = self.top.main_frame.mysql, \ + pida_modules = self.top.pida_modules, \ + pydbg = dbg, \ + print_bps = self.top.print_bps, \ + restore = self.top.restore_breakpoints.GetValue(), \ + tag_id = self.top.stalk_tag["id"], \ + target_id = self.top.stalk_tag["target_id"], \ + depth = self.top.coverage_depth.GetSelection() \ + ) + + self.top.attach_detach.SetLabel("Stop Stalking") + self.process_stalker.stalk() + + # reset state after stalking is finished. + self.top.attach_detach.SetLabel("Start Stalking") + self.process_stalker = None + + + #################################################################################################################### + def on_retrieve_list (self, event): + pydbg = self.top.main_frame.pydbg + + self.DeleteAllItems() + + idx = 0 + for (pid, proc) in pydbg.enumerate_processes(): + # ignore system processes. + if pid < 10: + continue + + self.InsertStringItem(idx, "") + self.SetStringItem(idx, 0, "%d" % pid) + self.SetStringItem(idx, 1, proc) + + portslist = pydbg.pid_to_port(pid) + ports = "" + for (lport, addr, proto) in portslist: + ports += addr + ":" + lport + "/" + proto + " " + + self.SetStringItem(idx, 2, ports) + + self.items_sort_map[idx] = (pid, proc) + self.SetItemData(idx, idx) + + idx += 1 + + #################################################################################################################### + def on_select (self, event): + ''' + ''' + + self.selected_pid = int(self.GetItem(event.m_itemIndex, 0).GetText()) + self.selected_proc = self.GetItem(event.m_itemIndex, 1).GetText() \ No newline at end of file