Härtung der öffentlich downloadbaren Magic-Apps gegen Reverse Engineering (Assessment: docs/specs/magic-re-hardening.md): - Windows: protection.json per ACL auf SYSTEM+Admins (DNS-Token nicht mehr von Standard-Usern lesbar) — setup.rs - Mac: MacProfileInstaller.remove() + Debug-Supervision-Modi/Reset nur noch #if DEBUG (kein Removal-/Debug-Pfad im Release-Binary) - Mac: staging-URL einmal als Konstante statt 4x Literal; interne Infra-Notizen aus String-Literalen raus - Backend: Rate-Limit (10/IP/min) auf /api/magic/pair/redeem NUR Backend-Teil deployt via Push; Mac/Win brauchen Xcode-/Cargo-Release-Build (zied) + Smoke-Tests vor Release. MagicAPIClient.swift trägt etwas vorbestehenden WIP mit (gleiche Magic-Client-Domäne). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
4.3 KiB
Rust
114 lines
4.3 KiB
Rust
//! Elevated One-Shot-Setup: EIN UAC-Prompt erledigt alles —
|
|
//! DoH-Konfiguration, ProgramData-State, Tamper-Service-Installation.
|
|
//! Danach läuft alles silent (Service als SYSTEM, Deaktivierung server-gesteuert).
|
|
|
|
use protection_core::{apply_script, ProtectionState, SERVICE_NAME};
|
|
use std::net::ToSocketAddrs;
|
|
use std::path::PathBuf;
|
|
|
|
use protection_core::{ps_run, DOH_FALLBACK_IP, DOH_HOST};
|
|
|
|
/// Löst die IPv4 des DoH-Hosts auf (solange DNS noch normal funktioniert).
|
|
/// Fallback auf die bekannte Hetzner-IP wenn Resolve fehlschlägt.
|
|
pub fn resolve_doh_ip() -> String {
|
|
if let Ok(addrs) = format!("{DOH_HOST}:443").to_socket_addrs() {
|
|
for addr in addrs {
|
|
if let std::net::SocketAddr::V4(v4) = addr {
|
|
return v4.ip().to_string();
|
|
}
|
|
}
|
|
}
|
|
DOH_FALLBACK_IP.to_string()
|
|
}
|
|
|
|
/// Pfad des Tamper-Service-Binaries (Tauri-Sidecar, liegt neben der Haupt-Exe).
|
|
pub fn service_exe_path() -> Result<PathBuf, String> {
|
|
let exe = std::env::current_exe().map_err(|e| e.to_string())?;
|
|
let dir = exe
|
|
.parent()
|
|
.ok_or_else(|| "Exe-Verzeichnis nicht ermittelbar".to_string())?;
|
|
Ok(dir.join("rebreak-protection-service.exe"))
|
|
}
|
|
|
|
/// Baut das komplette elevated Setup-Script:
|
|
/// 1. DoH anwenden 2. State nach %ProgramData%\ReBreak 3. ACL härten 4. Service installieren+starten
|
|
pub fn build_setup_script(state: &ProtectionState, service_exe: &str) -> String {
|
|
let mut script = String::new();
|
|
|
|
script.push_str(&apply_script(&state.server_ip, &state.doh_template));
|
|
|
|
let state_dir = protection_core::state_dir();
|
|
let state_path = protection_core::state_path();
|
|
let json = serde_json::to_string_pretty(state).unwrap_or_default();
|
|
|
|
script.push_str(&format!(
|
|
r#"
|
|
# Persistenter State für den Tamper-Service (SYSTEM liest das beim Polling)
|
|
New-Item -ItemType Directory -Force -Path '{state_dir}' | Out-Null
|
|
@'
|
|
{json}
|
|
'@ | Set-Content -Path '{state_path}' -Encoding UTF8
|
|
|
|
# ACL härten: nur SYSTEM + Administratoren dürfen lesen/schreiben.
|
|
# Standard-User verlieren Read-Zugriff → DNS-Token nicht trivial auslesbar.
|
|
$acl = New-Object System.Security.AccessControl.FileSecurity
|
|
$acl.SetAccessRuleProtection($true, $false) # Vererbung deaktivieren
|
|
$ruleSystem = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
"NT AUTHORITY\SYSTEM",
|
|
"FullControl",
|
|
"Allow"
|
|
)
|
|
$ruleAdmins = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
"BUILTIN\Administrators",
|
|
"FullControl",
|
|
"Allow"
|
|
)
|
|
$acl.AddAccessRule($ruleSystem)
|
|
$acl.AddAccessRule($ruleAdmins)
|
|
Set-Acl -Path '{state_path}' -AclObject $acl
|
|
|
|
# Tamper-Protection-Service als SYSTEM installieren (idempotent: alte Instanz weg)
|
|
sc.exe stop {SERVICE_NAME} 2>$null | Out-Null
|
|
sc.exe delete {SERVICE_NAME} 2>$null | Out-Null
|
|
sc.exe create {SERVICE_NAME} binPath= '"{service_exe}"' start= auto obj= LocalSystem DisplayName= "ReBreak Protection" | Out-Null
|
|
sc.exe description {SERVICE_NAME} "ReBreak DNS-Schutz — Tamper-Monitor (alle 5 Minuten)" | Out-Null
|
|
sc.exe failure {SERVICE_NAME} reset= 86400 actions= restart/60000/restart/60000/restart/60000 | Out-Null
|
|
sc.exe start {SERVICE_NAME} | Out-Null
|
|
|
|
exit 0
|
|
"#,
|
|
state_dir = state_dir.display(),
|
|
state_path = state_path.display(),
|
|
));
|
|
|
|
script
|
|
}
|
|
|
|
/// Schreibt das Script als Temp-.ps1 und startet es elevated (UAC) mit -Wait.
|
|
/// Gibt Err zurück wenn der User den UAC-Prompt abbricht oder das Script failt.
|
|
pub fn run_elevated(script: &str) -> Result<(), String> {
|
|
let tmp = std::env::temp_dir().join(format!(
|
|
"rebreak-setup-{}.ps1",
|
|
std::process::id()
|
|
));
|
|
std::fs::write(&tmp, script).map_err(|e| format!("Temp-Script: {e}"))?;
|
|
|
|
// Outer-PS startet Inner-PS elevated; -Wait + -PassThru für Exit-Code
|
|
let outer = format!(
|
|
r#"$p = Start-Process powershell.exe -Verb RunAs -Wait -PassThru -WindowStyle Hidden -ArgumentList @('-NonInteractive','-NoProfile','-ExecutionPolicy','Bypass','-File','{}')
|
|
exit $p.ExitCode"#,
|
|
tmp.display()
|
|
);
|
|
|
|
let result = ps_run(&outer);
|
|
let _ = std::fs::remove_file(&tmp);
|
|
|
|
match result {
|
|
Ok(_) => Ok(()),
|
|
Err(e) if e.contains("canceled") || e.contains("abgebrochen") => {
|
|
Err("Administrator-Bestätigung wurde abgebrochen.".into())
|
|
}
|
|
Err(e) => Err(format!("Setup fehlgeschlagen: {e}")),
|
|
}
|
|
}
|