diff --git a/src/main/kotlin/net/bytedance/security/app/MethodFinder.kt b/src/main/kotlin/net/bytedance/security/app/MethodFinder.kt index 0798c46..5d86cfe 100644 --- a/src/main/kotlin/net/bytedance/security/app/MethodFinder.kt +++ b/src/main/kotlin/net/bytedance/security/app/MethodFinder.kt @@ -84,14 +84,8 @@ object MethodFinder { } private fun filterByClassName(className: String): Collection { - val results = ArrayList() //to avoid java.util.ConcurrentModificationException - for (c in PLUtils.classes) { - if (isMatched(className, c.name)) { - results.add(c) - } - } - return results + return PLUtils.classes.filter { isMatched(className, it.name) } } /** @@ -105,10 +99,9 @@ object MethodFinder { private fun checkAndParseMethodSigInternal(methodSig: String): Set { val matchedMethodSet: MutableSet = HashSet() val fd = newFunctionSignature(methodSig) - if (!fd.className.contains("*") && !fd.functionName.contains("*") && !fd.args.contains("*") && !fd.returnType.contains( - "*" - ) - ) { + if (!fd.className.contains("*") && !fd.functionName.contains("*") && + !fd.args.contains("*") && !fd.returnType.contains("*")) + { val sc = Scene.v().getSootClassUnsafe(fd.className, false) val sm = Scene.v().grabMethod(methodSig) if (sc != null && sm != null) { @@ -122,45 +115,35 @@ object MethodFinder { } } } - return matchedMethodSet } + val targetClassSet = getClassesByName(fd.className) val possibleMethodSigSet: MutableSet = HashSet() - for (sc in targetClassSet) { - var methods: List - if (sc.name.startsWith(PLUtils.CUSTOM_CLASS)) { - continue - } else { - methods = sc.methods + targetClassSet.filterNot { it.name.startsWith(PLUtils.CUSTOM_CLASS) }.forEach { sc -> + val methods = sc.methods + + fun processMethod(method: SootMethod, predicate: (SootMethod) -> Boolean) { + if (predicate(method)) { + matchedMethodSet.add(method) + addMatchedMethodSet(possibleMethodSigSet, method) + } } + if (fd.functionName.contains("*") || fd.args.contains("*") || fd.returnType.contains("*")) { - if (fd.functionName == "*") { - methods.forEach { - matchedMethodSet.add(it) - addMatchedMethodSet(possibleMethodSigSet, it) - } - } else { - for (sm in methods) { - if (isMatched(fd.functionName, sm.name)) { - matchedMethodSet.add(sm) - addMatchedMethodSet(possibleMethodSigSet, sm) - } + methods.forEach { method -> + if (fd.functionName == "*") { + processMethod(method) { true } + } else { + processMethod(method) { isMatched(fd.functionName, it.name) } } } } else { - for (sm in methods) { - if (fd.subSignature() == sm.subSignature) { - matchedMethodSet.add(sm) - addMatchedMethodSet(possibleMethodSigSet, sm) - } - } + methods.forEach { processMethod(it) { fd.subSignature() == it.subSignature } } } } - for (otherSig in possibleMethodSigSet) { - matchedMethodSet.add(otherSig) - } + matchedMethodSet.addAll(possibleMethodSigSet) Log.logDebug(methodSig + " Parsed " + matchedMethodSet.size) return matchedMethodSet } @@ -179,6 +162,7 @@ object MethodFinder { val start = System.currentTimeMillis() val s = checkAndParseMethodSigInternal(methodSig) profiler.checkAndParseMethodSigInternalTake(System.currentTimeMillis() - start) + MethodSignatureCache[methodSig] = s return s } @@ -237,15 +221,10 @@ object MethodFinder { * pattern matching based on class name */ private fun getClassesByName(className: String): Collection { - return if (className == "*") { - PLUtils.classes - } else { - if (className.indexOf("*") >= 0) { - return filterByClassName(className) - } else { - val sc = Scene.v().getSootClassUnsafe(className, false) ?: return setOf() - return setOf(sc) - } + return when { + className == "*" -> PLUtils.classes + className.contains("*") -> filterByClassName(className) + else -> Scene.v().getSootClassUnsafe(className, false)?.let { setOf(it) } ?: setOf() } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/bytedance/security/app/PreAnalyzeContext.kt b/src/main/kotlin/net/bytedance/security/app/PreAnalyzeContext.kt index 0d38286..b92369d 100644 --- a/src/main/kotlin/net/bytedance/security/app/PreAnalyzeContext.kt +++ b/src/main/kotlin/net/bytedance/security/app/PreAnalyzeContext.kt @@ -178,26 +178,17 @@ open class PreAnalyzeContext { } fun findInstantCallSiteWithSubclass(className: String): Set { - val s = HashSet() - for (sc in PLUtils.classes) { - if (sc.name == className || sc.hasSuperclass() && className == sc.superclass.name) { - s.addAll(findInstantCallSite(sc)) - } - } - return s + return PLUtils.classes.filter { it.name == className + || (it.hasSuperclass() && className == it.superclass.name) } + .flatMap { findInstantCallSite(it) }.toSet() } - /** * field load callsites */ fun findFieldCallSite(field: String): Set { - val fields = MethodFinder.checkAndParseFieldSignature(field) - val results = HashSet() - for (f in fields) { - results.addAll(findFieldCallSite(f)) - } - return results + return MethodFinder.checkAndParseFieldSignature(field) + .flatMap { findFieldCallSite(it) }.toSet() } /** @@ -206,4 +197,4 @@ open class PreAnalyzeContext { fun findFieldCallSite(field: SootField): Set { return this.loadFieldRefs[field] ?: setOf() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/bytedance/security/app/RuleData.kt b/src/main/kotlin/net/bytedance/security/app/RuleData.kt index 3467dbf..9087310 100644 --- a/src/main/kotlin/net/bytedance/security/app/RuleData.kt +++ b/src/main/kotlin/net/bytedance/security/app/RuleData.kt @@ -96,6 +96,7 @@ data class RuleData( val ManifestCheckMode: Boolean? = null, val APIMode: Boolean? = null, + val APIPermission: String? = null, val InternalMediaLocation: Boolean? = null, diff --git a/src/main/kotlin/net/bytedance/security/app/StaticAnalyzeMain.kt b/src/main/kotlin/net/bytedance/security/app/StaticAnalyzeMain.kt index a0d6974..391d19c 100644 --- a/src/main/kotlin/net/bytedance/security/app/StaticAnalyzeMain.kt +++ b/src/main/kotlin/net/bytedance/security/app/StaticAnalyzeMain.kt @@ -82,7 +82,7 @@ object StaticAnalyzeMain { @Throws(Exception::class) fun main(args: Array) { if (args.isEmpty()) { - println("Usage: java -jar appshark.jar config.json5") + println("Usage: java -jar appshark.jar config.json5") return } val configPath = args[0] @@ -98,6 +98,7 @@ fun main(args: Array) { EngineConfig.libraryConfig.setPackage(it) } } + Log.logDebug(Json.encodeToString(argumentConfig)) runBlocking { StaticAnalyzeMain.startAnalyze(argumentConfig) } } catch (e: Exception) { e.printStackTrace() diff --git a/src/main/kotlin/net/bytedance/security/app/result/OutputSecResults.kt b/src/main/kotlin/net/bytedance/security/app/result/OutputSecResults.kt index c8cdd52..5485b34 100644 --- a/src/main/kotlin/net/bytedance/security/app/result/OutputSecResults.kt +++ b/src/main/kotlin/net/bytedance/security/app/result/OutputSecResults.kt @@ -35,14 +35,14 @@ import kotlin.system.exitProcess @Serializable class Results { var AppInfo: AppInfo? = null - var ManifestRisk: ManifestRisk? = null + var ManifestRisk = ManifestRisk() var SecurityInfo: MutableMap> = HashMap() var ComplianceInfo: MutableMap> = HashMap() var DeepLinkInfo: MutableMap>? = null var HTTP_API: MutableList? = null var JsBridgeInfo: MutableList? = null var BasicInfo: BasicInfo? = null - var UsePermissions: Set? = null + var UsePermissions: MutableMap = mutableMapOf() var DefinePermissions: Map? = null var Profile: String? = null } @@ -58,18 +58,17 @@ object OutputSecResults { private var BasicInfo = BasicInfo() private var DeepLinkInfo: MutableMap> = HashMap() var AppInfo = AppInfo() - var ManifestRisk = ManifestRisk() var APIList: MutableList = ArrayList() var JsBridgeList: MutableList = ArrayList() var JSList: MutableList = ArrayList() private var vulnerabilityItems = ArrayList() + var ApiPermissionMapping: MutableMap> = mutableMapOf() fun init() { AppInfo.appsharkTakeTime = profiler.totalRange.takes AppInfo.classCount = profiler.ProcessMethodStatistics.availableClasses AppInfo.methodCount = profiler.ProcessMethodStatistics.availableMethods Results.AppInfo = AppInfo - Results.ManifestRisk = ManifestRisk Results.DeepLinkInfo = DeepLinkInfo Results.HTTP_API = APIList Results.JsBridgeInfo = JsBridgeList @@ -99,13 +98,19 @@ object OutputSecResults { } private fun insertMani() { - ManifestRisk.debuggable = AndroidUtils.debuggable - ManifestRisk.allowBackup = AndroidUtils.allowBackup - ManifestRisk.usesCleartextTraffic = AndroidUtils.usesCleartextTraffic + Results.ManifestRisk.debuggable = AndroidUtils.debuggable + Results.ManifestRisk.allowBackup = AndroidUtils.allowBackup + Results.ManifestRisk.usesCleartextTraffic = AndroidUtils.usesCleartextTraffic } private fun insertPerm() { - Results.UsePermissions = AndroidUtils.usePermissionSet + AndroidUtils.usePermissionSet.forEach { + Results.UsePermissions[it] = when { + this.ApiPermissionMapping.contains(it) && this.ApiPermissionMapping[it]!!.isNotEmpty() -> "used" + this.ApiPermissionMapping.contains(it) -> "unused" + else -> "unknown" + } + } Results.DefinePermissions = AndroidUtils.permissionMap } diff --git a/src/main/kotlin/net/bytedance/security/app/ruleprocessor/ApiModeProcessor.kt b/src/main/kotlin/net/bytedance/security/app/ruleprocessor/ApiModeProcessor.kt index 53432aa..c470974 100644 --- a/src/main/kotlin/net/bytedance/security/app/ruleprocessor/ApiModeProcessor.kt +++ b/src/main/kotlin/net/bytedance/security/app/ruleprocessor/ApiModeProcessor.kt @@ -38,37 +38,50 @@ class ApiModeProcessor(val ctx: PreAnalyzeContext) : IRuleProcessor { if (rule !is ApiModeRule) { return } - val sinkObject = rule.sink - for (sinkRuleSig in sinkObject.keys) { - if (sinkRuleSig.isMethodSignature()) { - val methodSigSet = MethodFinder.checkAndParseMethodSig(sinkRuleSig) - methodSigSet.forEach { - val callMap = ctx.findInvokeCallSite(it) - apiModeToHtml(rule, callMap) - } - } else if (sinkRuleSig.isFieldSignature()) { - val callMap = ctx.findFieldCallSite( - sinkRuleSig - ) - apiModeToHtml(rule, callMap) - } else { - val matchedClasses = PLUtils.findMatchedChildClasses(setOf(sinkRuleSig)) - val sinkClass = Scene.v().getSootClassUnsafe(sinkRuleSig, false) - if (sinkClass != null) { - matchedClasses.add(sinkClass) + if (rule.apiPermission.isNotEmpty()) { + val callSet = mutableSetOf() + + rule.sink.keys.forEach { sinkRuleSig -> + if (sinkRuleSig.isMethodSignature()) { + val methodSigSet = MethodFinder.checkAndParseMethodSig(sinkRuleSig) + callSet.addAll(methodSigSet.map { it.signature }) + } else if (sinkRuleSig.isFieldSignature()) { + val fieldSigSet = MethodFinder.checkAndParseFieldSignature(sinkRuleSig) + callSet.addAll(fieldSigSet.map { it.signature }) } - for (sc in matchedClasses) { - val callMap = ctx.findInstantCallSite(sc.name) - apiModeToHtml(rule, callMap) + } + apiPermissionToResults(rule.apiPermission, callSet) + + } else { + rule.sink.keys.forEach { sinkRuleSig -> + val callMap = when { + sinkRuleSig.isMethodSignature() -> { + val methodSigSet = MethodFinder.checkAndParseMethodSig(sinkRuleSig) + methodSigSet.map { ctx.findInvokeCallSite(it) } + } + + sinkRuleSig.isFieldSignature() -> listOf(ctx.findFieldCallSite(sinkRuleSig)) + + else -> { + val matchedClasses = PLUtils.findMatchedChildClasses(setOf(sinkRuleSig)) + Scene.v().getSootClassUnsafe(sinkRuleSig, false)?.let { matchedClasses.add(it) } + matchedClasses.map { ctx.findInstantCallSite(it.name) } + } } + callMap.forEach { apiModeToHtml(rule, it) } } } } private suspend fun apiModeToHtml(rule: IRule, callMap: Set) { for (site in callMap) { - APIModeHtmlWriter(OutputSecResults, rule, site.method, site.stmt).addVulnerabilityAndSaveResultToOutput() + APIModeHtmlWriter(OutputSecResults, rule, site.method, site.stmt) + .addVulnerabilityAndSaveResultToOutput() } } + + private fun apiPermissionToResults(apiPermission: String, callSet: Set) { + OutputSecResults.ApiPermissionMapping[apiPermission] = callSet + } } diff --git a/src/main/kotlin/net/bytedance/security/app/rules/ApiModeRule.kt b/src/main/kotlin/net/bytedance/security/app/rules/ApiModeRule.kt index 978e4a3..9b6bd4b 100644 --- a/src/main/kotlin/net/bytedance/security/app/rules/ApiModeRule.kt +++ b/src/main/kotlin/net/bytedance/security/app/rules/ApiModeRule.kt @@ -23,8 +23,10 @@ import net.bytedance.security.app.SinkBody class ApiModeRule(name: String, ruleData: RuleData) : AbstractRule(name, ruleData) { override val mode: String = "APIMode" val sink: Map + val apiPermission: String init { sink = ruleData.sink!! + apiPermission = ruleData.APIPermission ?: "" } } \ No newline at end of file