def parse_device(): usbmux = context.usbmux devices = usbmux.device_list() if len(devices) == 0: raise MuxError("error: no devices/emulators found") if len(devices) == 1: return devices[0].udid logger.message("more than one device/emulator") offset = 1 for i in range(len(devices)): try: name = Device(devices[0].udid, usbmux).name except Exception: name = "" logger.message(f"%d: %-20s [%s]" % (i + offset, devices[i].udid, name)) while True: offset = 1 data = input("enter device index %d~%d (default %d): " % (offset, len(devices) + offset - 1, offset)) if utils.is_empty(data): return devices[0].udid index = utils.cast(int, data, offset - 1) - offset if 0 <= index < len(devices): return devices[index].udid
def on_binary(self, filename: str, mimetype: str): with open(filename, "rb") as fd: for line in fd.readlines(): if self.pattern.search(line) is not None: logger.message(Fore.CYAN, filename, Fore.RESET, ": ", Fore.RED, mimetype, Fore.RESET, " match") return
def parse_device(): devices = Adb.devices(alive=True) if len(devices) == 0: raise AdbError("error: no devices/emulators found") if len(devices) == 1: return devices[0] logger.message("more than one device/emulator") offset = 1 for i in range(len(devices)): try: name = Device(devices[i]).get_prop("ro.product.name", timeout=1) except Exception: name = "" logger.message("%d: %-20s [%s]" % (i + offset, devices[i], name)) while True: data = input("enter device index %d~%d (default %d): " % (offset, len(devices) + offset - 1, offset)) if utils.is_empty(data): return devices[0] index = utils.cast(int, data, offset - 1) - offset if 0 <= index < len(devices): return devices[index]
def on_text(self, filename: str, mimetype: str): with open(filename, "rb") as fd: lines = fd.readlines() for i in range(0, len(lines)): out = self.match_content(lines[i].rstrip()) if not utils.is_empty(out): logger.message(Fore.CYAN, filename, Fore.RESET, ":", Fore.GREEN, i + 1, Fore.RESET, ": ", out)
def on_elf(self, filename: str, mimetype: str): file = lief.parse(filename) for symbol in file.imported_symbols: out = self.match_content(symbol.name) if not utils.is_empty(out): logger.message(Fore.CYAN, filename, Fore.RESET, ":", Fore.GREEN, "import_symbols", Fore.RESET, ": ", out, Fore.RESET, " match") for symbol in file.exported_symbols: out = self.match_content(symbol.name) if not utils.is_empty(out): logger.message(Fore.CYAN, filename, Fore.RESET, ":", Fore.GREEN, "export_symbols", Fore.RESET, ": ", out, Fore.RESET, " match") self.on_binary(filename, mimetype=mimetype)
def main(): tool_names = sorted([tool.name for tool in iter(tools)]) parser = ArgumentParser(description='tools wrapper') group = parser.add_mutually_exclusive_group() group.add_argument('-c', '--config', action='store_true', default=False, help='show the config of tool') group.add_argument('--download', action='store_true', default=False, help='download tool files') group.add_argument('--clear', action='store_true', default=False, help='clear tool files') group.add_argument('-d', '--daemon', action='store_true', default=False, help='execute tools as a daemon') parser.add_argument('tool', nargs='...', choices=tool_names) args = parser.parse_args() if len(args.tool) == 0 or args.tool[0] not in tool_names: parser.print_help() exit(-1) tool_name = args.tool[0] tool_args = args.tool[1:] if args.config: logger.message( json.dumps(tools[tool_name].config, indent=2, ensure_ascii=False)) elif args.download: if not tools[tool_name].exists: tools[tool_name].prepare() logger.message( f"download tool files success: {tools[tool_name].absolute_path}") elif args.clear: tools[tool_name].clear() logger.message(f"clear tool files success") elif args.daemon: tools[tool_name].exec( *tool_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, daemon=True, ) else: process, _, _ = tools[tool_name].exec(*tool_args) exit(process.returncode)
def print(self, text: str = "", indent: int = 0, level=PrintLevel.normal): if not self.min <= level <= self.max: pass elif level == PrintLevel.title: logger.message(text, style=Style.BRIGHT, indent=indent) elif level == PrintLevel.dangerous: logger.message(text, fore=Fore.RED, back=Back.WHITE, style=Style.BRIGHT, indent=indent) elif level == PrintLevel.useless: logger.message(text, fore=Fore.YELLOW, back=Back.WHITE, style=Style.BRIGHT, indent=indent) else: logger.message(text, indent=indent)
def main(): parser = AndroidArgumentParser(description='Filter logcat by package name', conflict_handler='resolve') parser.add_argument('package', nargs='*', help='application package name(s)') parser.add_argument('-w', '--tag-width', metavar='N', dest='tag_width', type=int, default=23, help='width of log tag') parser.add_argument('-l', '--min-level', dest='min_level', type=str, choices=LOG_LEVELS + LOG_LEVELS.lower(), default='V', help='minimum level to be displayed') parser.add_argument('--color-gc', dest='color_gc', action='store_true', help='color garbage collection') parser.add_argument('--always-display-tags', dest='always_tags', action='store_true', help='always display the tag name') parser.add_argument('--top', '--current', dest='current_app', action='store_true', help='filter logcat by current running app') parser.add_argument('-c', '--clear', dest='clear_logcat', action='store_true', help='clear the entire log before running') parser.add_argument('-t', '--tag', dest='tag', action='append', help='filter output by specified tag(s)') parser.add_argument('-i', '--ignore-tag', dest='ignored_tag', action='append', help='filter output by ignoring specified tag(s)') parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__, help='print the version number and exit') parser.add_argument('-a', '--all', dest='all', action='store_true', default=False, help='print all log messages') args = parser.parse_args() device = args.parse_device() package = args.package min_level = LOG_LEVELS_MAP[args.min_level.upper()] if args.current_app: package.append(device.get_current_package()) if len(package) == 0: args.all = True # Store the names of packages for which to match all processes. catchall_package = list( filter(lambda package: package.find(":") == -1, package)) # Store the name of processes to match exactly. named_processes = list( filter(lambda package: package.find(":") != -1, package)) # Convert default process names from <package>: (cli notation) to <package> (android notation) in the exact names match group. named_processes = list( map( lambda package: package if package.find(":") != len(package) - 1 else package[:-1], named_processes)) header_size = args.tag_width + 1 + 3 + 1 # space, level, space width = -1 try: # Get the current terminal width import fcntl, termios, struct h, width = struct.unpack( 'hh', fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hh', 0, 0))) except: pass BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) RESET = '\033[0m' def termcolor(fg=None, bg=None): codes = [] if fg is not None: codes.append('3%d' % fg) if bg is not None: codes.append('10%d' % bg) return '\033[%sm' % ';'.join(codes) if codes else '' def colorize(message, fg=None, bg=None): return termcolor(fg, bg) + message + RESET def truncated_string(message, offset, width): next = offset length = len(message) while next < length: width = width - get_char_width(message[next]) if width < 0: break next = next + 1 return next def indent_wrap(message): if width == -1: return message message = message.replace('\t', ' ') wrap_area = width - header_size - 1 messagebuf = '' current = 0 next = truncated_string(message, current, wrap_area) while current < next: messagebuf += message[current:next] if next < len(message): messagebuf += '\n' messagebuf += ' ' * header_size current = next next = truncated_string(message, current, wrap_area) return messagebuf LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN] KNOWN_TAGS = { 'dalvikvm': WHITE, 'Process': WHITE, 'ActivityManager': WHITE, 'ActivityThread': WHITE, 'AndroidRuntime': CYAN, 'jdwp': WHITE, 'StrictMode': WHITE, 'DEBUG': YELLOW, } def allocate_color(tag): # this will allocate a unique format for the given tag # since we dont have very many colors, we always keep track of the LRU if tag not in KNOWN_TAGS: KNOWN_TAGS[tag] = LAST_USED[0] color = KNOWN_TAGS[tag] if color in LAST_USED: LAST_USED.remove(color) LAST_USED.append(color) return color RULES = { # StrictMode policy violation; ~duration=319 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1 re.compile(r'^(StrictMode policy violation)(; ~duration=)(\d+ ms)'): r'%s\1%s\2%s\3%s' % (termcolor(RED), RESET, termcolor(YELLOW), RESET), } # Only enable GC coloring if the user opted-in if args.color_gc: # GC_CONCURRENT freed 3617K, 29% free 20525K/28648K, paused 4ms+5ms, total 85ms key = re.compile( r'^(GC_(?:CONCURRENT|FOR_M?ALLOC|EXTERNAL_ALLOC|EXPLICIT) )(freed <?\d+.)(, \d+\% free \d+./\d+., )(paused \d+ms(?:\+\d+ms)?)' ) val = r'\1%s\2%s\3%s\4%s' % (termcolor(GREEN), RESET, termcolor(YELLOW), RESET) RULES[key] = val TAGTYPES = { 'V': colorize(' V ', fg=WHITE, bg=BLACK), 'D': colorize(' D ', fg=BLACK, bg=BLUE), 'I': colorize(' I ', fg=BLACK, bg=GREEN), 'W': colorize(' W ', fg=BLACK, bg=YELLOW), 'E': colorize(' E ', fg=BLACK, bg=RED), 'F': colorize(' F ', fg=BLACK, bg=RED), } PID_LINE = re.compile( r'^\w+\s+(\w+)\s+\w+\s+\w+\s+\w+\s+\w+\s+\w+\s+\w\s([\w|\.|\/]+)$') PID_START = re.compile( r'^.*: Start proc ([a-zA-Z0-9._:]+) for ([a-z]+ [^:]+): pid=(\d+) uid=(\d+) gids=(.*)$' ) PID_START_5_1 = re.compile( r'^.*: Start proc (\d+):([a-zA-Z0-9._:]+)/[a-z0-9]+ for (.*)$') PID_START_DALVIK = re.compile( r'^E/dalvikvm\(\s*(\d+)\): >>>>> ([a-zA-Z0-9._:]+) \[ userId:0 \| appId:(\d+) \]$' ) PID_KILL = re.compile(r'^Killing (\d+):([a-zA-Z0-9._:]+)/[^:]+: (.*)$') PID_LEAVE = re.compile( r'^No longer want ([a-zA-Z0-9._:]+) \(pid (\d+)\): .*$') PID_DEATH = re.compile( r'^Process ([a-zA-Z0-9._:]+) \(pid (\d+)\) has died.?$') LOG_LINE = re.compile(r'^([A-Z])/(.+?)\( *(\d+)\): (.*?)$') BUG_LINE = re.compile(r'.*nativeGetEnabledTags.*') BACKTRACE_LINE = re.compile(r'^#(.*?)pc\s(.*?)$') adb_command = ['logcat', '-v', 'brief'] # Clear log before starting logcat if args.clear_logcat: adb_clear_command = list(adb_command) adb_clear_command.append('-c') adb_clear = device.popen(*adb_clear_command) while adb_clear.poll() is None: pass # This is a ducktype of the device.popen object class FakeStdinProcess(): def __init__(self): self.stdout = sys.stdin def poll(self): return None if sys.stdin.isatty(): adb = device.popen(*adb_command, stdin=PIPE, stdout=PIPE) else: adb = FakeStdinProcess() pids = set() last_tag = None app_pid = None def match_packages(token): if len(package) == 0: return True if token in named_processes: return True index = token.find(':') return (token in catchall_package) if index == -1 else ( token[:index] in catchall_package) def parse_death(tag, message): if tag != 'ActivityManager': return None, None kill = PID_KILL.match(message) if kill: pid = kill.group(1) package_line = kill.group(2) if match_packages(package_line) and pid in pids: return pid, package_line leave = PID_LEAVE.match(message) if leave: pid = leave.group(2) package_line = leave.group(1) if match_packages(package_line) and pid in pids: return pid, package_line death = PID_DEATH.match(message) if death: pid = death.group(2) package_line = death.group(1) if match_packages(package_line) and pid in pids: return pid, package_line return None, None def parse_start_proc(line): start = PID_START_5_1.match(line) if start is not None: line_pid, line_package, target = start.groups() return line_package, target, line_pid, '', '' start = PID_START.match(line) if start is not None: line_package, target, line_pid, line_uid, line_gids = start.groups( ) return line_package, target, line_pid, line_uid, line_gids start = PID_START_DALVIK.match(line) if start is not None: line_pid, line_package, line_uid = start.groups() return line_package, '', line_pid, line_uid, '' return None def tag_in_tags_regex(tag, tags): return any( re.match(r'^' + t + r'$', tag) for t in map(str.strip, tags)) for line in device.shell("ps", stdout=PIPE, stderr=DEVNULL).splitlines(): pid_match = PID_LINE.match(line.strip()) if pid_match is not None: pid = pid_match.group(1) proc = pid_match.group(2) if proc in catchall_package: seen_pids = True pids.add(pid) for line in device.shell("ps", "-A", stdout=PIPE, stderr=DEVNULL).splitlines(): pid_match = PID_LINE.match(line.strip()) if pid_match is not None: pid = pid_match.group(1) proc = pid_match.group(2) if proc in catchall_package: seen_pids = True pids.add(pid) while adb.poll() is None: try: line = adb.stdout.readline().decode('utf-8', errors="ignore").strip() except KeyboardInterrupt: break bug_line = BUG_LINE.match(line) if bug_line is not None: continue log_line = LOG_LINE.match(line) if log_line is None: continue level, tag, owner, message = log_line.groups() tag = tag.strip() start = parse_start_proc(line) if start: line_package, target, line_pid, line_uid, line_gids = start if match_packages(line_package): pids.add(line_pid) app_pid = line_pid linebuf = '\n' linebuf += colorize(' ' * (header_size - 1), bg=WHITE) linebuf += indent_wrap(' Process %s created for %s\n' % (line_package, target)) linebuf += colorize(' ' * (header_size - 1), bg=WHITE) linebuf += ' PID: %s UID: %s GIDs: %s' % ( line_pid, line_uid, line_gids) linebuf += '\n' logger.message(linebuf) last_tag = None # Ensure next log gets a tag printed dead_pid, dead_pname = parse_death(tag, message) if dead_pid: pids.remove(dead_pid) linebuf = '\n' linebuf += colorize(' ' * (header_size - 1), bg=RED) linebuf += ' Process %s (PID: %s) ended' % (dead_pname, dead_pid) linebuf += '\n' logger.message(linebuf) last_tag = None # Ensure next log gets a tag printed # Make sure the backtrace is printed after a native crash if tag == 'DEBUG': bt_line = BACKTRACE_LINE.match(message.lstrip()) if bt_line is not None: message = message.lstrip() owner = app_pid if not args.all and owner not in pids: continue if level in LOG_LEVELS_MAP and LOG_LEVELS_MAP[level] < min_level: continue if args.ignored_tag and tag_in_tags_regex(tag, args.ignored_tag): continue if args.tag and not tag_in_tags_regex(tag, args.tag): continue linebuf = '' if args.tag_width > 0: # right-align tag title and allocate color if needed if tag != last_tag or args.always_tags: last_tag = tag color = allocate_color(tag) tag = tag[-args.tag_width:].rjust(args.tag_width) linebuf += colorize(tag, fg=color) else: linebuf += ' ' * args.tag_width linebuf += ' ' # write out level colored edge if level in TAGTYPES: linebuf += TAGTYPES[level] else: linebuf += ' ' + level + ' ' linebuf += ' ' # format tag message using rules for matcher in RULES: replace = RULES[matcher] message = matcher.sub(replace, message) linebuf += indent_wrap(message) logger.message(linebuf)
def main(): parser = AndroidArgumentParser( description='show current running app\'s basic information') group = parser.add_mutually_exclusive_group() group.add_argument('-p', '--package', action='store_const', const=True, default=False, help='show current running package name') group.add_argument('-a', '--activity', action='store_const', const=True, default=False, help='show current running activity name') group.add_argument('--path', action='store_const', const=True, default=False, help='show current running package path') group.add_argument('--kill', action='store_const', const=True, default=False, help='kill current running package') group.add_argument('--apk', metavar='DEST', action='store', type=str, nargs='?', default=".", help='pull current running apk file') group.add_argument('--screen', metavar='DEST', action='store', type=str, nargs='?', default=".", help='capture screen and pull file') args = parser.parse_args() device = args.parse_device() if args.package: logger.message(device.get_current_package()) elif args.activity: logger.message(device.get_current_activity()) elif args.path: logger.message(device.get_apk_path(device.get_current_package())) elif args.kill: device.shell("am", "force-stop", device.get_current_package(), capture_output=False) elif "--apk" in sys.argv: package_name = device.get_current_package() logger.message("get current running package: {}".format(package_name)) package = utils.get_item( device.get_packages(package_name, basic_info=True), 0) if package is not None: logger.message("get current running package path: {}".format( package.sourceDir)) path = device.get_storage_path("{}_{}.apk".format( package.name, package.versionName)) dest = args.apk if not utils.is_empty(args.apk) else "." device.shell("mkdir", "-p", device.get_storage_path(), capture_output=False) device.shell("cp", package.sourceDir, path, capture_output=False) device.pull(path, dest, capture_output=False) device.shell("rm", path) elif "--screen" in sys.argv: now = datetime.datetime.now() path = device.get_storage_path("screenshot-" + now.strftime("%Y-%m-%d-%H-%M-%S") + ".png") dest = args.screen if not utils.is_empty(args.screen) else "." device.shell("mkdir", "-p", device.get_storage_path(), capture_output=False) device.shell("screencap", "-p", path, capture_output=False) device.pull(path, dest, capture_output=False) device.shell("rm", path) else: package = device.get_current_package() logger.message("package: ", package) logger.message("activity: ", device.get_current_activity()) logger.message("path: ", device.get_apk_path(package))
def print_line(self): logger.message()