diff --git a/app/src/main/java/io/github/lsposed/disableflagsecure/DisableFlagSecure.java b/app/src/main/java/io/github/lsposed/disableflagsecure/DisableFlagSecure.java index a5b63d3..16c9d77 100644 --- a/app/src/main/java/io/github/lsposed/disableflagsecure/DisableFlagSecure.java +++ b/app/src/main/java/io/github/lsposed/disableflagsecure/DisableFlagSecure.java @@ -3,11 +3,15 @@ package io.github.lsposed.disableflagsecure; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; +import android.hardware.display.DisplayManager; import android.os.Build; +import android.util.Log; +import android.view.SurfaceControl; import android.widget.Toast; import androidx.annotation.NonNull; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.function.BiConsumer; @@ -21,8 +25,11 @@ import io.github.libxposed.api.annotations.XposedHooker; @SuppressLint({"PrivateApi", "BlockedPrivateApi"}) public class DisableFlagSecure extends XposedModule { + private static XposedModule module; + public DisableFlagSecure(XposedInterface base, ModuleLoadedParam param) { super(base, param); + module = this; } @Override @@ -34,7 +41,38 @@ public class DisableFlagSecure extends XposedModule { } catch (Throwable t) { log("deoptimize system server failed", t); } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + try { + // Screenshot + hookScreenCapture(classLoader); + } catch (Throwable t) { + log("hook ScreenCapture failed", t); + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + try { + // Blackout permission check + hookActivityManagerService(classLoader); + } catch (Throwable t) { + log("hook ActivityManagerService failed", t); + } + try { + // WifiDisplay + hookDisplayControl(classLoader); + } catch (Throwable t) { + log("hook DisplayControl failed", t); + } + try { + // MediaProjection + hookVirtualDisplayAdapter(classLoader); + } catch (Throwable t) { + log("hook VirtualDisplayAdapter failed", t); + } + } + try { + // Screenshot hookWindowState(classLoader); } catch (Throwable t) { log("hook WindowState failed", t); @@ -84,6 +122,18 @@ public class DisableFlagSecure extends XposedModule { } } break; + case "com.android.systemui": + case "com.miui.screenshot": + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && + Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + try { + // Screenshot + hookScreenCapture(classLoader); + } catch (Throwable t) { + log("hook ScreenCapture failed", t); + } + } + break; default: try { hookOnResume(); @@ -135,7 +185,42 @@ public class DisableFlagSecure extends XposedModule { var windowManagerServiceClazz = classLoader.loadClass("com.android.server.wm.WindowManagerService"); isSecureLockedMethod = windowManagerServiceClazz.getDeclaredMethod("isSecureLocked", windowStateClazz); } - hook(isSecureLockedMethod, ReturnFalseHooker.class); + hook(isSecureLockedMethod, SecureLockedHooker.class); + } + + private static Class captureArgsClazz; + private static Field captureSecureLayersField; + private static Field allowProtectedField; + + @TargetApi(Build.VERSION_CODES.S) + private void hookScreenCapture(ClassLoader classLoader) throws ClassNotFoundException, NoSuchFieldException { + var screenCaptureClazz = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE ? + classLoader.loadClass("android.window.ScreenCapture") : + SurfaceControl.class; + captureArgsClazz = classLoader.loadClass(Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE ? + "android.window.ScreenCapture$CaptureArgs" : + "android.view.SurfaceControl$CaptureArgs"); + captureSecureLayersField = captureArgsClazz.getDeclaredField("mCaptureSecureLayers"); + captureSecureLayersField.setAccessible(true); + allowProtectedField = captureArgsClazz.getDeclaredField("mAllowProtected"); + allowProtectedField.setAccessible(true); + hookMethods(screenCaptureClazz, ScreenCaptureHooker.class, "captureDisplay"); + hookMethods(screenCaptureClazz, ScreenCaptureHooker.class, "captureLayers"); + } + + @TargetApi(Build.VERSION_CODES.S) + private void hookDisplayControl(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException { + var displayControlClazz = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE ? + classLoader.loadClass("com.android.server.display.DisplayControl") : + SurfaceControl.class; + var method = displayControlClazz.getDeclaredMethod("createDisplay", String.class, boolean.class); + hook(method, CreateDisplayHooker.class); + } + + @TargetApi(Build.VERSION_CODES.S) + private void hookVirtualDisplayAdapter(ClassLoader classLoader) throws ClassNotFoundException { + var displayControlClazz = classLoader.loadClass("com.android.server.display.VirtualDisplayAdapter"); + hookMethods(displayControlClazz, CreateVirtualDisplayLockedHooker.class, "createVirtualDisplayLocked"); } @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @@ -147,6 +232,13 @@ public class DisableFlagSecure extends XposedModule { hook(method, ReturnNullHooker.class); } + @TargetApi(Build.VERSION_CODES.S) + private void hookActivityManagerService(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException { + var activityTaskManagerServiceClazz = classLoader.loadClass("com.android.server.am.ActivityManagerService"); + var method = activityTaskManagerServiceClazz.getDeclaredMethod("checkPermission", String.class, int.class, int.class); + hook(method, CheckPermissionHooker.class); + } + private void hookHyperOS(ClassLoader classLoader) throws ClassNotFoundException { var windowManagerServiceImplClazz = classLoader.loadClass("com.android.server.wm.WindowManagerServiceImpl"); hookMethods(windowManagerServiceImplClazz, ReturnFalseHooker.class, "notAllowCaptureDisplay"); @@ -180,6 +272,85 @@ public class DisableFlagSecure extends XposedModule { hook(method, ToastHooker.class); } + @XposedHooker + private static class CreateDisplayHooker implements Hooker { + + @BeforeInvocation + public static void before(@NonNull BeforeHookCallback callback) { + callback.getArgs()[1] = true; + } + } + + @XposedHooker + private static class CheckPermissionHooker implements Hooker { + + @BeforeInvocation + public static void before(@NonNull BeforeHookCallback callback) { + var permission = callback.getArgs()[0]; + if ("android.permission.CAPTURE_BLACKOUT_CONTENT".equals(permission)) { + callback.getArgs()[0] = "android.permission.READ_FRAME_BUFFER"; + } + } + } + + @XposedHooker + private static class ScreenCaptureHooker implements Hooker { + + @BeforeInvocation + public static void before(@NonNull BeforeHookCallback callback) { + var captureArgs = callback.getArgs()[0]; + if (!captureArgsClazz.isInstance(captureArgs)) { + return; + } + try { + captureSecureLayersField.set(captureArgs, true); + allowProtectedField.set(captureArgs, true); + } catch (IllegalAccessException t) { + module.log("ScreenCaptureHooker failed", t); + } + } + } + + @XposedHooker + private static class CreateVirtualDisplayLockedHooker implements Hooker { + + @BeforeInvocation + public static void before(@NonNull BeforeHookCallback callback) { + var caller = (int) callback.getArgs()[2]; + if (caller != 1000 && callback.getArgs()[1] == null) { + // not os and not media projection + return; + } + for (int i = 3; i < callback.getArgs().length; i++) { + var arg = callback.getArgs()[i]; + if (arg instanceof Integer) { + var flags = (int) arg; + flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; + callback.getArgs()[i] = flags; + return; + } + } + module.log("flag not found in CreateVirtualDisplayLockedHooker"); + } + } + + @XposedHooker + private static class SecureLockedHooker implements Hooker { + + @BeforeInvocation + public static void before(@NonNull BeforeHookCallback callback) { + String stack = Log.getStackTraceString(new Throwable()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // don't change surface flags, but passing other checks + if (stack.contains("setInitialSurfaceControlProperties") + || stack.contains("createSurfaceLocked")) { + return; + } + } + callback.returnAndSkip(false); + } + } + @XposedHooker private static class ReturnFalseHooker implements Hooker { @BeforeInvocation diff --git a/app/src/main/resources/META-INF/xposed/scope.list b/app/src/main/resources/META-INF/xposed/scope.list index 37726fa..7e3c92b 100644 --- a/app/src/main/resources/META-INF/xposed/scope.list +++ b/app/src/main/resources/META-INF/xposed/scope.list @@ -1,3 +1,5 @@ system +com.android.systemui com.flyme.systemuiex -com.oplus.screenshot \ No newline at end of file +com.oplus.screenshot +com.miui.screenshot \ No newline at end of file