def main(queue): global parser global tree results = [] count = 0 if common.minSdkVersion<19: weak_rng_warning(results) find_key_files(results) for j in common.java_files: count = count + 1 pub.sendMessage('progress', bar='Crypto issues', percent=round(count*100/common.java_files.__len__())) try: tree=parser.parse_file(j) if tree is not None: #if re.search(r'\.getInstance\(',str(tree)): # print "YES" for type_decl in tree.type_declarations: if type(type_decl) is m.ClassDeclaration: for t in type_decl.body: try: recursive_ecb_check(t,j,results) #fixedSeedCheck(t,j) except Exception as e: common.logger.debug("Error running recursive_ecb_check in cryptoFlaws.py: " + str(e)) report.write("parsingerror-issues-list", "Error running recursive_ecb_check in cryptoFlaws.py: " + str(e), "strong") except Exception as e: common.logger.debug("Unable to create tree for " + str(j)) report.write("parsingerror-issues-list", "Unable to create tree for " + str(j), "strong") queue.put(results) return
def start(queue,height): """ Start finding pending intents """ results = [] global tree global current_file count = 0 #TODO - Look for use of fillIn method which can make this a much more exploitable condition for j in common.java_files: count = count + 1 pub.sendMessage('progress', count1=None, count2=round(count*100/common.java_files.__len__()), count3=None) current_file=j tree=parser.parse_file(j) #TODO - Need to add scanning of the imports, to see if Intent or PendingIntent is extended, was working on it, #but the one issue where it arose was non-trivial, so I gave up for now if hasattr(tree,'type_declarations'): for type_decl in tree.type_declarations: if type(type_decl) is m.ClassDeclaration: for t in type_decl.body: for f in t._fields: #dynamically parse each token where is f is the field and t is the token try: recurse(f,t,results) except Exception as e: common.logger.debug("Problem in recurse function of findPending.py: " + str(e)) common.parsingerrors.add(str(current_file)) else: common.logger.debug("No type declarations: " + str(j)) report.write("parsingerror-issues-list", str(current_file), "strong") queue.put(results) return
def start(queue, height): """ Start finding pending intents """ results = [] global tree global current_file count = 0 # TODO - Look for use of fillIn method which can make this a much more exploitable condition for j in common.java_files: count = count + 1 pub.sendMessage("progress", count1=None, count2=round(count * 100 / common.java_files.__len__()), count3=None) current_file = j tree = parser.parse_file(j) # TODO - Need to add scanning of the imports, to see if Intent or PendingIntent is extended, was working on it, # but the one issue where it arose was non-trivial, so I gave up for now if hasattr(tree, "type_declarations"): for type_decl in tree.type_declarations: if type(type_decl) is m.ClassDeclaration: for t in type_decl.body: for f in t._fields: # dynamically parse each token where is f is the field and t is the token try: recurse(f, t, results) except Exception as e: common.logger.debug("Problem in recurse function of findPending.py: " + str(e)) common.parsingerrors.add(str(current_file)) else: common.logger.debug("No type declarations: " + str(j)) report.write("parsingerror-issues-list", str(current_file), "strong") queue.put(results) return
def main(queue): global parser global tree results = [] count = 0 if common.minSdkVersion < 19: weak_rng_warning(results) find_key_files(results) for j in common.java_files: count = count + 1 pub.sendMessage('progress', bar='Crypto issues', percent=round(count * 100 / common.java_files.__len__())) try: tree = parser.parse_file(j) if tree is not None: #if re.search(r'\.getInstance\(',str(tree)): # print "YES" for type_decl in tree.type_declarations: if type(type_decl) is m.ClassDeclaration: for t in type_decl.body: try: recursive_ecb_check(t, j, results) #fixedSeedCheck(t,j) except Exception as e: common.logger.debug( "Error running recursive_ecb_check in cryptoFlaws.py: " + str(e)) report.write( "parsingerror-issues-list", "Error running recursive_ecb_check in cryptoFlaws.py: " + str(e), "strong") except Exception as e: common.logger.debug("Unable to create tree for " + str(j)) report.write("parsingerror-issues-list", "Unable to create tree for " + str(j), "strong") queue.put(results) return
def decompile(path): """ Converts DEX to JAR(containing class files) and then class files to near original java code using 3 different decompilers and selecting the best available decompiled code """ common.pathToDEX = path pathToDex2jar = common.rootDir + "/lib/dex2jar/dex2jar.sh" sp = subprocess.Popen([pathToDex2jar, common.pathToDEX], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output, error = sp.communicate() common.pathToJar = common.pathToDEX.rsplit(".", 1)[0] + "_dex2jar.jar" dirname, extension = common.pathToJar.rsplit(".", 1) zf = zipfile.ZipFile(common.pathToJar) # Total number of class files that need to be decompiled total_files = len(zf.namelist()) report.write("totalfiles", total_files) common.count = len( [s for s in zf.namelist() if ((".class" in s) and ("$" not in s))]) pub.subscribe(decompiler_update, 'decompile') thread0 = Process(name='clear', target=clear, args=()) thread1 = Process(name='jdcore', target=jdcore, args=(zf.filename, dirname)) thread2 = Process(name='procyon', target=cfr, args=(zf.filename, dirname)) thread3 = Process(name='cfr', target=procyon, args=(zf.filename, dirname)) thread0.start() thread0.join() progressbar1.start() progressbar2.start() progressbar3.start() thread1.start() thread2.start() thread3.start() thread1.join(0) thread2.join(0) thread3.join(0) with common.term.cbreak(): val = None while val not in (u'c', u'C'): with common.term.location(0, common.term.height - 3): print "Decompilation may hang/take too long (usually happens when the source is obfuscated)." print "At any time," + common.term.bold_underline_red_on_white( 'Press C to continue' ) + " and QARK will attempt to run SCA on whatever was decompiled." val = common.term.inkey(timeout=1) if not (thread1.is_alive() or thread2.is_alive() or thread3.is_alive()): break if thread1.is_alive(): thread1.terminate() if thread2.is_alive(): thread2.terminate() if thread3.is_alive(): thread3.terminate() # Go back to the bottom of the screen with common.term.location(0, common.term.height): print "" g1 = grep_1(dirname, "// Byte code:") g2 = grep_1(dirname + "1", "// This method has failed to decompile.") g3 = grep_1(dirname + "2", "// This method could not be decompiled.") # print list(set(g1) - set(g2)) logger.info("Trying to improve accuracy of the decompiled files") restored = 0 try: for filename in g1: relative_filename = str(filename).split(dirname)[1] if any(relative_filename in s for s in g2): if any(relative_filename in s for s in g3): logger.debug("Failed to reconstruct: " + relative_filename) else: shutil.copy(dirname + "2" + relative_filename, filename) restored = restored + 1 else: shutil.copy(dirname + "1" + relative_filename, filename) restored = restored + 1 except Exception as e: print e.message report.write( "restorestats", "Restored " + str(restored) + " file(s) out of " + str(len(g1)) + " corrupt file(s)") logger.info("Restored " + str(restored) + " file(s) out of " + str(len(g1)) + " corrupt file(s)") logger.debug("Deleting redundant decompiled files") try: shutil.rmtree(dirname + "1") logger.debug("Deleted " + dirname + "1") shutil.rmtree(dirname + "2") logger.debug("Deleted " + dirname + "2") except Exception as e: logger.debug( "Unable to delete redundant decompiled files (no impact on scan results): " + str(e))
apkIndex = int(raw_input(common.config.get('qarkhelper', 'SELECT_AN_APK') + "(" + "0-" + str(len(apkList)-1) + "): " )) common.logger.info("Selected:"+ str(apkIndex) + " " + str(apkList[apkIndex])) common.apkPath = pull_apk(str(apkList[apkIndex])) apkName=str(os.path.abspath(common.apkPath)).split("/")[-1] common.sourceDirectory=re.sub(r''+apkName,'',os.path.abspath(common.apkPath)) else: print common.term.cyan + common.term.bold + str(common.config.get('qarkhelper','PATH_PROMPT_APK')).decode('string-escape').format(t=common.term) common.apkPath = str(raw_input(common.config.get('qarkhelper', 'PATH_APK'))).strip() else: if common.args.apkpath is not None: common.apkPath = common.args.apkpath common.logger.debug('User selected APK %s' + common.apkPath) common.apkPath = os.path.abspath(common.apkPath) common.apkPath = re.sub("\\\\\s",' ',common.apkPath) report.write("apkpath", common.apkPath) unpackAPK.unpack() break except Exception as e: continue try: package = defaultdict(list) result = Queue() pub.subscribe(get_manifestXML, 'manifest') thread_get_manifest = Thread(name='apktool-qark', target=apktool, args=(common.apkPath,)) thread_get_manifest.start() thread_get_manifest.join() common.manifest = minidom.parseString(common.manifest).toxml() if common.interactive_mode: show=raw_input("Inspect Manifest?[y/n]")
def recursive_find_verify(q,filename,results): ''' Find all .verify methods ''' global sslSessions global tree global verifyIteration global warningGiven if len(sslSessions)>0: if verifyIteration==0: try: for type_decl in tree.type_declarations: if type(type_decl) is m.ClassDeclaration: for t in type_decl.body: if type(t) is m.MethodInvocation: if hasattr(t,'name'): if str(t.name)=='verify': if hasattr(t,'arguments'): if len(t.arguments)!=2: if not warningGiven: issue = ReportIssue() issue.setCategory(ExploitType.CERTIFICATE) issue.setDetails("Custom verify method used in " + str(filename) + ". You should manually review certificate validation here." ) issue.setFile(filename) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData("Custom verify method used in " + str(filename) + ". You should manually review certificate validation here." ) results.append(issue) warningGiven=True else: for r in q.arguments: continue elif type(t) is m.MethodDeclaration: if hasattr(t,'name'): if str(t.name)=='verify': if not warningGiven: issue = ReportIssue() issue.setCategory(ExploitType.CERTIFICATE) issue.setDetails("Custom verify method declared in " + str(filename) + ". You should manually review certificate validation here." ) issue.setFile(filename) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData("Custom verify method declared in " + str(filename) + ". You should manually review certificate validation here." ) results.append(issue) warningGiven=True elif type(t) is list: for l in t: if type(l) is not None: verifyIteration=1 recursive_find_verify(l,filename,results) elif hasattr(t,'_fields'): for f in t._fields: if type(getattr(t,f)) is not None: verifyIteration=1 recursive_find_verify(getattr(t,f),filename,results) elif type(t) is list: for l in t: if type(l) is not None: verifyIteration=1 recursive_find_verify(l,filename,results) elif hasattr(t,'_fields'): for f in t._fields: if type(getattr(t,f)) is not None: verifyIteration=1 recursive_find_verify(getattr(t,f),filename,results) except Exception as e: common.logger.debug("Something went wrong in certValidation.py's findVerify: " + str(e)) report.write("parsingerror-issues-list", "Something went wrong in certValidation.py's findVerify: " + str(e), "strong") else: if type(q) is m.MethodInvocation: if hasattr(q,'name'): if str(q.name)=='verify': if hasattr(q,'arguments'): if len(q.arguments)!=2: if not warningGiven: issue = ReportIssue() issue.setCategory(ExploitType.CERTIFICATE) issue.setDetails("Custom verify method used in " + str(filename) + ". You should manually review certificate validation here." ) issue.setFile(filename) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData("Custom verify method used in " + str(filename) + ". You should manually review certificate validation here." ) results.append(issue) warningGiven=True else: common.logger.debug("sslSessions Before: " + str(sslSessions)) if str(q.arguments[1]) in sslSessions: sslSessions.remove(str(q.arguments[1])) common.logger.debug("sslSessions After: " + str(sslSessions)) #For each verify method, check whether the session being verified is in sslSessions and if so, remove it(considering it verified) #This is not full proof at all, for several reasons, but we know it's definitely vulnerable if it's never verified, assuming no strange code #TODO - check that the .verify actually belongs to a HostnameVerifier #TODO - verify that .verify is not over ridden somewhere elif type(q) is m.MethodDeclaration: if hasattr(q,'name'): if str(q.name)=='verify': if not warningGiven: issue = ReportIssue() issue.setCategory(ExploitType.CERTIFICATE) issue.setDetails("Custom verify method declared in " + str(filename) + ". You should manually review certificate validation here." ) issue.setFile(filename) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData("Custom verify method declared in " + str(filename) + ". You should manually review certificate validation here." ) results.append(issue) warningGiven=True elif type(q) is list: for l in q: if type(l) is not None: recursive_find_verify(l,filename,results) elif hasattr(q,'_fields'): for f in q._fields: if type(getattr(q,f)) is not None: recursive_find_verify(getattr(q,f),filename,results) elif type(q) is list: for l in q: if type(l) is not None: recursive_find_verify(l,filename,results) elif hasattr(q,'_fields'): for f in q._fields: if type(getattr(q,f)) is not None: recursive_find_verify(getattr(q,f),filename,results) verifyIteration=1 unverified_sessions(results) return
def recursive_find_verify(q, filename, results): ''' Find all .verify methods ''' global sslSessions global tree global verifyIteration global warningGiven if len(sslSessions) > 0: if verifyIteration == 0: try: for type_decl in tree.type_declarations: if type(type_decl) is m.ClassDeclaration: for t in type_decl.body: if type(t) is m.MethodInvocation: if hasattr(t, 'name'): if str(t.name) == 'verify': if hasattr(t, 'arguments'): if len(t.arguments) != 2: if not warningGiven: issue = ReportIssue() issue.setCategory( ExploitType.CERTIFICATE ) issue.setDetails( "Custom verify method used in " + str(filename) + ". You should manually review certificate validation here." ) issue.setFile(filename) issue.setSeverity( Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel( Severity.WARNING) issue.setData( "Custom verify method used in " + str(filename) + ". You should manually review certificate validation here." ) results.append(issue) warningGiven = True else: for r in q.arguments: continue elif type(t) is m.MethodDeclaration: if hasattr(t, 'name'): if str(t.name) == 'verify': if not warningGiven: issue = ReportIssue() issue.setCategory( ExploitType.CERTIFICATE) issue.setDetails( "Custom verify method declared in " + str(filename) + ". You should manually review certificate validation here." ) issue.setFile(filename) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData( "Custom verify method declared in " + str(filename) + ". You should manually review certificate validation here." ) results.append(issue) warningGiven = True elif type(t) is list: for l in t: if type(l) is not None: verifyIteration = 1 recursive_find_verify( l, filename, results) elif hasattr(t, '_fields'): for f in t._fields: if type(getattr(t, f)) is not None: verifyIteration = 1 recursive_find_verify( getattr(t, f), filename, results) elif type(t) is list: for l in t: if type(l) is not None: verifyIteration = 1 recursive_find_verify( l, filename, results) elif hasattr(t, '_fields'): for f in t._fields: if type(getattr(t, f)) is not None: verifyIteration = 1 recursive_find_verify( getattr(t, f), filename, results) except Exception as e: common.logger.debug( "Something went wrong in certValidation.py's findVerify: " + str(e)) report.write( "parsingerror-issues-list", "Something went wrong in certValidation.py's findVerify: " + str(e), "strong") else: if type(q) is m.MethodInvocation: if hasattr(q, 'name'): if str(q.name) == 'verify': if hasattr(q, 'arguments'): if len(q.arguments) != 2: if not warningGiven: issue = ReportIssue() issue.setCategory(ExploitType.CERTIFICATE) issue.setDetails( "Custom verify method used in " + str(filename) + ". You should manually review certificate validation here." ) issue.setFile(filename) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData( "Custom verify method used in " + str(filename) + ". You should manually review certificate validation here." ) results.append(issue) warningGiven = True else: common.logger.debug("sslSessions Before: " + str(sslSessions)) if str(q.arguments[1]) in sslSessions: sslSessions.remove(str(q.arguments[1])) common.logger.debug("sslSessions After: " + str(sslSessions)) # For each verify method, check whether the session being verified is in sslSessions and if so, remove it(considering it verified) # This is not full proof at all, for several reasons, but we know it's definitely vulnerable if it's never verified, assuming no strange code # TODO - check that the .verify actually belongs to a HostnameVerifier # TODO - verify that .verify is not over ridden somewhere elif type(q) is m.MethodDeclaration: if hasattr(q, 'name'): if str(q.name) == 'verify': if not warningGiven: issue = ReportIssue() issue.setCategory(ExploitType.CERTIFICATE) issue.setDetails( "Custom verify method declared in " + str(filename) + ". You should manually review certificate validation here." ) issue.setFile(filename) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData( "Custom verify method declared in " + str(filename) + ". You should manually review certificate validation here." ) results.append(issue) warningGiven = True elif type(q) is list: for l in q: if type(l) is not None: recursive_find_verify(l, filename, results) elif hasattr(q, '_fields'): for f in q._fields: if type(getattr(q, f)) is not None: recursive_find_verify(getattr(q, f), filename, results) elif type(q) is list: for l in q: if type(l) is not None: recursive_find_verify(l, filename, results) elif hasattr(q, '_fields'): for f in q._fields: if type(getattr(q, f)) is not None: recursive_find_verify(getattr(q, f), filename, results) verifyIteration = 1 unverified_sessions(results) return
def recurse(f, t, results): """ Recurse """ #Looking for pending intents global method_list current = getattr(t, f) if type(current) is str: for e in method_list: if str(type(current)) == str(e): #TODO - Remove this (never seems to be hit) report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Please report the following error, if you see this") common.logger.debug( "INCOMPLETE CODE BRANCH REACHED findPending.py #0 (results may be incomplete)" ) elif type(current) is list: for l in current: #TODO - There might be a way to get rid of this block and just use recurse if type(l) is m.MethodInvocation: for i in method_list: if str(l.name) == str(i): if hasattr(current, 'target'): if hasattr(current.target, 'value'): if str(current.target.value ) == "PendingIntent": #TODO - the interesting ones seem to be of type Name only #The intent is always the third parameter, for getActivities it is an array try: parseArgs(current.arguments[2], results) except Exception as e: report.write( "parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Problem in parseArgs function of findPending.py: " + str(e)) elif l is None: pass else: if type(l) is not str: for n in l._fields: try: recurse(n, l, results) except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Problem in recurse function of findPending.py: " + str(e)) else: for g in method_list: if str(g) == str(l): #TODO - Remove this (never seems to be hit) report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Please report the following error, if you see this" ) common.logger.debug( "INCOMPLETE CODE BRANCH REACHED findPending.py #2 - Press any key to continue (results may be incomplete)" ) elif type(current) is m.MethodInvocation: for j in method_list: if str(current.name) == str(j): if hasattr(current, 'target'): if hasattr(current.target, 'value'): if str(current.target.value) == "PendingIntent": #TODO - the interesting ones seem to be of type Name only #The intent is always the third parameter, for getActivities it is an array try: parseArgs(current.arguments[2], results) except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Problem in recurse function of findPending.py: " + str(e)) elif type(current) is m.VariableDeclaration: if hasattr(current, 'type'): if type(current.type) is m.Type: if hasattr(current.type, 'name'): if type(current.type.name) is not str: if hasattr(current.type.name, 'value'): if str(current.type.name.value) == "PendingIntent": report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "findPending - recurse, found PendingIntent, but not handled: " + str(current)) else: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "findPending - recurse, can't find PendingIntent: " + str(current)) elif hasattr(current, '_fields'): for q in current._fields: try: recurse(q, current, results) except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Problem in recurse function of findPending.py: " + str(e)) return
def findIntent(intent, results): """ Find Intent """ #TODO - This section is ugly and needs to be refactored #Looking for the intent used in the pending intent global tree found = False explicit = False if hasattr(tree, 'type_declarations'): for type_decl in tree.type_declarations: if type(type_decl) is m.ClassDeclaration: for t in type_decl.body: if not found: if type(t) is m.VariableDeclaration: if hasattr(t, 'type'): if type(t.type) is not str: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Please report the following error, if you see this" ) common.logger.debug( "ERROR: INCOMPLETE CODE BRANCH REACHED findPending.py #6 - Press any key to continue (results may be incomplete)" ) if str(t.type) == "Intent": common.logger.error( "FOUND Intent Variable 0: " + str(t)) else: if hasattr(t.type, 'name'): if hasattr(t.type.name, 'value'): if str(t.type.name.value ) == "Intent": common.logger.debug( "FOUND Intent Variable 1: " + str(t)) else: try: temp = intent_digger(t, intent) except Exception as e: common.logger.debug( "Error in intent_digger function of findPending.py: " + str(e)) found = temp[0] explicit = temp[1] if found: if not explicit: #See if the intent is explicit if type(t) is m.MethodInvocation: if str(t.name) == "setClass": explicit = True elif str(t.name) == "setPackage": explicit = True elif type(t) is m.MethodDeclaration: for f in t._fields: if type(getattr(t, f)) is list: for x in getattr(t, f): if type( x ) is m.MethodInvocation: if str(x.name ) == "setClass": explicit = True elif str( x.name ) == "setPackage": explicit = True else: if hasattr( x, '_fields'): for y in x._fields: if type( getattr( x, y) ) is m.MethodInvocation: if str( getattr( x, y ). name ) == "setClass": if hasattr( getattr( x, y ) . target, 'value' ): if str( getattr( x, y ) . target . value ) == str( intent ): explicit = True elif str( getattr( x, y ) . name ) == "setPackage": if str( getattr( x, y ) . target . value ) == str( intent ): explicit = True else: common.logger.debug( "Something went wrong in findPending, when determining if the Intent was explicit" ) elif type(x) is list: common.logger.debug( "ERROR: UNEXPECTED CODE PATH in findIntent - 1" ) elif type(getattr( t, f)) is m.MethodInvocation: common.logger.debug( "ERROR: UNEXPECTED CODE PATH in findIntent - 2" ) else: for f in t._fields: if type(getattr(t, f)) is list: for x in getattr(t, f): if type( x ) is m.MethodInvocation: if str(x.name ) == "setClass": explicit = True elif str( x.name ) == "setPackage": explicit = True else: break else: common.logger.debug("NO TYPE DECLARATIONS") if not explicit: if found: issue = ReportIssue() issue.setCategory(ExploitType.INTENT) issue.setDetails( "Implicit Intent: " + str(intent) + " used to create instance of PendingIntent. A malicious application could potentially intercept, redirect and/or modify (in a limited manner) this Intent. Pending Intents retain the UID of your application and all related permissions, allowing another application to act as yours. File: " + str(current_file) + " More details: https://www.securecoding.cert.org/confluence/display/android/DRD21-J.+Always+pass+explicit+intents+to+a+PendingIntent" ) issue.setFile(current_file) issue.setSeverity(Severity.VULNERABILITY) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.VULNERABILITY) issue.setData( "Implicit Intent: " + str(intent) + " used to create instance of PendingIntent. A malicious application could potentially intercept, redirect and/or modify (in a limited manner) this Intent. Pending Intents retain the UID of your application and all related permissions, allowing another application to act as yours. File: " + str(current_file) + " More details: https://www.securecoding.cert.org/confluence/display/android/DRD21-J.+Always+pass+explicit+intents+to+a+PendingIntent" ) results.append(issue) else: #TO DO - ensure we can resolve custom intents to avoid this error common.logger.debug( "ERROR: COULDN'T FIND THE INTENT: " + str(intent) + " This may either be due to a custom Intent class or other error" ) return
def parseArgs(arg, results): """ Checking to see whether the setClass method is called on the arguments of a pending intent, if it is a new instance of an Intent """ if type(arg) is m.MethodInvocation: if type(arg.target) is m.InstanceCreation: #If the intent only has one argument, it must be implicit if len(arg.target.arguments) > 1: if type(arg.target.arguments[1]) is m.Name: #At this point, I need to go through the tree and see if this arg is a class, making this explicit and safe #If the argument is not a name, what is it? if hasattr(arg.target.arguments[1], 'value'): try: if findClass(arg.target.arguments[1].value, results): return else: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "ERROR: CAN'T FIND THE CLASS in parseArgs") except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Problem in findClass function of findPending.py: " + str(e)) else: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("ERROR: UNEXPECTED Type " + str(type(arg.target.arguments[1]))) else: #TODO - need to add details here issue = ReportIssue() issue.setCategory(ExploitType.INTENT) issue.setDetails( "PendingIntent created with implicit intent. File: " + str(current_file)) issue.setFile(current_file) issue.setSeverity(Severity.VULNERABILITY) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.VULNERABILITY) issue.setData( "PendingIntent created with implicit intent. File: " + str(current_file)) results.append(issue) else: #TODO - Remove this (never seems to be hit) report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Please report the following error, if you see this") common.logger.debug( "ERROR: INCOMPLETE CODE BRANCH REACHED findPending.py #4 - Press any key to continue (results may be incomplete)" ) elif type(arg) is m.Name: if hasattr(arg, 'value'): try: findIntent(arg.value, results) except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Problem in findIntent function of findPending.py: " + str(e)) else: #TODO - Remove this (never seems to be hit) report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Please report the following error, if you see this") common.logger.debug( "ERROR: INCOMPLETE CODE BRANCH REACHED findPending.py #5 - Press any key to continue (results may be incomplete)" ) return
def main(queue): global parser global tree results = [] count = 0 if common.minSdkVersion < 19: weak_rng_warning(results) find_key_files(results) for j in common.java_files: count = count + 1 pub.sendMessage('progress', bar='Crypto issues', percent=round(count * 100 / common.java_files.__len__())) try: tree = parser.parse_file(j) if tree is not None: # if re.search(r'\.getInstance\(',str(tree)): # print "YES" for type_decl in tree.type_declarations: if type(type_decl) is m.ClassDeclaration: for t in type_decl.body: try: recursive_ecb_check(t, j, results) # fixedSeedCheck(t,j) except Exception as e: common.logger.debug("Error running recursive_ecb_check in cryptoFlaws.py: " + str(e)) report.write("parsingerror-issues-list", "Error running recursive_ecb_check in cryptoFlaws.py: " + str(e), "strong") # Using a fixed seed with SecureRandom for import_decl in tree.import_declarations: try: if 'SecureRandom' in import_decl.name.value: if "setSeed" in str(tree): issue = ReportIssue() issue.setCategory(ExploitType.CRYPTO) issue.setDetails( "setSeed should not be called with SecureRandom, as it is insecure. Specifying a fixed seed will cause the instance to return a predictable sequence of numbers. This may be useful for testing but it is not appropriate for secure use.") issue.setFile(str(j)) issue.setSeverity(Severity.VULNERABILITY) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.VULNERABILITY) issue.setData( "setSeed should not be called with SecureRandom, as it is insecure. Specifying a fixed seed will cause the instance to return a predictable sequence of numbers. This may be useful for testing but it is not appropriate for secure use.") results.append(issue) if "generateSeed" in str(tree): issue = ReportIssue() issue.setCategory(ExploitType.CRYPTO) issue.setDetails( "generateSeed should not be called with SecureRandom, as it is insecure. Specifying a fixed seed will cause the instance to return a predictable sequence of numbers. This may be useful for testing but it is not appropriate for secure use.") issue.setFile(str(j)) issue.setSeverity(Severity.VULNERABILITY) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.VULNERABILITY) issue.setData( "generateSeed should not be called with SecureRandom, as it is insecure. Specifying a fixed seed will cause the instance to return a predictable sequence of numbers. This may be useful for testing but it is not appropriate for secure use. ") results.append(issue) except Exception as e: common.logger.debug("Error checking insecure used of SecureRandom in cryptoFlaws.py: " + str(e)) report.write("parsingerror-issues-list", "Error checking insecure used of SecureRandom in cryptoFlaws.py: " + str(e)) except Exception as e: common.logger.debug("Unable to create tree for " + str(j)) report.write("parsingerror-issues-list", "Unable to create tree for " + str(j), "strong") queue.put(results) return
def start(queue, height): results = [] count = 0 # TODO - add check for getSharedPreferences specifically, to run before these more generalized ones # Check for world readable files file_wr = r'MODE_WORLD_READABLE' for i in text_scan(common.java_files, file_wr): if len(i) > 0: report.write( IssueType.FileSystem, IssueSeverity.High, common.config.get('qarkhelper', 'WR_FILE') + str(i[0]) + "<br>" + str(i[1])) issue = ReportIssue() issue.setCategory(ExploitType.PERMISSION) issue.setDetails( common.config.get('qarkhelper', 'WR_FILE') + str(i[0]) + str(i[1])) issue.setFile(str(i[1])) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData( common.config.get('qarkhelper', 'WR_FILE') + str(i[0]) + str(i[1])) results.append(issue) # Check for world writable files file_ww = r'MODE_WORLD_WRITEABLE' for i in text_scan(common.java_files, file_ww): if len(i) > 0: report.write( IssueType.FileSystem, IssueSeverity.High, common.config.get('qarkhelper', 'WW_FILE') + str(i[0]) + "<br>" + str(i[1])) issue = ReportIssue() issue.setCategory(ExploitType.PERMISSION) issue.setDetails( common.config.get('qarkhelper', 'WW_FILE') + str(i[0]) + " in file: " + str(i[1])) issue.setFile(str(i[1])) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData( common.config.get('qarkhelper', 'WW_FILE') + str(i[0]) + " in file: " + str(i[1])) results.append(issue) queue.put(results) ''' More checks from Android Lint to implement WorldReadableFiles ------------------ Summary: openFileOutput() call passing MODE_WORLD_READABLE Priority: 4 / 10 Severity: Warning Category: Security There are cases where it is appropriate for an application to write world readable files, but these should be reviewed carefully to ensure that they contain no private data that is leaked to other applications. WorldWriteableFiles ------------------- Summary: openFileOutput() call passing MODE_WORLD_WRITEABLE Priority: 4 / 10 Severity: Warning Category: Security There are cases where it is appropriate for an application to write world writeable files, but these should be reviewed carefully to ensure that they contain no private data, and that if the file is modified by a malicious application it does not trick or compromise your application. ''' return
def decompile(path): """ Converts DEX to JAR(containing class files) and then class files to near original java code using 3 different decompilers and selecting the best available decompiled code """ common.pathToDEX = path pathToDex2jar = common.rootDir + "/lib/dex2jar/dex2jar.sh" sp = subprocess.Popen([pathToDex2jar, common.pathToDEX], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output, error = sp.communicate() common.pathToJar = common.pathToDEX.rsplit(".",1)[0] + "_dex2jar.jar" dirname, extension = common.pathToJar.rsplit(".",1) zf = zipfile.ZipFile(common.pathToJar) #Total number of class files that need to be decompiled total_files = len(zf.namelist()) report.write("totalfiles", total_files) common.count = len([s for s in zf.namelist() if ((".class" in s) and ("$" not in s))]) pub.subscribe(decompiler_update, 'decompile') thread0 = Process(name='clear', target=clear, args = ()) thread1 = Process(name='jdcore', target=jdcore, args = (zf.filename,dirname)) thread2 = Process(name='procyon', target=cfr, args = (zf.filename,dirname)) thread3 = Process(name='cfr', target=procyon, args = (zf.filename,dirname)) thread0.start() thread0.join() progressbar1.start() progressbar2.start() progressbar3.start() thread1.start() thread2.start() thread3.start() thread1.join(0) thread2.join(0) thread3.join(0) with common.term.cbreak(): val = None while val not in (u'c', u'C'): with common.term.location(0,common.term.height-3): print "Decompilation may hang/take too long (usually happens when the source is obfuscated)." print "At any time," + common.term.bold_underline_red_on_white('Press C to continue') + " and QARK will attempt to run SCA on whatever was decompiled." val = common.term.inkey(timeout=1) if not (thread1.is_alive() or thread2.is_alive() or thread3.is_alive()): break if thread1.is_alive(): thread1.terminate() if thread2.is_alive(): thread2.terminate() if thread3.is_alive(): thread3.terminate() #Go back to the bottom of the screen with common.term.location(0,common.term.height): print "" g1 = grep_1(dirname, "// Byte code:") g2 = grep_1(dirname+"1", "// This method has failed to decompile.") g3 = grep_1(dirname+"2", "// This method could not be decompiled.") #print list(set(g1) - set(g2)) logger.info("Trying to improve accuracy of the decompiled files") restored = 0 try: for filename in g1: relative_filename = str(filename).split(dirname)[1] if any(relative_filename in s for s in g2): if any(relative_filename in s for s in g3): logger.debug("Failed to reconstruct: " + relative_filename) else: shutil.copy(dirname+"2"+relative_filename, filename) restored = restored +1 else: shutil.copy(dirname+"1"+relative_filename, filename) restored = restored +1 except Exception as e: print e.message report.write("restorestats","Restored " + str(restored) + " file(s) out of " + str(len(g1)) + " corrupt file(s)") logger.info("Restored " + str(restored) + " file(s) out of " + str(len(g1)) + " corrupt file(s)") logger.debug("Deleting redundant decompiled files") try: shutil.rmtree(dirname+"1") logger.debug("Deleted " + dirname+"1") shutil.rmtree(dirname+"2") logger.debug("Deleted " + dirname+"2") except Exception as e: logger.debug("Unable to delete redundant decompiled files (no impact on scan results): " + str(e))
def recurse(f, t, results): """ Recurse """ # Looking for pending intents global method_list current = getattr(t, f) if type(current) is str: for e in method_list: if str(type(current)) == str(e): # TODO - Remove this (never seems to be hit) report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Please report the following error, if you see this") common.logger.debug("INCOMPLETE CODE BRANCH REACHED findPending.py #0 (results may be incomplete)") elif type(current) is list: for l in current: # TODO - There might be a way to get rid of this block and just use recurse if type(l) is m.MethodInvocation: for i in method_list: if str(l.name) == str(i): if hasattr(current, "target"): if hasattr(current.target, "value"): if str(current.target.value) == "PendingIntent": # TODO - the interesting ones seem to be of type Name only # The intent is always the third parameter, for getActivities it is an array try: parseArgs(current.arguments[2], results) except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "Problem in parseArgs function of findPending.py: " + str(e) ) elif l is None: pass else: if type(l) is not str: for n in l._fields: try: recurse(n, l, results) except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Problem in recurse function of findPending.py: " + str(e)) else: for g in method_list: if str(g) == str(l): # TODO - Remove this (never seems to be hit) report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Please report the following error, if you see this") common.logger.debug( "INCOMPLETE CODE BRANCH REACHED findPending.py #2 - Press any key to continue (results may be incomplete)" ) elif type(current) is m.MethodInvocation: for j in method_list: if str(current.name) == str(j): if hasattr(current, "target"): if hasattr(current.target, "value"): if str(current.target.value) == "PendingIntent": # TODO - the interesting ones seem to be of type Name only # The intent is always the third parameter, for getActivities it is an array try: parseArgs(current.arguments[2], results) except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Problem in recurse function of findPending.py: " + str(e)) elif type(current) is m.VariableDeclaration: if hasattr(current, "type"): if type(current.type) is m.Type: if hasattr(current.type, "name"): if type(current.type.name) is not str: if hasattr(current.type.name, "value"): if str(current.type.name.value) == "PendingIntent": report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug( "findPending - recurse, found PendingIntent, but not handled: " + str(current) ) else: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("findPending - recurse, can't find PendingIntent: " + str(current)) elif hasattr(current, "_fields"): for q in current._fields: try: recurse(q, current, results) except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Problem in recurse function of findPending.py: " + str(e)) return
def findIntent(intent, results): """ Find Intent """ # TODO - This section is ugly and needs to be refactored # Looking for the intent used in the pending intent global tree found = False explicit = False if hasattr(tree, "type_declarations"): for type_decl in tree.type_declarations: if type(type_decl) is m.ClassDeclaration: for t in type_decl.body: if not found: if type(t) is m.VariableDeclaration: if hasattr(t, "type"): if type(t.type) is not str: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Please report the following error, if you see this") common.logger.debug( "ERROR: INCOMPLETE CODE BRANCH REACHED findPending.py #6 - Press any key to continue (results may be incomplete)" ) if str(t.type) == "Intent": common.logger.error("FOUND Intent Variable 0: " + str(t)) else: if hasattr(t.type, "name"): if hasattr(t.type.name, "value"): if str(t.type.name.value) == "Intent": common.logger.debug("FOUND Intent Variable 1: " + str(t)) else: try: temp = intent_digger(t, intent) except Exception as e: common.logger.debug("Error in intent_digger function of findPending.py: " + str(e)) found = temp[0] explicit = temp[1] if found: if not explicit: # See if the intent is explicit if type(t) is m.MethodInvocation: if str(t.name) == "setClass": explicit = True elif str(t.name) == "setPackage": explicit = True elif type(t) is m.MethodDeclaration: for f in t._fields: if type(getattr(t, f)) is list: for x in getattr(t, f): if type(x) is m.MethodInvocation: if str(x.name) == "setClass": explicit = True elif str(x.name) == "setPackage": explicit = True else: if hasattr(x, "_fields"): for y in x._fields: if type(getattr(x, y)) is m.MethodInvocation: if str(getattr(x, y).name) == "setClass": if hasattr(getattr(x, y).target, "value"): if str(getattr(x, y).target.value) == str( intent ): explicit = True elif ( str(getattr(x, y).name) == "setPackage" ): if str( getattr(x, y).target.value ) == str(intent): explicit = True else: common.logger.debug( "Something went wrong in findPending, when determining if the Intent was explicit" ) elif type(x) is list: common.logger.debug( "ERROR: UNEXPECTED CODE PATH in findIntent - 1" ) elif type(getattr(t, f)) is m.MethodInvocation: common.logger.debug("ERROR: UNEXPECTED CODE PATH in findIntent - 2") else: for f in t._fields: if type(getattr(t, f)) is list: for x in getattr(t, f): if type(x) is m.MethodInvocation: if str(x.name) == "setClass": explicit = True elif str(x.name) == "setPackage": explicit = True else: break else: common.logger.debug("NO TYPE DECLARATIONS") if not explicit: if found: issue = ReportIssue() issue.setCategory(ExploitType.INTENT) issue.setDetails( "Implicit Intent: " + str(intent) + " used to create instance of PendingIntent. A malicious application could potentially intercept, redirect and/or modify (in a limited manner) this Intent. Pending Intents retain the UID of your application and all related permissions, allowing another application to act as yours. File: " + str(current_file) + " More details: https://www.securecoding.cert.org/confluence/display/android/DRD21-J.+Always+pass+explicit+intents+to+a+PendingIntent" ) issue.setFile(current_file) issue.setSeverity(Severity.VULNERABILITY) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.VULNERABILITY) issue.setData( "Implicit Intent: " + str(intent) + " used to create instance of PendingIntent. A malicious application could potentially intercept, redirect and/or modify (in a limited manner) this Intent. Pending Intents retain the UID of your application and all related permissions, allowing another application to act as yours. File: " + str(current_file) + " More details: https://www.securecoding.cert.org/confluence/display/android/DRD21-J.+Always+pass+explicit+intents+to+a+PendingIntent" ) results.append(issue) else: # TO DO - ensure we can resolve custom intents to avoid this error common.logger.debug( "ERROR: COULDN'T FIND THE INTENT: " + str(intent) + " This may either be due to a custom Intent class or other error" ) return
def parseArgs(arg, results): """ Checking to see whether the setClass method is called on the arguments of a pending intent, if it is a new instance of an Intent """ if type(arg) is m.MethodInvocation: if type(arg.target) is m.InstanceCreation: # If the intent only has one argument, it must be implicit if len(arg.target.arguments) > 1: if type(arg.target.arguments[1]) is m.Name: # At this point, I need to go through the tree and see if this arg is a class, making this explicit and safe # If the argument is not a name, what is it? if hasattr(arg.target.arguments[1], "value"): try: if findClass(arg.target.arguments[1].value, results): return else: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("ERROR: CAN'T FIND THE CLASS in parseArgs") except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Problem in findClass function of findPending.py: " + str(e)) else: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("ERROR: UNEXPECTED Type " + str(type(arg.target.arguments[1]))) else: # TODO - need to add details here issue = ReportIssue() issue.setCategory(ExploitType.INTENT) issue.setDetails("PendingIntent created with implicit intent. File: " + str(current_file)) issue.setFile(current_file) issue.setSeverity(Severity.VULNERABILITY) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.VULNERABILITY) issue.setData("PendingIntent created with implicit intent. File: " + str(current_file)) results.append(issue) else: # TODO - Remove this (never seems to be hit) report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Please report the following error, if you see this") common.logger.debug( "ERROR: INCOMPLETE CODE BRANCH REACHED findPending.py #4 - Press any key to continue (results may be incomplete)" ) elif type(arg) is m.Name: if hasattr(arg, "value"): try: findIntent(arg.value, results) except Exception as e: report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Problem in findIntent function of findPending.py: " + str(e)) else: # TODO - Remove this (never seems to be hit) report.write("parsingerror-issues-list", str(current_file), "strong") common.logger.debug("Please report the following error, if you see this") common.logger.debug( "ERROR: INCOMPLETE CODE BRANCH REACHED findPending.py #5 - Press any key to continue (results may be incomplete)" ) return
apkIndex = int(raw_input(common.config.get('qarkhelper', 'SELECT_AN_APK') + "(" + "0-" + str(len(apkList)-1) + "): " )) common.logger.info("Selected:"+ str(apkIndex) + " " + str(apkList[apkIndex])) common.apkPath = pull_apk(str(apkList[apkIndex])) apkName=str(os.path.abspath(common.apkPath)).split("/")[-1] common.sourceDirectory=re.sub(r''+apkName,'',os.path.abspath(common.apkPath)) else: print common.term.cyan + common.term.bold + str(common.config.get('qarkhelper','PATH_PROMPT_APK')).decode('string-escape').format(t=common.term) common.apkPath = str(raw_input(common.config.get('qarkhelper', 'PATH_APK'))).strip() else: if common.args.apkpath is not None: common.apkPath = common.args.apkpath common.logger.debug('User selected APK %s' + common.apkPath) common.apkPath = os.path.abspath(common.apkPath) common.apkPath = re.sub("\\\\\s",' ',common.apkPath) report.write("apkpath", common.apkPath) unpackAPK.unpack() break except Exception as e: continue try: package = defaultdict(list) mf = unpackAPK.find_manifest_in_unpacked_apk(common.apkPath, common.manifestName) ap = axmlprinter.AXMLPrinter(open(mf, 'rb').read()) manifestInXML = minidom.parseString(ap.getBuff()).toxml() if common.interactive_mode: show=raw_input("Inspect Manifest?[y/n]") if show in ['y','Y']: common.logger.info(manifestInXML) raw_input("Press ENTER key to continue")
def start(queue, height): results = [] count = 0 # TODO - add check for getSharedPreferences specifically, to run before these more generalized ones # Check for world readable files file_wr = r'MODE_WORLD_READABLE' for i in text_scan(common.java_files, file_wr): if len(i) > 0: report.write(IssueType.FileSystem, IssueSeverity.High, common.config.get('qarkhelper', 'WR_FILE') + str(i[0]) + "<br>" + str(i[1])) issue = ReportIssue() issue.setCategory(ExploitType.PERMISSION) issue.setDetails(common.config.get('qarkhelper', 'WR_FILE') + str(i[0]) + str(i[1])) issue.setFile(str(i[1])) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData(common.config.get('qarkhelper', 'WR_FILE') + str(i[0]) + str(i[1])) results.append(issue) # Check for world writable files file_ww = r'MODE_WORLD_WRITEABLE' for i in text_scan(common.java_files, file_ww): if len(i) > 0: report.write(IssueType.FileSystem, IssueSeverity.High, common.config.get('qarkhelper', 'WW_FILE') + str(i[0]) + "<br>" + str(i[1])) issue = ReportIssue() issue.setCategory(ExploitType.PERMISSION) issue.setDetails(common.config.get('qarkhelper', 'WW_FILE') + str(i[0]) + " in file: " + str(i[1])) issue.setFile(str(i[1])) issue.setSeverity(Severity.WARNING) results.append(issue) issue = terminalPrint() issue.setLevel(Severity.WARNING) issue.setData(common.config.get('qarkhelper', 'WW_FILE') + str(i[0]) + " in file: " + str(i[1])) results.append(issue) queue.put(results) ''' More checks from Android Lint to implement WorldReadableFiles ------------------ Summary: openFileOutput() call passing MODE_WORLD_READABLE Priority: 4 / 10 Severity: Warning Category: Security There are cases where it is appropriate for an application to write world readable files, but these should be reviewed carefully to ensure that they contain no private data that is leaked to other applications. WorldWriteableFiles ------------------- Summary: openFileOutput() call passing MODE_WORLD_WRITEABLE Priority: 4 / 10 Severity: Warning Category: Security There are cases where it is appropriate for an application to write world writeable files, but these should be reviewed carefully to ensure that they contain no private data, and that if the file is modified by a malicious application it does not trick or compromise your application. ''' return