class FridaThread(threading.Thread): def __init__(self, device, install: bool, port: int, regexps: list): super().__init__() self.log = logging.getLogger(self.__class__.__name__ + '|' + device.id) if device.type == FakeDevice.type: # init remote device self.log.debug( 'device {} does not support get_usb_device, changing to get_remote_device' .format(device.id)) self.forward_port = port_manager.acquire_port(excludes=[port]) self.device = frida.get_device_manager().add_remote_device( '127.0.0.1:{}'.format(self.forward_port)) self.device.id = device.id else: self.device = device self.install = install self.port = port self.regexps = regexps if regexps else ['.*'] self.adb = Adb(self.device.id) if device.type == FakeDevice.type: result = self.adb.forward(self.forward_port, FRIDA_SERVER_DEFAULT_PORT) # port has been used if len(result['err'].strip()) > 0: port_manager.release_port(self.forward_port) raise RuntimeError('port {} has been used'.format( self.forward_port)) if self.port: self.iptables = Iptables(self.adb, self.port) self.arch = self.adb.unsafe_shell("getprop ro.product.cpu.abi")['out'] # maybe get 'arm64-v8a', 'arm-v7a' ... if 'arm64' in self.arch: self.arch = 'arm64' elif 'arm' in self.arch: self.arch = 'arm' elif 'x86_64' in self.arch: self.arch = 'x86_64' elif 'x86' in self.arch: self.arch = 'x86' else: raise RuntimeError('unknown arch: ' + self.arch) self.server_name = 'frida-server-{}-android-{}'.format( frida.__version__, self.arch) self.stop_flag = False thread_manager.add_thread(self) def run(self) -> None: self.log.info( "{} start with hook device: id={}, name={}, type={}".format( self.__class__.__name__, self.device.id, self.device.name, self.device.type)) try: self.prepare() self.hook_apps() except Exception as e: self.log.error('device {}: {}'.format(self.device.id, e)) try: self.shutdown() except Exception as e: self.log.error( 'unexpected error occurred when shutdown device {}: {}'.format( self.device.id, e)) self.log.debug('device {} exit'.format(self.device.id)) thread_manager.del_thread(self) # prepare for starting hook def prepare(self): # get root self.adb.root() # close selinux self.adb.unsafe_shell('setenforce 0', root=True) # install iptables and reverse tcp port if self.port: # enable tcp connections between frida server and binding self.iptables.install() self.adb.reverse(self.port) if self.install: self.install_frida_server() self.kill_frida_servers() self.run_frida_server() def download(self, url, file_path): # get total size of file r1 = requests.get(url, stream=True, verify=False) total_size = int(r1.headers['Content-Length']) # check downloaded size if os.path.exists(file_path): temp_size = os.path.getsize(file_path) else: temp_size = 0 if temp_size == total_size: self.log.info('{} has downloaded completely'.format(file_path)) return if temp_size > total_size: self.log.error( '{} has corrupted, download it again'.format(file_path)) os.remove(file_path) return self.download(url, file_path) self.log.debug('{} of {} needs to be download'.format( total_size - temp_size, total_size)) # download from temp size to end headers = {'Range': 'bytes={}-'.format(temp_size)} r = requests.get(url, stream=True, verify=False, headers=headers) with open(file_path, "ab") as f: for chunk in r.iter_content(chunk_size=1024): if self.stop_flag: break if chunk: temp_size += len(chunk) f.write(chunk) f.flush() # download progress done = int(50 * temp_size / total_size) sys.stdout.write("\r[{}{}] {:.2f}%".format( '█' * done, ' ' * (50 - done), 100 * temp_size / total_size)) sys.stdout.flush() sys.stdout.write(os.linesep) def install_frida_server(self): server_path = os.path.join(ROOT_DIR, 'assets', self.server_name) server_path_xz = server_path + '.xz' # if not exist frida server then install it if not self.adb.unsafe_shell("ls /data/local/tmp/" + self.server_name)['out']: self.log.info('download {} from github ...'.format( self.server_name)) with __lock__: self.download( 'https://github.com/frida/frida/releases/download/{}/{}.xz' .format(frida.__version__, self.server_name), server_path_xz) # extract frida server with open(server_path, 'wb') as f: with lzma.open(server_path_xz) as xz: f.write(xz.read()) # upload frida server self.adb.push(server_path, '/data/local/tmp/') def kill_frida_servers(self): try: apps = self.device.enumerate_processes() except (frida.ServerNotRunningError, frida.TransportError, frida.InvalidOperationError): # frida server has not been started, no need to start return for app in apps: if app.name == self.server_name: self.adb.unsafe_shell('kill -9 {}'.format(app.pid), root=True, debug=False) def run_frida_server(self): self.adb.unsafe_shell('chmod +x /data/local/tmp/' + self.server_name) threading.Thread(target=self.adb.unsafe_shell, args=('/data/local/tmp/{} -D'.format( self.server_name), True)).start() # waiting for frida server while True: try: time.sleep(0.5) self.device.enumerate_processes() break except (frida.ServerNotRunningError, frida.TransportError, frida.InvalidOperationError): continue def hook_apps(self): apps = set() # monitor apps while True: if self.stop_flag: break time.sleep(0.1) new_apps = set(p.name for p in self.device.enumerate_processes()) if not new_apps: continue incremental_apps = new_apps - apps decremental_apps = apps - new_apps for incremental_app in incremental_apps: if self.stop_flag: break for regexp in self.regexps: if re.search(regexp, incremental_app): # waiting for app startup completely time.sleep(0.1) try: self.hook(incremental_app) except Exception as e: self.log.error(e) finally: break for decremental_app in decremental_apps: if self.stop_flag: break for regexp in self.regexps: if re.search(regexp, decremental_app): self.log.info( 'app {} has died'.format(decremental_app)) break apps = new_apps def hook(self, app: str): app = app.strip() if not app: raise RuntimeError('try to hook empty app name') self.log.info('hook app ' + app) process = self.device.attach(app) js = 'Java.perform(function() {' # load all scripts under folder 'scripts' for (dirpath, dirnames, filenames) in os.walk(os.path.join(ROOT_DIR, 'scripts')): for filename in filenames: _ = open(os.path.join(dirpath, filename), encoding="utf-8").read() if _.startswith(r'''/*Deprecated*/'''): continue js += _ js += '\n' js += '});' script = process.create_script(js) script.on('message', self.on_message(app)) script.load() def on_message(self, app: str): app_log = logging.getLogger('{}|{}|{}'.format(self.__class__.__name__, self.device.id, app)) def on_message_inner(message, data): try: if message['type'] == 'error': text = message['description'].strip() if not text: return app_log.error(text) else: text = message['payload'].strip( ) if message['type'] == 'send' else message.strip() if not text: return app_log.info(text) except Exception as e: app_log.error(e) return on_message_inner def cancel(self): self.stop_flag = True def shutdown(self): self.log.debug('shutdown device ' + self.device.id) self.kill_frida_servers() if self.device.type == 'remote': port_manager.release_port(self.forward_port) self.adb.clear_forward(self.forward_port) if self.port: self.iptables.uninstall() self.adb.clear_reverse(self.port)
class FridaThread(threading.Thread): def __init__(self, device): super().__init__( name="{}-{}".format(self.__class__.__name__, device.id)) self.thread_tag = self.name self.server_executor = ThreadPoolExecutor(max_workers=1) self.log = logging.getLogger(self.thread_tag) self.frida_server_port = FRIDA_SERVER_DEFAULT_PORT if options.random_port: self.frida_server_port = port_manager.acquire_port( excludes=[FRIDA_SERVER_DEFAULT_PORT]) self.log.info("pick random port {} for frida-server".format( self.frida_server_port)) if options.random_port or device.type == FakeDevice.type: # init remote device self.log.debug('device {} does not support get_usb_device, ' 'changing to get_remote_device method'.format( device.id)) self.forward_port = port_manager.acquire_port( excludes=[options.port]) self.device = frida.get_device_manager().add_remote_device( '127.0.0.1:{}'.format(self.forward_port)) self.device.id = device.id else: self.device = device self.adb = Adb(self.device.id) self.log.info("frida-server port: {}".format(self.frida_server_port)) if options.random_port or device.type == FakeDevice.type: result = self.adb.forward(self.forward_port, self.frida_server_port) # port has been used if result.err: port_manager.release_port(self.forward_port) raise RuntimeError('port {} has been used'.format( self.forward_port)) if options.port: self.iptables = Iptables(self.adb, options.port) self.arch = self.adb.unsafe_shell("getprop ro.product.cpu.abi").out # maybe get 'arm64-v8a', 'arm-v7a' ... if 'arm64' in self.arch: self.arch = 'arm64' elif 'arm' in self.arch: self.arch = 'arm' elif 'x86_64' in self.arch: self.arch = 'x86_64' elif 'x86' in self.arch: self.arch = 'x86' else: raise RuntimeError('unknown arch: ' + self.arch) self.server_name = 'frida-server-{}-android-{}'.format( frida.__version__, self.arch) self._terminate = False def run(self) -> None: self.log.info("start with hook device: id={}, name={}, type={}".format( self.device.id, self.device.name, self.device.type)) try: self.prepare() self.hook_apps() except Exception as e: self.log.error('device {}: {}'.format(self.device.id, e)) try: self.shutdown() except Exception as e: self.log.error( 'unexpected error occurred when shutdown device {}: {}'.format( self.device.id, e)) self.log.debug('device {} exit'.format(self.device.id)) # prepare for starting hook def prepare(self): if self._terminate: return # get root if not options.no_root: self.adb.root() # close selinux self.adb.unsafe_shell('setenforce 0', root=True) # install iptables and reverse tcp port if options.port: # enable tcp connections between frida server and binding self.iptables.install() self.adb.reverse(options.port) if options.install: self.install_frida_server() self.kill_frida_servers() self.run_frida_server() def download(self, url, file_path): # get total size of file r1 = requests.get(url, stream=True, verify=False) total_size = int(r1.headers['Content-Length']) # check downloaded size if os.path.exists(file_path): temp_size = os.path.getsize(file_path) else: temp_size = 0 if temp_size == total_size: self.log.info('{} has downloaded completely'.format(file_path)) return if temp_size > total_size: self.log.error( '{} has corrupted, download it again'.format(file_path)) os.remove(file_path) return self.download(url, file_path) self.log.debug('{} of {} needs to be download'.format( total_size - temp_size, total_size)) # download from temp size to end headers = {'Range': 'bytes={}-'.format(temp_size)} r = requests.get(url, stream=True, verify=False, headers=headers) with open(file_path, "ab") as f: for chunk in r.iter_content(chunk_size=1024): if self._terminate: break if chunk: temp_size += len(chunk) f.write(chunk) f.flush() # download progress done = int(50 * temp_size / total_size) sys.stdout.write("\r[{}{}] {:.2f}%".format( '█' * done, ' ' * (50 - done), 100 * temp_size / total_size)) sys.stdout.flush() sys.stdout.write(os.linesep) def install_frida_server(self): server_path = os.path.join(ROOT_DIR, 'assets', self.server_name) server_path_xz = server_path + '.xz' # if not exist frida server then install it if not self.adb.unsafe_shell("ls /data/local/tmp/" + self.server_name).out: self.log.info('download {} from github ...'.format( self.server_name)) with __lock__: self.download( 'https://github.com/frida/frida/releases/download/{}/{}.xz' .format(frida.__version__, self.server_name), server_path_xz) # extract frida server with open(server_path, 'wb') as f: with lzma.open(server_path_xz) as xz: f.write(xz.read()) # upload frida server self.log.info("pushing frida-server-{}...".format( self.server_name)) self.adb.push(server_path, '/data/local/tmp/') def kill_frida_servers(self): try: apps = self.device.enumerate_processes() except (frida.ServerNotRunningError, frida.TransportError, frida.InvalidOperationError): # frida server has not been started, no need to start return for app in apps: if app.name == self.server_name: self.log.debug("killing {}...".format(self.server_name)) self.adb.unsafe_shell('kill -9 {}'.format(app.pid), root=True, quiet=True) time.sleep(0.5) def run_frida_server(self): self.adb.unsafe_shell('chmod +x /data/local/tmp/' + self.server_name) self.server_executor.submit( self.adb.unsafe_shell, '/data/local/tmp/{} -l 127.0.0.1:{} -D'.format( self.server_name, self.frida_server_port), True) # waiting for frida server max_try = 100 while True: try: if max_try < 0: self.log.info("{} can't run frida server ".format( self.thread_tag)) self.terminate() return time.sleep(0.5) if not self._terminate: self.device.enumerate_processes() self.log.info("frida-server connected") break except (frida.ServerNotRunningError, frida.TransportError, frida.InvalidOperationError): max_try -= 1 continue def hook_apps(self): apps = set() self.log.info("filter: {}".format(options.regexps)) # monitor apps while True: if self._terminate: break time.sleep(0.1) new_apps = set('{}:{}'.format(p.pid, p.identifier) for p in self.device.enumerate_applications()) if not new_apps: continue incremental_apps = new_apps - apps decremental_apps = apps - new_apps for incremental_app in incremental_apps: pid, name = incremental_app.split(':', 1) if self._terminate: break for regexp in options.regexps: if re.search(regexp, name): # waiting for app startup completely time.sleep(0.1) try: self.hook(int(pid), name) except Exception as e: self.log.error( 'error occurred when hook {}@{}: {}'.format( name, pid, e)) finally: break for decremental_app in decremental_apps: pid, name = decremental_app.split(':', 1) if self._terminate: break for regexp in options.regexps: if re.search(regexp, name): self.log.info('{}[pid:{}] has died'.format(name, pid)) break apps = new_apps def hook(self, pid, name): if self._terminate: return js = Project.preload() spawn = options.spawn projects = [] mode = "attach" if not spawn else "spawn" self.log.info('hook {}[pid={}] mode: {}'.format(name, pid, mode)) for project in Project.scan(os.path.join(ROOT_DIR, 'projects')): projects.append(project) projects.sort(key=lambda p: p.priority) for project in projects: if project.enable: # if app match regexp if not re.search(project.regexp, name): continue js += project.load(name) if project.spawn: spawn = True js += Project.postload() # save js content os.makedirs(os.path.join(ROOT_DIR, 'js'), exist_ok=True) open(os.path.join(ROOT_DIR, 'js', name + ".js"), 'w').write(js) while True: try: if spawn: process = self.device.attach(self.device.spawn(name)) else: process = self.device.attach(pid) break except frida.ServerNotRunningError: if self._terminate: return self.log.warning("frida server not running, wait one second") time.sleep(1) # wait for the app to start otherwise it will not hook the java function time.sleep(1) script = process.create_script(js) script.on('message', self.on_message(name)) script.load() if spawn: self.device.resume(pid) def on_message(self, app: str): app_log = logging.getLogger('{}|{}|{}'.format(self.__class__.__name__, self.device.id, app)) def on_message_inner(message, data): try: if message['type'] == 'error': text = message['description'].strip() if not text: return app_log.error(text) else: if message['type'] == 'send': text = message['payload'].strip() else: text = message.strip() if not text: return app_log.info(text) except Exception as e: app_log.error(e) return on_message_inner def terminate(self): self._terminate = True def shutdown(self): self.log.debug('shutdown device ' + self.device.id) if self.device.type == 'remote': port_manager.release_port(self.forward_port) self.adb.clear_forward(self.forward_port) if options.port: self.iptables.uninstall() self.adb.clear_reverse(options.port) self.kill_frida_servers() self.server_executor.shutdown()
class FridaThread(threading.Thread): def __init__(self, device, install: bool, port: int, regexps: list, daemon: bool): super().__init__(daemon=daemon) self.device = device self.install = install self.port = port self.regexps = regexps if regexps else ['.*'] self.adb = Adb(self.device.id) self.iptables = Iptables(self.adb) self.arch = self.adb.unsafe_shell("getprop ro.product.cpu.abi")['out'] # maybe get 'arm64-v8a', 'arm-v7a' ... if 'arm64' in self.arch: self.arch = 'arm64' elif 'arm' in self.arch: self.arch = 'arm' elif 'x86_64' in self.arch: self.arch = 'x86_64' elif 'x86' in self.arch: self.arch = 'x86' else: raise RuntimeError('unknown arch: ' + self.arch) self.server_name = 'frida-server-{}-android-{}'.format(frida.__version__, self.arch) def run(self) -> None: LOGGER.info("{} start with hook device: id={}, name={}, type={}".format( self.__class__.__name__, self.device.id, self.device.name, self.device.type)) try: self.prepare() self.hook_apps() except Exception as e: LOGGER.error(e) # prepare for starting hook def prepare(self): # get root self.adb.root() # close selinux self.adb.unsafe_shell('setenforce 0', root=True) self.iptables.uninstall() # install iptables and reverse tcp port if self.port: # enable tcp connections between frida server and binding self.iptables.install(self.port) self.adb.reverse(self.port) if self.install: self.install_frida_server() self.kill_frida_servers() self.run_frida_server() def install_frida_server(self): server_path = os.path.join(ROOT_DIR, 'assets', self.server_name) server_path_xz = server_path + '.xz' # if not exist frida server then install it if not self.adb.unsafe_shell("ls /data/local/tmp/" + self.server_name)['out']: LOGGER.info('download {} from github ...'.format(self.server_name)) with __lock__: download('https://github.com/frida/frida/releases/download/{}/{}.xz' .format(frida.__version__, self.server_name), server_path_xz) # extract frida server with open(server_path, 'wb') as f: with lzma.open(server_path_xz) as xz: f.write(xz.read()) # upload frida server self.adb.push(server_path, '/data/local/tmp/') def kill_frida_servers(self): try: apps = self.device.enumerate_processes() except frida.ServerNotRunningError: # frida server has not been started, no need to start return for app in apps: if app.name == self.server_name: self.device.kill(app.pid) def run_frida_server(self): self.adb.unsafe_shell('chmod +x /data/local/tmp/' + self.server_name) threading.Thread( target=self.adb.unsafe_shell, args=('/data/local/tmp/{} -D'.format(self.server_name), True) ).start() # waiting for frida server time.sleep(0.5) def hook_apps(self): apps = set() # monitor apps while True: time.sleep(0.1) new_apps = set(p.name for p in self.device.enumerate_processes()) if not new_apps: continue incremental_apps = new_apps - apps decremental_apps = apps - new_apps for incremental_app in incremental_apps: for regexp in self.regexps: if re.search(regexp, incremental_app): # waiting for app startup completely time.sleep(0.1) try: self.hook(incremental_app) except Exception as e: LOGGER.error(e) finally: break for decremental_app in decremental_apps: for regexp in self.regexps: if re.search(regexp, decremental_app): LOGGER.info('app {} has died'.format(decremental_app)) break apps = new_apps def hook(self, app: str): app = app.strip() if not app: raise RuntimeError('try to hook empty app name') LOGGER.info('hook app ' + app) process = self.device.attach(app) js = 'Java.perform(function() {' # load all scripts under folder 'scripts' for (dirpath, dirnames, filenames) in os.walk(os.path.join(ROOT_DIR, 'scripts')): for filename in filenames: _ = open(os.path.join(dirpath, filename), encoding="utf-8").read() if _.startswith(r'''/*Deprecated*/'''): continue js += _ js += '\n' js += '});' script = process.create_script(js) script.on('message', self.on_message) script.load() def on_message(self, message, data): try: if message['type'] == 'error': text = message['description'].strip() if not text: return LOGGER.error(text) else: text = message['payload'].strip() if message['type'] == 'send' else message.strip() if not text: return LOGGER.info(text) except Exception as e: LOGGER.error(e)