本次教程核心思路为:通过Smali语法层面的替换操作,使解密工具在检测时产生误判,从而规避对加密算法的识别。该方法具备普适性,可针对所有字符串解密检测场景(涵盖一键字符串解密、注入日志记录等)。
另一种方法则针对MT管理器设计(NP管理器亦可适用,但需对加密逻辑进行适应性调整)。
工具
MT管理器(看版本号选最新版本)
NP管理器(看版本号选最新版本)
教程
由于绕过MT管理器的「字符串一键解密」与NP管理器的过检测机制逻辑高度相似,因此合并讲解。
核心功能:通杀字符串一键解密(双工具通用方案)
(1)构造函数调用方案
在字符串加密的后续处理中,通过构造函数调用替代传统字符串定义方式,可直接绕过检测工具的规则匹配。
核心原理:将 const-string 指令替换为通过构造函数创建字符串,利用检测工具对 new-instance 指令的误判实现通杀。
操作示例:
传统写法: String str = "测试字符串"; (对应 const-string 指令,易被检测)
替换后: new String("测试字符串"); (对应 new-instance 指令,混淆检测逻辑
优势:相比「重定义输入流」等方案,无需复杂逻辑,直接修改字符串定义方式即可生效。
对应的 smali 代码:
# 原始的 Smali 代码
const-string v1, "测试字符串"
# 修改后的 Smali 代码
new-instance v0, Ljava/lang/String;
const-string v1, "测试字符串"
invoke-direct {v0, v1}, Ljava/lang/String;-><init>(Ljava/lang/String;)V
invoke-direct {v0, v1}, Лава/lang/String;-><init>(Лава/lang/String;)
需注意的是,寄存器临时变量的生成逻辑需自行编写。通过此方式,字符串不再以 const-string 形式直接出现,使得字符串解密工具在提取时会忽略该段代码。
(2)重定义输出流方案
该方案通过重定义输出流逻辑提升解密难度,进一步规避检测工具的规则匹配。
核心思路:利用 char 类型动态拼接生成 String ,通过非静态、非直接的字符串构造方式干扰检测。
实现逻辑:
拆解字符串为字符数组或逐字符生成,例如:
char[] chars = {'测', '试', '字', '符', '串'};
String dynamicStr = new String(chars);
结合循环、条件语句等动态逻辑合成字符串,避免静态字符串常量暴露。
效果:检测工具因无法识别静态字符串模式,直接跳过该段代码,实现深度过检。
# 定义一个字符数组
const/4 v0, 0x0
new-array v0, v0, [C
const/16 v1, 0x0
const/16 v3, 0x41
aput-char v3, v0, v1
const/16 v1, 0x1
const/16 v3, 0x42
aput-char v3, v0, v1
const/16 v1, 0x2
const/16 v3, 0x43
aput-char v3, v0, v1
# 将字符数组转换为字符串
new-instance v1, Ljava/lang/String;
invoke-direct {v1, v0}, Ljava/lang/String;-><init>([C)V
invoke-direct {v1, v0}, Ljava/lang/String;-><init>([C)V
Java代码示例对应的 Java 代码如下:
char[] chars = new char[3];
chars[0] = 'A';
chars[1] = 'B';
chars[2] = 'C';
String obfuscatedString = new String(chars);
String obfuscatedString = new String(chars);
通过这种方式不仅可通过字符串解密工具的检测,还能提升解密难度。鉴于重定义输入流属于相对主流的方法,网上相关讲解颇为丰富,此处便不再赘述。上述两种简易防御方案经实践验证,其思路可有效绕过Mt、NP的字符串检测。
功能说明
1. MT注入日志记录
2. NP log字符串解密(完美通杀)
先说完美通杀NP管理器的方法,在MT管理器未启用自定义解密功能的场景下,可采用以下思路实现完美通杀:
NP管理器的默认打印字符串方案与MT管理器的默认方案原理一致,均能通过 new-instance 调用解密方法来绕过字符串检测。具体操作是将解密工具类调整为先创建解密类实例、再执行解密操作的模式,此方式可完美通杀NP。需注意的是,需同步修改解密类的标识符,该方案可有效应对默认方案下的NP管理器及MT注入日志记录的检测。
原始调用解密方法方案:
const-string %s, "混淆字符串"
invoke-static {%s}, Lcom/example/application/sm/ScatterDecoderV4;->decrypt(Ljava/lang/String;)Ljava/lang/String;
move-result-object %s 移动结果对象 %s
创建解密类的实例方案:
new-instance p1, Llog/defense/example/ScatterDecoderV4;
新实例 p1,Llog/defense/example/ScatterDecoderV4;
invoke-direct {p1}, Llog/defense/example/ScatterDecoderV4;-><init>()V
invoke-direct {p1}, Llog/defense/example/ScatterDecoderV4;-><init>()V
const-string %s, "混淆字符串"
invoke-virtual {p1, %s}, Llog/defense/example/ScatterDecoderV4;->decrypt(Ljava/lang/String;)Ljava/lang/String;
move-result-object p1 移动结果对象 P1
这种思路足以完美应对 NP 和 MT 的默认方案,毕竟 MT 的自定义方案本身支持用户自定义逻辑,自然能绕过检测。不过,即便如此,仍有诸多应对手段。考虑到时间限制,这里仅讲解最简单的方案。
首先,先看一下这个 Java 例子:
原始 Java 代码
package log.defense.example;
import android.app.Activity;
import android.app.活动;
import android.os.Bundle;
导入 android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(0x7f030000);
FileAppender.appendToFile("/storage/emulated/0/a.txt", log_defense.detect("a"));
Toast.makeText(getApplication(), new StringBuffer().append(log_defense.detect("1=")).append(log_defense.callCount).toString(), 0).show();
Toast.makeText(getApplication(), new StringBuffer().append(log_defense.detect(“1=”)).append(log_defense.callCount).toString(), 0).show();
}
}
注入日志记录后:
package log.defense.example;
import android.app.Activity;
import android.app.活动;
import android.os.Bundle;
导入 android.os.Bundle;
import android.widget.Toast;
import mt.Log2F8B04;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(2130903040);
String str = "a";
str = log_defense.detect(str);
Log2F8B04.a(str); // 注意
Toast.makeText(getApplication(), str, 0).show();
}
}
注意这个 Log2F8B04.a 方法,它直接对 log_defense.detect(str); 进行调用,我的思路是这样的:首先,每次 Log 打印字符串时都是第一次调用,原始的逻辑都是第二次调用,我们可以写个这样的逻辑:
public class log_defense {
// 静态变量,用于创建类实例
static int callCount = 0;
静态 int callCount = 0;
// 将 detect 方法声明为静态方法
public static String detect(String a) {
public static 字符串检测(String a) {
callCount++;
if (callCount == 1) {
return "log 打印输出的返回";
} else if (callCount > 1) {
callCount = 0;
return "这里是调用解密方法:" + a;
}
return null;
}
}
为确保程序在未注入日志记录时仍可正常运行,确定优先级顺序为:mtLog > 充数空调用 > 原始调用逻辑。需说明的是,“充数空调用”旨在确保原始调用的callCount始终大于1,以此在保障软件正常运行的同时,实现对Log日志记录的拦截。
整个思路大致就是这样。
此次教程的原作者:YuriNao