class TestAPK(unittest.TestCase): def setUp(self): file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'test')) self.apk = APK(file_path) def test_get_manifest(self): self.assertIn('com.example', self.apk.get_manifest().get('@package')) def test_get_strings(self): self.assertEqual(len(self.apk.get_strings()), 8594) def test_get_files(self): for item in self.apk.get_files(): if item.get('crc') == 'FA974826': self.assertIn('AndroidManifest.xml', item.get('name')) break def test_get_opcodes(self): for item in self.apk.get_opcodes(): class_name = item.get('class_name') method_name = item.get('method_name') opcodes = item.get('opcodes') if class_name == 'com/example/hellojni/MainActivity' and method_name == 'onCreate': self.assertEqual(opcodes, '6F156E0E') break
def __init__(self, file_path): Cmd.__init__(self) self.prompt = 'saam > ' self.shortcuts.remove(('@', 'load')) self.shortcuts.append(('@', 'adb_cmd')) self.shortcuts.remove(('@@', '_relative_load')) self.shortcuts.append(('$', 'adb_shell_cmd')) self.shortcuts.append(('qc', 'quit_and_clean')) self.apk_path = file_path self.apk = APK(self.apk_path) self.apk_out = os.path.basename(file_path) + ".out" self.smali_files = None self.smali_dir = None self.adb = None self.smali_method_descs = [] self.java_files = [] vsmali_action = CmdLineApp.vsmali_parser.add_argument( 'sfile', help='smali file path') setattr(vsmali_action, argparse_completer.ACTION_ARG_CHOICES, ('delimiter_complete', { 'delimiter': '/', 'match_against': self.smali_method_descs })) vjava_action = CmdLineApp.vjava_parser.add_argument( 'jfile', help='java file path') setattr(vjava_action, argparse_completer.ACTION_ARG_CHOICES, ('delimiter_complete', { 'delimiter': '/', 'match_against': self.java_files }))
def parse_apkfile(file): ''' Args: - file: filename or file object Returns: Manifest(Class) ''' apk = APK(file) return Manifest(apk.get_org_manifest())
def test_kotlin_app(self): file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'kotlin-app.zip')) apk = APK(file_path) self.assertEqual([( 'CN=Android Debug, O=Android, C=US', '299D8DE477962C781714EAAB76A90C287BB67123CD2909DE0F743838CAD264E4') ], apk.get_certs('sha256'))
def retireve_info(file_in_check): proc = subprocess.Popen("{} d badging '{}'".format(AAPT, file_in_check), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) r = (proc.communicate()[0]).decode() items = re.compile("package: name='(.*?)' versionCode='(.*?)' versionName='(.*?)'").findall(r) try: pkg, vercode, vername = items[0] except: pkg = vercode = vername = '' try: appname = re.compile("application-label:'(.*?)'").findall(r)[0] except: appname = '' try: dn,cert = APK(file_in_check).get_certs('sha1')[0] except: dn, cert = '', '' cert = cert.lower() #info = [os.path.basename(apk), sha1, pkg, vername, appname, size] # log_writer(sha1, pkg, cert, dn, vername, appname) # db_data = {"sha1": None, "pkg": None, "cert": None, "dn": None, "vername": None, "appname": None} db_data = {"sha1": getSHA1(file_in_check), "pkg": pkg, "cert": cert, "dn": dn, "vername": vername, "appname": appname} globalDB.insert_one("apk_info", db_data)
def __init__(self, file_path): Cmd.__init__(self) self.prompt = 'saam > ' self.shortcuts.remove(('@', 'load')) self.shortcuts.append(('@', 'adb_cmd')) self.shortcuts.remove(('@@', '_relative_load')) self.shortcuts.append(('$', 'adb_shell_cmd')) self.shortcuts.append(('qc', 'quit_and_clean')) self.apk_path = file_path self.apk = APK(self.apk_path) self.apk_out = os.path.basename(file_path) + ".out" self.smali_files = None self.smali_dir = None self.adb = None
class TestAPK(unittest.TestCase): def setUp(self): file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'i15.zip')) self.apk = APK(file_path) def test_get_strings_refx(self): result = self.apk.get_strings_refx() self.assertEqual(len(result), 1477)
def parse_apkfile(file: str) -> Manifest: ''' Args: - file: filename Returns: Manifest(Class) ''' return Manifest(APK(file))
def main(args): apk = APK(args.p) if args.m: import json print(apk.get_mini_mani()) if apk.get_manifest(): print(json.dumps(apk.get_manifest(), indent=1)) elif apk.get_org_manifest(): print(apk.get_org_manifest()) elif args.s: for item in apk.get_strings(): print(binascii.unhexlify(item).decode(errors='ignore')) elif args.f: for item in apk.get_files(): print(item) elif args.c: for item in apk.get_certs(): print(item[0], item[1])
def test_youtube(self): file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'youtube.zip')) apk = APK(file_path) # Check that the default (md5) is correct self.assertEqual([( 'C=US, ST=CA, L=Mountain View, O=Google, Inc, OU=Google, Inc, CN=Unknown', 'D046FC5D1FC3CD0E57C5444097CD5449')], apk.get_certs()) # Check that sha1 is correct self.assertEqual([( 'C=US, ST=CA, L=Mountain View, O=Google, Inc, OU=Google, Inc, CN=Unknown', '24BB24C05E47E0AEFA68A58A766179D9B613A600')], apk.get_certs('sha1')) # Check that sha256 is correct self.assertEqual([( 'C=US, ST=CA, L=Mountain View, O=Google, Inc, OU=Google, Inc, CN=Unknown', '3D7A1223019AA39D9EA0E3436AB7C0896BFB4FB679F4DE5FE7C23F326C8F994A') ], apk.get_certs('sha256'))
class TestAPK(unittest.TestCase): def setUp(self): file_path = os.path.abspath(os.path.join( os.path.dirname(__file__), "..", 'data', 'test')) self.apk = APK(file_path) def test_get_strings_refx(self): result = self.apk.get_strings_refx() for clsname in result: for mtdname in result[clsname]: if b'hellojni' in result[clsname][mtdname]: print(clsname, mtdname, result[clsname][mtdname])
class TestAPK(unittest.TestCase): def setUp(self): file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'test_zip_fake_pwd')) self.apk = APK(file_path) def test_get_manifest(self): self.apk.get_manifest() def test_get_strings(self): self.apk.get_strings() def test_get_files(self): self.apk.get_files() def test_get_opcodes(self): self.apk.get_opcodes()
def scan_apk(apk_path, rules, timeout): td = None try: with zipfile.ZipFile(apk_path, 'r') as zf: for name in zf.namelist(): td = tempfile.mkdtemp() zf.extract(name, td) file_path = os.path.join(td, name) key_path = '{}!{}'.format(apk_path, name) match_dict = do_yara(file_path, rules, timeout) if len(match_dict) > 0: print_matches(key_path, match_dict) except Exception as e: print(e) from apkutils import APK txt = APK(apk_path).get_org_manifest() match_dict = scan_manifest(txt, rules, timeout) if len(match_dict) > 0: key_path = '{}!{}'.format(apk_path, 'AndroidManifest.xml') print_matches(key_path, match_dict)
def main(args): if os.path.isfile(args.file): if args.T: t = Magic(args.file).get_type() if t != 'apk': return trees = APK(args.file).get_trees() nodes = trees.get(args.T, []) for node in nodes: APK.pretty_print(node) else: return if not os.path.isdir(args.file): return apks = [] for root, _, files in os.walk(args.file): for f in files: path = os.path.join(root, f) t = Magic(path).get_type() if t != 'apk': continue apks.append(APK(path)) if not apks: return ai = APK_Intersection(apks) if args.m: ai.intersect_manifest() if args.s: ai.intersect_dex_string() # TODO 相同的字符串太多了,反编译删除干扰的数据 if args.t: ai.intersect_dex_tree() if args.p: ai.intersect_apis() if args.r: ai.intersect_resources()
def setUp(self): file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'i15.zip')) self.apk = APK(file_path)
import os import os import unittest import zipfile from collections import OrderedDict import xmltodict from apkutils.axml.arscparser import ARSCParser from apkutils import APK file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'youtube.zip')) # file_path = os.path.abspath(os.path.join( # os.path.dirname(__file__), "..", 'data', 'i15.zip')) apk = APK(file_path) arsc = apk.get_arsc() # file_path = os.path.abspath(os.path.join( # os.path.dirname(__file__), "..", 'data', 'youtube.zip')) # with zipfile.ZipFile(file_path, mode="r") as zf: # data = zf.read('resources.arsc') # arscobj = ARSCParser(data) # apk = apkutils.APK(file_path) # arsc = apk.get_asrc() package = arsc.get_packages_names()[0] print(package)
def main(apkPath): starttime = datetime.datetime.now() """ :param apkPath: apk文件的最终路径 :return: NULL """ print("[?]Analyzing {}".format(apkPath)) if "/" in apkPath: savePath = "./result/" + apkPath.split("/")[-1].split(".")[0] else: savePath = "./result/" + apkPath.split(".")[0] try: os.mkdir(savePath) except Exception as e: # print(e) pass if dex2jar(apkPath, savePath): print("[+]Dex2jar success!") # print(savePath) ''' 先判断这个安装包是否提取过,再开始处理。 ''' if len([ lists for lists in os.listdir(savePath) if os.path.isfile(os.path.join(savePath, lists)) ]) > 5: print( "[!]The information has been extracted to {}. Please delete it if you need to extract it again" .format(savePath)) else: apk = APK(apkPath) #获取AndroidManifest.xml的字符串信息 manifestInfo = getManifestInfo(apk) xml = open("{}/AndroidManifest.xml".format(savePath), "w") xml.write(manifestInfo) xml.close() #签名信息 sign = getSignInfo(apk) print("[?]Extracting all strings in apk") stringList = getStrings(apk) try: urlList = [] ipList = [] hashlist = [] forbidStrList = [] accessKeyList = [] for string in stringList: # 保存所有的字符串 base = open("{}/strings.txt".format(savePath), "a") try: base.write(str(string) + '\n') except Exception as e: # print(e) pass base.close() ''' 下面开始提取URLs ''' # url = re.findall('https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+', string) urlStrList = ["https://", "http://"] # 提取的特征 for urlStr in urlStrList: if urlStr in string: # print(string) if string not in urlList: u = open("{}/urls.txt".format(savePath), "a") u.write(str(string) + '\n') u.close() urlList.append(string) ''' 判断是否包含IP ''' p = re.compile( '(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)' ) if p.match(string): ip = open("{}/ips.txt".format(savePath), "a") ip.write(str(string) + '\n') ip.close() if string not in ipList: ipList.append(string) ''' 下面开始提取可能是base64编码以及hash的值 ''' ''' 下面开始匹配32位长度和15长度的hash ''' if len(string) == 32 or len(string) == 16: if re.match(r'^[a-z0-9]{16,32}$', string): if string not in hashlist: hashlist.append(string) hashs = open("{}/hash.txt".format(savePath), "a") hashs.write(str(string) + '\n') hashs.close() ''' 下面提取可能存在的敏感字符串 ''' # forbidStr = ["accessKey", "database","ssh","rdp","smb","mysql","sqlserver","oracle", # "ftp","mongodb","memcached","postgresql","telnet","smtp","pop3","imap", # "vnc","redis","admin","root","config","jdbc",".properties","aliyuncs", # "oss"] # 特征字典 forbidStr = ['accesskey', 'aliyuncs'] for forbid in forbidStr: if forbid in string: # print(string) if string not in forbidStrList: fb = open("{}/forbidStr.txt".format(savePath), "a") try: fb.write(str(string) + '\n') except Exception as e: print(e) pass fb.close() forbidStrList.append(string) ''' 下面开始匹配AccessKey,自己测试发现: AccessKeyId 约为24位 AccessKeySecret 约为30位 ''' if str(string).isalnum(): if re.match( r'^(?:(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])).{24,24}$', string): if string not in accessKeyList: accessKeyList.append(string) ak = open("{}/accessKey.txt".format(savePath), "a") if string.startswith("LTAI"): ak.write( "=============AccessKeyId================") ak.write("Id:{}\n".format(str(string))) ak.close() if re.match( r'^(?:(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])).{30,30}$', string): if string not in accessKeyList: accessKeyList.append(string) ak = open("{}/accessKey.txt".format(savePath), "a") ak.write("Secret:{}\n".format(str(string))) ak.close() except Exception as e: #print(e) pass print("""[*]Found: \tString:{}\tURL:{} \tIps:{}\tHash:{}\tForbidStr:{} \tMaybe it's accessKey:{}""".format(len(stringList), len(urlList), len(ipList), len(hashlist), len(forbidStrList), len(accessKeyList))) endtime = datetime.datetime.now() print("[+]Use time {}s\n".format((endtime - starttime).seconds))
def __init__(self, apk: APK): content = apk.get_org_manifest() self._dom = minidom.parseString(content) self._permissions = None self._apk = apk
class CmdLineApp(Cmd): sdcard = '/storage/sdcard0/' maps = None def __init__(self, file_path): Cmd.__init__(self) self.prompt = 'saam > ' self.shortcuts.remove(('@', 'load')) self.shortcuts.append(('@', 'adb_cmd')) self.shortcuts.remove(('@@', '_relative_load')) self.shortcuts.append(('$', 'adb_shell_cmd')) self.shortcuts.append(('qc', 'quit_and_clean')) self.apk_path = file_path self.apk = APK(self.apk_path) self.apk_out = os.path.basename(file_path) + ".out" self.smali_files = None self.smali_dir = None self.adb = None def do_quit_and_clean(self, arg): import shutil shutil.rmtree(self.apk_out) self._should_quit = True return self._STOP_AND_EXIT # ------------------- Hardware And System --------------------- def do_devinfos(self, arg): ''' 显示设备硬件信息 ''' cmd = 'getprop ro.product.brand' print('Brand :', self.adb.run_shell_cmd(cmd)[:-1].decode()) cmd = 'getprop ro.product.model' print('Model :', self.adb.run_shell_cmd(cmd)[:-1].decode()) cmd = 'getprop ro.product.device' print('Device :', self.adb.run_shell_cmd(cmd)[:-1].decode()) cmd = 'getprop ro.product.cpu.abi' print('CPU :', self.adb.run_shell_cmd(cmd)[:-1].decode()) cmd = 'getprop persist.radio.imei' print('IMEI :', self.adb.run_shell_cmd(cmd).decode()) def do_osinfos(self): '''显示设备系统信息''' cmd = 'getprop ro.build.description' print('Build Desc :', self.adb.run_shell_cmd(cmd)[:-1].decode()) cmd = 'getprop ro.build.date' print('Build Data :', self.adb.run_shell_cmd(cmd)[:-1].decode()) cmd = 'getprop ro.build.version.release' print('Build Version :', self.adb.run_shell_cmd(cmd)[:-1].decode()) cmd = 'getprop ro.build.version.sdk' print('SDK Version :', self.adb.run_shell_cmd(cmd)[:-1].decode()) # ---------------------- Manifest ------------------------- @staticmethod def serialize_xml(org_xml): import xmlformatter org_xml = re.sub(r'>[^<]+<', '><', org_xml) formatter = xmlformatter.Formatter() return formatter.format_string(org_xml).decode('utf-8') def do_manifest(self, arg): '''显示清单信息''' org_xml = self.apk.get_org_manifest() if org_xml: print(self.serialize_xml(org_xml)) def get_package(self): return self.apk.get_manifest()['@package'] def get_main_activity(self): try: activities = self.apk.get_manifest()['application']['activity'] except KeyError: return None if not isinstance(activities, list): activities = [activities] for item in activities: content = json.dumps(item) if 'android.intent.action.MAIN' in content\ and 'android.intent.category.LAUNCHER' in content: return item['@android:name'] return None def do_receiver(self, arg): try: receivers = self.apk.get_manifest()['application']['receiver'] except KeyError: return None print(json.dumps(receivers, indent=4, sort_keys=True)) def do_activity(self, arg): try: receivers = self.apk.get_manifest()['application']['activity'] except KeyError: return None print(json.dumps(receivers, indent=4, sort_keys=True)) def do_service(self, arg): try: receivers = self.apk.get_manifest()['application']['service'] except KeyError: return None print(json.dumps(receivers, indent=4, sort_keys=True)) def show_risk_children(self, flag=False): result = '' pflag = True self.apk.get_files().sort(key=lambda k: (k.get('type'), k.get('name'))) for item in self.apk.get_files(): if flag: print(item.get('type'), item.get('name')) continue if item.get('type') not in ['dex', 'apk', 'elf']: continue if pflag: result = Color.red('\n===== Risk Files =====\n') pflag = False result += item.get('type') + ' ' + item.get('name') + '\n' return result children_parser = argparse.ArgumentParser() children_parser.add_argument('-a', '--all', action='store_true') @with_argparser(children_parser) def do_children(self, args): ''' 列出APK中的特殊文件 ''' self.apk.get_files().sort(key=lambda k: (k.get('type'), k.get('name'))) for item in self.apk.get_files(): if args.all: print(item.get('type'), item.get('name')) continue if item.get('type') in ['dex', 'apk', 'elf']: print(item.get('type'), item.get('name')) # ------------------- Static Analysis ------------------------- def do_decompile(self, arg): ''' 使用apktool反编译apk, 默认初始化清单相关的包。 ''' pkgs = self.find_pkg_refx_manifest(self.apk.get_org_manifest()) apktool.decode(None, self.apk_out, self.apk_path, True) for item in os.listdir(self.apk_out): if not item.startswith('smali'): continue self.smali_dir = SmaliDir(os.path.join( self.apk_out, item), include=pkgs) @staticmethod def find_pkg_refx_manifest(manifest): ''' 找出与清单相关的包 ''' if not manifest: return pkgs = set() ptn_s = r'android:name="([^\.]*?\.[^\.]*?)\..*?"' ptn = re.compile(ptn_s) for item in ptn.finditer(manifest): pkgs.add(item.groups()[0]) pkgs.remove("android.intent") pkgs.remove("android.permission") return pkgs init_smali_argparser = argparse.ArgumentParser() init_smali_argparser.add_argument( '-m', '--manifest', action='store_true', help='根据manifest相关包初始化') init_smali_argparser.add_argument( '-f', '--filter', action='store_true', help='根据过滤列表初始化') @with_argparser(init_smali_argparser) def do_init_smali(self, args): ''' 初始化smali,默认初始化所有 ''' pkgs = None if args.manifest: pkgs = self.find_pkg_refx_manifest(self.apk.get_org_manifest()) self.smali_dir = None for item in os.listdir(self.apk_out): if not item.startswith('smali'): continue sd = SmaliDir(os.path.join(self.apk_out, item), include=pkgs) if self.smali_dir: self.smali_dir[len(self.smali_dir):] = sd else: self.smali_dir = sd print('初始化{0}个smali文件'.format(len(self.smali_dir))) print('初始化{0}个smali文件'.format(len(self.smali_dir))) def do_build(self, arg): ''' 使用apktool回编译apk ''' apktool.build(self.apk_out, force=True) def __init__smali_dir(self): pass def show_risk_perm(self): result = '' if not self.apk.get_manifest(): return result risk_perms = [ '_SMS', '_CALL', '_DEVICE_ADMIN', '_AUDIO', '_CONTACTS' ] ps = set() pflag = True for item in self.apk.get_manifest().get('uses-permission', []): for perm in risk_perms: if perm not in item.get('@android:name'): continue if pflag: result += Color.red('===== Risk Permissions =====\n') pflag = False name = item.get('@android:name') if name in ps: continue result += name + '\n' ps.add(name) app = self.apk.get_manifest().get('application') recs = app.get('receiver') def process_item(item): text = '' perm = item.get('@android:permission', '') if '_DEVICE' not in perm: return text if pflag: text += Color.red('===== Risk Permissions =====\n') text += perm + item.get('@android:name') + '\n' if isinstance(recs, dict): text = process_item(item) if text: result += text pflag = False elif isinstance(recs, list): for item in recs: text = process_item(item) if text: result += text pflag = False break if not pflag: result += '\n' return result def show_risk_code(self, level): result = Color.red('===== Risk Codes =====\n') for k, v in RISKS.items(): if v['lvl'] < level: continue kflag = True for sf in self.smali_dir: for mtd in sf.get_methods(): mflag = True lines = re.split(r'\n\s*', mtd.get_body()) for idx, line in enumerate(lines): for ptn in v['code']: if re.search(ptn, line): if kflag: result += Color.magenta('---' + k + '---\n') kflag = False if mflag: result += Color.green(' ' + str(mtd)) + '\n' mflag = False result += ' ' * 3 + str(idx) + line + '\n' break return result risk_parser = argparse.ArgumentParser() risk_parser.add_argument( '-l', '--level', help='指定风险级别,0/1/2/3,0为最低级别,3为最高级别') risk_parser.add_argument( '-f', '--force', action='store_true', help='强制覆盖已有文件') @with_argparser(risk_parser) def do_risk(self, args): if os.path.exists(self.apk_path + '.risk.txt'): if not args.force: # TODO read file return text = '' text += self.show_risk_perm() level = 3 if args.level: level = int(args.level) text += self.show_risk_code(level) text += self.show_risk_children() # TODO save to file self.ppaged(text) # so # 文本 # 二进制文件 # 图片 def ref(self, desc): global notes global classes global recursion_times recursion_times += 1 if recursion_times > recursion_limit: return body = None for smali_file in self.smali_dir: for mtd in smali_file.get_methods(): if desc in str(mtd): body = mtd.get_body() break print(desc) if body: for smali_file in self.smali_dir: for mtd in smali_file.get_methods(): if desc in mtd.get_body(): if desc.startswith('L'): tmp = desc[: desc.index(';')] classes.add(tmp[: tmp.rindex('/')]) tmp = str(mtd)[: str(mtd).index(';')] classes.add(tmp[: tmp.rindex('/')]) notes.append([desc, str(mtd)]) self.ref(str(mtd)) def refs(self, desc): self.ref(desc) def do_xrefs(self, arg): pass ref_parser = argparse.ArgumentParser() ref_parser.add_argument('-l', '--limit', help='递归次数') @with_argparser(ref_parser) def do_ref(self, args): if len(args) != 1: return if args.limit: recursion_limit = args.l mtd = args[0] self.refs(mtd) dot = Digraph() if notes: dot.edges(notes) dot.render('refs.gv', view=True) def main_ref(self, arg): ''' 生成入口函数调用数,调用层数默认100。 可以传入一个数值修改递归次数。 ''' args = arg.split() if len(args) == 1: times = int(args[0]) global recursion_limit recursion_limit = times else: recursion_limit = 100 dot = Digraph() dot.attr('node', style='filled', fillcolor='red') # global recursion_times # recursion_times = 0 # if not self.smali_files: # self.smali_files = parse_smali(self.apk_out + os.sep + 'smali') main_acitivity = self.get_main_activity() main_acitivity = main_acitivity.replace( '.', '/') + ';->onCreate' self.refs(main_acitivity) dot.node(main_acitivity) # recs = self.apk.get_receivers() # for item in recs: # dot.node(item) # self.refs(item) # dot.attr('node', shape='box', style='filled', # fillcolor='white', color='black') # if notes: # dot.edges(notes) # dot.render('refs.gv', view=True) # for item in sorted(list(classes)): # print(item) search_parser = argparse.ArgumentParser() search_parser.add_argument('txt', help='默认在Dex中搜索字符串') # <public type="string" name="failed_text" id="0x7f050002" /> search_parser.add_argument( '-r', '--res', action='store_true', help='查找资源(id/name/图片名)') search_parser.add_argument( '-a', '--all', action='store_true', help='在所有文本文件中查找(不包含Dex)') @with_argparser(search_parser) def do_search(self, args): def find_in_the_layout_xml(txt): results = [] layout_path = os.path.join(self.apk_out, 'res', 'layout') for root, _, names in os.walk(layout_path): for name in names: xml_path = os.path.join(root, name) with open(xml_path, mode='r') as f: lines = f.readlines() for line in lines: if txt in line: results.append(os.path.splitext(name)[0]) break return results def find_in_the_smali_dir(txt): for smali_file in self.smali_dir: if txt in smali_file.get_content(): print(smali_file) def find_in_the_txt(content): self.apk.get_files().sort(key=lambda k: (k.get('type'), k.get('name'))) for item in self.apk.get_files(): if item.get('type') != 'txt': continue if 'META-INF' in item.get('name'): continue txt_path = os.path.join(self.apk_out, item.get('name')) with open(txt_path, mode='r') as f: if 'txt_path' in f.read(): print(item.get('name')) if args.res: public_xml = os.path.join( self.apk_out, 'res', 'values', 'public.xml') rtype = None rname = None with open(public_xml, mode='r') as f: lines = f.readlines() flag = False for line in lines: if args.txt in line: match = line.strip() print(match) g = re.search( r'type="(.*?)" name="(.*?)"', match).groups() if g: rtype = g[0] rname = g[1] break else: return if rtype in {'string', 'layout'}: find_in_the_smali_dir(rname) elif rtype in {'id', 'drawable'}: txts = find_in_the_layout_xml(rname) for txt in txts: print(os.path.join('res', 'layout', txt + '.xml')) find_in_the_smali_dir(txt) print() elif args.all: find_in_the_txt(args.txt) else: find_in_the_smali_dir(args.txt) # ------------------- ADB ------------------------- adb_parser = argparse.ArgumentParser() adb_parser.add_argument( '-s', '--serial', help='use device with given serial (overrides $ANDROID_SERIAL)') @with_argparser(adb_parser) def do_adb_ready(self, args): ''' 连接设备/模拟器,准备adb命令。 ''' serial = None if args.serial: serial = args.serial self.adb = pyadb3.ADB(device=serial) if len(self.adb.get_output().decode()) > 10: print('ADB ready.') else: print("unable to connect to device.") def do_adb(self, arg): ''' 执行adb命令 ''' if not self.adb: self.adb = pyadb3.ADB() self.adb.run_cmd(arg) print(self.adb.get_output().decode('utf-8', errors='ignore')) def do_adb_shell(self, arg): ''' 执行adb shell命令 ''' if not self.adb: self.adb = pyadb3.ADB() self.adb.run_shell_cmd(arg) print(self.adb.get_output().decode('utf-8', errors='ignore')) def do_topactivity(self, args): if not self.adb: self.adb = pyadb3.ADB() self.adb.run_shell_cmd( "dumpsys activity activities | grep mFocusedActivity") print(self.adb.get_output().decode( 'utf-8', errors='ignore').split()[-2]) def do_details(self, args): if not self.adb: self.adb = pyadb3.ADB() self.adb.run_shell_cmd("dumpsys package {}".format(self.get_package())) print(self.adb.get_output().decode('utf-8', errors='ignore')) # ------------------- 备份还原 ------------------------- # ------------------- 应用管理 ------------------------- lspkgs_parser = argparse.ArgumentParser() lspkgs_parser.add_argument( '-f', '--file', action='store_true', help='显示应用关联的 apk 文件') lspkgs_parser.add_argument( '-d', '--disabled', action='store_true', help='只显示 disabled 的应用') lspkgs_parser.add_argument('-e', '--enabled', action='store_true', help='只显示 enabled 的应用') lspkgs_parser.add_argument( '-s', '--system', action='store_true', help='只显示系统应用') lspkgs_parser.add_argument( '-3', '--three', action='store_true', help='只显示第三方应用') lspkgs_parser.add_argument( '-i', '--installer', action='store_true', help='显示应用的 installer') lspkgs_parser.add_argument( '-u', '--uninstall', action='store_true', help='包含已卸载应用') lspkgs_parser.add_argument( 'filter', type=str, nargs="?", help='包名包含 < FILTER > 字符串') @with_argparser(lspkgs_parser) def do_lspkgs(self, args): ''' 查看应用列表,默认所有应用 ''' cmd = 'pm list packages' if args.file: cmd += ' -f' if args.disabled: cmd += ' -d' elif args.enabled: cmd += ' -e' if args.system: cmd += ' -s' elif args.three: cmd += ' -3' if args.installer: cmd += ' -i' elif args.uninstall: cmd += ' -u' if args.filter: cmd += ' ' + args.filter self.adb.run_shell_cmd(cmd) print(self.adb.get_output().decode()) def do_install(self, arg): ''' 安装应用到手机或模拟器 ''' self.adb.run_cmd('install -r -f %s' % self.apk_path) output = self.adb.get_output().decode().split() if output[-2] == 'Failure': print(output[-1]) else: # TODO if the sdcard path doesn't exist. cmd = 'touch %s.now' % self.sdcard self.adb.run_shell_cmd(cmd) def do_uninstall(self, arg): ''' 卸载应用 ''' self.adb.run_cmd('uninstall %s' % self.get_package()) self.clearsd() def clearsd(self): ''' pull the newer files from sdcard. ''' cmd = 'find %s -path "%slost+found" -prune -o -type d -print -newer %s.now -delete' % ( self.sdcard, self.sdcard, self.sdcard) self.adb.run_shell_cmd(cmd) startapp_parser = argparse.ArgumentParser() startapp_parser.add_argument('-d', '--debug', action='store_true') def do_strace(self, args): ''' 使用 strace 跟踪应用 setenforce 0 # In Android 4.3 and later, if SELinux is enabled, strace will fail with "strace: wait: Permission denied" 有两种方式: 1. 使用调试的方式启动应用,strace -p $pid 2. trace -p $zygote_pid,启动应用 ''' # cmd = "strace -f -p `ps | grep zygote | awk '{print $2}'`" print(self.sdcard) cmd = "set `ps | grep zygote`; strace -p $2 -f -tt -T -s 500 -o {}strace.txt".format( self.sdcard) self.adb.run_shell_cmd(cmd) @with_argparser(startapp_parser) def do_startapp(self, args): '''启动应用''' main_acitivity = self.get_main_activity() if not main_acitivity: print("It does not have main activity.") return cmd = 'am start -n %s/%s' % (self.get_package(), main_acitivity) if args.debug: cmd = 'am start -D -n %s/%s' % (self.get_package(), main_acitivity) self.adb.run_shell_cmd(cmd) def do_stopapp(self, arg): '''停止应用''' cmd = 'am force-stop %s' % self.get_package() self.adb.run_shell_cmd(cmd) kill_parser = argparse.ArgumentParser() kill_parser.add_argument('-a', '--all', action='store_true') @with_argparser(kill_parser) def do_kill(self, args): '''杀死应用''' cmd = 'am kill %s' % self.get_package() if args.all: cmd = 'am kill-all' self.adb.run_shell_cmd(cmd) def do_clear(self, args): cmd = 'pm clear {}'.format(self.get_package()) self.adb.run_shell_cmd(cmd) def do_screencap(self, args): import time cmd = 'screencap -p /sdcard/{}.png'.format(time.time()) if not self.adb: self.adb = pyadb3.ADB() self.adb.run_shell_cmd(cmd) monkey_parser = argparse.ArgumentParser() monkey_parser.add_argument('-v', '--verbose', action='store_true') monkey_parser.add_argument('count', type=int, help="Count") @with_argparser(monkey_parser) def do_monkey(self, args): if not self.adb: self.adb = pyadb3.ADB() cmd = "monkey -p {} ".format(self.get_package()) if args.verbose: cmd += '-v ' cmd += str(args.count) self.adb.run_shell_cmd(cmd) print(self.adb.get_output().decode()) # -------------------------------------------------------- def do_set_sdcard(self, arg): ''' 设置sdcard位置 ''' if len(arg.split()) != 1: print('Please give one argument to set sdcard path.') return self.sdcard = arg # @options([make_option('-d', '--debug', action='store_true'), # make_option('-e', '--edit', action='store_true')]) def do_test(self, args): ''' 测试应用(未支持) ''' print(args) print(''.join(args)) if args.debug: print('debug') # TODO 增加自动化测试 # 获取Receiver, 启动 # 获取Service,启动 # 获取Acitivity,启动 def do_pids(self, arg): ''' 显示应用进程 ''' print(self.adb.run_shell_cmd('ps | grep %s' % self.get_package())) def lsof(self): axml = self.apk.get_manifest() if axml: lines = self.adb.run_shell_cmd('ps | grep %s' % axml.getPackageName()).decode() if not lines: return pids = [] for line in lines.strip().split('\r\r\n'): pids.append(line.split()[1]) for pid in pids: self.adb.run_shell_cmd('lsof | grep %s' % pid) lines = self.adb.get_output().decode().split('\r\r\n').decode() for line in lines: # print(line) if not line.endswith('(deleted)'): continue tmps = line.split() fdno = tmps[3] if not fdno.isdecimal(): continue print(line) filename = tmps[8].replace('/', '_') print('%s %s' % (fdno, filename)) self.adb.run_shell_cmd( "cat /proc/%s/fd/%s > /storage/sdcard0/%s" % (pid, fdno, filename)) self.adb.run_cmd( 'pull -a -p /storage/sdcard0/%s' % filename) def do_lssd(self, arg): '''列出SDCARD新增的文件''' command = ('find /storage/sdcard0 -path "/storage/sdcard0/lost+found"' ' -prune -o -type f -print -newer /storage/sdcard0/.now') self.adb.run_shell_cmd(command) print(self.adb.get_output().decode()) def pulldata(self): ''' pull /data/data/pkg ''' pkg = self.get_package() self.adb.run_shell_cmd('cp -r /data/data/%s /storage/sdcard0' % pkg) # self.adb.run_cmd('pull -a -p /storage/sdcard0/%s %s' % (pkg, pkg)) # self.adb.run_shell_cmd('rm -r /storage/sdcard0/%s' % pkg) def pullsd(self): ''' pull the newer files from sdcard. ''' command = ( 'find /storage/sdcard0 -path "/storage/sdcard0/lost+found"' ' -prune -o -type f -print -newer /storage/sdcard0/.now' ) ret = self.adb.run_shell_cmd(command).decode() dir_set = set([]) import os for line in ret.split('\r\r\n'): if line == '/storage/sdcard0/.now': continue if line: print('->', line) path = os.path.dirname(line) flag = 0 skip_path = None for item in dir_set: if item == path: flag == 2 break if item in path: flag = 2 break if path in item: flag = 1 skip_path = item break if flag == 1: print(path, skip_path) dir_set.add(path) dir_set.remove(skip_path) elif flag == 0: dir_set.add(path) for line in dir_set: print(line, ) local_path = os.path.dirname(line)[1:] if not os.path.exists(local_path): os.makedirs(local_path) self.adb.run_cmd('pull -a %s %s' % (line, local_path)) def do_pull(self, arg): '''导出样本的所有的运行生成的文件''' self.pulldata() self.pullsd() # ----------------------------- 内存操作 ----------------------------- # 内存字符串查看?内存字符串修改? def do_memview(self, arg): '''查看内存分布''' if not self.maps: self.get_maps() print(self.maps) def get_maps(self): pkg = self.get_package() lines = self.adb.run_shell_cmd('ps | grep %s' % pkg).decode() pids = [] for line in lines.strip().split('\r\r\n'): pids.append(line.split()[1]) for pid in pids: lines = self.adb.run_shell_cmd('ls /proc/%s/task/' % pid) clone = lines.decode().split()[-1] cmd = 'cat /proc/%s/maps' % clone self.adb.run_shell_cmd(cmd).decode() self.maps = self.adb.get_output().decode() def memdump(self, arg): ''' 内存Dump(仍未支持,需要gdb) 用法: memdump 内存起始地址 内存结束地址 ''' pass
import binascii import os from apkutils import APK file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'test')) apk = APK(file_path) org_strs = apk.get_org_strings() # the strings from all of classes\d*.dex for item in org_strs: if 'helloword' in item.decode('utf-8'): print(item) strs = apk.get_strings() # the strings from all of classes\d*.dex for item in strs: s = binascii.unhexlify(item).decode('utf-8', errors='ignore') if 'helloword' in s: print(s) result = apk.get_strings_refx() for clsname in result: for mtdname in result[clsname]: if b'hellojni' in result[clsname][mtdname]: print(clsname, mtdname, result[clsname][mtdname])
import os import unittest import zipfile from collections import OrderedDict import xmltodict from apkutils.axml.arscparser import ARSCParser import os from apkutils import APK file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'test')) apk = APK(file_path) package = apk.get_manifest().get('@package', None) if not package: exit() icon_id = apk.get_manifest().get('application', {}).get('@android:icon', None) if not icon_id: exit() icon_id = icon_id[1:].lower() datas = xmltodict.parse(apk.get_arsc().get_public_resources(package)) def get_icon_path(): for item in datas['resources']['public']: if icon_id not in item.get('@id'):
import binascii import os from apkutils import APK file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'test')) apk = APK(file_path) strs = apk.get_strings() # the strings from all of classes\d*.dex for item in strs: s = binascii.unhexlify(item).decode('utf-8', errors='ignore') if 'hello' in s: print(s)
def setUp(self): file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'test_zip_fake_pwd')) self.apk = APK(file_path)
import json import os from apkutils import APK file_path = os.path.abspath(os.path.join( os.path.dirname(__file__), "..", 'data', 'test')) apk = APK(file_path) m_xml = apk.get_org_manifest() print(m_xml) m_dict = apk.get_manifest() print(json.dumps(m_dict, indent=1)) # get any item you want from dict print('package:', m_dict['@package']) print('android:versionName:', m_dict['@android:versionName'])
exit(-1) if not os.path.exists("./result"): os.mkdir("./result") target_path = sys.argv[1] ns = "{http://schemas.android.com/apk/res/android}" safeAttr = lambda x, y: x.attrib[y] if y in x.attrib else "" for item in glob(f"{target_path}/*.apk"): filename = Path(item).stem manifest_uuid = str(uuid.uuid4()) apk = APK(item) root = ET.fromstring(apk.get_org_manifest()) app = root.find("application") result = {} for child in list(app): if child.tag not in result: result[child.tag] = [] name = safeAttr(child, f"{ns}name") enabled = safeAttr(child, f"{ns}enabled") != "false" exported = safeAttr(child, f"{ns}exported") == "true" permission = safeAttr(child, f"{ns}permission") read_permission = safeAttr(child, f"{ns}readpermission") write_permission = safeAttr(child, f"{ns}writepermission") intents = []
import os from apkutils import APK file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'test')) apk = APK(file_path) res = apk.get_methods_refx() for item in res: print(item, res[item])
import os from apkutils import APK file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", 'data', 'test')) apk = APK(file_path) files = apk.get_files() for item in files: print(item['name'])