反编译工具
Smali、Baksmali、ApkTool(基于 Smali 和 Baksmali)、dex2jar、jadx、jd-gui
java -jar ./apktool.jar d ./wechat.apk
回编译问题
AndResGuard 资源混淆,apkTool 无法回编译:修改资源名称(if、do等)
入口定位&逻辑跟踪
通过 Xposed 框架 hook 视图的 mOnClickListener 对象和 XLog 类的相关方法来定位入口逻辑,通过 Smali 动态调试确定执行流程,通过 jadx 回编译工具查看对应的 Java 逻辑
XposedHelpers.findAndHookMethod(View.class, "performClick", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Field mListenerInfoField = View.class.getDeclaredField("mListenerInfo");
mListenerInfoField.setAccessible(true);
Object mListenerInfo = mListenerInfoField.get(param.thisObject);
Field mOnClickListenerField = mListenerInfo.getClass().getDeclaredField("mOnClickListener");
mOnClickListenerField.setAccessible(true);
Object mOnClickListener = mOnClickListenerField.get(mListenerInfo);
Log.d("DebugTools","clicked "+param.thisObject+" / "+mOnClickListener);
}
});
XposedHelpers.findAndHookMethod(
"com.tencent.mars.xlog.Xlog",
classLoader,
"getLogLevel",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
param.setResult(0);
}
}
);
XposedHelpers.findAndHookMethod(
"com.tencent.mars.xlog.Xlog",
classLoader,
"setConsoleLogOpen",
boolean.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
param.args[0] = true;
}
}
);
Xposed 框架原理
Xposed 分为三个部分:Xposed-Installer、Xposed、XposedBridge.jar
Xposed-Installer 会将 app_process 拷贝到 /system/app/ 目录下,将 XposedBridge.jar 拷贝到 /data/data/package 下,初始化 /data/data/package/modules.list 和 enabled_modules.list
//通过meta-data声明Xposed模块,通过assets目录下的xposed-init文件声明入口类
for (PackageInfo pkg :
PackageManager.getInstalledPackage(PackageManager.GET_META_DATA)) {
ApplicationInfo app = pkg.applicationInfo;
if (app.metaData.containsKey("xposedmodule")) {
}
}
app_process 是 Android 的 zygote 进程,Android 系统所有进程都由 zygote 进程 fork 出来的
Zygote 执行流程
- 创建虚拟机实例
- 注册 Java 核心类及 JNI 方法
- 注册Android 核心类和JNI方法
- 创建 Socket 通讯接口
- 启动 Looper 循环监听 Socket 接口
- 启动 SystemServer 进程
替换后的 app_process 在原有基础上加载 xposed.so、XposedBridge.jar 并注册相关 Jni方 法,关键步骤是 Native Hook 了 ActivityThread 的 handleBindApplication 方法,在应用加载时会调用各个 Xposed 应用的入口方法
Smali 语法
数据类型(Z、J、[、L)、函数定义(函数名+方法签名)(foo(IILjava/lang/String;)Z
方法调用(invoke-static、invoke-virtual(protected或public函数)、invoke-interface、invoke-direct(private函数)、invoke-super)
绕过应用签名校验:安装正常微信,拿到签名字符串,替换应用校验逻辑
心跳上传
用于监控微信助手存活状态(微信版本、网络连接等)
应用僵死
public static synchronized boolean isGrantRootPermission() {
Process process = null;
DataOutputStream os = null;
try {
process = Runtime.getRuntime().exec("su");
os = new DataOutputStream(process.getOutputStream());
os.writeBytes("dumpsys deviceidle disable all\n");
os.writeBytes("settings put global low_power_trigger_level 0\n");
os.writeBytes("settings put global low_power 0\n");
os.writeBytes("exit\n");
os.flush();
process.waitFor();
} catch (Exception e) {
return false;
} finally {
try {
if (os != null) {
os.close();
}
process.destroy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
应用保活
监控功能需要应用始终存活,最终采用 android:persistent 和系统应用的解决方案
微信助手演进
一代助手通过辅助功能实现,效率低、稳定性差,只能使用微信已有功能
二代助手通过静态注入和Xposed框架实现,可以实现微信深度定制以及系统监控、应用保活、心跳上传(锁屏)等功能
封号问题
可以肯定的是微信获取了手机和应用的相关信息(是否安装了 Xposed 模块,是否是 root 环境,是否是二次打包的客户端),我们 hook 了 Java 层的相关代码(检测 root状态、检测是否安装了 xposed 框架)
//Throwable.getstackTrace()
public static boolean a(StackTraceElement[] stackTraceElementArr) {
for (StackTraceElement className : stackTraceElementArr) {
String className2 = className.getClassName();
if (className2 != null && className2.contains("de.robv.android.xposed.XposedBridge")) {
return true;
}
}
return false;
}
public static boolean cEl() {
try {
Object obj = System.getenv("PATH");
if (TextUtils.isEmpty(obj)) {
for (String file : obj.split(":")) {
File file2 = new File(file, "su");
if (file2.exists()) {
h.abG("SuFile found : " + file2.toString());
return true;
}
}
} else if (new File("/system/bin/su").exists()) {
h.abG("SuFile found : /system/bin/su");
return true;
} else if (new File("/system/xbin/su").exists()) {
h.abG("SuFile found : /system/xbin/su");
return true;
}
} catch (Throwable th) {
h.k(th);
}
h.abG("SuFile not found");
return false;
}