diff --git a/Core/dexkit/dex_item_batch_find.cpp b/Core/dexkit/dex_item_batch_find.cpp index 15453325..e3a7ffda 100644 --- a/Core/dexkit/dex_item_batch_find.cpp +++ b/Core/dexkit/dex_item_batch_find.cpp @@ -65,7 +65,7 @@ DexItem::BatchFindClassUsingStrings( std::map> find_result; for (int type_idx = 0; type_idx < this->type_names.size(); ++type_idx) { if (class_method_ids[type_idx].empty()) continue; - if (query->in_classes() && in_class_set.contains(type_idx)) continue; + if (query->in_classes() && !in_class_set.contains(type_idx)) continue; if (query->search_packages() || query->exclude_packages()) { auto hit = packageTrie.search(this->type_names[type_idx], query->ignore_packages_case()); if (query->exclude_packages() && (hit & 1)) continue; @@ -165,7 +165,7 @@ DexItem::BatchFindMethodUsingStrings( std::map> find_result; for (int type_idx = 0; type_idx < this->type_names.size(); ++type_idx) { if (class_method_ids[type_idx].empty()) continue; - if (query->in_classes() && in_class_set.contains(type_idx)) continue; + if (query->in_classes() && !in_class_set.contains(type_idx)) continue; if (query->search_packages() || query->exclude_packages()) { auto hit = packageTrie.search(this->type_names[type_idx], query->ignore_packages_case()); if (query->exclude_packages() && (hit & 1)) continue; @@ -173,7 +173,7 @@ DexItem::BatchFindMethodUsingStrings( } for (auto method_idx: class_method_ids[type_idx]) { - if (query->in_methods() && in_method_set.contains(method_idx)) continue; + if (query->in_methods() && !in_method_set.contains(method_idx)) continue; auto code = this->method_codes[method_idx]; if (code == nullptr) continue; diff --git a/Core/dexkit/dex_item_matcher.cpp b/Core/dexkit/dex_item_matcher.cpp index bb00262c..deb50382 100644 --- a/Core/dexkit/dex_item_matcher.cpp +++ b/Core/dexkit/dex_item_matcher.cpp @@ -22,6 +22,7 @@ #include "matcher_thread_cache_registry.h" #include "utils/dex_descriptor_util.h" +#include #include namespace dexkit { @@ -1494,16 +1495,20 @@ bool DexItem::IsOpCodesMatched(uint32_t method_idx, const schema::OpCodesMatcher } if (!matcher_opcodes.empty()) { - auto index = kmp::FindIndex(opt_opcodes.value(), matcher_opcodes); - if (index == -1) { - return false; - } + auto &opcodes = opt_opcodes.value(); bool condition = false; - switch (matcher->match_type()) { - case schema::OpCodeMatchType::Equal: condition = index == 0 && matcher_opcodes.size() == op_code_size; break; - case schema::OpCodeMatchType::StartWith: condition = index == 0; break; - case schema::OpCodeMatchType::EndWith: condition = index + matcher_opcodes.size() == op_code_size; break; - case schema::OpCodeMatchType::Contains: condition = true; break; + if (matcher->match_type() == schema::OpCodeMatchType::EndWith) { + // kmp::FindIndex returns the first occurrence, which is not + // necessarily the tail; compare the tail directly. + condition = std::equal(matcher_opcodes.begin(), matcher_opcodes.end(), opcodes.end() - matcher_opcodes.size()); + } else { + auto index = kmp::FindIndex(opcodes, matcher_opcodes); + switch (matcher->match_type()) { + case schema::OpCodeMatchType::Equal: condition = index == 0 && matcher_opcodes.size() == op_code_size; break; + case schema::OpCodeMatchType::StartWith: condition = index == 0; break; + case schema::OpCodeMatchType::Contains: condition = index != -1; break; + default: break; + } } if (!condition) { return false; diff --git a/dexkit/src/test/java/org/luckypray/dexkit/UnitTest.kt b/dexkit/src/test/java/org/luckypray/dexkit/UnitTest.kt index ebd4e9eb..b3244548 100644 --- a/dexkit/src/test/java/org/luckypray/dexkit/UnitTest.kt +++ b/dexkit/src/test/java/org/luckypray/dexkit/UnitTest.kt @@ -2,6 +2,7 @@ package org.luckypray.dexkit import org.junit.Test import org.luckypray.dexkit.annotations.DexKitExperimentalApi +import org.luckypray.dexkit.query.enums.OpCodeMatchType import org.luckypray.dexkit.query.enums.StringMatchType import org.luckypray.dexkit.query.enums.UsingType import java.io.File @@ -669,6 +670,80 @@ class UnitTest { } + @Test + fun testBatchFindClassUsingStringsSearchIn() { + val groupName = "SearchGroup" + val playActivity = bridge.getClassData("org.luckypray.dexkit.demo.PlayActivity")!! + val mainActivity = bridge.getClassData("org.luckypray.dexkit.demo.MainActivity")!! + + val baseline = bridge.batchFindClassUsingStrings { + addSearchGroup(groupName, listOf("PlayActivity"), StringMatchType.Contains, true) + } + assert(baseline[groupName]!!.map { it.descriptor } == listOf(playActivity.descriptor)) + + val inSelf = bridge.batchFindClassUsingStrings { + searchIn(listOf(playActivity)) + addSearchGroup(groupName, listOf("PlayActivity"), StringMatchType.Contains, true) + } + assert(inSelf[groupName]!!.map { it.descriptor } == listOf(playActivity.descriptor)) + + val inOther = bridge.batchFindClassUsingStrings { + searchIn(listOf(mainActivity)) + addSearchGroup(groupName, listOf("PlayActivity"), StringMatchType.Contains, true) + } + assert(inOther[groupName]!!.isEmpty()) + } + + @Test + fun testBatchFindMethodUsingStringsSearchInScope() { + val groupName = "SearchGroup" + val groups = mapOf(groupName to listOf("getRandomDice: ")) + val randomUtil = bridge.getClassData("org.luckypray.dexkit.demo.RandomUtil")!! + val mainActivity = bridge.getClassData("org.luckypray.dexkit.demo.MainActivity")!! + // demo release apk is minified: method names are obfuscated, locate by used string + val hit = bridge.findMethod { + excludePackages("org.luckypray.dexkit.demo.hook") + matcher { + declaredClass("org.luckypray.dexkit.demo.RandomUtil") + usingStrings("getRandomDice: ") + } + }.single() + val unrelated = mainActivity.methods.single { it.name == "onCreate" } + + // no scope, the hook package also uses this literal, so exclude it + val baseline = bridge.batchFindMethodUsingStrings { + excludePackages("org.luckypray.dexkit.demo.hook") + groups(groups) + } + assert(baseline[groupName]!!.map { it.descriptor } == listOf(hit.descriptor)) + + // in_classes + val inDeclaringClass = bridge.batchFindMethodUsingStrings { + searchInClasses(listOf(randomUtil)) + groups(groups) + } + assert(inDeclaringClass[groupName]!!.map { it.descriptor } == listOf(hit.descriptor)) + + val inOtherClass = bridge.batchFindMethodUsingStrings { + searchInClasses(listOf(mainActivity)) + groups(groups) + } + assert(inOtherClass[groupName]!!.isEmpty()) + + // in_methods + val inHitMethod = bridge.batchFindMethodUsingStrings { + searchInMethods(listOf(hit)) + groups(groups) + } + assert(inHitMethod[groupName]!!.map { it.descriptor } == listOf(hit.descriptor)) + + val inUnrelatedMethod = bridge.batchFindMethodUsingStrings { + searchInMethods(listOf(unrelated)) + groups(groups) + } + assert(inUnrelatedMethod[groupName]!!.isEmpty()) + } + @Test fun testConcurrentBatchFindClassUsingStringsOnSharedBridge() { DexKitBridge.create(demoApkPath).use { parallelBridge -> @@ -848,4 +923,26 @@ class UnitTest { } } } + + @Test + fun testOpCodesEndsWith() { + // A method whose last opcode also occurs earlier in the sequence is + // the regression case: EndsWith must anchor at the tail, not at the + // first occurrence found when scanning from the start. + val method = bridge.findMethod { + searchPackages("org.luckypray.dexkit.demo") + }.first { + val ops = it.opCodes + ops.size >= 2 && ops.indexOf(ops.last()) != ops.size - 1 + } + val opCodes = method.opCodes + for (len in 1..opCodes.size) { + val res = bridge.findMethod { + matcher { + opCodes(opCodes.takeLast(len), OpCodeMatchType.EndsWith) + } + } + assert(res.any { it.getEncodeId() == method.getEncodeId() }) + } + } }