def _do_detect_plain_pins_x509(self) -> Set[str]: with self._context.store() as store: pins: Set[str] = set() q = store.query() for m in store.query().methods_in_class('checkServerTrusted', 'X509TrustManager'): if any(q.matches_in_method(m, InvocationPattern('verify', ''))): classname = q.class_name_of(q.class_of_method(m)) if classname: pins.add(classname) if any(q.matches_in_method(m, InvocationPattern('throw', ''))): classname = q.class_name_of(q.class_of_method(m)) if classname: pins.add(classname) if pins: # XXX crude detection custom_sslcontext_detected = False for cl in self._context.store().query().invocations( InvocationPattern('invoke-virtual', 'Ljavax/net/ssl/SSLContext;->init')): custom_sslcontext_detected = True pins = DataFlows.solved_typeset_in_invocation( store, cl, 1) & pins if not custom_sslcontext_detected: return set() else: return pins else: return pins
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 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]: import pkg_resources with open(pkg_resources.resource_filename(__name__, os.path.join('..', 'libs', 'tlds.txt')), 'r', encoding='utf-8') as f: self._re_tlds = re.compile('^(?:{})$'.format('|'.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='firm', cvss3_vector=self._cvss, summary=f'detected {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='firm', cvss3_vector=self._cvss, summary=f'detected {match["type_"]}', info1=v, source='R.string.%s' % name)
def detect(self) -> Iterable[Issue]: 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='tentative', cvss3_vector=self._cvss, summary=self._summary, 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='tentative', cvss3_vector=self._cvss, summary=self._summary, 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='tentative', cvss3_vector=self._cvss, summary=self._summary, 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='tentative', cvss3_vector=self._cvss, summary=self._summary, info1=cl.p[1].v, source=store.query().qualname_of(cl)) else: yield Issue(detector_id=self.option, confidence='tentative', cvss3_vector=self._cvss, summary=self._summary, info1=cl.p[1].v, source=store.query().qualname_of(cl))
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 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 _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_plain_pins_hostnameverifier(self) -> Set[str]: with self._context.store() as store: pins: Set[str] = set() q = store.query() for m in itertools.chain(store.query().methods_in_class( 'verify(Ljava/lang/String;Ljavax/net/ssl/SSLSession;)Z', 'HostnameVerifier')): if any( q.matches_in_method( m, InvocationPattern( 'invoke', 'contains|equals|verify|Ljavax/net/ssl/SSLSession;->getPeerCertificates' ))): classname = q.class_name_of(q.class_of_method(m)) if classname: pins.add(classname) return pins
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 detect(self) -> Iterable[Issue]: 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='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) -> Iterable[Issue]: 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='certain', cvss3_vector=self._cvss, summary=self._summary, info1={ 1: 'MODE_WORLD_READABLE', 2: 'MODE_WORLD_WRITEABLE' }[target_val], source=store.query().qualname_of(cl)) except (DataFlows.NoSuchValueError): pass
def detect(self) -> Iterable[Issue]: 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(f'L.*{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', f'{target}->addJavascriptInterface' )): try: if DataFlows.solved_constant_data_in_invocation( store, q, 0): yield Issue( detector_id=self.option, confidence='firm', cvss3_vector=self._cvss, summary=self._summary1, source=store.query( ).qualname_of(q)) except (DataFlows.NoSuchValueError): yield Issue( detector_id=self.option, confidence='tentative', cvss3_vector=self._cvss, summary=self._summary1, 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( 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='firm', cvss3_vector=self._cvss, summary=self._summary2, info1='MIXED_CONTENT_ALWAYS_ALLOW', source=store.query().qualname_of(q)) except (DataFlows.NoSuchValueError): pass