Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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',
                        bar='Pending Intents',
                        percent=round(count * 100 /
                                      common.java_files.__len__()))
        current_file = j
        try:
            tree = parser.parse_file(j)
        except ValueError as e:
            continue
        #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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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', bar='Pending Intents', percent=round(count*100/common.java_files.__len__()))
		current_file=j
		try:
			tree=parser.parse_file(j)
		except ValueError as e:
			continue
		#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
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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))
Ejemplo n.º 7
0
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 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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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:
										parse_args(current.arguments[2],results)
									except Exception as e:
										report.write("parsingerror-issues-list", str(current_file), "strong")
										common.logger.debug("Problem in parse_args 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:
								parse_args(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
Ejemplo n.º 11
0
def find_intent(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 find_intent - 1")
											elif type(getattr(t,f)) is m.MethodInvocation:
												common.logger.debug("ERROR: UNEXPECTED CODE PATH in find_intent - 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
Ejemplo n.º 12
0
def parse_args(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 find_class(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 parse_args")
						except Exception as e:
							report.write("parsingerror-issues-list", str(current_file), "strong")
							common.logger.debug("Problem in find_class 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:
				find_intent(arg.value,results)
			except Exception as e:
				report.write("parsingerror-issues-list", str(current_file), "strong")
				common.logger.debug("Problem in find_intent 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
Ejemplo n.º 13
0
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:
                                        parse_args(current.arguments[2],
                                                   results)
                                    except Exception as e:
                                        report.write(
                                            "parsingerror-issues-list",
                                            str(current_file), "strong")
                                        common.logger.debug(
                                            "Problem in parse_args 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:
                                parse_args(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
Ejemplo n.º 14
0
def find_intent(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 find_intent - 1"
                                                            )
                                            elif type(getattr(
                                                    t,
                                                    f)) is m.MethodInvocation:
                                                common.logger.debug(
                                                    "ERROR: UNEXPECTED CODE PATH in find_intent - 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
Ejemplo n.º 15
0
def parse_args(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 find_class(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 parse_args"
                                )
                        except Exception as e:
                            report.write("parsingerror-issues-list",
                                         str(current_file), "strong")
                            common.logger.debug(
                                "Problem in find_class 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:
                find_intent(arg.value, results)
            except Exception as e:
                report.write("parsingerror-issues-list", str(current_file),
                             "strong")
                common.logger.debug(
                    "Problem in find_intent 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