Ejemplo n.º 1
0
    def processInvoke(self, cls, method, prototype, stack):
        logger.debug("Processing method invoke: [%s %s %s]..."% (cls, method, prototype))
        #test code
#         print "\n"
#         for stackEntry in stack:
#             print stackEntry
#         print "\n"
        #end of test code

        invokeDstFromClient = (cls, method, prototype)
        invokePosInStack = self._findFirstInvokePos(stack)
        throughMethod = stack[invokePosInStack]
        invokeSrcFromStack = stack[invokePosInStack + 1]
        if invokePosInStack == -1:
            logger.info("Cannot find the first occurrence of invoke method in the stack! Adding method to suspicious list!")
            self._addSuspiciousInvoke(throughMethod, invokeDstFromClient, stack)
            return


        for invokePathFromSources in self._sources_invoke:
            invokeSrcFromSources = invokePathFromSources[0]
            if invokeSrcFromSources != invokeSrcFromStack:
                continue

            self._addInvokePathToMCG(invokeSrcFromSources, throughMethod, invokeDstFromClient)

            if invokePathFromSources in self._uncovered_invoke:
                self._uncovered_invoke.remove(invokePathFromSources)
            return

        self._addSuspiciousInvoke(throughMethod, invokeDstFromClient, stack)
Ejemplo n.º 2
0
    def processDexLoad(self, fileName, source, output, stack):
        logger.debug("Processing dex load message...")

        file_hash = getSha256(fileName)
        file_load_count = self._loaded_files_count.setdefault(file_hash, 0) + 1
        newFilePath = self._rename_source_file(fileName, file_hash, str(file_load_count))

        self._loaded_files_count[file_hash] = file_load_count
        self._codeFiles[newFilePath] = file_hash

        dexloadPathFromStack = self._getDexLoadPathFromStack(stack)

        if dexloadPathFromStack:
            srcFromStack = dexloadPathFromStack[0]
            throughMethod = dexloadPathFromStack[1]
            if dexloadPathFromStack in self._uncovered_dexload:
                self._uncovered_dexload.remove(dexloadPathFromStack)

            self._addDexloadPathToMCG(srcFromStack, throughMethod, newFilePath)
            #we do analyse files if appropriate dex load calls have found in sources of application
            #and if we have not analysed file yet
            if file_load_count > 1:
                logger.info("File [%s] with hash [%s] is loaded for the [%d]th time! Skipping its analysis!" % (newFilePath, file_hash, file_load_count))
            else:
                self.makeFileAnalysis(newFilePath)
            return

        #if the stack does not contain a dexload method detected in the sources
        self._addSuspiciousDexload(newFilePath, stack)
        logger.debug("Dex load message processed!")
Ejemplo n.º 3
0
 def _addNewInstancePathToMCG(self, src, through, dst):
     logger.debug("Adding newInstance method path to our graph...")
     tupl = (src, through, dst)
     if tupl not in self._covered_newInstance:
         self._covered_newInstance.append(tupl)
         self._stadynaMcg.addNewInstancePath(src, through, dst)
         logger.info("The path [%s] -- [%s] through [%s] for newInstance method is added to our graph!" % (str(src), str(dst), str(through)))
     else:
         logger.info("The path [%s] -- [%s] through [%s] for newInstance method is already in our graph!" % (str(src), str(dst), str(through)))
Ejemplo n.º 4
0
 def _addDexloadPathToMCG(self, src, through, filename):
     logger.debug("Adding dexload method path to our graph...")
     tupl = (src, through, filename)
     if tupl not in self._covered_dexload:
         self._covered_dexload.append(tupl)
         self._stadynaMcg.addDexloadPath(src, through, filename)
         logger.info("The path [%s] -- [%s] through [%s] for dexload is added to our graph!" % (str(src), str(filename), str(through)))
     else:
         logger.info("The path [%s] -- [%s] through [%s] for dexload is already in our graph!" % (str(src), str(filename), str(through)))
Ejemplo n.º 5
0
    def addNewInstancePath(self, src, through, dst):
        src_class_name, src_method_name, src_descriptor = src
        dst_class_name, dst_method_name, dst_descriptor = dst
        through_class_name, through_method_name, through_descriptor = through
        key = "%s %s %s %s %s %s %s" % \
            (src_class_name,
             src_method_name,
             src_descriptor,
             through_class_name,
             through_method_name,
             through_descriptor,
             POSTFIX_REFL_NEWINSTANCE)
        n1 = self._get_existed_node(key)
        if n1 is None:
            logger.error("Something wrong has happened! Could not find Node in Graph with key [%s]" % str(key))
            return

        n2 = self._get_node(NODE_CONSTRUCTOR, (dst_class_name,
                                               dst_method_name,
                                               dst_descriptor))
        n2.set_attribute(ATTR_CLASS_NAME, dst_class_name)
        n2.set_attribute(ATTR_METHOD_NAME, dst_method_name)
        n2.set_attribute(ATTR_DESCRIPTOR, dst_descriptor)

        self.G.add_edge(n1.id, n2.id)

        # we also need to add link to the class node
        # TODO: Think in the future what to do with this
        n_class = self._get_node(NODE_FAKE_CLASS, dst_class_name, None, False)
        n_class.set_attribute(ATTR_CLASS_NAME, dst_class_name)
        self.G.add_edge(n_class.id, n2.id)

        # checking if we need to add additional permission nodes
        data = "%s-%s-%s" % (dst_class_name, dst_method_name, dst_descriptor)
        if data in DVM_PERMISSIONS_BY_API_CALLS:
            logger.info("YESS!The protected method is called through reflection")
            perm = DVM_PERMISSIONS_BY_API_CALLS[data]
            key1 = "%s %s %s %s %s %s %s %s" % \
                (through_class_name,
                 through_method_name,
                 through_descriptor,
                 dst_class_name,
                 dst_method_name,
                 dst_descriptor,
                 POSTFIX_PERM,
                 perm)
            n3 = self._get_node(NODE_FAKE_PERMISSION, key1, perm, False)
            n3.set_attribute(ATTR_CLASS_NAME, dst_class_name)
            n3.set_attribute(ATTR_METHOD_NAME, dst_method_name)
            n3.set_attribute(ATTR_DESCRIPTOR, dst_descriptor)
            n3.set_attribute(ATTR_PERM_NAME, perm)
            n3.set_attribute(ATTR_PERM_LEVEL, MANIFEST_PERMISSIONS[perm][0])
            self.G.add_edge(n2.id, n3.id)
Ejemplo n.º 6
0
 def decompile(self):
     jadx = os.path.join(TOOLS_DIR, 'jadx/bin/jadx')
     args = [jadx, "-d", self.decompile_dir, self.apk]
     fire_jadx = subprocess.Popen(args, stdout=subprocess.PIPE,
                                  stderr=subprocess.STDOUT)
     # set to communicate with the logger
     stdout, stderr = fire_jadx.communicate()
     if stdout:
         logger.info(stdout)
     return True
     if stderr:
         logger.error(stderr)
     return False
Ejemplo n.º 7
0
 def post(self):
     try:
         apk = self.request.files['files[]']
         filename = apk[0]['filename']
         if apk and allowed_file(filename):
             # save the file in the uploads folder
             with open(os.path.join(UPLOADS_DIR, filename), "w") as out:
                 out.write(apk[0]['body'])
             logger.info("APK uploaded!")
             self.redirect('/dashboard?apk='+filename)
         else:
             logger.error("Invalid file!")
             self.render('index.html')
     except:
         logger.error("Cannot upload!")
Ejemplo n.º 8
0
    def addInvokePath(self, src, through, dst):
        src_class_name, src_method_name, src_descriptor = src
        dst_class_name, dst_method_name, dst_descriptor = dst
        through_class_name, through_method_name, through_descriptor = through
        key = "%s %s %s %s %s %s %s" % (src_class_name,
                                        src_method_name,
                                        src_descriptor,
                                        through_class_name,
                                        through_method_name,
                                        through_descriptor,
                                        POSTFIX_REFL_INVOKE)
        n1 = self._get_existed_node(key)
        if n1 is None:
            logger.warning("Something wrong has happened! Could not find invoke Node in Graph with key [%s]" % str(key))
            return

        n2 = self._get_node(NODE_METHOD, (dst_class_name,
                                          dst_method_name,
                                          dst_descriptor))
        n2.set_attribute(ATTR_CLASS_NAME, dst_class_name)
        n2.set_attribute(ATTR_METHOD_NAME, dst_method_name)
        n2.set_attribute(ATTR_DESCRIPTOR, dst_descriptor)

        self.G.add_edge(n1.id, n2.id)

        # check if called method calls protected feature
        data = "%s-%s-%s" % (dst_class_name, dst_method_name, dst_descriptor)
        if data in DVM_PERMISSIONS_BY_API_CALLS:
            logger.info("BINGOOOOOOO! The protected method is called through reflection!")
            perm = DVM_PERMISSIONS_BY_API_CALLS[data]
            key1 = "%s %s %s %s %s %s %s %s" % \
                   (through_class_name,
                    through_method_name,
                    through_descriptor,
                    dst_class_name,
                    dst_method_name,
                    dst_descriptor,
                    POSTFIX_PERM,
                    perm)
            n3 = self._get_node(NODE_FAKE_PERMISSION, key1, perm, False)
            n3.set_attribute(ATTR_CLASS_NAME, dst_class_name)
            n3.set_attribute(ATTR_METHOD_NAME, dst_method_name)
            n3.set_attribute(ATTR_DESCRIPTOR, dst_descriptor)
            n3.set_attribute(ATTR_PERM_NAME, perm)
            n3.set_attribute(ATTR_PERM_LEVEL, MANIFEST_PERMISSIONS[perm][0])
            self.G.add_edge(n2.id, n3.id)
Ejemplo n.º 9
0
def log_result_path_information(res, res_prefix, res_type) :
    """
        @param res : a result from the detector's result list
        @param res_prefix : result's category name
        @param res_type : result's type

        @rtype : void - it only logs extra information about the analysis result
    """
    res_info = res.get_info()
    if len(res_info) > 0:
        paths = res.get_paths()

        for path in res.get_paths() :
            access, idx = path[0]
            m_idx = path[1]
            logger.info("'%s' %s found '%s'" % (res_prefix, res_type, res_info ) )
            logger.debug("\t=> access_flag %s, index %s, method_index %s" % (access, idx, m_idx))
Ejemplo n.º 10
0
 def cert_info(self):
     logger.info("Extracting certificate...")
     certdir = os.path.join(self.extract_dir, 'META-INF')
     if "CERT.RSA" in os.listdir(certdir):
         certfile = os.path.join(certdir, "CERT.RSA")
     else:
         for file in os.listdir(certdir):
             if file.lower().endswith(".rsa"):
                 certfile = os.path.join(certdir, file)
                 break
             elif file.lower().endswith(".dsa"):
                 certfile = os.path.join(certdir, file)
             else:
                 certfile = ''
     data = subprocess.Popen('openssl pkcs7 -inform DER -noout -print_certs -text -in %s' % certfile,
                             shell=True,
                             stdout=subprocess.PIPE).communicate()[0]
     return data.replace('\n', '<br>')
Ejemplo n.º 11
0
 def hash_generator(self):
     logger.info("[*] Generating hashes..")
     md5 = hashlib.md5()
     sha1 = hashlib.sha1()
     sha256 = hashlib.sha256()
     # fixed blocksize
     BLOCKSIZE = 65536
     with io.open(self.apk, mode='rb') as app:
         buf_block = app.read(BLOCKSIZE)
         while len(buf_block) > 0:
             md5.update(buf_block)
             sha1.update(buf_block)
             sha256.update(buf_block)
             buf_block = app.read(BLOCKSIZE)
     md5_val = md5.hexdigest()
     sha1_val = sha1.hexdigest()
     sha256_val = sha256.hexdigest()
     return {"md5": md5_val, "sha1": sha1_val, "sha256": sha256_val}
Ejemplo n.º 12
0
def data_flow_analysis(tab, result, x) :
    """
        @param tab : structural analysis results tab
        @param result : current iteration
        @param x : a VMAnalysis instance

        @rtype : an ordered list of dictionaries of each register content [{ 'register #': 'value' }, { 'register #': 'value' } ...]
    """
    method = tab[result].get_method()
    method_call_index_to_find = tab[result].get_idx()

    registers = backtrace_registers_before_call(x, method, method_call_index_to_find)
    #log.info("Class '%s' - Method '%s' - register state before call %s" % (tab[result].get_class_name(),tab[result].get_name(), registers))

    class_str   = "Class '%s'" % tab[result].get_class_name()
    method_str  = "Method '%s'" % tab[result].get_name()
    regs_str    = "Register state before call %s" %  registers

    formatted_str = "{0:50}- {1:35}- {2:30}".format(class_str,method_str, regs_str)

    logger.info(formatted_str)

    return registers
Ejemplo n.º 13
0
    def manifest_analysis(self, mfxml, mainact):
        logger.info("Manifest Analysis Started")
        manifest = mfxml.getElementsByTagName("manifest")
        services = mfxml.getElementsByTagName("service")
        providers = mfxml.getElementsByTagName("provider")
        receivers = mfxml.getElementsByTagName("receiver")
        applications = mfxml.getElementsByTagName("application")
        datas = mfxml.getElementsByTagName("data")
        intents = mfxml.getElementsByTagName("intent-filter")
        actions = mfxml.getElementsByTagName("action")
        granturipermissions = mfxml.getElementsByTagName("grant-uri-permission")
        for node in manifest:
            package = node.getAttribute("package")
        RET = ''
        EXPORTED = []
        # SERVICES
        # search for services without permissions set
        # if a service is exporeted and has no permission
        # nor an intent filter, flag it
        # I doubt if this part gets executed ever
        for service in services:
            if service.getAttribute("android:exported") == 'true':
                perm = ''
                if service.getAttribute("android:permission"):
                    # service permission exists
                    perm = ' (permission '+service.getAttribute("android:permission")+' exists.) '
                servicename = service.getAttribute("android:name")
                RET = RET + '<tr><td>Service (' + servicename + ') is not Protected.'+perm+' <br>[android:exported=true]</td><td><span class="label label-danger">high</span></td><td> A service was found to be shared with other apps on the device without an intent filter or a permission requirement therefore leaving it accessible to any other application on the device.</td></tr>'

        # APPLICATIONS
        for application in applications:

            if application.getAttribute("android:debuggable") == "true":
                RET = RET + '<tr><td>Debug Enabled For App <br>[android:debuggable=true]</td><td><span class="label label-danger">high</span></td><td>Debugging was enabled on the app which makes it easier for reverse engineers to hook a debugger to it. This allows dumping a stack trace and accessing debugging helper classes.</td></tr>'
            if application.getAttribute("android:allowBackup") == "true":
                RET = RET+ '<tr><td>Application Data can be Backed up<br>[android:allowBackup=true]</td><td><span class="label label-warning">medium</span></td><td>This flag allows anyone to backup your application data via adb. It allows users who have enabled USB debugging to copy application data off of the device.</td></tr>'
            elif application.getAttribute("android:allowBackup") == "false":
                pass
            else:
                RET = RET + '<tr><td>Application Data can be Backed up<br>[android:allowBackup] flag is missing.</td><td><span class="label label-warning">medium</span></td><td>The flag [android:allowBackup] should be set to false. By default it is set to true and allows anyone to backup your application data via adb. It allows users who have enabled USB debugging to copy application data off of the device.</td></tr>'
            if application.getAttribute("android:testOnly") == "true":
                RET = RET + '<tr><td>Application is in Test Mode <br>[android:testOnly=true]</td><td><span class="label label-danger">high</span></td><td> It may expose functionality or data outside of itself that would cause a security hole.</td></tr>'
            for node in application.childNodes:
                ad = ''
                if node.nodeName == 'activity':
                    itmname = 'Activity'
                    ad = 'n'
                elif node.nodeName == 'activity-alias':
                    itmname = 'Activity-Alias'
                    ad = 'n'
                elif node.nodeName == 'provider':
                    itmname = 'Content Provider'
                elif node.nodeName == 'receiver':
                    itmname = 'Broadcast Receiver'
                elif node.nodeName == 'service':
                    itmname = 'Service'
                else:
                    itmname = 'NIL'
                item = ''
                # Task Affinity
                if ((itmname == 'Activity' or itmname == 'Activity-Alias') and (node.getAttribute("android:taskAffinity"))):
                    item = node.getAttribute("android:name")
                    RET = RET + '<tr><td>TaskAffinity is set for Activity </br>('+item + ')</td><td><span class="label label-danger">high</span></td><td>If taskAffinity is set, then other application could read the Intents sent to Activities belonging to another task. Always use the default setting keeping the affinity as the package name in order to prevent sensitive information inside sent or received Intents from being read by another application.</td></tr>'
                # LaunchMode
                if ((itmname == 'Activity' or itmname =='Activity-Alias') and ((node.getAttribute("android:launchMode")=='singleInstance') or (node.getAttribute("android:launchMode")=='singleTask'))):
                    item = node.getAttribute("android:name")
                    RET = RET + '<tr><td>Launch Mode of Activity ('+item + ') is not standard.</td><td><span class="label label-danger">high</span></td><td>An Activity should not be having the launch mode attribute set to "singleTask/singleInstance" as it becomes root Activity and it is possible for other applications to read the contents of the calling Intent. So it is required to use the "standard" launch mode attribute when sensitive information is included in an Intent.</td></tr>'
                # Exported Check
                item = ''
                isExp = False
                if ('NIL' != itmname) and (node.getAttribute("android:exported") == 'true'):
                    isExp = True
                    perm = ''
                    item = node.getAttribute("android:name")
                    if node.getAttribute("android:permission"):
                        # permission exists
                        perm = ' (permission '+node.getAttribute("android:permission")+' exists.) '
                    if item != mainact:
                        if (itmname == 'Activity' or itmname =='Activity-Alias'):
                            EXPORTED.append(item)
                        RET = RET +'<tr><td>'+itmname+' (' + item + ') is not Protected.'+perm+' <br>[android:exported=true]</td><td><span class="label label-danger">high</span></td><td> A'+ad+' '+itmname+' was found to be shared with other apps on the device therefore leaving it accessible to any other application on the device.</td></tr>'
                else:
                    isExp = False
                impE = False
                if ('NIL' != itmname) and (node.getAttribute("android:exported") == 'false'):
                    impE = True
                else:
                    impE = False
                if (isExp == False and impE == False):
                    isInf = False
                    # Logic to support intent-filter
                    intentfilters = node.childNodes
                    for i in intentfilters:
                        inf = i.nodeName
                        if inf == "intent-filter":
                            isInf = True
                    if isInf:
                        item = node.getAttribute("android:name")
                        if item != mainact:
                            if (itmname == 'Activity' or itmname == 'Activity-Alias'):
                                EXPORTED.append(item)
                            RET = RET +'<tr><td>'+itmname+' (' + item + ') is not Protected.<br>An intent-filter exists.</td><td><span class="label label-danger">high</span></td><td> A'+ad+' '+itmname+' was found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. The presence of intent-filter indicates that the '+itmname+' is explicitly exported.</td></tr>'

        ##GRANT-URI-PERMISSIONS
        title = 'Improper Content Provider Permissions'
        desc = ('A content provider permission was set to allows access from any other app on the ' +
                'device. Content providers may contain sensitive information about an app and therefore should not be shared.')
        for granturi in granturipermissions:
            if granturi.getAttribute("android:pathPrefix") == '/':
                RET = RET + '<tr><td>' + title + '<br> [pathPrefix=/] </td>' + '<td><span class="label label-danger">high</span></td><td>'+ desc+'</td></tr>'
            elif granturi.getAttribute("android:path") == '/':
                RET = RET + '<tr><td>' + title + '<br> [path=/] </td>' + '<td><span class="label label-danger">high</span></td><td>'+ desc+'</td></tr>'
            elif granturi.getAttribute("android:pathPattern") == '*':
                RET = RET + '<tr><td>' + title + '<br> [path=*]</td>' + '<td><span class="label label-danger">high</span></td><td>'+ desc +'</td></tr>'

        ##DATA
        for data in datas:
            if data.getAttribute("android:scheme") == "android_secret_code":
                xmlhost = data.getAttribute("android:host")
                desc = ("A secret code was found in the manifest. These codes, when entered into the dialer " +
                    "grant access to hidden content that may contain sensitive information.")
                RET = RET +  '<tr><td>Dailer Code: '+ xmlhost + 'Found <br>[android:scheme="android_secret_code"]</td><td><span class="label label-danger">high</span></td><td>'+ desc + '</td></tr>'
            elif data.getAttribute("android:port"):
                dataport = data.getAttribute("android:port")
                title = "Data SMS Receiver Set"
                desc = "A binary SMS recevier is configured to listen on a port. Binary SMS messages sent to a device are processed by the application in whichever way the developer choses. The data in this SMS should be properly validated by the application. Furthermore, the application should assume that the SMS being received is from an untrusted source."
                RET = RET +  '<tr><td> on Port: ' + dataport +  'Found<br>[android:port]</td><td><span class="label label-danger">high</span></td><td>'+ desc +'</td></tr>'

        ##INTENTS

        for intent in intents:
            if intent.getAttribute("android:priority").isdigit():
                value = intent.getAttribute("android:priority")
                if int(value) > 100:
                    RET = RET + '<tr><td>High Intent Priority ('+ value +')<br>[android:priority]</td><td><span class="label label-warning">medium</span></td><td>By setting an intent priority higher than another intent, the app effectively overrides other requests.</td></tr>'
        ##ACTIONS
        for action in actions:
            if action.getAttribute("android:priority").isdigit():
                value = action.getAttribute("android:priority")
                if int(value) > 100:
                    RET = RET + '<tr><td>High Action Priority (' + value+')<br>[android:priority]</td><td><span class="label label-warning">medium</span></td><td>By setting an action priority higher than another action, the app effectively overrides other requests.</td></tr>'
        if len(RET) < 2:
            RET = '<tr><td>None</td><td>None</td><td>None</td><tr>'
        return RET
Ejemplo n.º 14
0
 def _addSuspiciousNewInstance(self, throughMethod, dst, stack):
     logger.info("The destination for newInstance method [%s] was not found in the list of uncovered methods! Adding it to the list of suspicious methods!" % str(dst))
     self._suspicious_newInstance.append((throughMethod, dst, stack))
Ejemplo n.º 15
0
 def _addSuspiciousInvoke(self, throughMethod, dst, stack):
     #TODO: Write logic later to filter out unsuspicious methods
     logger.info("The destination for invoke method [%s] was not found in the list of uncovered methods! Adding it to the list of suspicious methods!" % str(dst))
     self._suspicious_invoke.append((throughMethod, dst, stack))
Ejemplo n.º 16
0
 def _addSuspiciousDexload(self, filePath, stack):
     logger.info("No dexload method is found in the stack! Leaving the file [%s] for analysis! Adding it to suspicious dexload files!" % str(filePath))
     self._suspicious_dexload.append((filePath, stack))