diff --git a/README.md b/README.md index 3367057..4f83641 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ dependencyResolutionManagement { ```groovy dependencies { - implementation 'com.github.HChenX:HookTool:v.0.9.8' + implementation 'com.github.HChenX:HookTool:v.0.9.9' } ``` @@ -59,7 +59,7 @@ public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) { - HCInit.initOther(/* 你模块的包名 */, /* tag */, /* 日志等级 */); // 必须,tip:建议放在第一位 + HCInit.initBasicData(/* 你模块的包名 */, /* tag */, /* 日志等级 */); // 必须,tip:建议放在第一位 HCInit.initLoadPackageParam(lpparam); // 必须 } ``` @@ -70,7 +70,7 @@ public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) { public static class MainActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { - HCInit.initOther(/* 你模块的包名 */, /* tag */, /* 日志等级 */); // 必须 + HCInit.initBasicData(/* 你模块的包名 */, /* tag */, /* 日志等级 */); // 必须 } } ``` @@ -202,7 +202,6 @@ public class MainTest extends BaseHC { public void before() throws Throwable { // hook 方法所属的类 Class c = mClass; - Context context = thisObject(); String string = first(); second(1); @@ -211,7 +210,6 @@ public class MainTest extends BaseHC { setThisField("demo", 1); callThisMethod("method"); getThisField("test"); - // 非静态本类外 Object o = null; setField(o, "demo", 1); @@ -223,9 +221,13 @@ public class MainTest extends BaseHC { callStaticMethod("com.demo.Main", "callStatic", new Object[]{thisObject(), second()}); int i = getStaticField("com.demo.Main", "field"); setStaticField("com.demo.Main", "test", true); - + // 你可调用此方法,使得挂钩自己失效 removeSelf(); + // 观察调用 + observeCall(); + // 获取堆栈 + getStackTrace(); } }; } @@ -360,8 +362,8 @@ public class MainTest extends BaseHC { s = prefs("myPrefs").getString("test", "1"); // 可指定读取文件名 Context context = null; // nativePrefs() 即可切换为原生模式,配置会保存到寄生应用的私有目录,读取也会从寄生应用私有目录读取。 - nativePrefs().prefs(context).editor().putString("test", "1").commit(); - + nativePrefs().prefs(context).editor().putString("test", "1").commit(); + // 如果不方便获取 context 可用使用此方法,异步获取寄生应用 context,再设置。 asynPrefs(new PrefsTool.IAsynPrefs() { @Override @@ -383,7 +385,7 @@ public class MainTest { // 读取,写入同理。 Context context = null; prefs(context).editor().putString("test", "1").commit(); - prefs(context,"myPrefs").editor().putString("test", "1").commit(); + prefs(context, "myPrefs").editor().putString("test", "1").commit(); } } @@ -409,6 +411,14 @@ public class MainTest { ---- +- PackagesUtils 类: +- 快速获取软件包信息! + +---- + +- BitmapUtils 类: +- Drawable 转 Bitmap。 + - 其他更多精彩正在加载··· # 💕工具使用者 diff --git a/app/build.gradle b/app/build.gradle index 4db81a5..aa18bf3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,8 +3,8 @@ plugins { id 'maven-publish' } -def defVersion = 'v.0.9.8' -int defVersionCode = 2024071600 +def defVersion = 'v.0.9.9' +int defVersionCode = 2024071900 group = 'com.github.HChenX' version = defVersion diff --git a/app/src/main/java/com/hchen/hooktool/BaseHC.java b/app/src/main/java/com/hchen/hooktool/BaseHC.java index ababa38..b738b5d 100644 --- a/app/src/main/java/com/hchen/hooktool/BaseHC.java +++ b/app/src/main/java/com/hchen/hooktool/BaseHC.java @@ -131,8 +131,8 @@ final public IPrefs prefs(Context context, String prefsName) { return prefs.prefs(context, prefsName); } - final public void asyncPrefs(PrefsTool.IAsyncPrefs asynPrefs) { - prefs.asyncPrefs(asynPrefs); + final public void asyncPrefs(PrefsTool.IAsyncPrefs asyncPrefs) { + prefs.asyncPrefs(asyncPrefs); } final public PrefsTool nativePrefs() { diff --git a/app/src/main/java/com/hchen/hooktool/HCInit.java b/app/src/main/java/com/hchen/hooktool/HCInit.java index 6824aff..bb2d0b8 100644 --- a/app/src/main/java/com/hchen/hooktool/HCInit.java +++ b/app/src/main/java/com/hchen/hooktool/HCInit.java @@ -83,23 +83,24 @@ public static void initStartupParam(IXposedHookZygoteInit.StartupParam startupPa /** * 务必设置! */ - public static void initOther(String modulePackageName, String tag, @Duration int level) { + public static void initBasicData(String modulePackageName, String tag, @Duration int level) { setTag(tag); /* 设置 TAG */ ToolData.mInitLogLevel = level; /* 设置日志等级 */ ToolData.modulePackageName = modulePackageName; /* 设置模块包名 */ } + /** + * 是否允许使用系统的 classloader,一般不需要开启。 + */ public static void canUseSystemClassLoader(boolean use) { canUseSystemClassLoader = use; /* 允许使用系统 classloader */ } - public static void logFilter(boolean use, String[] filter) { - ToolData.useFieldObserver = use; /* 使用全局日志过滤 */ - ToolData.filter = filter; /* 过滤规则 */ - } - - public static void filedObserver(boolean use) { - ToolData.useFieldObserver = use; /* 使用字段设置观察 */ + /** + * 是否自动对每个被 hook 的方法,在其被调用时打印日志。 + */ + public static void autoObserveCall(boolean auto) { + ToolData.autoObserveCall = auto; } // ---------- END!---------- @@ -108,7 +109,6 @@ private static void setTag(String tag) { ToolData.spareTag = tag; } - protected static XC_LoadPackage.LoadPackageParam getLoadPackageParam() { if (lpparam != null) return lpparam; throw new RuntimeException(ToolData.mInitTag + "[E]: failed to obtain LoadPackageParam, it is null!"); diff --git a/app/src/main/java/com/hchen/hooktool/MainTest.java b/app/src/main/java/com/hchen/hooktool/MainTest.java index fe07bdc..d15fa8f 100644 --- a/app/src/main/java/com/hchen/hooktool/MainTest.java +++ b/app/src/main/java/com/hchen/hooktool/MainTest.java @@ -28,6 +28,8 @@ /** * 测试和示例类 + * + * @hide */ public class MainTest extends BaseHC { @@ -98,7 +100,6 @@ public void before() throws Throwable { setThisField("demo", 1); callThisMethod("method"); getThisField("test"); - // 非静态本类外 Object o = null; setField(o, "demo", 1); @@ -113,6 +114,10 @@ public void before() throws Throwable { // 移除自身 removeSelf(); + // 观察调用 + observeCall(); + // 获取堆栈 + getStackTrace(); } }; } diff --git a/app/src/main/java/com/hchen/hooktool/data/AppData.java b/app/src/main/java/com/hchen/hooktool/data/AppData.java new file mode 100644 index 0000000..06d4126 --- /dev/null +++ b/app/src/main/java/com/hchen/hooktool/data/AppData.java @@ -0,0 +1,37 @@ +/* + * This file is part of HookTool. + + * HookTool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + + * Copyright (C) 2023-2024 HookTool Contributions + */ +package com.hchen.hooktool.data; + +import android.graphics.Bitmap; + +/** + * App 数据类 + */ +public class AppData { + public int user = 0; /* user id*/ + public int uid = -1; /* uid */ + public Bitmap icon; /* 图标 */ + public String label; /* 应用名 */ + public String packageName; /* 包名 */ + public String activityName; /* 活动名 */ + public String versionName; /* 版本名 */ + public String versionCode; /* 版本号 */ + public boolean isSystemApp; /* 是否为系统应用 */ + public boolean enabled; /* 是否启用 */ +} diff --git a/app/src/main/java/com/hchen/hooktool/data/ChainData.java b/app/src/main/java/com/hchen/hooktool/data/ChainData.java index a3dbe90..97edbb8 100644 --- a/app/src/main/java/com/hchen/hooktool/data/ChainData.java +++ b/app/src/main/java/com/hchen/hooktool/data/ChainData.java @@ -25,6 +25,7 @@ /** * 链式调用数据 + * @hide */ public class ChainData { public ArrayList members = new ArrayList<>(); /*目标成员组*/ diff --git a/app/src/main/java/com/hchen/hooktool/data/StateEnum.java b/app/src/main/java/com/hchen/hooktool/data/StateEnum.java index 56cf03b..9e1ba50 100644 --- a/app/src/main/java/com/hchen/hooktool/data/StateEnum.java +++ b/app/src/main/java/com/hchen/hooktool/data/StateEnum.java @@ -20,6 +20,7 @@ /** * 状态枚举 + * @hide */ public enum StateEnum { NONE, /*未 hook*/ diff --git a/app/src/main/java/com/hchen/hooktool/tool/ActionTool.java b/app/src/main/java/com/hchen/hooktool/tool/ActionTool.java index 80c842b..baff4dd 100644 --- a/app/src/main/java/com/hchen/hooktool/tool/ActionTool.java +++ b/app/src/main/java/com/hchen/hooktool/tool/ActionTool.java @@ -25,7 +25,6 @@ import com.hchen.hooktool.callback.IAction; import com.hchen.hooktool.data.ChainData; import com.hchen.hooktool.data.StateEnum; -import com.hchen.hooktool.utils.LogExpand; import com.hchen.hooktool.utils.ToolData; import java.lang.reflect.Member; @@ -38,6 +37,8 @@ /** * Hook 执行 + * + * @hide */ public class ActionTool { private final ToolData data; @@ -78,7 +79,7 @@ protected void doAction(ChainTool chain) { "type: [" + data.mType + "]. member is null, will skip hook!"); continue; } - XposedBridge.hookMethod(m, createHook(m, data.iAction)); + XposedBridge.hookMethod(m, createHook(data.iAction)); logD(this.data.getTAG(), "success to hook: " + m); } data.stateEnum = StateEnum.HOOKED; @@ -99,12 +100,14 @@ protected void doAction(ChainTool chain) { } } - protected Action createHook(Member member, IAction iAction) { + protected Action createHook(IAction iAction) { iAction.putUtils(data); - return new Action(member, data.getTAG()) { + return new Action(data.getTAG()) { @Override protected void before(MethodHookParam param) throws Throwable { iAction.putMethodHookParam(param); + if (ToolData.autoObserveCall) + iAction.observeCall(); iAction.before(); } @@ -122,9 +125,7 @@ void putThis(XC_MethodHook xcMethodHook) { } protected abstract static class Action extends XC_MethodHook { - private String TAG = null; - private LogExpand logExpand = null; - private boolean useLogExpand = false; + private final String TAG; protected void before(MethodHookParam param) throws Throwable { } @@ -134,32 +135,22 @@ protected void after(MethodHookParam param) throws Throwable { abstract void putThis(XC_MethodHook xcMethodHook); - public Action(Member member, String tag) { + public Action(String tag) { super(); TAG = tag; putThis(this); - this.useLogExpand = ToolData.useLogExpand; - if (useLogExpand) this.logExpand = new LogExpand(member, TAG); } - public Action(Member member, String tag, int priority) { + public Action(String tag, int priority) { super(priority); TAG = tag; putThis(this); - this.useLogExpand = ToolData.useLogExpand; - if (useLogExpand) this.logExpand = new LogExpand(member, TAG); } @Override protected void beforeHookedMethod(MethodHookParam param) { try { before(param); - if (useLogExpand) { - if (logExpand != null) { - logExpand.setParam(param); - logExpand.detailedLogs(); - } - } } catch (Throwable e) { logE(TAG + ":" + "before", e); } diff --git a/app/src/main/java/com/hchen/hooktool/tool/CoreTool.java b/app/src/main/java/com/hchen/hooktool/tool/CoreTool.java index 46d218c..8954498 100644 --- a/app/src/main/java/com/hchen/hooktool/tool/CoreTool.java +++ b/app/src/main/java/com/hchen/hooktool/tool/CoreTool.java @@ -27,7 +27,6 @@ import com.hchen.hooktool.itool.IMember; import com.hchen.hooktool.itool.IStatic; import com.hchen.hooktool.utils.ConvertHelper; -import com.hchen.hooktool.utils.FieldObserver; import com.hchen.hooktool.utils.ToolData; import java.lang.reflect.Constructor; @@ -46,12 +45,9 @@ * 核心工具 */ public class CoreTool extends ConvertHelper implements IDynamic, IStatic, IMember { - private final FieldObserver observer; - private final boolean useFieldObserver = ToolData.useFieldObserver; public CoreTool(ToolData toolData) { super(toolData); - observer = new FieldObserver(data); } //------------ 检查指定类是否存在 -------------- @@ -282,7 +278,7 @@ public XC_MethodHook.Unhook hook(Member member, IAction iAction) { return null; } try { - XC_MethodHook.Unhook unhook = XposedBridge.hookMethod(member, data.getActionTool().createHook(member, iAction)); + XC_MethodHook.Unhook unhook = XposedBridge.hookMethod(member, data.getActionTool().createHook(iAction)); logD(data.getTAG(), "success hook: " + member); return unhook; } catch (Throwable e) { @@ -297,7 +293,7 @@ public ArrayList hook(ArrayList members, IAction iActio if (o instanceof Method || o instanceof Constructor) { try { unhooks.add(XposedBridge.hookMethod((Member) o, - data.getActionTool().createHook((Member) o, iAction))); + data.getActionTool().createHook(iAction))); logD(data.getTAG(), "success hook: " + o); } catch (Throwable e) { logE(data.getTAG(), "hook: [" + o + "], failed!", e); @@ -462,8 +458,6 @@ public boolean setField(Object instance, String name, Object value) { } try { XposedHelpers.setObjectField(instance, name, value); - if (useFieldObserver) - observer.dynamicObserver(instance, name, value); return true; } catch (Throwable e) { logE(data.getTAG(), "set field failed!", e); @@ -479,8 +473,6 @@ public boolean setField(Object instance, Field field, Object value) { } field.setAccessible(true); field.set(instance, value); - if (useFieldObserver) - observer.dynamicObserver(field, instance, value); return true; } catch (Throwable e) { logE(data.getTAG(), "set field failed!", e); @@ -638,8 +630,6 @@ public boolean setStaticField(Class clz, String name, Object value) { if (clz != null) { try { XposedHelpers.setStaticObjectField(clz, name, value); - if (useFieldObserver) - observer.staticObserver(clz, name, value); return true; } catch (Throwable e) { logE(data.getTAG(), "set static field failed!", e); @@ -652,8 +642,6 @@ public boolean setStaticField(Field field, Object value) { try { field.setAccessible(true); field.set(null, value); - if (useFieldObserver) - observer.staticObserver(field, value); return true; } catch (Throwable e) { logE(data.getTAG(), "set static field failed!", e); diff --git a/app/src/main/java/com/hchen/hooktool/tool/ParamTool.java b/app/src/main/java/com/hchen/hooktool/tool/ParamTool.java index ae1e9b6..bfd2c32 100644 --- a/app/src/main/java/com/hchen/hooktool/tool/ParamTool.java +++ b/app/src/main/java/com/hchen/hooktool/tool/ParamTool.java @@ -41,6 +41,9 @@ public class ParamTool extends Arguments { private XC_MethodHook xcMethodHook; + /** + * @hide + */ final protected void putMethodHookParam(XC_MethodHook.MethodHookParam param) { if (param == null) throw new RuntimeException(ToolData.mInitTag + "[" + mTag + "][E]: param is null!!"); @@ -50,10 +53,16 @@ final protected void putMethodHookParam(XC_MethodHook.MethodHookParam param) { mArgs = param.args; } + /** + * @hide + */ final protected void putXCMethodHook(XC_MethodHook xcMethodHook) { this.xcMethodHook = xcMethodHook; } + /** + * @hide + */ final protected void putUtils(ToolData data) { mTag = data.getTAG(); iDynamic = data.getCoreTool(); diff --git a/app/src/main/java/com/hchen/hooktool/tool/param/ActAchieve.java b/app/src/main/java/com/hchen/hooktool/tool/param/ActAchieve.java index 8663ac2..eb9325c 100644 --- a/app/src/main/java/com/hchen/hooktool/tool/param/ActAchieve.java +++ b/app/src/main/java/com/hchen/hooktool/tool/param/ActAchieve.java @@ -19,6 +19,7 @@ package com.hchen.hooktool.tool.param; import com.hchen.hooktool.itool.IDynamic; +import com.hchen.hooktool.utils.LogExpand; import de.robv.android.xposed.XC_MethodHook; @@ -26,9 +27,19 @@ * 动作 */ public class ActAchieve { + /** + * @hide + */ protected XC_MethodHook.MethodHookParam methodHookParam; + /** + * @hide + */ protected String mTag; + /** + * @hide + */ protected IDynamic iDynamic; + private LogExpand logExpand; // protected ToolData data; /** @@ -98,6 +109,18 @@ final public T getResultOrThrowable() throws Throwable { return (T) methodHookParam.getResultOrThrowable(); } + // --------- 观察调用 -------------- + + /** + * 观察方法是否被调用,如果被调用则打印一些日志。 + */ + final public void observeCall() { + if (logExpand == null) { + logExpand = new LogExpand(methodHookParam, mTag); + } + logExpand.detailedLogs(); + } + // --------- 调用方法 -------------- /** * 方法具体介绍请看实现类。
@@ -128,6 +151,7 @@ final public boolean setThisField(String name, Object value) { } // ---------- 设置自定义字段 -------------- + final public boolean setThisAdditionalInstanceField(String key, Object value) { return iDynamic.setAdditionalInstanceField(methodHookParam.thisObject, key, value); } diff --git a/app/src/main/java/com/hchen/hooktool/utils/BitmapUtils.java b/app/src/main/java/com/hchen/hooktool/utils/BitmapUtils.java new file mode 100644 index 0000000..030951d --- /dev/null +++ b/app/src/main/java/com/hchen/hooktool/utils/BitmapUtils.java @@ -0,0 +1,84 @@ +/* + * This file is part of HookTool. + + * HookTool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + + * Copyright (C) 2023-2024 HookTool Contributions + */ +package com.hchen.hooktool.utils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; + +import java.io.ByteArrayOutputStream; + +/** + * Drawable 转 Bitmap + */ +public class BitmapUtils { + public static Bitmap drawableToBitmap(Drawable drawable) { + // 取 drawable 的长宽 + int w = drawable.getIntrinsicWidth(); + int h = drawable.getIntrinsicHeight(); + // 取 drawable 的颜色格式 + Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; + // 建立对应 bitmap + Bitmap bitmap = Bitmap.createBitmap(w, h, config); + // 建立对应 bitmap 的画布 + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, w, h); + // 把 drawable 内容画到画布中 + drawable.draw(canvas); + return bitmap; + } + + Bitmap drawable2Bitmap(Drawable drawable) { + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } else if (drawable instanceof NinePatchDrawable) { + Bitmap bitmap = Bitmap + .createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 + : Bitmap.Config.RGB_565); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight()); + drawable.draw(canvas); + return bitmap; + } else { + return null; + } + } + + public static byte[] Bitmap2Bytes(Bitmap bm) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bm.compress(Bitmap.CompressFormat.PNG, 100, baos); + return baos.toByteArray(); + } + + public static Bitmap Bytes2Bimap(byte[] b) { + if (b.length != 0) { + return BitmapFactory.decodeByteArray(b, 0, b.length); + } else { + return null; + } + } +} diff --git a/app/src/main/java/com/hchen/hooktool/utils/ConvertHelper.java b/app/src/main/java/com/hchen/hooktool/utils/ConvertHelper.java index 69de30b..06ad91d 100644 --- a/app/src/main/java/com/hchen/hooktool/utils/ConvertHelper.java +++ b/app/src/main/java/com/hchen/hooktool/utils/ConvertHelper.java @@ -28,6 +28,9 @@ * 本工具的快捷转换 */ public class ConvertHelper { + /** + * @hide + */ protected ToolData data; public ConvertHelper(ToolData data) { diff --git a/app/src/main/java/com/hchen/hooktool/utils/FieldObserver.java b/app/src/main/java/com/hchen/hooktool/utils/FieldObserver.java deleted file mode 100644 index 474e61f..0000000 --- a/app/src/main/java/com/hchen/hooktool/utils/FieldObserver.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * This file is part of HookTool. - - * HookTool is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - - * Copyright (C) 2023-2024 HookTool Contributions - */ -package com.hchen.hooktool.utils; - -import static com.hchen.hooktool.log.AndroidLog.logE; -import static com.hchen.hooktool.log.AndroidLog.logI; -import static com.hchen.hooktool.log.AndroidLog.logW; - -import java.lang.reflect.Field; - -import de.robv.android.xposed.XposedHelpers; - -/** - * 字段设置结果观察 - */ -public class FieldObserver { - private final ToolData data; - - public FieldObserver(ToolData data) { - this.data = data; - } - - /** - * 观察字段设置结果。 - */ - public void dynamicObserver(Field field, Object instance, Object value) { - Object o = null; - field.setAccessible(true); - try { - o = field.get(instance); - } catch (IllegalAccessException e) { - logE(data.getTAG(), e); - return; - } - doInspect("", field.getName(), value, o); - } - - public void dynamicObserver(Object instance, String name, Object value) { - Object o = null; - try { - o = XposedHelpers.getObjectField(instance, name); - } catch (Throwable e) { - logE(data.getTAG(), e); - return; - } - doInspect("", name, value, o); - } - - /** - * 观察静态字段设置结果。 - */ - public void staticObserver(Field field, Object value) { - Object o = null; - field.setAccessible(true); - try { - o = field.get(null); - } catch (IllegalAccessException e) { - logE(data.getTAG(), e); - return; - } - doInspect("static", field.getName(), value, o); - } - - public void staticObserver(Class clazz, String name, Object value) { - Object o = null; - try { - o = XposedHelpers.getStaticObjectField(clazz, name); - } catch (Throwable e) { - logE(data.getTAG(), e); - return; - } - doInspect("static", name, value, o); - } - - private void doInspect(String call, String name, Object value, Object o) { - if (o == null && value == null) { - logI(data.getTAG(), "set field: [" + name + "], value to null"); - return; - } - if ((o == null && value != null) || (o != null && value == null)) { - logW(data.getTAG(), "failed set " + call + " field: [" + name + "]," + - " value to: [" + value + "], now is: " + o); - return; - } - if (o == value || o.equals(value)) { - logI(data.getTAG(), "success set " + call + " field: [" + name + "]," + - " value to: [" + value + "], now is: " + o); - } else { - logW(data.getTAG(), "failed set " + call + " field: [" + name + "]," + - " value to: [" + value + "], now is: " + o); - } - } -} diff --git a/app/src/main/java/com/hchen/hooktool/utils/LogExpand.java b/app/src/main/java/com/hchen/hooktool/utils/LogExpand.java index 1dac9b4..4f52d6d 100644 --- a/app/src/main/java/com/hchen/hooktool/utils/LogExpand.java +++ b/app/src/main/java/com/hchen/hooktool/utils/LogExpand.java @@ -33,16 +33,15 @@ * 日志增强 */ public class LogExpand { + private final XC_MethodHook.MethodHookParam param; + private final String TAG; private String methodName; private String className; - private XC_MethodHook.MethodHookParam param; - private String[] filter = null; - private final String TAG; - public LogExpand(Member member, String TAG) { + public LogExpand(XC_MethodHook.MethodHookParam param, String TAG) { this.TAG = TAG; - this.filter = ToolData.filter; - getName(member); + this.param = param; + getName(param.method); } public static String printStackTrace(Throwable t) { @@ -57,17 +56,13 @@ private void getName(Member member) { methodName = method.getName(); className = method.getDeclaringClass().getSimpleName(); } else if (member instanceof Constructor constructor) { - methodName = constructor.getDeclaringClass().getSimpleName(); + methodName = "Constructor"; className = constructor.getDeclaringClass().getSimpleName(); } else { logE(TAG, "unknown type! member: " + member); } } - public void setParam(XC_MethodHook.MethodHookParam param) { - this.param = param; - } - public void detailedLogs() { if (param.args == null || param.args.length == 0) { logI(TAG, "class: [" + className + "], method: [" + methodName + "], param: { }"); @@ -76,22 +71,12 @@ public void detailedLogs() { StringBuilder log = new StringBuilder(); for (int i = 0; i < param.args.length; i++) { - log.append("(").append(i).append(")->").append("[").append(param.args[i]).append("]"); + log.append("(").append(param.args[i].getClass().getSimpleName()) + .append(")->").append("[").append(param.args[i]).append("]"); if (i < param.args.length - 1) { log.append(", "); } } - if (!isFilter()) - logI(TAG, "class: [" + className + "], method: [" + methodName + "], param: {" + log + "}"); - } - - private boolean isFilter() { - if (filter == null) return false; - for (String s : filter) { - if (s.equals(TAG)) { - return true; - } - } - return false; + logI(TAG, "class: [" + className + "], method: [" + methodName + "], param: {" + log + "}"); } } diff --git a/app/src/main/java/com/hchen/hooktool/utils/PackagesUtils.java b/app/src/main/java/com/hchen/hooktool/utils/PackagesUtils.java new file mode 100644 index 0000000..f57fd54 --- /dev/null +++ b/app/src/main/java/com/hchen/hooktool/utils/PackagesUtils.java @@ -0,0 +1,280 @@ +/* + * This file is part of HookTool. + + * HookTool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + + * Copyright (C) 2023-2024 HookTool Contributions + */ +package com.hchen.hooktool.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.os.Parcelable; +import android.os.UserHandle; + +import com.hchen.hooktool.data.AppData; +import com.hchen.hooktool.log.AndroidLog; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * 软件包实用程序 + */ +public class PackagesUtils { + private static final String TAG = "PackagesUtils"; + + public static boolean isUninstall(String pkg) { + return isUninstall(context(), pkg); + } + + /** + * 判断目标包名应用是否已经被卸载。 + */ + public static boolean isUninstall(Context context, String pkg) { + if (context == null) return false; + PackageManager packageManager = context.getPackageManager(); + try { + packageManager.getPackageInfo(pkg, PackageManager.MATCH_ALL); + return false; + } catch (PackageManager.NameNotFoundException e) { + AndroidLog.logW(TAG, + "didn't find this app on the machine, it may have been uninstalled! pkg: " + pkg, e); + return true; + } + } + + public static boolean isDisable(String pkg) { + return isDisable(context(), pkg); + } + + /** + * 获取包名应用是否被禁用。 + */ + public static boolean isDisable(Context context, String pkg) { + if (context == null) return false; + PackageManager packageManager = context.getPackageManager(); + try { + ApplicationInfo result = packageManager.getApplicationInfo(pkg, 0); + if (!result.enabled) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + return false; + } + + public static boolean isHidden(String pkg) { + return isHidden(context(), pkg); + } + + /** + * 获取包名应用是否被 Hidden,一般来说被隐藏视为未安装,可以使用 isUninstall() 来判断。 + */ + public static boolean isHidden(Context context, String pkg) { + try { + if (context == null) return false; + PackageManager packageManager = context.getPackageManager(); + packageManager.getApplicationInfo(pkg, 0); + return false; + } catch (PackageManager.NameNotFoundException e) { + return true; + } + } + + /** + * 通过自定义代码获取 Package 信息, + * 支持 + *

+ * PackageInfo + *

+ * ResolveInfo + *

+ * ActivityInfo + *

+ * ApplicationInfo + *

+ * ProviderInfo + *

+ * 类型的返回值. + * 返回使用 return new ArrayList<>(XX); 包裹。 + * + * @param iCode 需要执行的代码 + * @return ListAppData 包含各种应用详细信息 + * @see #addAppData(Parcelable, PackageManager) + */ + public static List getPackagesByCode(ICode iCode) { + List appDataList = new ArrayList<>(); + Context context = context(); + if (context == null) return appDataList; + PackageManager packageManager = context.getPackageManager(); + Parcelable parcelable = iCode.getPackageCode(packageManager); + List packageCodeList = iCode.getPackageCodeList(packageManager); + try { + if (parcelable != null) { + appDataList.add(addAppData(parcelable, packageManager)); + } else { + if (packageCodeList != null) { + for (Parcelable get : packageCodeList) { + appDataList.add(addAppData(get, packageManager)); + } + } + } + } catch (Throwable e) { + AndroidLog.logE(TAG, "failed to get package via code!", e); + } + return appDataList; + } + + @SuppressLint("QueryPermissionsNeeded") + public static List getInstalledPackages(int flag) { + List appDataList = new ArrayList<>(); + Context context = context(); + if (context == null) return appDataList; + try { + PackageManager packageManager = context.getPackageManager(); + List packageInfos = packageManager.getInstalledPackages(flag); + for (PackageInfo packageInfo : packageInfos) { + appDataList.add(addAppData(packageInfo, packageManager)); + } + return appDataList; + } catch (Throwable e) { + AndroidLog.logE(TAG, "failed to get the list of installed apps via flag!", e); + } + return appDataList; + } + + private static AppData addAppData(Parcelable parcelable, PackageManager pm) throws Throwable { + AppData appData = new AppData(); + try { + if (parcelable instanceof PackageInfo) { + appData.icon = BitmapUtils.drawableToBitmap(((PackageInfo) parcelable).applicationInfo.loadIcon(pm)); + appData.label = ((PackageInfo) parcelable).applicationInfo.loadLabel(pm).toString(); + appData.packageName = ((PackageInfo) parcelable).applicationInfo.packageName; + appData.versionName = ((PackageInfo) parcelable).versionName; + appData.versionCode = Long.toString(((PackageInfo) parcelable).getLongVersionCode()); + appData.isSystemApp = isSystem(((PackageInfo) parcelable).applicationInfo); + appData.enabled = ((PackageInfo) parcelable).applicationInfo.enabled; + appData.user = getUserId(((PackageInfo) parcelable).applicationInfo.uid); + appData.uid = ((PackageInfo) parcelable).applicationInfo.uid; + } else if (parcelable instanceof ResolveInfo) { + appData.icon = BitmapUtils.drawableToBitmap(aboutResolveInfo((ResolveInfo) parcelable).applicationInfo.loadIcon(pm)); + appData.label = aboutResolveInfo((ResolveInfo) parcelable).applicationInfo.loadLabel(pm).toString(); + appData.packageName = aboutResolveInfo((ResolveInfo) parcelable).applicationInfo.packageName; + appData.isSystemApp = isSystem(aboutResolveInfo((ResolveInfo) parcelable).applicationInfo); + appData.enabled = aboutResolveInfo((ResolveInfo) parcelable).applicationInfo.enabled; + appData.user = getUserId(aboutResolveInfo((ResolveInfo) parcelable).applicationInfo.uid); + appData.uid = aboutResolveInfo((ResolveInfo) parcelable).applicationInfo.uid; + } else if (parcelable instanceof ActivityInfo) { + appData.icon = BitmapUtils.drawableToBitmap(((ActivityInfo) parcelable).applicationInfo.loadIcon(pm)); + appData.label = ((ActivityInfo) parcelable).applicationInfo.loadLabel(pm).toString(); + appData.packageName = ((ActivityInfo) parcelable).applicationInfo.packageName; + appData.isSystemApp = isSystem(((ActivityInfo) parcelable).applicationInfo); + appData.activityName = ((ActivityInfo) parcelable).name; + appData.enabled = ((ActivityInfo) parcelable).applicationInfo.enabled; + appData.user = getUserId(((ActivityInfo) parcelable).applicationInfo.uid); + appData.uid = ((ActivityInfo) parcelable).applicationInfo.uid; + } else if (parcelable instanceof ApplicationInfo) { + appData.icon = BitmapUtils.drawableToBitmap(((ApplicationInfo) parcelable).loadIcon(pm)); + appData.label = ((ApplicationInfo) parcelable).loadLabel(pm).toString(); + appData.packageName = ((ApplicationInfo) parcelable).packageName; + appData.isSystemApp = isSystem(((ApplicationInfo) parcelable)); + appData.enabled = ((ApplicationInfo) parcelable).enabled; + appData.user = getUserId(((ApplicationInfo) parcelable).uid); + appData.uid = ((ApplicationInfo) parcelable).uid; + } else if (parcelable instanceof ProviderInfo) { + appData.icon = BitmapUtils.drawableToBitmap(((ProviderInfo) parcelable).applicationInfo.loadIcon(pm)); + appData.label = ((ProviderInfo) parcelable).applicationInfo.loadLabel(pm).toString(); + appData.packageName = ((ProviderInfo) parcelable).applicationInfo.packageName; + appData.isSystemApp = isSystem(((ProviderInfo) parcelable).applicationInfo); + appData.enabled = ((ProviderInfo) parcelable).applicationInfo.enabled; + appData.user = getUserId(((ProviderInfo) parcelable).applicationInfo.uid); + appData.uid = ((ProviderInfo) parcelable).applicationInfo.uid; + } + } catch (Throwable e) { + throw new Throwable("error in obtaining application information: " + parcelable, e); + } + return appData; + } + + private static ComponentInfo aboutResolveInfo(ResolveInfo resolveInfo) { + if (resolveInfo.activityInfo != null) return resolveInfo.activityInfo; + if (resolveInfo.serviceInfo != null) return resolveInfo.serviceInfo; + if (resolveInfo.providerInfo != null) return resolveInfo.providerInfo; + return null; + } + + /** + * 根据 uid 获取 user id。 + */ + public static int getUserId(int uid) { + return InvokeUtils.callStaticMethod(UserHandle.class, "getUserId", new Class[]{int.class}, uid); + } + + /** + * 可用于判断是否是系统应用。 + * 如果 app 为 null 则固定返回 false,请注意检查 app 是否为 null。 + * + * @return 返回 true 代表是系统应用 + */ + public static boolean isSystem(ApplicationInfo app) { + if (Objects.isNull(app)) { + AndroidLog.logE(TAG, "isSystem app is null, will return false"); + return false; + } + if (app.uid < 10000) { + return true; + } + return (app.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0; + } + + private static Context context() { + try { + return getContext(); + } catch (Throwable e) { + AndroidLog.logE(TAG, e); + return null; + } + } + + private static Context getContext() throws Throwable { + Context context = ContextUtils.getContext(ContextUtils.FLAG_CURRENT_APP); + if (context == null) { + context = ContextUtils.getContext(ContextUtils.FlAG_ONLY_ANDROID); + } + if (context == null) { + throw new Throwable("context is null"); + } + return context; + } + + public interface ICode { + default Parcelable getPackageCode(PackageManager pm) { + return null; + } + + default List getPackageCodeList(PackageManager pm) { + return null; + } + } +} diff --git a/app/src/main/java/com/hchen/hooktool/utils/SystemSDK.java b/app/src/main/java/com/hchen/hooktool/utils/SystemSDK.java index 6ed3c14..92fb05d 100644 --- a/app/src/main/java/com/hchen/hooktool/utils/SystemSDK.java +++ b/app/src/main/java/com/hchen/hooktool/utils/SystemSDK.java @@ -33,16 +33,6 @@ * 此类用于获取设备基本信息 */ public class SystemSDK { - private static String TAG; - - public SystemSDK() { - - } - - public SystemSDK(String tag) { - TAG = tag; - } - public static String getSystemVersionIncremental() { return getProp("ro.system.build.version.incremental"); } diff --git a/app/src/main/java/com/hchen/hooktool/utils/ToolData.java b/app/src/main/java/com/hchen/hooktool/utils/ToolData.java index 3088e4b..5084567 100644 --- a/app/src/main/java/com/hchen/hooktool/utils/ToolData.java +++ b/app/src/main/java/com/hchen/hooktool/utils/ToolData.java @@ -30,6 +30,7 @@ /** * 这是本工具的读写数据类,请不要继承重写。 + * @hide */ public class ToolData { // HCInit @@ -37,9 +38,7 @@ public class ToolData { public static int mInitLogLevel = HCInit.LOG_I; public static String spareTag = "Unknown"; public static String modulePackageName = null; - public static boolean useLogExpand = false; - public static String[] filter = new String[]{}; - public static boolean useFieldObserver = false; + public static boolean autoObserveCall = false; public static IXposedHookZygoteInit.StartupParam startupParam = null; // HCHook public static XC_LoadPackage.LoadPackageParam lpparam = null;