Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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)
Exemple #5
0
 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))
Exemple #6
0
  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
Exemple #7
0
 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))
Exemple #8
0
  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.
'''
          )
Exemple #9
0
 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
Exemple #10
0
 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}')
Exemple #11
0
  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
Exemple #12
0
 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
Exemple #13
0
    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