diff --git a/.github/release-notes/versions/1.7.0.md b/.github/release-notes/versions/1.7.0.md new file mode 100644 index 0000000..edc0ffe --- /dev/null +++ b/.github/release-notes/versions/1.7.0.md @@ -0,0 +1,15 @@ +### 运行时界面与调试环境保护 + +- APK/AAB 的 CLI 与 GUI 新增可选防截图、Activity 豁免、ADB 策略及前台环境复查;所有新选项默认关闭,已有配置保持原行为。 +- 防截图覆盖 Activity、Dialog 和平台 AlertDialog.Builder,保护调用保留业务虚方法分派;业务通过 Window API 清除安全标记时会重新保留该标记。 +- ADB 支持关闭、仅报告、限制运行三种策略。限制运行会在调试开启或无法确认关闭时拒绝启动,并在前台复查;这是主动阻断策略,调试设备应使用报告模式。 +- APK 增加保守的资源名称混淆,只改写可证明安全的 id 名称并保留资源数值 ID;检测到 Native 库、assets、动态查找或不支持的资源格式时跳过。AAB 暂不支持资源名称混淆。 +- 将通用线程名 gmain/gdbus 调整为弱信号,降低普通应用被误判的风险。旧运行时资源会明确拒绝不支持的新选项。 + +### 使用与验证范围 + +- 窗口调用改写和资源名称混淆需要 JDK 17;普通加固与签名仍保持原有 Java 要求。 +- 已在 Android 15 / arm64 荣耀设备验证界面保护、Dialog、平台 Builder、页面豁免、资源 ID 兼容和 ADB 启动阻断;默认及严格加固模式验证包含双 DEX、业务 SO、资源 openFd、覆盖安装、首次启动与缓存后启动。 +- 防截图会影响截屏、录屏、投屏和任务缩略图,应为分享等必要页面设置豁免。FLAG_SECURE 不隐藏无障碍视图树,也不阻止 ADB 输入操作。 +- 自测不代表所有业务包、ROM 或 Android 版本均已验证。无线调试切换、第三方窗口实现、多窗口及业务全流程仍需按实际应用回归;启用前请阅读 [界面保护说明](https://github.com/kairowan/Shellsmith/blob/main/docs/design/runtime-ui-protection.md)。 +- DEXB v6 仍不提供针对离线静态分析的载荷机密性,本次没有改变这一边界。 diff --git a/Cargo.lock b/Cargo.lock index da00a63..c4788d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2201,7 +2201,7 @@ dependencies = [ [[package]] name = "mocika-shield" -version = "1.6.3" +version = "1.7.0" dependencies = [ "chacha20poly1305", "dunce", @@ -2231,7 +2231,7 @@ dependencies = [ [[package]] name = "mocikashield" -version = "1.6.3" +version = "1.7.0" dependencies = [ "cc", "chacha20poly1305", @@ -3637,7 +3637,7 @@ dependencies = [ [[package]] name = "shield-cli" -version = "1.6.3" +version = "1.7.0" dependencies = [ "anyhow", "clap", @@ -3655,7 +3655,7 @@ dependencies = [ [[package]] name = "shield-core" -version = "1.6.3" +version = "1.7.0" dependencies = [ "anyhow", "chacha20poly1305", @@ -3666,6 +3666,7 @@ dependencies = [ "log", "rand 0.9.4", "serde", + "serde_json", "sha1", "sha2", "tempfile", diff --git a/apps/shield-cli/Cargo.toml b/apps/shield-cli/Cargo.toml index 17f420a..2318122 100644 --- a/apps/shield-cli/Cargo.toml +++ b/apps/shield-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shield-cli" -version = "1.6.3" +version = "1.7.0" edition = "2021" authors = ["MocikaDev"] license = "MIT OR Apache-2.0" 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/package-lock.json b/apps/shield-gui/package-lock.json index 27bca74..bfc5e36 100644 --- a/apps/shield-gui/package-lock.json +++ b/apps/shield-gui/package-lock.json @@ -1,12 +1,12 @@ { "name": "mocika-shield-gui", - "version": "1.6.3", + "version": "1.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mocika-shield-gui", - "version": "1.6.3", + "version": "1.7.0", "license": "MIT OR Apache-2.0", "dependencies": { "@radix-ui/react-dialog": "^1.1.19", diff --git a/apps/shield-gui/package.json b/apps/shield-gui/package.json index cf1cd74..f14aed0 100644 --- a/apps/shield-gui/package.json +++ b/apps/shield-gui/package.json @@ -1,6 +1,6 @@ { "name": "mocika-shield-gui", - "version": "1.6.3", + "version": "1.7.0", "license": "MIT OR Apache-2.0", "private": true, "type": "module", diff --git a/apps/shield-gui/src-tauri/Cargo.toml b/apps/shield-gui/src-tauri/Cargo.toml index 33db338..cc6733d 100644 --- a/apps/shield-gui/src-tauri/Cargo.toml +++ b/apps/shield-gui/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mocika-shield" -version = "1.6.3" +version = "1.7.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false 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-tauri/tauri.conf.json b/apps/shield-gui/src-tauri/tauri.conf.json index 8b8a035..49ed430 100644 --- a/apps/shield-gui/src-tauri/tauri.conf.json +++ b/apps/shield-gui/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Shellsmith", - "version": "1.6.3", + "version": "1.7.0", "identifier": "dev.mocika.shield-gui", "build": { "beforeDevCommand": "npm run dev -- --host 127.0.0.1", 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-about-page.ts b/apps/shield-gui/src/hooks/use-about-page.ts index 6301597..5c2627d 100644 --- a/apps/shield-gui/src/hooks/use-about-page.ts +++ b/apps/shield-gui/src/hooks/use-about-page.ts @@ -9,7 +9,7 @@ type AppInfo = { }; const defaultAppInfo: AppInfo = { - version: "1.6.3", + version: "1.7.0", git_hash: "dev", build_date: "unknown", }; 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({