为Clash for Android内置配置

zip目录结构

f.zip/
├── databases/*
├── files/*
├── shared_prefs/*

方法一:编写模块

思路:写入url配置后打包文件,将zip硬编码base64预置xposed模块String常量,hook主Activity的启动并释放文件到data目录,顺便写个弹窗装备,最后lspatch打包:

package com.sunight.rejectconfig;


import android.app.AlertDialog;
import android.content.Context;
import android.util.Base64;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class HookInit implements IXposedHookLoadPackage {

    private static final String TARGET_ACTIVITY = "com.github.kr328.clash.MainActivity";

    private static final String TARGET_PACKAGE = "com.github.metacubex.clash.meta";

    private static final String TARGET_PARENT_DIR = "/data/user/0/" + TARGET_PACKAGE + "/";

    private static final String BASE64_ZIP = "自行生成";
    @Override
    public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
        if (!lpparam.packageName.equals(TARGET_PACKAGE)) {
            return;
        }

        XposedBridge.log("目标应用已加载 - " + lpparam.packageName);

        // 直接Hook主Activity的onCreate方法
        XposedHelpers.findAndHookMethod(
            TARGET_ACTIVITY,
            lpparam.classLoader,
            "onCreate",
            android.os.Bundle.class,
            new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    extractZipToParentDir();
                    Context context = (Context) param.thisObject;
                    // 在主线程显示对话框
                    new AlertDialog.Builder(context)
                        .setTitle("嘻嘻")
                        .setMessage("Url配置文件已释放")
                        .setPositiveButton("我知道了", null)
                        .show();
                }
            });

    } 

    private void extractZipToParentDir() {
        try {
            // 解码Base64字符串
            byte[] zipBytes = Base64.decode(BASE64_ZIP, Base64.DEFAULT);

            // 创建目标目录
            File targetDir = new File(TARGET_PARENT_DIR);
            if (!targetDir.exists()) {
                targetDir.mkdirs();
            }

            // 解压ZIP文件
            extractZip(new ByteArrayInputStream(zipBytes), targetDir);

            XposedBridge.log("成功解压ZIP文件到 - " + TARGET_PARENT_DIR);

            // 设置目录权限
            setFilePermissions(targetDir);

        } catch (Exception e) {
            XposedBridge.log("解压失败 - " + e.getMessage());
        }
    }

    private void extractZip(ByteArrayInputStream bais, File targetDir) throws Exception {
        ZipInputStream zis = null;
        try {
            zis = new ZipInputStream(bais);
            ZipEntry entry;

            while ((entry = zis.getNextEntry()) != null) {
                String entryName = entry.getName();
                File entryFile = new File(targetDir, entryName);

                if (entry.isDirectory()) {
                    entryFile.mkdirs();
                } else {
                    // 确保父目录存在
                    entryFile.getParentFile().mkdirs();

                    // 写入文件内容
                    FileOutputStream fos = null;
                    try {
                        fos = new FileOutputStream(entryFile);
                        byte[] buffer = new byte[1024];
                        int length;
                        while ((length = zis.read(buffer)) > 0) {
                            fos.write(buffer, 0, length);
                        }
                    } finally {
                        if (fos != null) {
                            fos.close();
                        }
                    }
                }
                zis.closeEntry();
            }
        } finally {
            if (zis != null) {
                zis.close();
            }
        }
    }

    private void setFilePermissions(File file) {
        try {
            // 递归设置目录权限为755 (rwxr-xr-x)
            Runtime.getRuntime().exec("chmod -R 755 " + file.getAbsolutePath()).waitFor();

            // 递归设置文件所有者为目标应用
            String command = "chown -R " + TARGET_PACKAGE + ":" + TARGET_PACKAGE + " " + file.getAbsolutePath();
            Runtime.getRuntime().exec(command).waitFor();

            XposedBridge.log("已设置权限 - " + file.getAbsolutePath());
        } catch (Exception e) {
            XposedBridge.log("设置权限失败 - " + e.getMessage());
        }
    }
}

效果图:

方法二:Smali注入

思路:与方法一相同,解决Lspatch不适配安卓9以下的问题

进入com.github.kr328.clash.MainActivity

新增以下smali代码

.field private static final BASE64_ZIP:Ljava/lang/String; = "自行生成"

.field private static final TARGET_ACTIVITY:Ljava/lang/String; = "com.github.kr328.clash.MainActivity"

.field private static final TARGET_PACKAGE:Ljava/lang/String; = "com.github.metacubex.clash.meta"

.field private static final TARGET_PARENT_DIR:Ljava/lang/String; = "/data/user/0/com.github.metacubex.clash.meta/"

.method private extractZip(Ljava/io/ByteArrayInputStream;Ljava/io/File;)V
    .registers 8
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "(",
            "Ljava/io/ByteArrayInputStream;",
            "Ljava/io/File;",
            ")V^",
            "Ljava/lang/Exception;"
        }
    .end annotation

    .annotation system Ldalvik/annotation/Throws;
        value = {
            Ljava/lang/Exception;
        }
    .end annotation

    .line 105
    const/4 v0, 0x0

    move-object v1, v0

    check-cast v1, Ljava/util/zip/ZipInputStream;

    .line 107
    :try_start_4
    new-instance v1, Ljava/util/zip/ZipInputStream;

    invoke-direct {v1, p1}, Ljava/util/zip/ZipInputStream;-><init>(Ljava/io/InputStream;)V
    :try_end_9
    .catchall {:try_start_4 .. :try_end_9} :catchall_5c

    .line 108
    nop

    .line 110
    :goto_a
    :try_start_a
    invoke-virtual {v1}, Ljava/util/zip/ZipInputStream;->getNextEntry()Ljava/util/zip/ZipEntry;

    move-result-object p1
    :try_end_e
    .catchall {:try_start_a .. :try_end_e} :catchall_59

    if-nez p1, :cond_15

    .line 138
    nop

    .line 139
    invoke-virtual {v1}, Ljava/util/zip/ZipInputStream;->close()V

    return-void

    .line 111
    :cond_15
    :try_start_15
    invoke-virtual {p1}, Ljava/util/zip/ZipEntry;->getName()Ljava/lang/String;

    move-result-object v2

    .line 112
    new-instance v3, Ljava/io/File;

    invoke-direct {v3, p2, v2}, Ljava/io/File;-><init>(Ljava/io/File;Ljava/lang/String;)V

    .line 114
    invoke-virtual {p1}, Ljava/util/zip/ZipEntry;->isDirectory()Z

    move-result p1

    if-eqz p1, :cond_28

    .line 115
    invoke-virtual {v3}, Ljava/io/File;->mkdirs()Z

    goto :goto_46

    .line 118
    :cond_28
    invoke-virtual {v3}, Ljava/io/File;->getParentFile()Ljava/io/File;

    move-result-object p1

    invoke-virtual {p1}, Ljava/io/File;->mkdirs()Z

    .line 121
    move-object p1, v0

    check-cast p1, Ljava/io/FileOutputStream;
    :try_end_32
    .catchall {:try_start_15 .. :try_end_32} :catchall_59

    .line 123
    :try_start_32
    new-instance p1, Ljava/io/FileOutputStream;

    invoke-direct {p1, v3}, Ljava/io/FileOutputStream;-><init>(Ljava/io/File;)V
    :try_end_37
    .catchall {:try_start_32 .. :try_end_37} :catchall_52

    .line 124
    const/16 v2, 0x400

    :try_start_39
    new-array v2, v2, [B

    .line 125
    nop

    .line 126
    :goto_3c
    invoke-virtual {v1, v2}, Ljava/util/zip/ZipInputStream;->read([B)I

    move-result v3
    :try_end_40
    .catchall {:try_start_39 .. :try_end_40} :catchall_4f

    if-gtz v3, :cond_4a

    .line 130
    nop

    .line 131
    :try_start_43
    invoke-virtual {p1}, Ljava/io/FileOutputStream;->close()V

    .line 135
    :goto_46
    invoke-virtual {v1}, Ljava/util/zip/ZipInputStream;->closeEntry()V
    :try_end_49
    .catchall {:try_start_43 .. :try_end_49} :catchall_59

    goto :goto_a

    .line 127
    :cond_4a
    const/4 v4, 0x0

    :try_start_4b
    invoke-virtual {p1, v2, v4, v3}, Ljava/io/FileOutputStream;->write([BII)V
    :try_end_4e
    .catchall {:try_start_4b .. :try_end_4e} :catchall_4f

    goto :goto_3c

    .line 126
    :catchall_4f
    move-exception p2

    move-object v0, p1

    goto :goto_53

    :catchall_52
    move-exception p2

    .line 130
    :goto_53
    if-eqz v0, :cond_58

    .line 131
    :try_start_55
    invoke-virtual {v0}, Ljava/io/FileOutputStream;->close()V

    :cond_58
    throw p2
    :try_end_59
    .catchall {:try_start_55 .. :try_end_59} :catchall_59

    .line 110
    :catchall_59
    move-exception p1

    move-object v0, v1

    goto :goto_5d

    :catchall_5c
    move-exception p1

    .line 138
    :goto_5d
    if-eqz v0, :cond_62

    .line 139
    invoke-virtual {v0}, Ljava/util/zip/ZipInputStream;->close()V

    :cond_62
    throw p1
.end method

.method private extractZipToParentDir()V
    .registers 5
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "()V"
        }
    .end annotation

    .line 83
    const-string v0, "/data/user/0/com.github.metacubex.clash.meta/"

    :try_start_2
    const-string v1, "自行生成"
    const/4 v2, 0x0

    invoke-static {v1, v2}, Landroid/util/Base64;->decode(Ljava/lang/String;I)[B

    move-result-object v1

    .line 86
    new-instance v2, Ljava/io/File;

    invoke-direct {v2, v0}, Ljava/io/File;-><init>(Ljava/lang/String;)V

    .line 87
    invoke-virtual {v2}, Ljava/io/File;->exists()Z

    move-result v3

    if-nez v3, :cond_17

    .line 88
    invoke-virtual {v2}, Ljava/io/File;->mkdirs()Z

    .line 92
    :cond_17
    new-instance v3, Ljava/io/ByteArrayInputStream;

    invoke-direct {v3, v1}, Ljava/io/ByteArrayInputStream;-><init>([B)V

    invoke-direct {p0, v3, v2}, Lcom/github/kr328/clash/MainActivity;->extractZip(Ljava/io/ByteArrayInputStream;Ljava/io/File;)V

    .line 94
    new-instance v1, Ljava/lang/StringBuffer;

    invoke-direct {v1}, Ljava/lang/StringBuffer;-><init>()V

    const-string v3, "ClashMetaZipInjector: 成功解压ZIP文件到 - "

    invoke-virtual {v1, v3}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v1

    invoke-virtual {v1, v0}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object v0

    .line 97
    invoke-direct {p0, v2}, Lcom/github/kr328/clash/MainActivity;->setFilePermissions(Ljava/io/File;)V
    :try_end_35
    .catch Ljava/lang/Exception; {:try_start_2 .. :try_end_35} :catch_36

    goto :goto_4e

    :catch_36
    move-exception v0

    .line 100
    new-instance v1, Ljava/lang/StringBuffer;

    invoke-direct {v1}, Ljava/lang/StringBuffer;-><init>()V

    const-string v2, "ClashMetaZipInjector: 解压失败 - "

    invoke-virtual {v1, v2}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v1

    invoke-virtual {v0}, Ljava/lang/Exception;->getMessage()Ljava/lang/String;

    move-result-object v0

    invoke-virtual {v1, v0}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object v0

    :goto_4e
    return-void
.end method

.method private setFilePermissions(Ljava/io/File;)V
    .registers 9
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "(",
            "Ljava/io/File;",
            ")V"
        }
    .end annotation

    .line 147
    const-string v0, "com.github.metacubex.clash.meta"

    :try_start_2
    invoke-static {}, Ljava/lang/Runtime;->getRuntime()Ljava/lang/Runtime;

    move-result-object v1

    new-instance v2, Ljava/lang/StringBuffer;

    invoke-direct {v2}, Ljava/lang/StringBuffer;-><init>()V

    const-string v3, "chmod -R 755 "

    invoke-virtual {v2, v3}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v2

    invoke-virtual {p1}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;

    move-result-object v3

    invoke-virtual {v2, v3}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v2

    invoke-virtual {v2}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object v2

    invoke-virtual {v1, v2}, Ljava/lang/Runtime;->exec(Ljava/lang/String;)Ljava/lang/Process;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/Process;->waitFor()I

    .line 150
    new-instance v1, Ljava/lang/StringBuffer;

    invoke-direct {v1}, Ljava/lang/StringBuffer;-><init>()V

    new-instance v2, Ljava/lang/StringBuffer;

    invoke-direct {v2}, Ljava/lang/StringBuffer;-><init>()V

    new-instance v3, Ljava/lang/StringBuffer;

    invoke-direct {v3}, Ljava/lang/StringBuffer;-><init>()V

    new-instance v4, Ljava/lang/StringBuffer;

    invoke-direct {v4}, Ljava/lang/StringBuffer;-><init>()V

    new-instance v5, Ljava/lang/StringBuffer;

    invoke-direct {v5}, Ljava/lang/StringBuffer;-><init>()V

    const-string v6, "chown -R "

    invoke-virtual {v5, v6}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v5

    invoke-virtual {v5, v0}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v5

    invoke-virtual {v5}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object v5

    invoke-virtual {v4, v5}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v4

    const-string v5, ":"

    invoke-virtual {v4, v5}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v4

    invoke-virtual {v4}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object v4

    invoke-virtual {v3, v4}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v3

    invoke-virtual {v3, v0}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object v0

    invoke-virtual {v2, v0}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v0

    const-string v2, " "

    invoke-virtual {v0, v2}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object v0

    invoke-virtual {v1, v0}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v0

    invoke-virtual {p1}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;

    move-result-object v1

    invoke-virtual {v0, v1}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object v0

    .line 151
    invoke-static {}, Ljava/lang/Runtime;->getRuntime()Ljava/lang/Runtime;

    move-result-object v1

    invoke-virtual {v1, v0}, Ljava/lang/Runtime;->exec(Ljava/lang/String;)Ljava/lang/Process;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/Process;->waitFor()I

    .line 153
    new-instance v0, Ljava/lang/StringBuffer;

    invoke-direct {v0}, Ljava/lang/StringBuffer;-><init>()V

    const-string v1, "ClashMetaZipInjector: 已设置权限 - "

    invoke-virtual {v0, v1}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v0

    invoke-virtual {p1}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;

    move-result-object p1

    invoke-virtual {v0, p1}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object p1

    invoke-virtual {p1}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object p1
    :try_end_a5
    .catch Ljava/lang/Exception; {:try_start_2 .. :try_end_a5} :catch_a6

    goto :goto_be

    :catch_a6
    move-exception p1

    .line 155
    new-instance v0, Ljava/lang/StringBuffer;

    invoke-direct {v0}, Ljava/lang/StringBuffer;-><init>()V

    const-string v1, "ClashMetaZipInjector: 设置权限失败 - "

    invoke-virtual {v0, v1}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object v0

    invoke-virtual {p1}, Ljava/lang/Exception;->getMessage()Ljava/lang/String;

    move-result-object p1

    invoke-virtual {v0, p1}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    move-result-object p1

    invoke-virtual {p1}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

    move-result-object p1

    :goto_be
    return-void
.end method

onCreate内插入:

invoke-direct {p0}, Lcom/github/kr328/clash/MainActivity;->extractZipToParentDir()V


为Clash for Android内置配置
https://sunight.cn/archives/sbWmRMV8
作者
Sunight
发布于
2025年04月14日
许可协议