Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/release-notes/versions/1.8.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### 离线项目资料与方法级保护

- 加固时按包名与运行证书自动生成、复用项目专属保护资料,每次构建派生不同材料;设置页支持查看身份、导出与恢复备份。默认完全离线,手机安装即用。
- APK/AAB 支持必保方法清单与逐产物报告,区分选择、转换、产物核验和未覆盖原因;签名后绑定最终文件摘要。未转换的方法不会计入 VMP 覆盖,必保项失败会阻断交付。
- 扩展 PVM2 参数类型和异常路径验证,修复包装对象跨 JNI 传递的语义;加强并发调用结束后的镜像、解析副本和寄存器清理,以及运行时完整性核验。
- Native VMP 源码 SDK 扩展整数、float/double、分支/循环、min/max 和漏斗移位,并增加对象、最终 ELF 及 APK/AAB 条目的构建证据核验;修复 CMake 接入与增量构建问题。
- 从仓库源码重建 PVM2 打包器并核对依赖摘要;修复默认 Application 的自闭合 Manifest 标签属性写入,减少普通 APK 接入失败。

### 使用与验证范围

- Native VMP 需要业务源码或兼容 LLVM bitcode,通过函数标注接入;当前仍不支持指针、内存访问、通用调用、异常、原子和向量,不能自动虚拟化任意预编译 SO。推荐 LLVM 21、NDK r29 与 Ninja。
- 111 个 Native 语义测试函数通过宿主检查及单台 Android 15 设备 ARM64/ARMv7 对照。授权业务样本验证了 19 个 DEX 方法及 1 个 SO 内的 1 个 Native 函数;这不是整包或全部业务覆盖。
- 项目私有种子保存在本机,备份包含私有材料,应妥善保存。安装即用的 APK 仍有公开恢复路径;本版不承诺无法静态还原,也不宣称达到某个加固代数。
- 新 SO 产物尚未完成正式整包性能与跨设备验收;无资源 APK 重打包及 AAB 业务全流程仍需按项目验证。升级后请重新加固并进行业务回归,已有 APK 不会自动获得新保护。
- 方法保护需要 JDK 17。详细接入与边界见[使用指南](https://github.com/kairowan/Shellsmith/blob/v1.8.0/docs/usage.md)和[Native SDK](https://github.com/kairowan/Shellsmith/blob/v1.8.0/native-vmp/README.md)。
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ jobs:
- name: 检查 Rust 格式
run: cargo fmt --all --check

- name: 从源码重建并验证 PVM2 方法保护
run: make test-pvm2

- name: 检查 PVM2 运行时并发与明文生命周期
run: make test-pvm2-runtime

- name: 运行融合核心测试
run: make test-fusion

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ jobs:
shell: bash
run: bash scripts/bump-version.sh "$VERSION"

- name: 从源码重建 PVM2 打包器
shell: bash
run: python3 scripts/build_pvm2_packer.py

# 打包器 JAR 与运行时源码现在都在本仓库内(third_party/xopprotector),
# 因此这里恢复为硬性闸门:两侧格式版本不一致就直接失败,不再放行。
- name: 校验 PVM2 打包器与运行时格式版本一致
Expand Down
11 changes: 5 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 15 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ else
CLEAN_CMD := rm -rf
endif

.PHONY: build-cli build-stub audit-stub-dex build-gui build-all \
.PHONY: build-pvm2 test-pvm2 test-pvm2-runtime build-cli build-stub audit-stub-dex build-gui build-all \
release release-linux release-windows \
release-macos release-macos-universal \
bump-version test test-fusion test-native-vmp check-aab clean help

help:
@echo "用法: make <目标>"
@echo ""
@echo " build-pvm2 从仓库 Java 源码重建 PVM2 打包器(JDK 17+)"
@echo " test-pvm2 从源码构建并执行通用 DEX 方法验证"
@echo " test-pvm2-runtime 检查 Native 明文生命周期、并发与 JNI 执行"
@echo " build-cli 编译 shield-cli(当前平台 release)"
@echo " build-stub 构建 shield-stub(Android AAR + 资源包)"
@echo " audit-stub-dex 输出 Stub DEX 指标并检查膨胀上限(需先 build-stub)"
Expand All @@ -43,7 +46,17 @@ build-cli:
cargo build --release -p shield-cli
@echo "✅ 产物: $(CLI_BIN)"

build-stub:
build-pvm2:
$(PYTHON) scripts/build_pvm2_packer.py

test-pvm2:
$(PYTHON) tests/scripts/check-pvm2-packer.py

test-pvm2-runtime:
$(PYTHON) tests/scripts/check-pvm2-runtime.py
cargo test --manifest-path shield-stub/src/main/rust/Cargo.toml xop_pvm2

build-stub: build-pvm2
@echo "🤖 构建 shield-stub..."
$(BUILD_STUB_CMD)

Expand Down
3 changes: 1 addition & 2 deletions apps/shield-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "shield-cli"
version = "1.7.0"
version = "1.8.0"
edition = "2021"
authors = ["MocikaDev"]
license = "MIT OR Apache-2.0"
Expand All @@ -24,5 +24,4 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"

[dev-dependencies]
tempfile = "3.20"
12 changes: 12 additions & 0 deletions apps/shield-cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ pub struct ProtectAabArgs {
/// AAB 中要交给嵌入式 PVM2 的类描述符前缀,可重复传入。
#[arg(long = "xop-true-vmp-prefix")]
pub xop_true_vmp_prefix: Vec<String>,
/// 关键方法清单(UTF-8):每行 模块名<TAB>完整 DEX 方法描述符。
#[arg(long, value_name = "FILE")]
pub pvm2_required_methods: Option<PathBuf>,
/// Native 证据清单(UTF-8):模块/lib/ABI/库名.so<TAB>SDK schema 2 报告路径。
#[arg(long, value_name = "FILE")]
pub native_vmp_evidence: Option<PathBuf>,
/// `--engine xop` 时使用的 Xop shell-files 目录。
#[arg(long, value_name = "DIR")]
pub xop_shell_dir: Option<PathBuf>,
Expand Down Expand Up @@ -367,6 +373,12 @@ pub(crate) struct ProtectArgs {
/// 要交给 Xop PVM2 的 DEX 类型描述符前缀,可重复传入(例如 Lcom/example/pay/)。
#[arg(long = "xop-true-vmp-prefix")]
pub xop_true_vmp_prefix: Vec<String>,
/// 关键方法清单(UTF-8):每行 模块名<TAB>完整 DEX 方法描述符。
#[arg(long, value_name = "FILE")]
pub pvm2_required_methods: Option<PathBuf>,
/// Native 证据清单(UTF-8):模块/lib/ABI/库名.so<TAB>SDK schema 2 报告路径。
#[arg(long, value_name = "FILE")]
pub native_vmp_evidence: Option<PathBuf>,
/// 每行输出一个 JSON 事件,兼容原有 --json-progress 参数。
#[arg(long, alias = "json-progress")]
pub json: bool,
Expand Down
49 changes: 43 additions & 6 deletions apps/shield-cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub(crate) fn run_protect(args: ResolvedProtectArgs) -> Result<()> {
},
xop_pvm2_packer_path: args.xop_pvm2_packer,
xop_true_vmp_prefixes: args.xop_true_vmp_prefix,
pvm2_required_methods: args.pvm2_required_methods,
native_vmp_evidence: args.native_vmp_evidence,
external_pvm2_dex_dir: None,
external_pas2_assets_dir: None,
deferred_pas2_asset_modules: Vec::new(),
Expand Down Expand Up @@ -183,6 +185,7 @@ pub fn run_protect_aab_with_control(
if !args.input.is_file() {
anyhow::bail!("输入 AAB 不存在:{}", args.input.display());
}
shield_core::protection_report::checked_archive(&args.input)?;
if args.input == args.output {
anyhow::bail!("输入和输出 AAB 不能是同一路径");
}
Expand Down Expand Up @@ -211,9 +214,11 @@ pub fn run_protect_aab_with_control(
if !matches!(args.engine, ProtectionEngineArg::Mocika) {
anyhow::bail!("AAB 多模块当前只支持 Shellsmith 单 Stub + 嵌入式 PVM2");
}
if args.xop_pvm2_packer.is_none() || args.xop_true_vmp_prefix.is_empty() {
if args.xop_pvm2_packer.is_none()
|| (args.xop_true_vmp_prefix.is_empty() && args.pvm2_required_methods.is_none())
{
anyhow::bail!(
"AAB 多模块必须配置嵌入式 Xop PVM2 Packer 和业务类前缀,确保代码/资源模块共用单 Stub 密钥生命周期"
"AAB 多模块必须配置嵌入式 Xop PVM2 Packer 和业务类前缀或关键方法清单,确保代码/资源模块共用单 Stub 密钥生命周期"
);
}
}
Expand Down Expand Up @@ -260,9 +265,13 @@ pub fn run_protect_aab_with_control(
);
}
if matches!(args.engine, ProtectionEngineArg::Xop) {
if args.xop_pvm2_packer.is_some() || !args.xop_true_vmp_prefix.is_empty() {
if args.xop_pvm2_packer.is_some()
|| !args.xop_true_vmp_prefix.is_empty()
|| args.pvm2_required_methods.is_some()
|| args.native_vmp_evidence.is_some()
{
anyhow::bail!(
"--xop-pvm2-packer/--xop-true-vmp-prefix 只用于 --engine mocika;--engine xop 使用独立 Xop 单壳"
"--xop-pvm2-packer/--xop-true-vmp-prefix/--pvm2-required-methods/--native-vmp-evidence 只用于 --engine mocika;--engine xop 使用独立 Xop 单壳"
);
}
let packer = args
Expand Down Expand Up @@ -421,6 +430,8 @@ pub fn run_protect_aab_with_control(
},
xop_pvm2_packer_path: args.xop_pvm2_packer.clone(),
xop_true_vmp_prefixes: args.xop_true_vmp_prefix.clone(),
pvm2_required_methods: args.pvm2_required_methods.clone(),
native_vmp_evidence: args.native_vmp_evidence.clone(),
external_pvm2_dex_dir: (!extra_module_dirs.is_empty())
.then_some(extra_modules_root.clone()),
external_pas2_assets_dir: has_external_assets.then_some(extra_modules_root.clone()),
Expand Down Expand Up @@ -516,9 +527,18 @@ pub fn run_protect_aab_with_control(
)?;
on_progress("SignAab", "使用所选上传证书签署 AAB");
ensure_aab_not_cancelled(&cancel)?;
let staged_aab = tempfile::Builder::new()
.prefix("shield-aab-")
.suffix(".aab")
.tempfile_in(
args.output
.parent()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or(std::path::Path::new(".")),
)?;
sign_aab_with_jarsigner(
&unsigned_aab,
&args.output,
staged_aab.path(),
&args.ks,
&args.key_alias,
&pass,
Expand All @@ -527,7 +547,24 @@ pub fn run_protect_aab_with_control(
)?;
on_progress("ValidateBundle", "使用 bundletool 校验最终 AAB");
ensure_aab_not_cancelled(&cancel)?;
run_bundletool_validate(&args.bundletool, &args.output)?;
run_bundletool_validate(&args.bundletool, staged_aab.path())?;
if matches!(args.engine, ProtectionEngineArg::Mocika)
&& (args.xop_pvm2_packer.is_some() || args.native_vmp_evidence.is_some())
{
shield_core::protection_report::rebind_report(
&protected_apk,
staged_aab.path(),
&shield_core::protection_report::report_path(&args.output),
true,
Some(&args.input),
)?;
} else {
let obsolete = shield_core::protection_report::report_path(&args.output);
if obsolete.exists() {
std::fs::remove_file(obsolete)?;
}
}
staged_aab.persist(&args.output).map_err(|e| e.error)?;
let mut device_install = "not-requested";
let mut smoke_test = "not-requested";
if let Some(apks_output) = args.apks_output.as_ref() {
Expand Down
12 changes: 12 additions & 0 deletions apps/shield-cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ struct ProtectConfig {
environment_policy: Option<String>,
profile: Option<String>,
ai_resistance: Option<String>,
pvm2_required_methods: Option<PathBuf>,
native_vmp_evidence: Option<PathBuf>,
}

#[derive(Debug, Default, Deserialize)]
Expand Down Expand Up @@ -102,6 +104,12 @@ impl CliConfig {
.unwrap_or_default(),
xop_pvm2_packer: args.xop_pvm2_packer,
xop_true_vmp_prefix: args.xop_true_vmp_prefix,
pvm2_required_methods: args
.pvm2_required_methods
.or_else(|| self.protect.pvm2_required_methods.clone()),
native_vmp_evidence: args
.native_vmp_evidence
.or_else(|| self.protect.native_vmp_evidence.clone()),
json: args.json,
verbose: args.verbose,
})
Expand Down Expand Up @@ -142,6 +150,8 @@ impl CliConfig {
&mut self.protect.ui_packer,
&mut self.protect.resources,
&mut self.protect.apksigner,
&mut self.protect.pvm2_required_methods,
&mut self.protect.native_vmp_evidence,
&mut self.sign.input,
&mut self.sign.output,
&mut self.sign.keystore,
Expand All @@ -168,6 +178,8 @@ pub(crate) struct ResolvedProtectArgs {
pub ai_resistance: AiResistanceArg,
pub xop_pvm2_packer: Option<PathBuf>,
pub xop_true_vmp_prefix: Vec<String>,
pub pvm2_required_methods: Option<PathBuf>,
pub native_vmp_evidence: Option<PathBuf>,
pub json: bool,
pub verbose: bool,
}
Expand Down
4 changes: 2 additions & 2 deletions apps/shield-gui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/shield-gui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mocika-shield-gui",
"version": "1.7.0",
"version": "1.8.0",
"license": "MIT OR Apache-2.0",
"private": true,
"type": "module",
Expand Down
4 changes: 1 addition & 3 deletions apps/shield-gui/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mocika-shield"
version = "1.7.0"
version = "1.8.0"
edition = "2021"
license = "MIT OR Apache-2.0"
publish = false
Expand Down Expand Up @@ -31,9 +31,7 @@ url = "2"
toml = "0.8"
rusqlite = { version = "0.37", features = ["bundled"] }
uuid = { version = "1", features = ["v4"] }
chacha20poly1305 = { workspace = true }
sha2 = { workspace = true }
rand = { workspace = true }

[dev-dependencies]
tempfile = "3"
Expand Down
25 changes: 18 additions & 7 deletions apps/shield-gui/src-tauri/src/aab_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub(crate) struct AabProtectRequest {
pub(crate) ai_resistance: AiResistanceRequest,
#[serde(default)]
pub(crate) xop_true_vmp_prefixes: Vec<String>,
pub(crate) pvm2_required_methods: Option<String>,
pub(crate) native_vmp_evidence: Option<String>,
}

pub(crate) fn check_aab(app: &tauri::AppHandle, path: String) -> Result<serde_json::Value, String> {
Expand Down Expand Up @@ -76,13 +78,14 @@ pub(crate) async fn execute_protect_aab(
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 {
Some(find_xop_pvm2_packer_path(&app).ok_or_else(|| {
ExecutionFailure::from("当前安装包缺少内置 Xop PVM2 Packer".to_string())
})?)
};
let pvm2 =
if request.xop_true_vmp_prefixes.is_empty() && request.pvm2_required_methods.is_none() {
None
} else {
Some(find_xop_pvm2_packer_path(&app).ok_or_else(|| {
ExecutionFailure::from("当前安装包缺少内置 Xop PVM2 Packer".to_string())
})?)
};
let cancel = Arc::clone(&cancel_handle.0);
let task_id = request.task_id.clone();
let task_window = window.clone();
Expand Down Expand Up @@ -132,6 +135,14 @@ pub(crate) async fn execute_protect_aab(
xop_packer: None,
xop_pvm2_packer: pvm2,
xop_true_vmp_prefix: request.xop_true_vmp_prefixes,
pvm2_required_methods: request
.pvm2_required_methods
.map(PathBuf::from)
.map(strip_unc_prefix),
native_vmp_evidence: request
.native_vmp_evidence
.map(PathBuf::from)
.map(strip_unc_prefix),
xop_shell_dir: None,
xop_profile: XopProfileArg::Industry,
xop_no_protect_so: false,
Expand Down
Loading
Loading