Rust implementation of a Mythic Framework agent (wrapper=False, raw shellcode output). Features per-build function shuffle, ChaCha20 data-section obfuscation, dynamic API resolution via PEB walk, and a full Python Mythic Payload Type service.
25 KiB
STATE_OF_WORK.md — Proteus avancement
Ce fichier sert de carnet de bord pour ne pas se perdre entre sessions. À mettre à jour à chaque session significative.
Ne pas confondre avec :
ROADMAP.md— plan macro par phases (réf. opérationnelle long-terme)DATA_SECTIONS_CIPHERING.md— design de la Phase 1.5 (figé)docs/ARCHITECTURE.md— schéma global du système
1. Ligne du temps brève
| Date | Session | Précis |
|---|---|---|
| 2026-05-05 | — | Phase 1.5 design validé (ChaCha20 sur les data-sections + nonces per-build). |
| 2026-05-07 | — | Reprise du debug sample.exe qui s'arrête après [init] winhttp OK. Mise en place de logs granulaires + fix d'un bug d'init introduit en session précédente (null_mut() non importé). |
| 2026-05-08 | this | Restructuration interne des deux crates Rust (win/ regroupe l'ancien instance/ + rustic/ + utils/), split de tasking.rs en tasking.rs + messages.rs + commands.rs, dead-code dbj2_hash_seeded supprimé, OUT_DIR/hashes.rs renommé en OUT_DIR/build_config.rs. End-to-end cargo make mythic-payload revérifié. |
1.6. Build seed + déterminisme + split builder.py (2026-05-08, suite)
Quoi
BuildParameter("build_seed", String, default="auto")— l'opérateur choisit une seed (decimal ou0x…) ou laisseautopour qu'on en pioche une fresh viasecrets.randbits(64), echo'd dans le build log.- La seed est passée par
builders/common.make_env()à la fois enPROTEUS_BUILD_SEED(lu parbuild.rs) et enSHUFFLE_SEED(lu partools/gen-shuffled-linker.py), donc UNE valeur pilote les deux couches en lockstep. build.rs(proteus-agent + loaders) : remplacethread_rng()parmatch seed { Some(s) => StdRng::seed_from_u64(s), None => StdRng::from_entropy() }.Makefile.toml::SHUFFLE_SEED: passe en{ value="0", condition={ env_not_set=["SHUFFLE_SEED"] } }pour ne plus écraser l'override env.tools/link-shuffled.sh(loader) : ajout de--no-insert-timestampet--build-id=none.Makefile.toml::shuffle-strip(proteus-agent + loaders) :env = { SOURCE_DATE_EPOCH = "0" }pour quebinutils stripne réinsère pas un timestamp PE après ld.builder.py(Mythic builder) : split en 3 modules sousproteus/mythic/builders/:common.py—stage_crates,make_env,run(helpers asyncio + paths)shellcode.py—build_shellcode(tmp, cfg_json, seed, log) -> Pathloader.py—build_loader(tmp, cfg_json, seed, technique, log) -> PathLeProteus(PayloadType)ne fait plus que parser les paramètres + dispatch.
Vérification (cargo make mythic-payload, depuis la racine)
| Commande | proteus-agent sha256 | loader sha256 |
|---|---|---|
PROTEUS_BUILD_SEED=42 SHUFFLE_SEED=42 cargo make mythic-payload (run 1) |
9e334330… |
5a289291… |
PROTEUS_BUILD_SEED=42 SHUFFLE_SEED=42 cargo make mythic-payload (run 2) |
9e334330… ✓ identique |
5a289291… ✓ identique |
PROTEUS_BUILD_SEED=12345 SHUFFLE_SEED=12345 … |
8dfa43a4… ≠ |
différent |
cargo make mythic-payload (auto) |
différent à chaque run ✓ | différent à chaque run ✓ |
cmp -l entre deux builds avec la même seed retourne 0 — byte-pour-byte
identique, des octets de la DOS stub jusqu'à la fin du PE.
1.5. Restructuration interne des crates (2026-05-08)
Pourquoi
L'arbo intra-crate accumulait des modules grab-bag :
rustic/(nom vide de sens) mélangeait PEB walking, allocator, mem* shims, et un fauxhashes.rsqui n'était plus qu'uninclude!()du fichier généré parbuild.rs.instance/mélangeait le structInstancecentral et tous les wrappers Win32 par DLL.utils/était lui aussi un grab-bag (flags PE + UnicodeString + helpers nocrt).tasking.rsfaisait 702 lignes mêlant : main-loop, JSON builders Mythic, command handlers, parsers (ascii_trim,parse_u32,push_u64), sleep, RNG.
Quoi
Pour chacun des deux crates (proteus-agent, loaders), trois sous-arbres
disparaissent (instance/, rustic/, utils/) au profit d'un seul
win/ clair :
win/
├── peb.rs # NT structures + find_peb + get_cstr_len + UnicodeString
├── resolver.rs # find_module_by_name_w + ExportsIter + ascii_eq_ci(_w)
├── allocator.rs # #[global_allocator] (proteus-agent only)
├── nocrt.rs # mem* shims
├── instance.rs # Instance + INSTANCE_MAGIC + get_instance + load_dll
└── <dll>.rs # ntdll, kernel32, advapi32, winhttp, bcrypt, crypt32
Côté agent/, tasking.rs est splité en trois :
tasking.rs— main loop (run,do_initial_checkin_loop,do_tasking_loop,exchange_message).messages.rs— JSON builders (build_checkin_message,parse_tasking_response, …).commands.rs—cmd_*handlers + dispatch +sleep_with_jitter+ parsers.
Côté build :
OUT_DIR/hashes.rs→OUT_DIR/build_config.rs(le nom legacy n'avait plus rien à voir avec son contenu).- Dans proteus-agent,
crate::rustic::hashes→crate::build_config. - Dans loaders, le
hashes.rsqui n'incluait qu'un fichier vide est carrément supprimé (lebuild.rsdu loader n'écrit plus dehashes.rs).
Dead code supprimé
loaders/src/utils/utils.rs::dbj2_hash_seeded— la dernière fonction djb2 vivante.build.rs::djb2_hash_seededavait disparu lors du refacto PEB du 2026-05-07 mais le côté runtime du loader n'avait pas été nettoyé.- Re-exports inutilisés dans
win/mod.rs(init_crypt32,load_dll— toujours utilisés, mais via le path completcrate::win::instance::*, pas via la re-exportation top-level).
Confirmation que le compile-time hashing est mort
Recherche git grep -in 'djb2' après nettoyage : seul résultat est ce
paragraphe + les notes historiques dans build.rs. Plus aucun appel à
runtime, plus aucune table émise par build.rs. Toute résolution Win32
passe par crate::win::resolver + comparaison ASCII-CI directe contre
les noms obf_bytes!-encryptés.
Vérif end-to-end
$ cargo make mythic-payload
[shuffle] seed=4151969711624575314 fns=322 rdata_prx=247 -> Linker.shuffled.ld
[link] -> Proteus.bin (34232 bytes)
[shuffle] seed=4643722448869141158 fns=61 rdata_prx=33 pe=True
[link] -> proteus-loader.exe (41984 bytes)
[mythic-payload] tech=apc-self
$ strings -n 6 Proteus.bin | grep -iE 'checkin|action|tasking|kernel32' | wc -l
0
Aucun literal Mythic-protocole / Win32 ne fuit ; Phase 1.5 toujours intacte après le refacto.
2. Bug résolu (2026-05-07) — sample.exe silencieux après winhttp OK
Cause racine
Collision de INSTANCE_MAGIC entre loader et proteus-agent. Les deux crates définissaient INSTANCE_MAGIC = 0x17171717 et registraient leur propre Instance dans PEB.ProcessHeaps. Le loader's slot venait avant celui du shellcode dans le tableau ; quand proteus-agent::get_instance() walkait PEB.ProcessHeaps, il trouvait LE LOADER en premier et retournait son Instance (avec un AgentState tout zéro — le loader n'a pas d'AgentState). Résultat : running lu comme false, le do-while du checkin loop sautait, l'agent quittait silencieusement via NtTerminateProcess.
À noter : crates/loaders/src/utils/ était un symlink vers crates/proteus-agent/src/utils/, donc impossible de tenir deux valeurs distinctes. Le symlink est maintenant cassé, et chaque crate a son propre flags.rs.
Fix appliqué
proteus-agent/src/utils/flags.rs:INSTANCE_MAGIC = 0x17171717(inchangé)loaders/src/utils/flags.rs:INSTANCE_MAGIC = 0x4C4F4144("LOAD")- Symlink
loaders/src/utils → proteus-agent/src/utilssupprimé, fichiers indépendants null_mutimporté dansmain.rs(compile-error qui bloquait la rebuild)dbg_log!self-guarded (null-check sur les fp kernel32) — safe avant init_kernel32- Logs granulaires ajoutés dans
main.rs::initialize()etinstance/advapi32.rs
Vérification
End-to-end après fix :
[task] instance ptr: 1975572695136 ← matches alloc returned ptr
[task] running: 1 ← TRUE
[task] sleep_seconds: 10 ← from CFG_SLEEP_SECONDS
[task] entering initial checkin loop
[checkin] attempt ← effectivement tente
[seal] plaintext bytes: 171
L'agent essaie maintenant de checkin contre 127.0.0.1:7443 (config par défaut). Pour valider end-to-end, il faut un serveur Mythic réel.
Lecture pour reprendre le contexte plus tard
crates/loaders/src/utils/flags.rs— commentaire explicite sur la distinctioncrates/proteus-agent/src/utils/flags.rs— idemcrates/loaders/src/main.rs::initialize()— où le loader register son Instance (slot K)crates/proteus-agent/src/main.rs::initialize()— où proteus-agent register la sienne (slot K+1)crates/proteus-agent/src/instance/instance.rs::get_instance()— la fonction qui matchait au mauvais Instance
3. Refacto résolution PEB (livrée 2026-05-07)
Avant
Pour chaque module, ldr_module(MODULE_HASH) walkait PEB.LoaderData en hashant chaque nom rencontré. Pour chaque fonction, ldr_function(base, FUNC_HASH) itérait toute la table d'exports en hashant chaque nom.
- N=30 fonctions résolues ⇒ 30 walks d'export tables
- Build.rs émettait une table
MODULE_SEED + FUNCTION_SEED + *_HASHrégénérée à chaque cargo build - OPSEC : justifiable AVANT Phase 1.5, redondant APRÈS
Après
crates/proteus-agent/src/rustic/peb_enum.rs(NEW) —find_module_by_name_w(un walk) +ExportsIter(itérateur direct sur les exports d'un module).- Même fichier dupliqué côté loader :
crates/loaders/src/rustic/peb_enum.rs. - Chaque
init_*:let target_w = obf_utf16!(b"<module>.dll")— décodé sur la stacklet base = find_module_by_name_w(&target_w)— un seul walk PEB- Pré-décode TOUTES les fonctions cibles avec
obf_bytes! - Une seule itération
for (name, addr) in ExportsIter::new(base) - Cascade
if ascii_eq_ci(name, &target) { instance.X.field = transmute(addr); }pour chaque cible
- Comparaison ASCII-CI directe via
peb_enum::ascii_eq_ci{,_w}— pas de djb2. - Pour les modules pas encore mappés (winhttp/advapi32) :
crate::instance::load_dll(&path_z)(kernel32!LoadLibraryW) puis re-walk.
Suppressions
crates/proteus-agent/src/rustic/ldrapi.rs— supprimécrates/loaders/src/rustic/ldrapi.rs(était symlink vers le précédent) — supprimécrates/proteus-agent/build.rs— toute la machinerieMODULE_SEED, FUNCTION_SEED, libraries[], functions[], *_HASHretirée. Conservé : émission CFG_* + master_key + nonce_salt.crates/loaders/build.rs— idem (et émet désormais master_key + nonce_salt pour proteus-obf, qu'il n'utilisait pas avant).crates/proteus-agent/src/utils/utils.rs—dbj2_hash_seededretirée (inutile).- Symlinks
loaders/src/utils → proteus-agent/src/utilsetloaders/src/rustic/{hashes,nocrt,ntpeb}.rs → proteus-agent/...cassés : chaque crate a maintenant ses propres copies indépendantes.
Validation end-to-end (run réussi sur la VM lab)
[init] ntdll + kernel32 OK
[init] heap OK
[init] winhttp OK
[init] advapi32 OK
[init] agent state populated
[init] -> run()
[task] tasking::run entry
[task] runtime config loaded
[task] callback port: 7443
[task] get_uri: /api/v1.4/agent_message
[task] post_uri: /api/v1.4/agent_message
[task] entering initial checkin loop
[checkin] attempt
[seal] plaintext bytes: 171
L'agent boote, peuple son AgentState (running=true, sleep_seconds=10), tente le checkin contre 127.0.0.1:7443. Pour aller plus loin, il faut un Mythic réel.
3.5 Layout du repo (refacto 2026-05-07)
L'arborescence des Rust crates a été déplacée de Proteus/crates/ (top-level) vers Proteus/Payload_Type/proteus/crates/. Raison : mythic-cli install folder ne suit pas les symlinks de directories — l'ancien layout reposait sur trois symlinks Payload_Type/proteus/proteus/{agent_code,loader_code,layout_manifest_code} qui pointaient vers le top-level crates/, et mythic-cli les copiait comme fichiers vides de 0 octet. Le shutil.copytree du builder.py crashait ensuite avec NotADirectoryError.
Layout actuel :
Proteus/
├── config.json # gateway mythic-cli
├── Makefile.toml # délègue vers Payload_Type/proteus/crates/*
├── README.md, ROADMAP.md, docs/ # doc projet
└── Payload_Type/
└── proteus/
├── Dockerfile # COPY crates crates (build context = ce dir)
├── main.py
├── requirements.txt # mythic-container == 0.6.16
├── crates/ # ← canonical location, plus de symlinks
│ ├── proteus-agent/
│ ├── loaders/
│ ├── layout-manifest/
│ ├── proteus-obf/
│ ├── proteus-obf-macros/
│ └── mini-loader/
└── proteus/ # python package
├── __init__.py
└── mythic/
├── __init__.py # auto-import des agent_functions/
└── agent_functions/
├── builder.py
├── exit.py, pwd.py, sleep.py, whoami.py
└── ...
Conséquences :
cargo make shuffleau top-level marche : rootMakefile.tomldélègue versPayload_Type/proteus/crates/proteus-agent.sudo mythic-cli install folder <path-to-repo> -fmarche directement : pas de staging, pas de tmp dir, pas de symlinks.- La règle
cargo make install-mythic(staging hack) a été supprimée — n'avait plus lieu d'être.
4. Conventions de l'architecture (post-refacto)
Le principe single-pass à respecter
Ne jamais ajouter une nouvelle fonction Win32 à résoudre par hash + walk dédié. Ajouter à la place :
- Le type Win32 + le champ dans la struct du module
- La déclaration dans
Module::new()(transmute null) - Un nouveau
let n_xxx = obf_bytes!(b"FuncName");dans leinit_*correspondant - Une nouvelle branche
else if ascii_eq_ci(name, &n_xxx) { instance.module.xxx = transmute(addr); }
Loader vs proteus-agent — code partiellement dupliqué
Les helpers peb_enum, nocrt, ntpeb existent en deux copies indépendantes (loader + proteus-agent). C'est volontaire après le bug INSTANCE_MAGIC : les symlinks ont été cassés pour permettre des magic distincts. Si on veut éviter la divergence, extraire en crate partagée — mais à coût de refacto et out-of-scope ici.
INSTANCE_MAGIC — TOUJOURS distincts entre loader et proteus-agent
crates/loaders/src/utils/flags.rs:0x4C4F4144("LOAD")crates/proteus-agent/src/utils/flags.rs:0x17171717Tout changement doit garder cette distinction. Lire les commentaires dans les deux fichiers.
Diagnostic
Trois bugs distincts ont été identifiés dans crates/proteus-agent/src/main.rs/log.rs :
Bug A — null_mut() utilisé sans import (compile-error)
La session précédente a introduit un reentrancy guard :
(*peb).sub_system_data = null_mut();
…sans ajouter use core::ptr::null_mut;. Donc le code actuel ne compile pas. Le binaire que l'opérateur a lancé date d'avant ce changement. Fix : ajouter l'import.
Bug B — dbg_log!("[init] ntdll OK") appelé avant init_kernel32()
La macro dbg_log! appelle instance.kernel32.write_file(...) et instance.kernel32.get_std_handle(...). Avant init_kernel32(), ces pointeurs sont transmute(null_mut()) — appeler un function pointer NULL en x64 = access violation.
Fix défense en profondeur : write_raw doit guard les pointeurs avant de les déréférencer, pour qu'on puisse appeler dbg_log! à n'importe quel moment du boot sans risque. Bonus : si on perd un log précoce, le programme survit.
Bug C — Pas de log granulaire dans le segment qui crashe
Entre [init] winhttp OK et le premier log de agent::tasking::run ([task] tasking::run entry), il y a au minimum :
init_advapi32()Layout::new::<Instance>()+alloc(layout)copy_nonoverlapping(temp → permanent)*process_heaps.add(N) = permanent_instance_ptr(swap PEB slot)(*permanent_instance_ptr).agent.populate()(decode CFG_UUID via ChaCha20)run()→agent::tasking::run()
Sans logs intermédiaires, impossible de savoir lequel échoue. Fix : un dbg_log! autour de chaque étape.
Hypothèses ordonnées (à invalider une par une après rebuild)
alloc(layout)retourne NULL.NT_HEAPALLOCATOR.initialize()a peut-être échoué silencieusement (RtlCreateHeap → 0). On a pas vérifié son retour.- PEB slot écrasé.
LoadLibraryW("winhttp.dll")charge winhttp → DllMain peut faireHeapCreate()→ écrit dans le slot adjacent. Notre slot Instance reste valable mais la séquence*process_heaps.add(number_of_heaps) = …(re-write avec le permanent ptr) écrit à un index qui peut-être occupé par autre chose maintenant. init_advapi32échoue silencieusement. Hash mismatch (peu probable), ouobf_utf16_z!qui décode mal le path (très peu probable, c'est de l'arithmétique pure).agent.populate()panic.cfg_decodefaitunreachable_uncheckedsidst.len() < blob.len(). Tous les call-sites passent des buffersMAX_*_LENdonc ça ne devrait pas se déclencher.
3. État du code (par crate)
Payload_Type/proteus/crates/proteus-agent/
Agent core, no_std, x86_64-pc-windows-gnu, output ~34KB flat binary
(Proteus.bin) après Phase 1.5. Layout après refacto 2026-05-08 :
| Sous-arbre | Contenu |
|---|---|
src/main.rs |
_start, initialize, run, _Unwind_Resume |
src/log.rs |
dbg_log! family (gated par feature debug-log) |
src/obf.rs |
MASTER_KEY static + re-exports obf!* macros |
src/build_config.rs |
include!() du fichier généré par build.rs |
src/win/{peb,resolver,allocator,nocrt,instance}.rs |
OS surface — PEB, single-pass resolver, NT heap allocator, mem* shims, Instance + INSTANCE_MAGIC + get_instance |
src/win/{ntdll,kernel32,winhttp,advapi32,bcrypt,crypt32}.rs |
Wrappers Win32 par DLL avec init_* single-pass |
src/agent/{config,state}.rs |
AgentConfig + AgentState |
src/agent/{tasking,messages,commands}.rs |
Loop principale + JSON builders + handlers de commandes (split du tasking.rs original 702 lignes) |
src/comms/{http,crypto,envelope,json}.rs |
Stack comms (WinHTTP + BCrypt + Mythic envelope + JSON) |
build.rs |
Émet master_key + nonce_salt + build_config.rs (CFG_*) par build |
Payload_Type/proteus/crates/loaders/
Phase 0.5 done. Embed Proteus.bin via include_bytes!, applique une
technique d'injection (tech-self-rwx / tech-self-rw-to-rx /
tech-apc-self / tech-local-thread). Layout symétrique à proteus-agent
mais réduit (pas de comms/, pas de agent/, win/ sans allocator) :
| Sous-arbre | Contenu |
|---|---|
src/main.rs |
_start, initialize (sans heap step) |
src/obf.rs |
MASTER_KEY static + re-exports obf!* macros |
src/shellcode.rs |
pub static SHELLCODE = include_bytes!("../../proteus-agent/Proteus.bin") |
src/win/{peb,resolver,nocrt,instance,ntdll,kernel32}.rs |
Subset OS surface (pas d'allocator, pas de winhttp/bcrypt/advapi32/crypt32) |
src/techniques/{self_rwx,self_rw_to_rx,apc_self,local_thread}.rs |
Une technique par fichier, gated par feature tech-<name> |
Payload_Type/proteus/crates/proteus-obf + proteus-obf-macros
Phase 1.5 livrée. ChaCha20 keystream + nonce salt per-build. Aucun changement structurel le 2026-05-08.
Payload_Type/proteus/crates/layout-manifest
Outil parse-COFF (host-side, dev tool) qui sort un manifest JSON. Utilisé pré-link pour valider les sections / relocations. Indépendant des autres crates.
Payload_Type/proteus/proteus/ (Mythic side, Python style thanatos — pas Go)
Skeleton en place : Dockerfile, builder.py, main.py. Auto-import des agent_functions/. Pas testé contre un vrai serveur Mythic.
4. Phase 1.5 — État final
Acceptance partielle :
- CFG_* migrés sur ChaCha20 (
build.rs::emit_chacha) obf!/obf_bytes!/obf_utf16_z!couvrent toutes les literals Mythic-protocol- Pas de
format!()dans le code agent unwrap_unchecked()au lieu deunwrap()pour drop le panic-format de.rdata- Per-build nonce salt → samples non-corrélables byte-à-byte
strings -n 6 Proteus.binne montre plus aucun literal Mythic- Reste un run de 643 bytes commun entre 2 builds — probablement le
chacha20_blockpur arithmétique. À traiter par inline+specialize ou par instruction substitution. Reporté.
5. Conventions et pièges connus
Boot sequence — fragile, lire avant de modifier
_start (asm) → initialize() ──► find_peb()
├─► reentrancy guard via peb.sub_system_data
├─► register temp Instance dans PEB.ProcessHeaps
├─► init_ntdll (résolution hash de RtlCreateHeap, etc.)
├─► init_kernel32 ⚠ DOIT venir avant tout dbg_log!
├─► NT_HEAPALLOCATOR.initialize()
├─► init_winhttp (LoadLibraryW + ldr_module)
├─► init_advapi32 (idem)
├─► alloc Instance permanent + copy_nonoverlapping
├─► swap PEB slot vers le permanent
├─► agent.populate() (decode CFG_UUID etc.)
└─► run() → tasking
Règles de vie pour dbg_log!
- Active uniquement avec la feature
debug-log(cargo make shuffleavecPROTEUS_FEATURES=debug-logou similaire). - Sûr d'usage à tout moment dès que log.rs a la garde sur les fp (cf. fix 2026-05-07).
- Les literals texte cassent l'objectif Phase 1.5 — production builds = feature OFF.
Règles de vie pour obf! / obf_bytes! / obf_utf16_z!
- Toute literal Mythic-protocole, header HTTP, ou nom de DLL DOIT passer par
obf*!. - Pas de
format!()côté agent —format!fait revenir les tablescore::fmt::numen clair. - Ne jamais logger une obf-decoded value ; ça réintroduit le plaintext temporairement dans
.rdatavia le format-string du log.
Règle de vie pour la mémoire
Instancevit dansPEB.ProcessHeaps[N]identifié parmagic = 0x17171717.get_instance()walk le tableau et match surmagic.- Ne jamais ajouter un champ avant
magicdans la structInstancesans réfléchir.
6. Prochaines étapes immédiates
- ✅ Cartographier le code (sessions 2026-05-07 + 2026-05-08)
- ✅ Diagnostiquer + fixer les 3 bugs main.rs/log.rs (livré 2026-05-07)
- ✅ Refacto interne des crates (livré 2026-05-08, voir §1.5)
- ⬜ Test end-to-end checkin contre un vrai serveur Mythic sur le lab
- ⬜ Continuer Phase 2 (filesystem ops :
cd,ls,cat,cp,mv,rm,mkdir) — sites canoniques sontagent/commands.rspour les handlers etwin/kernel32.rspour ajouter les imports Win32 (single-pass dansinit_kernel32). - ⬜ Phase 1.5 §6 (Phase 2 ROADMAP) — si possible casser le run de
~643 bytes commun entre 2 builds (probablement
chacha20_block— inline+specialize per call site ou instruction substitution).
7. Comment reprendre cette session si elle est interrompue
- Lire ce fichier en entier (notamment §1.5 pour le layout actuel).
- Lire les memory entries (
MEMORY.md). - Lire
crates/proteus-agent/src/main.rspour le boot order. - Lire
crates/proteus-agent/src/agent/tasking.rspour la main loop. cargo make mythic-payloaddepuis la racineProteus/— doit produire unProteus.bin~34KB et unproteus-loader.exe~42KB sans warning.