From 53871705d28d32fd3028c9a756fb2e6a57558c3a Mon Sep 17 00:00:00 2001 From: haonan Date: Tue, 13 Oct 2026 12:42:15 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E7=95=8C=E9=9D=A2=E4=BF=9D=E6=8A=A4=E4=B8=8E?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E7=8E=AF=E5=A2=83=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + apps/shield-cli/src/args.rs | 77 ++++++ apps/shield-cli/src/commands.rs | 11 + apps/shield-cli/src/config.rs | 49 ++++ apps/shield-cli/src/lib.rs | 2 +- apps/shield-gui/src-tauri/src/aab_runner.rs | 4 + apps/shield-gui/src-tauri/src/app_config.rs | 4 + apps/shield-gui/src-tauri/src/main.rs | 3 + .../src-tauri/src/protect_runner.rs | 4 + .../app/protect-configuration-panel.tsx | 7 + .../components/app/runtime-ui-settings.tsx | 51 ++++ apps/shield-gui/src/hooks/use-app-config.ts | 3 +- .../src/hooks/use-protect-workflow.ts | 7 +- apps/shield-gui/src/lib/tauri.ts | 19 +- .../shield-gui/src/pages/aab-protect-page.tsx | 5 + apps/shield-gui/src/pages/protect-page.tsx | 7 +- crates/shield-core/Cargo.toml | 1 + crates/shield-core/src/lib.rs | 2 + crates/shield-core/src/protect/runtime.rs | 2 + .../src/protect/runtime_metadata.rs | 3 + crates/shield-core/src/protect/xop_pvm2.rs | 16 ++ crates/shield-core/src/protect_api.rs | 76 +++++- crates/shield-core/src/runtime_ui.rs | 257 ++++++++++++++++++ crates/shield-core/src/utils.rs | 24 ++ docs/design/runtime-ui-protection.md | 67 +++++ scripts/build-stub.ps1 | 2 + scripts/build-stub.sh | 2 + scripts/build-ui-packer.sh | 14 + scripts/check-release-ready.sh | 2 +- shield-stub/proguard-rules.pro | 3 + .../dev/mocika/shield/loader/MocikaUi.java | 215 +++++++++++++++ .../mocika/shield/loader/RuntimeSecurity.java | 1 + .../dev/mocika/shield/loader/StubApp.java | 1 + shield-stub/src/main/rust/src/anti_debug.rs | 32 ++- .../mocika/shield/loader/MocikaUiTest.java | 16 ++ tests/fixtures/runtime-ui/AndroidManifest.xml | 9 + tests/fixtures/runtime-ui/UiProbe.java | 60 ++++ tests/fixtures/runtime-ui/res/values/ids.xml | 5 + .../protector/packer/UiProtectionCheck.java | 92 +++++++ tests/scripts/check-ui-packer.sh | 7 + tests/scripts/run-runtime-ui-e2e.sh | 84 ++++++ .../com/yqsh/protector/packer/PackerMain.java | 4 + .../packer/ResourceNameObfuscator.java | 131 +++++++++ .../protector/packer/UiCallsiteRewriter.java | 102 +++++++ .../protector/packer/UiProtectionTool.java | 95 +++++++ tools/xop-pvm2-packer.jar | Bin 8448915 -> 8463787 bytes 46 files changed, 1558 insertions(+), 21 deletions(-) create mode 100644 apps/shield-gui/src/components/app/runtime-ui-settings.tsx create mode 100644 crates/shield-core/src/runtime_ui.rs create mode 100644 docs/design/runtime-ui-protection.md create mode 100644 scripts/build-ui-packer.sh create mode 100644 shield-stub/src/main/java/dev/mocika/shield/loader/MocikaUi.java create mode 100644 shield-stub/src/test/java/dev/mocika/shield/loader/MocikaUiTest.java create mode 100644 tests/fixtures/runtime-ui/AndroidManifest.xml create mode 100644 tests/fixtures/runtime-ui/UiProbe.java create mode 100644 tests/fixtures/runtime-ui/res/values/ids.xml create mode 100644 tests/java/com/yqsh/protector/packer/UiProtectionCheck.java create mode 100644 tests/scripts/check-ui-packer.sh create mode 100644 tests/scripts/run-runtime-ui-e2e.sh create mode 100644 third_party/xopprotector/packer/src/main/java/com/yqsh/protector/packer/ResourceNameObfuscator.java create mode 100644 third_party/xopprotector/packer/src/main/java/com/yqsh/protector/packer/UiCallsiteRewriter.java create mode 100644 third_party/xopprotector/packer/src/main/java/com/yqsh/protector/packer/UiProtectionTool.java diff --git a/Cargo.lock b/Cargo.lock index da00a63..a29b138 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3666,6 +3666,7 @@ dependencies = [ "log", "rand 0.9.4", "serde", + "serde_json", "sha1", "sha2", "tempfile", diff --git a/apps/shield-cli/src/args.rs b/apps/shield-cli/src/args.rs index 572dbfa..9485988 100644 --- a/apps/shield-cli/src/args.rs +++ b/apps/shield-cli/src/args.rs @@ -168,6 +168,8 @@ pub(crate) struct ProtectIosArgs { #[derive(Args, Default)] pub struct ProtectAabArgs { + #[command(flatten)] + pub runtime_ui: RuntimeUiArgs, #[arg(short, long, value_name = "AAB")] pub input: PathBuf, #[arg(short, long, value_name = "AAB")] @@ -334,6 +336,8 @@ pub(crate) struct ProtectXopArgs { #[derive(Args, Default)] pub(crate) struct ProtectArgs { + #[command(flatten)] + pub runtime_ui: RuntimeUiArgs, /// 明确排除不需要且不受支持的 ABI(逗号分隔),仅对本次任务有效。 #[arg(long, value_delimiter = ',')] pub exclude_abis: Vec, @@ -402,3 +406,76 @@ pub(crate) struct SignArgs { #[arg(long)] pub json: bool, } + +/// APK/AAB 共用界面保护参数;Option 保留命令行覆盖配置文件的语义。 +#[derive(Args, Default)] +pub struct RuntimeUiArgs { + /// 防止受保护窗口被系统截图;不阻止视图树读取。 + #[arg(long, num_args = 0..=1, default_missing_value = "true")] + pub secure_screen: Option, + /// 防截图例外,使用完整 Activity 类名;逗号分隔。 + #[arg(long, value_delimiter = ',')] + pub screen_exempt_activities: Option>, + /// 调试环境策略:off、report、block;block 对未知状态也拒绝运行。 + #[arg(long)] + pub adb_policy: Option, + /// 在应用前台定期复查已有反调试/Root 策略。 + #[arg(long, num_args = 0..=1, default_missing_value = "true")] + pub runtime_recheck: Option, + /// 保守混淆可安全改名的资源 id 条目(暂仅 APK)。 + #[arg(long, num_args = 0..=1, default_missing_value = "true")] + pub obfuscate_resource_names: Option, + /// 资源条目保留名;逗号分隔。 + #[arg(long, value_delimiter = ',')] + pub resource_name_keep: Option>, + /// 界面/资源变换器 JAR;默认查找随包分发的 xop-pvm2-packer.jar。 + #[arg(long, value_name = "JAR")] + pub ui_packer: Option, +} + +impl RuntimeUiArgs { + pub fn resolve( + &self, + mut base: shield_core::RuntimeUiProtection, + ) -> anyhow::Result { + if let Some(v) = self.secure_screen { + base.secure_screen = v; + if !v { + base.screen_exempt_activities.clear(); + } + } + if let Some(v) = &self.screen_exempt_activities { + base.screen_exempt_activities = v.clone(); + } + if let Some(v) = self.adb_policy { + base.adb_policy = v; + } + if let Some(v) = self.runtime_recheck { + base.runtime_recheck = v; + } + if let Some(v) = self.obfuscate_resource_names { + base.obfuscate_resource_names = v; + if !v { + base.resource_name_keep.clear(); + } + } + if let Some(v) = &self.resource_name_keep { + base.resource_name_keep = v.clone(); + } + base.validate()?; + Ok(base) + } + + #[allow(dead_code)] // GUI 通过库入口使用,CLI 二进制不调用。 + pub fn from_options(v: shield_core::RuntimeUiProtection, ui_packer: Option) -> Self { + Self { + secure_screen: Some(v.secure_screen), + screen_exempt_activities: Some(v.screen_exempt_activities), + adb_policy: Some(v.adb_policy), + runtime_recheck: Some(v.runtime_recheck), + obfuscate_resource_names: Some(v.obfuscate_resource_names), + resource_name_keep: Some(v.resource_name_keep), + ui_packer, + } + } +} diff --git a/apps/shield-cli/src/commands.rs b/apps/shield-cli/src/commands.rs index 62fecc3..e817bdf 100644 --- a/apps/shield-cli/src/commands.rs +++ b/apps/shield-cli/src/commands.rs @@ -41,6 +41,8 @@ pub(crate) fn run_protect(args: ResolvedProtectArgs) -> Result<()> { } let opts = ProtectOptions { + runtime_ui: args.runtime_ui, + ui_packer_path: args.ui_packer, excluded_abis: args.excluded_abis, input: args.input, output: args.output, @@ -165,6 +167,13 @@ pub fn run_protect_aab_with_control( on_progress: impl Fn(&str, &str) + Send + Sync + 'static, cancel: Arc, ) -> Result<()> { + let runtime_ui = args.runtime_ui.resolve(Default::default())?; + if runtime_ui.enabled() && matches!(args.engine, ProtectionEngineArg::Xop) { + anyhow::bail!("独立 Xop 壳尚不支持运行时界面保护选项"); + } + if runtime_ui.obfuscate_resource_names { + anyhow::bail!("资源名称混淆暂仅支持 APK,AAB 跨模块资源名称一致性尚未验证"); + } let on_progress = Arc::new(on_progress); on_progress("CheckTools", "检查 AAB、签名和构建工具"); ensure_aab_not_cancelled(&cancel)?; @@ -386,6 +395,8 @@ pub fn run_protect_aab_with_control( match args.engine { ProtectionEngineArg::Mocika => { let protect_opts = ProtectOptions { + runtime_ui: runtime_ui.clone(), + ui_packer_path: args.runtime_ui.ui_packer.clone(), excluded_abis: args.exclude_abis.clone(), input: universal_apk, output: protected_unsigned.clone(), diff --git a/apps/shield-cli/src/config.rs b/apps/shield-cli/src/config.rs index 33926b9..a24be28 100644 --- a/apps/shield-cli/src/config.rs +++ b/apps/shield-cli/src/config.rs @@ -21,6 +21,9 @@ pub(crate) struct CliConfig { #[derive(Debug, Default, Deserialize)] #[serde(deny_unknown_fields)] struct ProtectConfig { + #[serde(default)] + runtime_ui: shield_core::RuntimeUiProtection, + ui_packer: Option, input: Option, output: Option, apktool: Option, @@ -68,6 +71,12 @@ impl CliConfig { pub(crate) fn merge_protect(&self, args: ProtectArgs) -> Result { Ok(ResolvedProtectArgs { + runtime_ui: args.runtime_ui.resolve(self.protect.runtime_ui.clone())?, + ui_packer: args + .runtime_ui + .ui_packer + .clone() + .or_else(|| self.protect.ui_packer.clone()), excluded_abis: args.exclude_abis, input: required_path(args.input.or_else(|| self.protect.input.clone()), "--input")?, output: required_path( @@ -130,6 +139,7 @@ impl CliConfig { &mut self.protect.input, &mut self.protect.output, &mut self.protect.apktool, + &mut self.protect.ui_packer, &mut self.protect.resources, &mut self.protect.apksigner, &mut self.sign.input, @@ -145,6 +155,8 @@ impl CliConfig { } pub(crate) struct ResolvedProtectArgs { + pub runtime_ui: shield_core::RuntimeUiProtection, + pub ui_packer: Option, pub excluded_abis: Vec, pub input: PathBuf, pub output: PathBuf, @@ -224,6 +236,43 @@ fn parse_keystore_type(value: Option<&str>) -> Result> { #[cfg(test)] mod tests { + #[test] + fn 界面策略命令行关闭可覆盖配置名单且非法值拒绝() { + use clap::Parser; + let config: CliConfig = toml::from_str( + r#" +schema_version = 1 +[protect] +input = "input.apk" +output = "output.apk" +[protect.runtime_ui] +secure_screen = true +screen_exempt_activities = ["com.example.ShareActivity"] +adb_policy = "report" +"#, + ) + .unwrap(); + let cli = crate::args::Cli::try_parse_from([ + "shield", + "protect", + "--secure-screen=false", + "--adb-policy", + "block", + ]) + .unwrap(); + let crate::args::Commands::Protect(args) = cli.command else { + panic!("命令类型错误") + }; + let options = config.merge_protect(args).unwrap().runtime_ui; + assert!(!options.secure_screen); + assert!(options.screen_exempt_activities.is_empty()); + assert_eq!(options.adb_policy, shield_core::AdbPolicy::Block); + assert!( + crate::args::Cli::try_parse_from(["shield", "protect", "--adb-policy", "allow"]) + .is_err() + ); + } + #[test] fn 排除架构参数仅对本次命令有效() { use clap::Parser; diff --git a/apps/shield-cli/src/lib.rs b/apps/shield-cli/src/lib.rs index cd26903..27997de 100644 --- a/apps/shield-cli/src/lib.rs +++ b/apps/shield-cli/src/lib.rs @@ -9,7 +9,7 @@ mod config; pub use args::{ AiResistanceArg, EnvironmentPolicyArg, KeystoreTypeArg, ProtectAabArgs, ProtectionEngineArg, - ProtectionProfileArg, XopProfileArg, + ProtectionProfileArg, RuntimeUiArgs, XopProfileArg, }; pub use commands::{run_check_aab, run_protect_aab_with_control}; pub use shield_core::*; diff --git a/apps/shield-gui/src-tauri/src/aab_runner.rs b/apps/shield-gui/src-tauri/src/aab_runner.rs index 0b06aac..5231b1e 100644 --- a/apps/shield-gui/src-tauri/src/aab_runner.rs +++ b/apps/shield-gui/src-tauri/src/aab_runner.rs @@ -19,6 +19,8 @@ use tauri::Manager; #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct AabProtectRequest { + #[serde(default)] + pub(crate) runtime_ui: shield_core::RuntimeUiProtection, pub(crate) task_id: String, pub(crate) input: String, pub(crate) output: String, @@ -73,6 +75,7 @@ pub(crate) async fn execute_protect_aab( .ok_or_else(|| ExecutionFailure::from("当前安装包缺少 Android 运行时资源".to_string()))?; let apksigner = find_apksigner_path(&app) .ok_or_else(|| ExecutionFailure::from("当前安装包缺少 apksigner".to_string()))?; + let ui_packer = find_xop_pvm2_packer_path(&app); let pvm2 = if request.xop_true_vmp_prefixes.is_empty() { None } else { @@ -89,6 +92,7 @@ pub(crate) async fn execute_protect_aab( let key_password = (!certificate.key_password.is_empty()).then_some(certificate.key_password); let args = ProtectAabArgs { + runtime_ui: shield_cli::RuntimeUiArgs::from_options(request.runtime_ui, ui_packer), input: strip_unc_prefix(PathBuf::from(request.input)), output: strip_unc_prefix(PathBuf::from(request.output)), bundletool, diff --git a/apps/shield-gui/src-tauri/src/app_config.rs b/apps/shield-gui/src-tauri/src/app_config.rs index 7995ec8..4fb9d6e 100644 --- a/apps/shield-gui/src-tauri/src/app_config.rs +++ b/apps/shield-gui/src-tauri/src/app_config.rs @@ -70,6 +70,7 @@ impl ApplicationSharingConfig { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub(crate) struct ProtectDefaults { + pub runtime_ui: shield_core::RuntimeUiProtection, pub runtime_mode: String, pub environment_policy: String, pub protection_profile: String, @@ -85,6 +86,7 @@ pub(crate) struct ProtectDefaults { impl Default for ProtectDefaults { fn default() -> Self { Self { + runtime_ui: Default::default(), runtime_mode: "standard".to_string(), environment_policy: "compatible".to_string(), protection_profile: "balanced".to_string(), @@ -228,6 +230,7 @@ pub(crate) fn normalize_theme_mode(value: &str) -> String { pub(crate) fn normalize_protect_defaults(value: &ProtectDefaults) -> ProtectDefaults { ProtectDefaults { + runtime_ui: value.runtime_ui.clone(), runtime_mode: match value.runtime_mode.as_str() { "android_api19" => "android_api19", _ => "standard", @@ -435,6 +438,7 @@ theme_mode = "system" #[test] fn 非法加固默认项会被规范化() { let normalized = normalize_protect_defaults(&ProtectDefaults { + runtime_ui: Default::default(), runtime_mode: "unknown".to_string(), environment_policy: "unknown".to_string(), protection_profile: "unknown".to_string(), diff --git a/apps/shield-gui/src-tauri/src/main.rs b/apps/shield-gui/src-tauri/src/main.rs index 17d3eb7..361706c 100644 --- a/apps/shield-gui/src-tauri/src/main.rs +++ b/apps/shield-gui/src-tauri/src/main.rs @@ -61,6 +61,8 @@ use updates::{check_update_impl, UpdateCheckResult}; #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] struct ProtectRequest { + #[serde(default)] + runtime_ui: shield_core::RuntimeUiProtection, sharing: Option, #[serde(default)] excluded_abis: Vec, @@ -213,6 +215,7 @@ async fn protect_apk( apktool_path: request.apktool_path, runtime_mode: request.runtime_mode, environment_policy: request.environment_policy, + runtime_ui: request.runtime_ui, protection_profile: request.protection_profile, ai_resistance: request.ai_resistance, xop_pvm2_packer_path: request.xop_pvm2_packer_path, diff --git a/apps/shield-gui/src-tauri/src/protect_runner.rs b/apps/shield-gui/src-tauri/src/protect_runner.rs index 651320f..5e07e87 100644 --- a/apps/shield-gui/src-tauri/src/protect_runner.rs +++ b/apps/shield-gui/src-tauri/src/protect_runner.rs @@ -84,6 +84,7 @@ fn resolve_xop_pvm2_packer( } pub(crate) struct ProtectExecution { + pub(crate) runtime_ui: shield_core::RuntimeUiProtection, pub(crate) excluded_abis: Vec, pub(crate) task_id: String, pub(crate) input: String, @@ -137,6 +138,7 @@ pub(crate) async fn execute_protect_apk( } }; let resolved_apksigner = find_apksigner_path(&app); + let ui_packer = find_xop_pvm2_packer_path(&app); let resolved_xop_pvm2_packer = resolve_xop_pvm2_packer( request.xop_pvm2_packer_path.clone(), &request.xop_true_vmp_prefixes, @@ -166,6 +168,8 @@ pub(crate) async fn execute_protect_apk( ExecutionFailure::diagnosed(err.to_string(), Diagnostic::from_error(&err)) })?; let opts = ProtectOptions { + runtime_ui: request.runtime_ui, + ui_packer_path: ui_packer, excluded_abis: request.excluded_abis, input: strip_unc_prefix(PathBuf::from(request.input)), output: strip_unc_prefix(PathBuf::from(request.output)), diff --git a/apps/shield-gui/src/components/app/protect-configuration-panel.tsx b/apps/shield-gui/src/components/app/protect-configuration-panel.tsx index 28d630b..c5417f0 100644 --- a/apps/shield-gui/src/components/app/protect-configuration-panel.tsx +++ b/apps/shield-gui/src/components/app/protect-configuration-panel.tsx @@ -1,3 +1,5 @@ +import { RuntimeUiSettings } from "@/components/app/runtime-ui-settings"; +import type { RuntimeUiProtection } from "@/lib/tauri"; import { FolderKey, FolderOpen, Play, Settings2 } from "lucide-react"; import { AppButton, SelectInput, TextInput } from "@/components/app/common"; import { @@ -13,6 +15,7 @@ import { t, tf, type Locale } from "@/lib/i18n"; import type { CertificateRecord } from "@/lib/tauri"; export function ProtectConfigurationPanel({ + runtimeUi, onRuntimeUiChange, locale, disabled, startDisabled, @@ -50,6 +53,8 @@ export function ProtectConfigurationPanel({ onRestoreRecommended, onStart, }: { + runtimeUi: RuntimeUiProtection; + onRuntimeUiChange: (value: RuntimeUiProtection) => void; locale: Locale; disabled: boolean; startDisabled: boolean; @@ -108,6 +113,7 @@ export function ProtectConfigurationPanel({ {t(locale, "protectSettingsHint")}
+ onRuntimeModeChange(value as RuntimeMode)} hint={runtimeMode === "android_api19" ? t(locale, "runtimeAndroid44Hint") : t(locale, "runtimeStandardHint")} guidance={runtimeModeGuidance}> @@ -196,6 +202,7 @@ export function ProtectConfigurationPanel({
+ diff --git a/apps/shield-gui/src/components/app/runtime-ui-settings.tsx b/apps/shield-gui/src/components/app/runtime-ui-settings.tsx new file mode 100644 index 0000000..ddaf1f5 --- /dev/null +++ b/apps/shield-gui/src/components/app/runtime-ui-settings.tsx @@ -0,0 +1,51 @@ +import { SelectInput, TextInput } from "@/components/app/common"; +import type { Locale } from "@/lib/i18n"; +import type { RuntimeUiProtection } from "@/lib/tauri"; + +export function RuntimeUiSettings({ value, onChange, locale, disabled, aab = false }: { + value: RuntimeUiProtection; + onChange: (value: RuntimeUiProtection) => void; + locale: Locale; + disabled: boolean; + aab?: boolean; +}) { + const zh = locale === "zh"; + const names = (text: string) => text.split(/[,\s]+/).filter(Boolean); + return
+ {zh ? "界面与调试保护" : "UI & debugging protection"} + +

{zh ? "保护 Activity 和可识别的 Dialog 窗口;不阻止视图树读取,Root/系统修改可能绕过。" : "Protects activities and recognized dialog windows. Does not hide the view tree; modified systems can bypass it."}

+ {value.secure_screen && } + +

{zh ? "检测调试开关,不代表正在逆向。限制模式在开启或无法读取状态时拒绝运行,可能影响开发机与特殊 ROM;建议先用仅记录模式验证。" : "Checks debugging settings, not reverse engineering activity. Block mode denies enabled or unreadable states; validate report mode on your devices first."}

+ + {!aab && <> + +

{zh ? "只修改可确认安全的 id 名称。动态名称查询、Native/资产引用无法确认时保留并报告;不隐藏界面文字。" : "Renames safe ID entries only. Dynamic lookup and opaque native/assets are retained and reported. Visible text stays readable."}

+ {value.obfuscate_resource_names && } + } + {aab &&

{zh ? "AAB 暂不支持资源名称混淆。" : "Resource name obfuscation is not yet available for AAB."}

} +
; +} diff --git a/apps/shield-gui/src/hooks/use-app-config.ts b/apps/shield-gui/src/hooks/use-app-config.ts index afcad46..5ef1891 100644 --- a/apps/shield-gui/src/hooks/use-app-config.ts +++ b/apps/shield-gui/src/hooks/use-app-config.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { detectSystemLocale, type Locale } from "@/lib/i18n"; -import { api, type AppConfig, type ProtectDefaults, type ThemeMode, type UpdateCheckResult } from "@/lib/tauri"; +import { api, defaultRuntimeUi, type AppConfig, type ProtectDefaults, type ThemeMode, type UpdateCheckResult } from "@/lib/tauri"; import { normalizeThemeMode } from "@/hooks/use-applied-theme-mode"; export function useAppConfigState() { @@ -9,6 +9,7 @@ export function useAppConfigState() { const [configLoaded, setConfigLoaded] = useState(false); const [telemetryEnabled, setTelemetryEnabled] = useState(true); const [protectDefaults, setProtectDefaults] = useState({ + runtime_ui: defaultRuntimeUi, runtime_mode: "standard", environment_policy: "compatible", protection_profile: "balanced", diff --git a/apps/shield-gui/src/hooks/use-protect-workflow.ts b/apps/shield-gui/src/hooks/use-protect-workflow.ts index 1a1d94f..e83b1c3 100644 --- a/apps/shield-gui/src/hooks/use-protect-workflow.ts +++ b/apps/shield-gui/src/hooks/use-protect-workflow.ts @@ -14,6 +14,7 @@ import { getProtectJavaError } from "@/lib/java"; import { notifyError, notifySuccess } from "@/lib/notify"; import { api, + defaultRuntimeUi, onTauriEvent, openFileDialog, type ApkCheckResult, @@ -55,6 +56,7 @@ export function useProtectWorkflow({ const [error, setError] = useState(""); const [precheck, setPrecheck] = useState(""); const [preflight, setPreflight] = useState(null); + const [runtimeUi, setRuntimeUi] = useState(defaults.runtime_ui ?? defaultRuntimeUi); const [runtimeMode, setRuntimeMode] = useState(defaults.runtime_mode); const [environmentPolicy, setEnvironmentPolicy] = useState(defaults.environment_policy); const [protectionProfile, setProtectionProfile] = useState(defaults.protection_profile); @@ -92,6 +94,7 @@ export function useProtectWorkflow({ useEffect(() => { if (taskLocked.current) return; + setRuntimeUi(defaults.runtime_ui ?? defaultRuntimeUi); setRuntimeMode(defaults.runtime_mode); setEnvironmentPolicy(defaults.environment_policy); setProtectionProfile(defaults.protection_profile); @@ -312,6 +315,7 @@ export function useProtectWorkflow({ autoSignReady && certificate ? certificate.id : null, exclusions, sharingChoice, + runtimeUi, ); setFinishedAt(Date.now()); setState("done"); @@ -327,7 +331,7 @@ export function useProtectWorkflow({ setState("idle"); } } - }, [aiResistance, autoSignReady, buildInfo, certificate, environmentPolicy, fixedOutputDirectory, input, locale, output, outputDirectory, outputDirectoryMode, outputFilenameError, precheck, preflight, protectionProfile, runtimeMode, sharing, xopPvm2PackerPath, xopTrueVmpPrefixes]); + }, [aiResistance, autoSignReady, buildInfo, certificate, environmentPolicy, fixedOutputDirectory, input, locale, output, outputDirectory, outputDirectoryMode, outputFilenameError, precheck, preflight, protectionProfile, runtimeMode, runtimeUi, sharing, xopPvm2PackerPath, xopTrueVmpPrefixes]); const cancel = useCallback(async () => { await api.cancelProtect().catch(() => undefined); @@ -360,6 +364,7 @@ export function useProtectWorkflow({ runtimeMode, setRuntimeMode, environmentPolicy, + runtimeUi, setRuntimeUi, setEnvironmentPolicy, protectionProfile, setProtectionProfile, diff --git a/apps/shield-gui/src/lib/tauri.ts b/apps/shield-gui/src/lib/tauri.ts index d58ebf5..05bb2af 100644 --- a/apps/shield-gui/src/lib/tauri.ts +++ b/apps/shield-gui/src/lib/tauri.ts @@ -22,7 +22,22 @@ export type AppConfig = { protect_defaults?: ProtectDefaults; }; +export type RuntimeUiProtection = { + secure_screen: boolean; + screen_exempt_activities: string[]; + adb_policy: "off" | "report" | "block"; + runtime_recheck: boolean; + obfuscate_resource_names: boolean; + resource_name_keep: string[]; +}; + +export const defaultRuntimeUi: RuntimeUiProtection = { + secure_screen: false, screen_exempt_activities: [], adb_policy: "off", + runtime_recheck: false, obfuscate_resource_names: false, resource_name_keep: [], +}; + export type ProtectDefaults = { + runtime_ui: RuntimeUiProtection; runtime_mode: "standard" | "android_api19"; environment_policy: "compatible" | "strict"; protection_profile: "compat" | "balanced" | "strict"; @@ -272,6 +287,7 @@ export type AabInspection = { }; export type AabProtectRequest = { + runtimeUi: RuntimeUiProtection; taskId: string; input: string; output: string; @@ -406,7 +422,7 @@ export const api = { invoke("check_apk", { path, runtimeMode, certificateId: certificateId ?? null }), checkAab: (path: string) => invoke("check_aab", { path }), protectAab: (request: AabProtectRequest) => invoke("protect_aab", { request }), - protectApk: (taskId: string, input: string, output: string, runtimeMode: "standard" | "android_api19", environmentPolicy: "compatible" | "strict", protectionProfile: "compat" | "balanced" | "strict", aiResistance: "off" | "balanced" | "high", xopPvm2PackerPath: string | null, xopTrueVmpPrefixes: string[], signedOutput?: string | null, certificateId?: string | null, excludedAbis: string[] = [], sharing: SharingChoice | null = null) => + protectApk: (taskId: string, input: string, output: string, runtimeMode: "standard" | "android_api19", environmentPolicy: "compatible" | "strict", protectionProfile: "compat" | "balanced" | "strict", aiResistance: "off" | "balanced" | "high", xopPvm2PackerPath: string | null, xopTrueVmpPrefixes: string[], signedOutput?: string | null, certificateId?: string | null, excludedAbis: string[] = [], sharing: SharingChoice | null = null, runtimeUi: RuntimeUiProtection = defaultRuntimeUi) => invoke("protect_apk", { request: { taskId, @@ -416,6 +432,7 @@ export const api = { signedOutput: signedOutput ?? null, apktoolPath: null, runtimeMode, + runtimeUi, environmentPolicy, protectionProfile, aiResistance, diff --git a/apps/shield-gui/src/pages/aab-protect-page.tsx b/apps/shield-gui/src/pages/aab-protect-page.tsx index 57b9b31..fa8ed2e 100644 --- a/apps/shield-gui/src/pages/aab-protect-page.tsx +++ b/apps/shield-gui/src/pages/aab-protect-page.tsx @@ -1,3 +1,5 @@ +import { RuntimeUiSettings } from "@/components/app/runtime-ui-settings"; +import { defaultRuntimeUi } from "@/lib/tauri"; import type React from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FolderOpen, RotateCcw, Square } from "lucide-react"; @@ -60,6 +62,7 @@ export function AabProtectPage({ const [allowUploadCertBinding, setAllowUploadCertBinding] = useState(true); const [playDeliveryAdapter, setPlayDeliveryAdapter] = useState(false); const [generateApks, setGenerateApks] = useState(true); + const [runtimeUi, setRuntimeUi] = useState(defaultRuntimeUi); const [profile, setProfile] = useState<"balanced" | "strict">("strict"); const [prefixesText, setPrefixesText] = useState(""); const [currentStep, setCurrentStep] = useState(""); @@ -202,6 +205,7 @@ export function AabProtectPage({ runtimeCertSha256: allowUploadCertBinding ? null : runtimeCertSha256.trim(), allowUploadCertBinding, playDeliveryAdapter, + runtimeUi, environmentPolicy: "compatible", protectionProfile: profile, aiResistance: profile === "strict" ? "high" : "balanced", @@ -300,6 +304,7 @@ export function AabProtectPage({