feat(assets): brouiller les blocs embarques dans l'exe

Le frontend etait lisible dans l'onglet Image de Process Explorer, avec
les commentaires. BuildAssets.ps1 brouille chaque fichier avant brcc32,
PM.EmbeddedAssets le debrouille au moment de servir.

C'est de l'opacite, pas du secret : la cle est dans l'exe. Ca supprime
strings et l'onglet Image ; la memoire, un debogueur et la reponse HTTP
rendent toujours le texte. D'ou XOR plutot qu'AES — a securite egale
(nulle contre qui a l'exe), dix lignes, et aucune force affichee qu'on
n'a pas.

Une magie PMA1 prefixe le clair : sans elle, deux constantes qui derivent
serviraient du binaire au navigateur, ecran blanc sans trace.

Verifie en comparant les flux des deux cotes bloc par bloc, puis par
aller-retour sur app.js, index.html et style.css. Le frontend ne se
trouve plus dans le binaire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-05 17:17:15 +01:00
parent 8f0bbb580f
commit 427d294b22
4 changed files with 195 additions and 13 deletions
+54 -1
View File
@@ -155,6 +155,59 @@ if ($jsFiles) {
}
}
# --- Brouillage des blocs embarques -------------------------------------------
#
# CE N'EST PAS DU SECRET, C'EST DE L'OPACITE. La cle vit dans l'exe, donc qui
# tient le binaire retrouve le texte. Ce que ca supprime, c'est l'onglet
# "Image" de Process Explorer et n'importe quel `strings` : le frontend n'est
# plus lisible en survolant le fichier. La memoire du processus, un debogueur
# ou la reponse HTTP le rendent toujours - WebView2 doit recevoir du
# JavaScript en clair pour l'executer.
#
# L'AUTRE MOITIE EST DANS PM.EmbeddedAssets.pas ET LES DEUX DOIVENT S'ACCORDER :
#
# bloc i = SHA256( secret(32) || nom de ressource UTF-8 || i en 4 octets LE )
# sortie = XOR( magie 'PMA1' + contenu du fichier )
#
# La magie prefixe le CLAIR : elle permet a l'exe de reconnaitre une cle qui a
# derive et de refuser, au lieu de servir du binaire au navigateur (ecran blanc
# sans trace). Symptome d'un desaccord : /index.html en 404.
$ObfSecret = [byte[]]@(
0x9A,0xAC,0xC6,0x05,0xDE,0xD6,0xFA,0xE7,
0x4F,0x95,0x04,0xE4,0x2C,0x1D,0xDF,0x26,
0x9B,0xBA,0xC9,0xD2,0x0A,0x52,0xD5,0x90,
0xB1,0x5E,0xA8,0x09,0x12,0x9B,0x9E,0x6C
)
$ObfMagic = [Text.Encoding]::ASCII.GetBytes('PMA1')
function Obfuscate([byte[]] $plain, [string] $resName) {
$sha = [System.Security.Cryptography.SHA256]::Create()
$seed = $ObfSecret + [Text.Encoding]::UTF8.GetBytes($resName)
$out = New-Object byte[] $plain.Length
$i = 0; $idx = 0
while ($i -lt $plain.Length) {
$block = $sha.ComputeHash($seed + [BitConverter]::GetBytes([uint32]$idx))
for ($j = 0; $j -lt $block.Length -and ($i + $j) -lt $plain.Length; $j++) {
$out[$i + $j] = $plain[$i + $j] -bxor $block[$j]
}
$i += $block.Length; $idx++
}
return $out
}
# Les blocs brouilles vont dans un dossier a part : brcc32 lit ce que le .rc
# lui montre, et on ne veut surtout pas ecraser les sources en clair.
$ObfDir = Join-Path $ScriptDir 'obf'
if (Test-Path $ObfDir) { Remove-Item $ObfDir -Recurse -Force }
New-Item -ItemType Directory -Path $ObfDir | Out-Null
foreach ($f in $files) {
$plain = $ObfMagic + [System.IO.File]::ReadAllBytes($f.FullPath)
$dest = Join-Path $ObfDir ($f.ResName + '.bin')
[System.IO.File]::WriteAllBytes($dest, (Obfuscate $plain $f.ResName))
$f | Add-Member -NotePropertyName ObfPath -NotePropertyValue $dest -Force
}
Log "Brouille $($files.Count) bloc(s) dans $ObfDir"
# --- Generate assets.rc -------------------------------------------------------
$rc = New-Object System.Text.StringBuilder
[void]$rc.AppendLine('// Auto-generated by BuildAssets.ps1 - do not edit by hand.')
@@ -162,7 +215,7 @@ $rc = New-Object System.Text.StringBuilder
[void]$rc.AppendLine('')
foreach ($f in $files) {
# brcc32 accepts forward slashes; backslashes need to be doubled in C strings
$rcPath = $f.FullPath -replace '\\','\\'
$rcPath = $f.ObfPath -replace '\\','\\'
[void]$rc.AppendLine("$($f.ResName) RCDATA `"$rcPath`"")
}
[System.IO.File]::WriteAllText($OutRC, $rc.ToString(), [System.Text.Encoding]::ASCII)