Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Core/dexkit/dex_item_batch_find.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ DexItem::BatchFindClassUsingStrings(
std::map<std::string_view, std::vector<uint32_t>> 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;
Expand Down Expand Up @@ -165,15 +165,15 @@ DexItem::BatchFindMethodUsingStrings(
std::map<std::string_view, std::vector<uint32_t>> 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;
if (query->search_packages() && !(hit >> 1)) continue;
}

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;

Expand Down
23 changes: 14 additions & 9 deletions Core/dexkit/dex_item_matcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "matcher_thread_cache_registry.h"
#include "utils/dex_descriptor_util.h"

#include <algorithm>
#include <mutex>

namespace dexkit {
Expand Down Expand Up @@ -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;
Expand Down
97 changes: 97 additions & 0 deletions dexkit/src/test/java/org/luckypray/dexkit/UnitTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ->
Expand Down Expand Up @@ -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() })
}
}
}
Loading