forked from jetelain/bis-file-formats
-
Notifications
You must be signed in to change notification settings - Fork 0
PBO Deobfuscator
Matthew Barker edited this page Jun 14, 2026
·
1 revision
PBO obfuscation reversal with config-driven profile system and reference updaters. Recovers corrupted or obfuscated PBO files where standard tools fail.
Depends on: BIS.PBO, BIS.P3D
Some PBO files use obfuscation techniques to hinder analysis:
- Decoy injection — extra fake entries scattered through the archive
- Cyrillic/garbage filenames — non-ASCII characters that crash naive parsers
- Missing extensions — files with no extension that must be identified by header
- Cross-file reference obfuscation — config.bin references pointing to Cyrillic names that were originally ASCII
The deobfuscator profiles detect these patterns structurally (not by tool signature), making them adaptable to new variants.
Main entry point. Runs registered profiles against a PBO and merges results.
var deobf = new PboDeobfuscator();
var pbo = new PBO("obfuscated.pbo", keepStreamOpen: true);
// Analyse
var result = deobf.Process(pbo);
Console.WriteLine($"Matched profile: {result.MatchedProfile}");
Console.WriteLine($"Filtered decoys: {result.FilteredOut.Count}");
Console.WriteLine($"Recovered names: {result.RecoveredNames.Count}");
// Rebuild a clean PBO
deobf.Rebuild(pbo, result, "cleaned.pbo");
pbo.Dispose();Holds the analysis results from all matched profiles.
| Property | Description |
|---|---|
MatchedProfile |
Name of the profile(s) that matched the PBO |
RecoveredNames |
Dictionary: entry index → recovered ASCII name |
FilteredOut |
List of entry indices identified as decoys |
Stats |
Per-profile diagnostic counters |
| Profile | Description |
|---|---|
DecoyInjectionProfile |
Detects and filters decoy/stub entries (small files, non-ASCII names, unusual size patterns) |
ModularSuffixRecoveryProfile |
Handles complex Cyrillic-based renaming via modular detection modules |
SuffixRecoveryProfile |
Recovers filenames from P3D model paths and known suffixes |
HeuristicFallbackProfile |
Catch-all for structural anomalies (high small-file ratio, unusual naming density) |
| Interface | Description |
|---|---|
IObfuscationProfile |
Implement to add a custom detection/recovery profile |
IDetectionModule |
A single detection heuristic used by multi-module profiles |
IRecoveryModule |
A single recovery strategy for name reconstruction |
IReferenceUpdater |
Updates cross-file references after renaming (e.g., config.bin → P3D paths) |
| Updater | Description |
|---|---|
P3DTextureReferenceUpdater |
Updates texture paths in P3D files to match recovered names |
RVMATReferenceUpdater |
Updates material file references to match recovered names |
ConfigReferenceUpdater |
Updates config.bin paths to match recovered names |
var deobf = new PboDeobfuscator();
using var pbo = new PBO("suspicious.pbo", keepStreamOpen: true);
var result = deobf.Process(pbo);
if (result.MatchedProfile != null)
{
Console.WriteLine($"Obfuscation detected: {result.MatchedProfile}");
Console.WriteLine($" Decoys removed: {result.FilteredOut.Count}");
Console.WriteLine($" Names recovered: {result.RecoveredNames.Count}");
foreach (var kvp in result.RecoveredNames)
{
Console.WriteLine($" [{kvp.Key}] -> {kvp.Value}");
}
}var deobf = new PboDeobfuscator();
using var pbo = new PBO("obfuscated.pbo", keepStreamOpen: true);
var result = deobf.Process(pbo);
if (result.MatchedProfile != null)
{
deobf.Rebuild(pbo, result, "recovered.pbo");
}public class MyCustomProfile : IObfuscationProfile
{
public string ProfileName => "MyCustomDetector";
public bool IsMatch(PBO pbo) { /* check signatures */ }
public DeobfuscationResult Deobfuscate(PBO pbo) { /* custom logic */ }
}
var deobf = new PboDeobfuscator();
deobf.RegisterProfile(new MyCustomProfile());
deobf.RegisterReferenceUpdater(new MyCustomUpdater());- Non-decoy entries are kept and their data preserved byte-for-byte.
- Decoy entries (in
FilteredOut) and zero-byte entries are excluded. - Recovered names are applied from
RecoveredNames(from profile heuristics) or generated via class-name analysis of config.bin. - Reference updaters scan kept entries (P3D files, config.bin, RVMATs) and rewrite internal paths to match the new filenames.
- A clean PBO is written with only essential properties (prefix, product) and a fresh SHA1 hash.
PBO deobfuscator tests are extensive (191 unit tests) and use sample
obfuscated PBOs. Test data is generated programmatically or sourced
from _testdata/pbo/.