def do_detect(self): with open(pkg_resources.resource_filename( __name__, os.path.join('..', 'libs', 'tlds.txt')), 'r', encoding='utf-8') as f: self.re_tlds = re.compile('^(?:%s)$' % '|'.join( re.escape(l.strip()) for l in f if l and not l.startswith('#')), flags=re.IGNORECASE) with self.context.store() as store: for cl in store.query().consts( InvocationPattern( 'const-string', r'://|^/[{}$%a-zA-Z0-9_-]+(/[{}$%a-zA-Z0-9_-]+)+|^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+(:[0-9]+)?$' )): for match in self.analyzed(cl.p[1].v): for v in match['value']: yield Issue(detector_id=self.option, confidence=IssueConfidence.FIRM, cvss3_vector=self.cvss, summary='detected %s' % match['type_'], info1=v, source=store.query().qualname_of(cl)) for name, val in self.context.string_resources(): for match in self.analyzed(val): for v in match['value']: yield Issue(detector_id=self.option, confidence=IssueConfidence.FIRM, cvss3_vector=self.cvss, summary='detected %s' % match['type_'], info1=v, source='R.string.%s' % name)
def detect(self) -> Iterable[Issue]: policy = ComponentNamePolicy() ns = dict(android='http://schemas.android.com/apk/res/android') for name in set(itertools.chain( self._context.parsed_manifest().getroot().xpath('//activity[not(@android:permission)]/intent-filter/../@android:name', namespaces=ns), self._context.parsed_manifest().getroot().xpath('//activity[not(@android:permission) and (@android:exported="true")]/@android:name', namespaces=ns), )): filter_ = [name for name in self._context.parsed_manifest().getroot().xpath(f'//activity[@android:name="{name}"]/intent-filter/action/@android:name', namespaces=ns) if not policy.looks_public(name)] if not filter_: yield Issue( detector_id=self.option, confidence='certain', cvss3_vector=self._cvss1, summary='manipulatable Activity', info1=name, source='AndroidManifest.xml', synopsis="Application is exporting one or more activities.", description="Application is exporting one or more activities. Activities are entrypoints to the application. Exporting enables them to be invoked from other applications or system. Unnecessary export increases attack surfaces. Please note that Android automatically exports ones with IntentFilter defined in the manifest. This issue is just an observation; exporting activities alone does not constitute an security issue.", solution="Review them, and restrict access with application-specific permissions if necessary." ) else: yield Issue( detector_id=self.option, confidence='certain', cvss3_vector=self._cvss2, summary='manipulatable Activity with private action names', info1=name, info2=', '.join(filter_), source='AndroidManifest.xml', synopsis="Application is exporting one or more activities using seemingly private action names, suggesting inadvent export.", description="Application is exporting one or more activities using seemingly private action names, suggesting inadvent export. Activities are entrypoints to the application. Exporting enables them to be invoked from other applications or system. Inadvent exporting enables malwares or malicious users to manipulate the application. Please note that Android automatically exports ones with IntentFilter defined in the manifest.", solution="Review them, and restrict access with application-specific permissions if necessary." )
def do_detect(self): policy = ComponentNamePolicy() ns = dict(android='http://schemas.android.com/apk/res/android') for name in set( itertools.chain( self.context.parsed_manifest().getroot().xpath( '//provider[not(@android:permission)]/intent-filter/../@android:name', namespaces=dict( android='http://schemas.android.com/apk/res/android' )), self.context.parsed_manifest().getroot().xpath( '//provider[not(@android:permission) and (@android:exported="true")]/@android:name', namespaces=dict( android='http://schemas.android.com/apk/res/android' )), )): filter_ = [ name for name in self.context.parsed_manifest().getroot().xpath( '//receiver[@android:name="%s"]/intent-filter/action/@android:name' % name, namespaces=ns) if not policy.looks_public(name) ] if not filter_: yield Issue( detector_id=self.option, confidence=IssueConfidence.CERTAIN, cvss3_vector=self.cvss1, summary='manipulatable ContentProvider', info1=name, source='AndroidManifest.xml', synopsis= "Application is exporting one or more content providers.", description= "Application is exporting one or more content providers. Content providers defines REST/RDBMS-like IPC mechanism for the application. Exporting enables them to be invoked from other applications or system. Unnecessary export increases attack surfaces. Please note that Android automatically exports them (API 8 or ealier) or ones with IntentFilter defined in the manifest (API level 9 or later). This issue is just an observation; exporting content providers alone does not constitute an security issue.", solution='''\ Review them and explicitly unexport or restrict access with application-specific permissions if necessary. To explicitly unexporting an content provider, define the following attribute to the <provider> tag in the manifest: android:export="false" ''') else: yield Issue( detector_id=self.option, confidence=IssueConfidence.CERTAIN, cvss3_vector=self.cvss2, summary= 'manipulatable ContentProvider with private action names', info1=name, info2=', '.join(filter_), source='AndroidManifest.xml', synopsis= "Application is exporting one or more content providers using seemingly private action names, suggesting inadvent export.", description= "Application is exporting one or more content providers using seemingly private action names, suggesting inadvent export. Content providers defines REST/RDBMS-like IPC mechanism for the application. Exporting enables them to be invoked from other applications or system. Inadvent exporting enables malwares or malicious users to manipulate the application. Please note that Android automatically exports them (API 8 or ealier) or ones with IntentFilter defined in the manifest (API level 9 or later).", solution='''\ Review them and explicitly unexport or restrict access with application-specific permissions if necessary. To explicitly unexporting an content provider, define the following attribute to the <provider> tag in the manifest: android:export="false" ''')
def detect(self) -> Iterable[Issue]: for c in (self._class_name_of(self._context.source_name_of_disassembled_class(r)) for r in self._context.disassembled_classes()): if re.search('(?:^|\.)a$', c): yield Issue(detector_id=self.option, confidence='certain', cvss3_vector=self._cvss_true, summary='detected obfuscator', info1='ProGuard') break else: yield Issue(detector_id=self.option, confidence='firm', cvss3_vector=self._cvss_false, summary='lack of obfuscation')
def _formatted(self, issue: Issue) -> str: return '{source}:{row}:{col}:{severity}{{{confidence}}}:{description} [-W{detector_id}]'.format( source=noneif(issue.source, '(global)'), row=noneif(issue.row, 0), col=noneif(issue.col, 0), severity=issue.severity(), confidence=issue.confidence, description=issue.brief_description(), detector_id=issue.detector_id)
def do_detect(self): with self.context.store() as store: targets = {'WebView', 'XWalkView', 'GeckoView'} more = True while more: more = False for cl in store.query().related_classes('|'.join(targets)): name = store.query().class_name_of(cl) if name not in targets: targets.add(name) more = True for fn in (n for n in self.context.disassembled_resources() if 'layout' in n): with open(fn, 'rb') as f: r = ET.parse(f, parser=ET.XMLParser(recover=True)).getroot() for t in functools.reduce( lambda x, y: x + y, (r.xpath('//%s' % self.context.class_name_of_dalvik_class_type( c).replace('$', '_')) for c in targets)): size = LayoutSizeGuesser().guessed_size(t, fn) if size > 0.5: try: yield Issue( detector_id=self.option, confidence=IssueConfidence.TENTATIVE, cvss3_vector=self.cvss1, summary='tamperable webview', info1='{0} (score: {1:.02f})'.format( t.attrib['{0}id'.format( self.xmlns_android)], size), source=self.context. source_name_of_disassembled_resource(fn)) except KeyError as e: log.warning( 'SecurityTamperableWebViewDetector.do_detect: missing key {0}' .format(e)) # XXX: crude detection for op in store.query().invocations( InvocationPattern('invoke-', ';->loadUrl')): try: v = DataFlows.solved_constant_data_in_invocation( store, op, 0) if v.startswith('http://'): yield Issue(detector_id=self.option, confidence=IssueConfidence.FIRM, cvss3_vector=self.cvss2, summary='tamperable webview with URL', info1=v, source=store.query().qualname_of(op)) except DataFlows.NoSuchValueError: pass
def detect(self) -> Iterable[Issue]: import lxml.etree as ET from functools import reduce with self._context.store() as store: targets = {'WebView', 'XWalkView', 'GeckoView'} more = True while more: more = False for cl in store.query().related_classes('|'.join(targets)): name = store.query().class_name_of(cl) if name is not None and name not in targets: targets.add(name) more = True for fn in (n for n in self._context.disassembled_resources() if 'layout' in n): with open(fn, 'rb') as f: r = ET.parse(f, parser=ET.XMLParser(recover=True)).getroot() for t in reduce(lambda x, y: x + y, (r.xpath('//{}'.format( self._context.class_name_of_dalvik_class_type( c).replace('$', '_'))) for c in targets)): size = LayoutSizeGuesser().guessed_size(t, fn) if size > 0.5: try: yield Issue( detector_id=self.option, confidence='tentative', cvss3_vector=self._cvss1, summary=self._summary1, info1='{0} (score: {1:.02f})'.format( t.attrib[f'{self._xmlns_android}id'], size), source=self._context. source_name_of_disassembled_resource(fn)) except KeyError as e: ui.warn( f'SecurityTamperableWebViewDetector.do_detect: missing key {e}' ) # XXX: crude detection for op in store.query().invocations( InvocationPattern('invoke-', ';->loadUrl')): try: v = DataFlows.solved_constant_data_in_invocation( store, op, 0) if v.startswith('http://'): yield Issue(detector_id=self.option, confidence='firm', cvss3_vector=self._cvss2, summary=self._summary2, info1=v, source=store.query().qualname_of(op)) except DataFlows.NoSuchValueError: pass
def do_detect(self): policy = ComponentNamePolicy() ns = dict(android='http://schemas.android.com/apk/res/android') for name in set( itertools.chain( self.context.parsed_manifest().getroot().xpath( '//receiver[not(@android:permission)]/intent-filter/../@android:name', namespaces=ns), self.context.parsed_manifest().getroot().xpath( '//receiver[not(@android:permission) and (@android:exported="true")]/@android:name', namespaces=ns), )): filter_ = [ name for name in self.context.parsed_manifest().getroot().xpath( '//receiver[@android:name="%s"]/intent-filter/action/@android:name' % name, namespaces=ns) if not policy.looks_public(name) ] if not filter_: yield Issue( detector_id=self.option, confidence=IssueConfidence.CERTAIN, cvss3_vector=self.cvss1, summary='manipulatable BroadcastReceiver', info1=name, source='AndroidManifest.xml', synopsis= "Application is exporting one or more broadcast receivers.", description= "Application is exporting one or more broadcast receivers. Broadcast receivers are system-wide event listeners of the application. Exporting enables them to be invoked from other applications or system. Unnecessary export increases attack surfaces. Please note that Android automatically exports ones with IntentFilter defined in the manifest. This issue is just an observation; exporting broadcast receivers alone does not constitute an security issue.", solution= "Review them and restrict access with application-specific permissions if necessary. Consider the use of LocalBroadcastReceiver for ones that system-wide reachability is not needed." ) else: yield Issue( detector_id=self.option, confidence=IssueConfidence.CERTAIN, cvss3_vector=self.cvss2, summary= 'manipulatable BroadcastReceiver with private action names', info1=name, info2=', '.join(filter_), source='AndroidManifest.xml', synopsis= "Application is exporting one or more broadcast receivers using seemingly private action names, suggesting inadvent export.", description= "Application is exporting one or more broadcast receivers using seemingly private action names, suggesting inadvent export. Broadcast receivers are system-wide event listeners of the application. Exporting enables them to be invoked from other applications or system. Inadvent exporting enables malwares or malicious users to manipulate the application. Please note that Android automatically exports ones with IntentFilter defined in the manifest.", solution= "Review them, and restrict access with application-specific permissions if necessary." )
def do_detect(self): with self.context.store() as store: for cl in store.query().invocations( InvocationPattern( 'invoke-', 'L.*->([dwie]|debug|error|exception|warning|info|notice|wtf)\(Ljava/lang/String;Ljava/lang/String;.*?Ljava/lang/(Throwable|.*?Exception);|L.*;->print(ln)?\(Ljava/lang/String;|LException;->printStackTrace\(' )): if 'print' not in cl.p[1].v: try: yield Issue( detector_id=self.option, confidence=IssueConfidence.TENTATIVE, cvss3_vector=self.cvss, summary='detected logging', info1=cl.p[1].v, info2=DataFlows.solved_constant_data_in_invocation( store, cl, 1), source=store.query().qualname_of(cl)) except (DataFlows.NoSuchValueError): yield Issue(detector_id=self.option, confidence=IssueConfidence.TENTATIVE, cvss3_vector=self.cvss, summary='detected logging', info1=cl.p[1].v, source=store.query().qualname_of(cl)) elif 'Exception;->' not in cl.p[1].v: try: yield Issue( detector_id=self.option, confidence=IssueConfidence.TENTATIVE, cvss3_vector=self.cvss, summary='detected logging', info1=cl.p[1].v, info2=DataFlows.solved_constant_data_in_invocation( store, cl, 0), source=store.query().qualname_of(cl)) except (DataFlows.NoSuchValueError): yield Issue(detector_id=self.option, confidence=IssueConfidence.TENTATIVE, cvss3_vector=self.cvss, summary='detected logging', info1=cl.p[1].v, source=store.query().qualname_of(cl)) else: yield Issue(detector_id=self.option, confidence=IssueConfidence.TENTATIVE, cvss3_vector=self.cvss, summary='detected logging', info1=cl.p[1].v, source=store.query().qualname_of(cl))
def do_detect_case2(self): # XXX: Crude detection def should_be_secret(store, k, val): return any( x in store.query().qualname_of(k).lower() for x in ['inapp', 'billing', 'iab', 'sku', 'store', 'key']) pat = '^MI[IG][0-9A-Za-z+/=-]{32,}AQAB' with self.context.store() as store: for cl in store.query().consts( InvocationPattern('const-string', pat)): val = cl.p[1].v yield Issue( detector_id=self.option, cvss3_vector=self.cvss, confidence={ True: IssueConfidence.FIRM, False: IssueConfidence.TENTATIVE }[should_be_secret(store, cl, val)], summary='insecure cryptography: static keys (2)', info1='"%(target_val)s" [%(target_val_len)d] (X.509)' % dict(target_val=val, target_val_len=len(val)), source=store.query().qualname_of(cl), synopsis= 'Traces of X.509 certificates has been found the application binary.', description='''\ Traces of X.509 certificates has been found in the application binary. X.509 certificates describe public key materials. Their notable uses include Google Play in-app billing identity. If is hardcoded, attackers can extract or replace them. ''', solution='''\ Use a device or installation specific information, or obfuscate them. Especially, do not use the stock implementation of in-app billing logic. ''') for name, val in self.context.string_resources(): if re.match(pat, val): yield Issue( detector_id=self.option, cvss3_vector=self.cvss, confidence=IssueConfidence.TENTATIVE, summary='insecure cryptography: static keys (2)', info1='"%(target_val)s" [%(target_val_len)d] (X.509)' % dict(target_val=val, target_val_len=len(val)), source='R.string.%s' % name, synopsis= 'Traces of X.509 certificates has been found the application binary.', description='''\ Traces of X.509 certificates has been found in the application binary. X.509 certificates describe public key materials. Their notable uses include Google Play in-app billing identity. If is hardcoded, attackers can extract or replace them. ''', solution='''\ Use a device or installation specific information, or obfuscate them. Especially, do not use the stock implementation of in-app billing logic. ''')
def _do_detect_case1(self) -> Iterable[Issue]: import base64 import binascii def looks_like_real_key(k: str) -> bool: # XXX: silly return len(k) >= 8 and not any(x in k for x in ('Padding', 'SHA1', 'PBKDF2', 'Hmac', 'emulator')) with self._context.store() as store: for cl in store.query().invocations(InvocationPattern('invoke-', '^Ljavax?.*/(SecretKey|(Iv|GCM)Parameter|(PKCS8|X509)EncodedKey)Spec|^Ljavax?.*/MessageDigest;->(update|digest)')): try: for nr in self._important_args_on_invocation(cl): for found in DataFlows.solved_possible_constant_data_in_invocation(store, cl, nr): try: decoded = base64.b64decode(found) info1 = '"{target_val}" [{target_val_len}] (base64; "{decoded_val}" [{decoded_val_len}])'.format(target_val=found, target_val_len=len(found), decoded_val=binascii.hexlify(decoded).decode('ascii'), decoded_val_len=len(decoded)) except (ValueError, binascii.Error): info1 = f'"{found}" [{len(found)}]' if looks_like_real_key(found): yield Issue( detector_id=self.option, cvss3_vector=self._cvss, confidence='firm', summary='insecure cryptography: static keys', info1=info1, source=store.query().qualname_of(cl), synopsis='Traces of cryptographic material has been found the application binary.', description='''\ Traces of cryptographic material has been found in the application binary. If cryptographic material is hardcoded, attackers can extract or replace them. ''', solution='''\ Use a device or installation specific information, or obfuscate them. ''' ) else: yield Issue( detector_id=self.option, cvss3_vector=self._cvss_nonkey, confidence='tentative', summary='Cryptographic constants detected', info1=info1, source=store.query().qualname_of(cl), synopsis='Possible cryptographic constants have been found.', description='''\ Possible cryptographic constants has been found in the application binary. ''' ) except IndexError: pass
def do_detect(self): with self.context.store() as store: for cl in store.query().invocations( InvocationPattern( 'invoke-static', 'Ljavax/crypto/Cipher;->getInstance\(Ljava/lang/String;.*?\)' )): try: target_val = DataFlows.solved_possible_constant_data_in_invocation( store, cl, 0) if any((('ECB' in x or '/' not in x) and 'RSA' not in x) for x in target_val): yield Issue( detector_id=self.option, cvss3_vector=self.cvss, confidence=IssueConfidence.CERTAIN, summary= 'insecure cryptography: cipher might be operating in ECB mode', info1=','.join(target_val), source=store.query().qualname_of(cl), synopsis= 'The application might be using ciphers in ECB mode.', description='''\ The application might be using symmetric ciphers in ECB mode. ECB mode is the most basic operating mode that independently transform data blocks. Indepent transformation leaks information about distribution in plaintext. ''', solution='''\ Use CBC or CTR mode. ''') except (DataFlows.NoSuchValueError): pass
def detect(self): for c in (self.class_name_of( self.context.source_name_of_disassembled_class(r)) for r in self.context.disassembled_classes()): if re.search('(?:^|\.)a$', c): yield Issue(detector_id=self.option, confidence=IssueConfidence.CERTAIN, cvss3_vector=self.cvss_true, summary='detected obfuscator', info1='ProGuard') break else: yield Issue(detector_id=self.option, confidence=IssueConfidence.FIRM, cvss3_vector=self.cvss_false, summary='lack of obfuscation')
def do_detect(self): package = self.context.parsed_manifest().getroot().xpath( '/manifest/@package', namespaces=dict( android='http://schemas.android.com/apk/res/android'))[0] packages = dict() for fn in (self.context.source_name_of_disassembled_class(r) for r in self.context.disassembled_classes()): family = self.package_family_of(self.package_name_of(fn)) if family is not None: try: packages[family].append(fn) except KeyError: packages[family] = [fn] else: pass packages = { k: v for k, v in packages.items() if not self.is_kind_of(k, package) and re.search(r'\.[a-zA-Z0-9]{4,}(?:\.|$)', k) } yield from (Issue( detector_id=self.option, confidence=IssueConfidence.FIRM, cvss3_vector=self.cvss, summary='detected library', info1='%s (score: %d)' % (p, len(packages[p])), ) for p in sorted(packages.keys()))
def _do_detect_case2(self) -> Iterable[Issue]: # XXX: Crude detection def should_be_secret(store: Store, k: Op, val: str) -> bool: name = store.query().qualname_of(k) if name: return name.lower() in ['inapp','billing','iab','sku','store','key'] else: return False pat = '^MI[IG][0-9A-Za-z+/=-]{32,}AQAB' with self._context.store() as store: for cl in store.query().consts(InvocationPattern('const-string', pat)): val = cl.p[1].v yield Issue( detector_id=self.option, cvss3_vector=self._cvss, confidence={True:'firm', False:'tentative'}[should_be_secret(store, cl, val)], # type: ignore[arg-type] summary='insecure cryptography: static keys (2)', info1=f'"{val}" [{len(val)}] (X.509)', source=store.query().qualname_of(cl), synopsis='Traces of X.509 certificates has been found the application binary.', description='''\ Traces of X.509 certificates has been found in the application binary. X.509 certificates describe public key materials. Their notable uses include Google Play in-app billing identity. If is hardcoded, attackers can extract or replace them. ''', solution='''\ Use a device or installation specific information, or obfuscate them. Especially, do not use the stock implementation of in-app billing logic. ''' ) for name, val in self._context.string_resources(): if re.match(pat, val): yield Issue( detector_id=self.option, cvss3_vector=self._cvss, confidence='tentative', summary='insecure cryptography: static keys (2)', info1=f'"{val}" [{len(val)}] (X.509)', source=f'R.string.{name}', synopsis='Traces of X.509 certificates has been found the application binary.', description='''\ Traces of X.509 certificates has been found in the application binary. X.509 certificates describe public key materials. Their notable uses include Google Play in-app billing identity. If is hardcoded, attackers can extract or replace them. ''', solution='''\ Use a device or installation specific information, or obfuscate them. Especially, do not use the stock implementation of in-app billing logic. ''' )
def do_detect(self): if self.context.get_min_sdk_version() <= 23: if not self.do_detect_plain_pins_x509(): if not self.do_detect_plain_pins_hostnameverifier(): yield Issue(detector_id=self.option, confidence=IssueConfidence.CERTAIN, cvss3_vector=self.cvss, summary='insecure TLS connection', info1='no pinning detected')
def detect(self) -> Iterable[Issue]: if self._context.get_min_sdk_version() <= 23: if not self._do_detect_plain_pins_x509(): if not self._do_detect_plain_pins_hostnameverifier(): yield Issue(detector_id=self.option, confidence='certain', cvss3_vector=self._cvss, summary=self._summary, info1='no pinning detected')
def detect(self) -> Iterable[Issue]: with self._context.store() as store: for cl in store.query().consts( InvocationPattern('const-string', r'%s')): for t in self._analyzed(cl.p[1].v): yield Issue(detector_id=self.option, confidence=t['confidence'], cvss3_vector=self._cvss, summary=self._summary, info1=t['value'], source=store.query().qualname_of(cl)) for name, val in self._context.string_resources(): for t in self._analyzed(val): yield Issue(detector_id=self.option, confidence=t['confidence'], cvss3_vector=self._cvss, summary=self._summary, info1=t['value'], source=f'R.string.{name}')
def do_detect(self): with self.context.store() as store: for cl in store.query().consts( InvocationPattern('const-string', r'%s')): for t in self.analyzed(cl.p[1].v): yield Issue(detector_id=self.option, confidence=t['confidence'], cvss3_vector=self.cvss, summary='detected format string', info1=t['value'], source=store.query().qualname_of(cl)) for name, val in self.context.string_resources(): for t in self.analyzed(val): yield Issue(detector_id=self.option, confidence=t['confidence'], cvss3_vector=self.cvss, summary='detected format string', info1=t['value'], source='R.string.%s' % name)
def do_detect(self): with self.context.store() as store: for cl in store.query().ops_of('xor-int/lit8'): target_val = int(cl.p[2].v, 16) if (cl.p[0].v == cl.p[1].v) and target_val > 1: yield Issue( detector_id=self.option, cvss3_vector=self.cvss, confidence=IssueConfidence.FIRM, summary='insecure cryptography: non-random XOR cipher', info1='0x%02x' % target_val, source=store.query().qualname_of(cl))
def detect(self) -> Iterable[Issue]: with self._context.store() as store: for cl in store.query().ops_of('xor-int/lit8'): target_val = int(cl.p[2].v, 16) if (cl.p[0].v == cl.p[1].v) and target_val > 1: yield Issue( detector_id=self.option, cvss3_vector=self._cvss, confidence='firm', summary='insecure cryptography: non-random XOR cipher', info1=f'0x{target_val:02x}', source=store.query().qualname_of(cl) )
def do_detect(self): # TBD: compare with actual permission needs yield from (Issue( detector_id=self.option, confidence=IssueConfidence.CERTAIN, cvss3_vector=self.cvss, summary='open permissions', info1=p, source='AndroidManifest.xml', synopsis="Application is requesting one or more permissions.", description= "Application is requesting one or more permissions. Permissions are an important security system of Android. They control accesses to sensitive information (e.g. GPS, IMEI/IMSI, process stats, accounts, contacts, SMSs) or possibly dangerous/costly operation (e.g. SMSs, internet access, controlling system services, obstructing screens.) Requesting ones are vital for proper functioning of application, though abusage leads to hurt privacy or device availability. This issue is just an observation; requesting permissions alone does not constitute an security issue.", ) for p in self.context.permissions_declared())
def detect(self) -> Iterable[Issue]: with self._context.store() as store: for op in store.query().invocations( InvocationPattern( 'invoke-', 'Landroid/net/Uri;->parse\(Ljava/lang/String;\)Landroid/net/Uri;' )): try: if DataFlows.solved_constant_data_in_invocation( store, op, 0).startswith('content://sms/'): yield Issue(detector_id=self.option, confidence='certain', cvss3_vector=self._cvss, summary=self._summary, info1='accessing SMS', source=store.query().qualname_of(op)) except DataFlows.NoSuchValueError: pass for op in store.query().invocations( InvocationPattern('invoke-', 'Landroid/telephony/SmsManager;->send')): yield Issue(detector_id=self.option, confidence='certain', cvss3_vector=self._cvss, summary=self._summary, info1='sending SMS', source=store.query().qualname_of(op)) for op in store.query().invocations( InvocationPattern( 'invoke-', 'Landroid/telephony/SmsMessage;->createFromPdu\(')): yield Issue(detector_id=self.option, confidence='firm', cvss3_vector=self._cvss, summary=self._summary, info1='intercepting incoming SMS', source=store.query().qualname_of(op))
def detect(self) -> Iterable[Issue]: with self._context.store() as store: for op in store.query().invocations( InvocationPattern( 'invoke-', 'Landroid/provider/Settings\$Secure;->getString\(Landroid/content/ContentResolver;Ljava/lang/String;\)Ljava/lang/String;|Landroid/telephony/TelephonyManager;->getDeviceId\(\)Ljava/lang/String;|Landroid/telephony/TelephonyManager;->getSubscriberId\(\)Ljava/lang/String;|Landroid/telephony/TelephonyManager;->getLine1Number\(\)Ljava/lang/String;|Landroid/bluetooth/BluetoothAdapter;->getAddress\(\)Ljava/lang/String;|Landroid/net/wifi/WifiInfo;->getMacAddress\(\)Ljava/lang/String;|Ljava/net/NetworkInterface;->getHardwareAddress\(\)' )): val_type = self.analyzed(store, op) if val_type is not None: yield Issue(detector_id=self.option, confidence='certain', cvss3_vector=self._cvss, summary=self._summary, info1=f'getting {val_type}', source=store.query().qualname_of(op))
def detect(self) -> Iterable[Issue]: if self._context.parsed_manifest().getroot().xpath('//application[not(@android:allowBackup="false")]', namespaces=dict(android='http://schemas.android.com/apk/res/android')): yield Issue( detector_id=self.option, confidence='certain', cvss3_vector=self._cvss, summary='manipulatable backups', source='AndroidManifest.xml', synopsis="Application data can be backed up and restored with the Full Backup feature.", description="Application data can be backed up and restored with the Full Backup feature, thusly making it subjectible to the backup attack.", solution='''\ Review them and opt-out from the Full Backup feature if necessary. To opt-out, define the following attribute to the <application> tag in the manifest: android:allowBackup="false" ''' )
def detect(self) -> Iterable[Issue]: if self._context.parsed_manifest().getroot().xpath('//application[@android:debuggable="true"]', namespaces=dict(android='http://schemas.android.com/apk/res/android')): yield Issue( detector_id=self.option, confidence='certain', cvss3_vector=self._cvss, summary='app is debuggable', source='AndroidManifest.xml', synopsis="Application can be debugged.", description="Application can be debugged (the debuggable bit is set.) Debugging it gives attackers complete control of its process memory and control flow.", solution='''\ Disable the debuggable bit. To disable it, define the following attribute to the <application> tag in the manifest: android:debuggable="false" ''' )
def generate(self) -> None: super().generate() from json import dumps with self._context.store().db as db: issues = [] for no, row in enumerate( db.execute( 'select distinct detector, summary, synopsis, description, seealso, solution, cvss3_score, cvss3_vector from analysis_issues order by cvss3_score desc' )): instances: List[Dict[str, Any]] = [] issues.append( dict(no=no, detector=row[0], summary=row[1].title(), synopsis=row[2], description=row[3], seealso=row[4], solution=row[5], cvss3_score=row[6], cvss3_vector=row[7], severity=CVSS3Scoring.severity_of(row[6]).title(), instances=instances)) for m in db.execute( 'select * from analysis_issues where detector=:detector and summary=:summary and cvss3_score=:cvss3_score', { v: row[k] for k, v in { 0: 'detector', 1: 'summary', 6: 'cvss3_score' }.items() }): issue = Issue.from_analysis_issues_row(m) instances.append( dict(info=issue.brief_info(), source=issue.source, row=issue.row, col=issue.col)) app = dict(package=self._context.parsed_manifest().getroot().xpath( '/manifest/@package', namespaces=dict( android='http://schemas.android.com/apk/res/android'))[0], issues=len(issues)) self._write(dumps({"app": app, "issues": issues}, indent=2))
def do_detect(self): with self.context.store() as store: for cl in store.query().invocations( InvocationPattern( 'invoke-virtual', 'Landroid/content/Context;->openFileOutput\(Ljava/lang/String;I\)' )): try: target_val = int( DataFlows.solved_constant_data_in_invocation( store, cl, 1), 16) if target_val & 3: yield Issue(detector_id=self.option, confidence=IssueConfidence.CERTAIN, cvss3_vector=self.cvss, summary='insecure file permission', info1={ 1: 'MODE_WORLD_READABLE', 2: 'MODE_WORLD_WRITEABLE' }[target_val], source=store.query().qualname_of(cl)) except (DataFlows.NoSuchValueError): pass
def generate(self): super().generate() with self._context.store().db as db: issues = [] for row, no in zip( db.execute( 'select distinct detector, summary, synopsis, description, seealso, solution, cvss3_score, cvss3_vector from analysis_issues order by cvss3_score desc' ), range(1, 2**32)): instances = [] issues.append( dict(no=no, detector=row[0], summary=row[1].title(), synopsis=row[2], description=row[3], seealso=row[4], solution=row[5], cvss3_score=row[6], cvss3_vector=row[7], severity=CVSS3Scoring.severity_of(row[6]).title(), instances=instances, severity_panel_style={ 'critical': 'panel-danger', 'high': 'panel-warning', 'medium': 'panel-warning', 'low': 'panel-success', 'info': 'panel-info' }[CVSS3Scoring.severity_of(row[6])])) for m in db.execute( 'select * from analysis_issues where detector=:detector and summary=:summary and cvss3_score=:cvss3_score', { v: row[k] for k, v in { 0: 'detector', 1: 'summary', 6: 'cvss3_score' }.items() }): issue = Issue.from_analysis_issues_row(m) instances.append( dict(info=issue.brief_info(), source=issue.source, row=issue.row, col=issue.col)) app = dict(package=self._context.parsed_manifest().getroot().xpath( '/manifest/@package', namespaces=dict( android='http://schemas.android.com/apk/res/android'))[0], issues=len(issues), issues_critical=len( [_ for _ in issues if _['severity'] == 'Critical']), issues_high=len( [_ for _ in issues if _['severity'] == 'High']), issues_medium=len( [_ for _ in issues if _['severity'] == 'Medium']), issues_low=len( [_ for _ in issues if _['severity'] == 'Low']), issues_info=len( [_ for _ in issues if _['severity'] == 'Info'])) self._write(self._template.render(app=app, issues=issues))
def do_detect(self): with self.context.store() as store: targets = set() seeds = {'WebView', 'XWalkView', 'GeckoView'} more = True while more: more = False for cl in store.query().related_classes('|'.join(seeds)): name = store.query().class_name_of(cl) if name not in targets: targets.add(name) more = True for seed in seeds: targets.add('L.*%s;' % seed) # XXX: Crude detection # https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object,%2520java.lang.String) if self.context.get_min_sdk_version() <= 16: for p in store.query().invocations( InvocationPattern( 'invoke-virtual', 'Landroid/webkit/WebSettings;->setJavaScriptEnabled' )): try: if DataFlows.solved_constant_data_in_invocation( store, p, 0): for target in targets: for q in store.query().invocations_in_class( p, InvocationPattern( 'invoke-virtual', '%s->addJavascriptInterface' % target)): try: if DataFlows.solved_constant_data_in_invocation( store, q, 0): yield Issue( detector_id=self.option, confidence=IssueConfidence. FIRM, cvss3_vector=self.cvss, summary= 'insecure Javascript interface', source=store.query( ).qualname_of(q)) except (DataFlows.NoSuchValueError): yield Issue( detector_id=self.option, confidence=IssueConfidence. TENTATIVE, cvss3_vector=self.cvss, summary= 'insecure Javascript interface', source=store.query().qualname_of( q)) except (DataFlows.NoSuchValueError): pass # https://developer.android.com/reference/android/webkit/WebSettings#setMixedContentMode(int) if self.context.get_min_sdk_version() <= 20: for q in store.query().invocations_in_class( p, InvocationPattern( 'invoke-virtual', 'Landroid/webkit/WebSettings;->setMixedContentMode' )): try: val = int( DataFlows.solved_constant_data_in_invocation( store, q, 0), 16) if val == 0: yield Issue( detector_id=self.option, confidence=IssueConfidence.FIRM, cvss3_vector=self.cvss, summary='insecure mixed content mode', info1='MIXED_CONTENT_ALWAYS_ALLOW', source=store.query().qualname_of(q)) except (DataFlows.NoSuchValueError): pass