use std::fs;
use std::path::Path;
use std::process::Command;
const JAVA_HOME: &str = "/opt/homebrew/Cellar/openjdk/27/libexec/openjdk.jdk/Contents/Home";
const D8: &str = "/Users/e/Library/Android/sdk/build-tools/36.0.0/d8";
fn have_tools() -> bool {
let ok = Path::new(JAVA_HOME).is_dir()
&& Command::new("ddc").arg("version").output().is_ok()
&& Command::new("javac").arg("-version").output().is_ok()
&& Path::new(D8).exists();
if !ok {
eprintln!("skip ddc_tools: 需要 ddc / javac / d8({D8}) / JAVA_HOME");
}
ok
}
const CLEAN_SRC: &str = r#"
import java.util.ArrayList;
import java.util.List;
public class Demo3 {
private static final int LIMIT = 100;
private int base;
public Demo3(int base) {
this.base = base;
}
private int scale(int value, int factor) {
int result = value * factor + base;
if (result > LIMIT) {
return LIMIT;
}
return result;
}
private String label(int value) {
if (value < 0) {
return "neg";
} else if (value == 0) {
return "zero";
}
return value > LIMIT ? "big" : "small";
}
public static void main(String[] args) {
Demo3 d = new Demo3(7);
int total = 0;
for (int i = 1; i <= 5; i++) {
total += d.scale(i, 3);
}
System.out.println("total=" + total);
List<String> names = new ArrayList<>();
names.add("alpha");
names.add("beta");
names.add("gamma");
for (String n : names) {
System.out.println(d.label(n.length()) + ":" + n);
}
int p;
p = 11;
int q;
q = p;
System.out.println("copy=" + q);
boolean flag = total > 50 && total % 2 == 0;
System.out.println("flag=" + flag);
System.out.println(d.label(total));
}
}
"#;
fn run_java(cmd: &mut Command) -> std::process::Output {
cmd.env("JAVA_HOME", JAVA_HOME).output().unwrap()
}
#[test]
fn ddc_real_pipeline_semantics_preserved() {
if !have_tools() {
return;
}
let base = std::env::temp_dir().join("cure_ddc_test");
let _ = fs::remove_dir_all(&base);
fs::create_dir_all(&base).unwrap();
let src_file = base.join("Demo3.java");
fs::write(&src_file, CLEAN_SRC).unwrap();
let out = run_java(Command::new("javac").arg("-encoding").arg("UTF-8").arg("-nowarn").arg("--release").arg("17").arg("-d").arg(&base).arg(&src_file));
assert!(out.status.success(), "javac: {}", String::from_utf8_lossy(&out.stderr));
let out = run_java(Command::new(D8).arg("--release").arg("--output").arg(&base).arg(base.join("Demo3.class")));
assert!(out.status.success(), "d8: {}", String::from_utf8_lossy(&out.stderr));
let decomp = base.join("decomp");
let out = run_java(Command::new("ddc").arg(base.join("classes.dex")).arg("-o").arg(&decomp));
assert!(out.status.success(), "ddc: {}", String::from_utf8_lossy(&out.stderr));
let decomp_src = find_java(&decomp);
let ddc_src = fs::read_to_string(&decomp_src).unwrap();
assert!(ddc_src.contains("class Demo3"), "ddc 产物异常");
let ddc_dir = base.join("ddc_run");
fs::create_dir_all(&ddc_dir).unwrap();
fs::write(ddc_dir.join("Demo3.java"), strip_package(&ddc_src)).unwrap();
let out = run_java(Command::new("javac").arg("-encoding").arg("UTF-8").arg("-nowarn").arg("-d").arg(&ddc_dir).arg(ddc_dir.join("Demo3.java")));
assert!(out.status.success(), "ddc 输出无法编译:\n{}", String::from_utf8_lossy(&out.stderr));
let ddc_run = run_java(Command::new("java").arg("-cp").arg(&ddc_dir).arg("Demo3"));
let ddc_out = String::from_utf8_lossy(&ddc_run.stdout).to_string();
use cure_engine::Config;
use cure_java_parser::parse;
use cure_java_print::print_unit;
use cure_java_simplify::simplify_unit;
let mut outcome = parse(&ddc_src);
assert!(outcome.errors.is_empty(), "ddc 输出解析失败:{:?}", &outcome.errors[..outcome.errors.len().min(3)]);
let report = simplify_unit(&mut outcome.ast, &mut outcome.unit, &Config::default());
let cured = print_unit(&outcome.ast, &outcome.unit);
let cured_dir = base.join("cured");
fs::create_dir_all(&cured_dir).unwrap();
fs::write(cured_dir.join("Demo3.java"), strip_package(&cured)).unwrap();
let out = run_java(Command::new("javac").arg("-encoding").arg("UTF-8").arg("-nowarn").arg("-d").arg(&cured_dir).arg(cured_dir.join("Demo3.java")));
assert!(
out.status.success(),
"cure 产物无法编译:\n{}\n== 源码 ==\n{cured}",
String::from_utf8_lossy(&out.stderr)
);
let cured_run = run_java(Command::new("java").arg("-cp").arg(&cured_dir).arg("Demo3"));
let cured_out = String::from_utf8_lossy(&cured_run.stdout).to_string();
assert_eq!(ddc_run.status.code(), cured_run.status.code());
assert_eq!(
ddc_out, cured_out,
"ddc→cure 语义改变!\n== ddc ==\n{ddc_out}\n== cure 后 ==\n{cured_out}"
);
let ddc_lines = ddc_src.lines().filter(|l| !l.trim().is_empty()).count();
let cured_lines = cured.lines().filter(|l| !l.trim().is_empty()).count();
eprintln!(
"ddc 链路通过:{} 行 ddc 输出 → {} 行(-{}%),{} 次改写,输出一致",
ddc_lines,
cured_lines,
100 - cured_lines * 100 / ddc_lines,
report.edits
);
assert!(report.edits >= 10, "预期显著净化,实际 {} 次", report.edits);
}
fn strip_package(src: &str) -> String {
src.lines()
.filter(|l| !l.trim_start().starts_with("package "))
.collect::<Vec<_>>()
.join("\n")
}
fn find_java(dir: &std::path::PathBuf) -> std::path::PathBuf {
let mut stack = vec![dir.clone()];
while let Some(d) = stack.pop() {
if let Ok(entries) = fs::read_dir(&d) {
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
stack.push(p);
} else if p.extension().and_then(|x| x.to_str()) == Some("java") {
return p;
}
}
}
}
panic!("no java file under {}", dir.display());
}
const HARD_SRC: &str = r#"
public class HardObf {
static int trace = 0;
static int mark(int v) {
trace += v;
return v;
}
public static void main(String[] args) {
// ===== [多层常量隐藏]:声明链 + 双异或 + 位噪声 + 拆分赋值 =====
int k = 20;
int k2 = k + 22;
int k3 = k2 - 22;
int k4 = ((k3 ^ 0x5A) ^ 0x5A) | 0;
int k5;
k5 = k4 & -1;
System.out.println("k=" + k5);
// ===== [嵌套不透明谓词]:双层 + 死分支 =====
boolean o1 = 2 > 1;
boolean o2 = 3 < 4;
if (o1 && o2) {
System.out.println("live");
} else {
System.out.println("dead1");
}
if (1 > 2) {
System.out.println("dead2");
}
// ===== [循环混合]:while(true) + 真实断路 + 寄存器回拷噪声 =====
int v24 = 0;
int v25 = 0;
v24 = 1;
while (true) {
if (v24 >= 4) {
break;
} else {
int v32 = v25 + v24;
int v33 = v24 + 1;
v25 = v32;
v24 = v33;
}
}
System.out.println("sum=" + v25);
// ===== [多层字符串]:SB 常量链 + new String + valueOf + 分散常量 =====
String s1 = new String(new StringBuilder().append("he").append("llo").toString());
String s2 = String.valueOf(s1.length()) + "!";
String s3 = "a" + s2 + "b" + "c" + "d";
System.out.println(s3);
// ===== [布尔旗标三元嵌套] =====
boolean flag = (v25 > 3 ? true : false);
boolean flag2 = flag ? (v25 > 5 ? true : false) : false;
System.out.println("f=" + (flag2 ? 1 : 0));
// ===== [迭代器 + SB 语句链 + continue 组合] =====
java.util.List<String> list = new java.util.ArrayList<>();
list.add("x1");
list.add("yy");
java.util.Iterator<String> it = list.iterator();
String acc = "";
while (true) {
if (!(it.hasNext())) {
break;
} else {
String e = (String) it.next();
StringBuilder sb = new StringBuilder().append(acc);
sb = sb.append(e);
StringBuilder sb2 = sb.append("-");
String acc2 = sb2.toString();
acc = acc2;
continue;
}
}
System.out.println("acc=" + acc);
// ===== [副作用异或包裹]:mark 调用恰好一次、位置不变 =====
int r = (mark(5) ^ 0x5A) ^ 0x5A;
System.out.println("r=" + r);
System.out.println("trace=" + trace);
}
}"#;
#[test]
fn ddc_dual_layer_obfuscation() {
if !have_tools() {
return;
}
let base = std::env::temp_dir().join("cure_ddc_hard");
let _ = fs::remove_dir_all(&base);
fs::create_dir_all(&base).unwrap();
let src_file = base.join("HardObf.java");
fs::write(&src_file, HARD_SRC).unwrap();
let out = run_java(Command::new("javac").arg("-encoding").arg("UTF-8").arg("-nowarn").arg("--release").arg("17").arg("-d").arg(&base).arg(&src_file));
assert!(out.status.success());
let out = run_java(Command::new(D8).arg("--release").arg("--output").arg(&base).arg(base.join("HardObf.class")));
assert!(out.status.success());
let decomp = base.join("decomp");
let out = run_java(Command::new("ddc").arg(base.join("classes.dex")).arg("-o").arg(&decomp));
assert!(out.status.success());
let decomp_src = fs::read_to_string(find_java(&decomp)).unwrap();
let ddc_dir = base.join("ddc_run");
fs::create_dir_all(&ddc_dir).unwrap();
fs::write(ddc_dir.join("HardObf.java"), strip_package(&decomp_src)).unwrap();
let out = run_java(Command::new("javac").arg("-encoding").arg("UTF-8").arg("-nowarn").arg("-d").arg(&ddc_dir).arg(ddc_dir.join("HardObf.java")));
assert!(out.status.success());
let ddc_run = run_java(Command::new("java").arg("-cp").arg(&ddc_dir).arg("HardObf"));
let ddc_out = String::from_utf8_lossy(&ddc_run.stdout).to_string();
use cure_engine::Config;
use cure_java_parser::parse;
use cure_java_print::print_unit;
use cure_java_simplify::simplify_unit;
let mut outcome = parse(&decomp_src);
assert!(outcome.errors.is_empty(), "双层数 ddc 输出解析失败");
let report = simplify_unit(&mut outcome.ast, &mut outcome.unit, &Config::default());
let cured = print_unit(&outcome.ast, &outcome.unit);
let cured_dir = base.join("cured");
fs::create_dir_all(&cured_dir).unwrap();
fs::write(cured_dir.join("HardObf.java"), strip_package(&cured)).unwrap();
let out = run_java(Command::new("javac").arg("-encoding").arg("UTF-8").arg("-nowarn").arg("-d").arg(&cured_dir).arg(cured_dir.join("HardObf.java")));
assert!(out.status.success(), "cure 产物编译失败:\n{}\n{cured}", String::from_utf8_lossy(&out.stderr));
let cured_run = run_java(Command::new("java").arg("-cp").arg(&cured_dir).arg("HardObf"));
let cured_out = String::from_utf8_lossy(&cured_run.stdout).to_string();
assert_eq!(ddc_run.status.code(), cured_run.status.code());
assert_eq!(ddc_out, cured_out, "双层数语义改变!\nddc={ddc_out}\ncured={cured_out}");
let a = decomp_src.lines().filter(|l| !l.trim().is_empty()).count();
let b = cured.lines().filter(|l| !l.trim().is_empty()).count();
eprintln!(
"双层数链路通过:ddc 输出 {} 行 → {} 行(-{}%),{} 次改写",
a,
b,
100 - b * 100 / a,
report.edits
);
assert!(report.edits >= 10, "双层数预期显著净化,实际 {}", report.edits);
}