├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── DurangoKeyExtractor ├── DurangoKeyExtractor.csproj ├── Extractor.cs └── Program.cs ├── LICENSE ├── LibXboxOne.Tests ├── AesCipherTests.cs ├── AesXtsTests.cs ├── BCryptImportTests.cs ├── DurangoKeysTests.cs ├── HashUtilsTests.cs ├── LibXboxOne.Tests.csproj ├── Resources │ ├── DataBlobs │ │ ├── xbfs_header.bin │ │ ├── xts_cipher.bin │ │ └── xts_plain.bin │ └── RsaKeys │ │ ├── RSA_1024_CAPIPRIVATEBLOB.bin │ │ ├── RSA_1024_CAPIPUBLICBLOB.bin │ │ ├── RSA_1024_RSAFULLPRIVATEBLOB.bin │ │ ├── RSA_1024_RSAPRIVATEBLOB.bin │ │ ├── RSA_1024_RSAPUBLICBLOB.bin │ │ ├── RSA_2048_CAPIPRIVATEBLOB.bin │ │ ├── RSA_2048_CAPIPUBLICBLOB.bin │ │ ├── RSA_2048_RSAFULLPRIVATEBLOB.bin │ │ ├── RSA_2048_RSAPRIVATEBLOB.bin │ │ ├── RSA_2048_RSAPUBLICBLOB.bin │ │ ├── RSA_3072_CAPIPRIVATEBLOB.bin │ │ ├── RSA_3072_CAPIPUBLICBLOB.bin │ │ ├── RSA_3072_RSAFULLPRIVATEBLOB.bin │ │ ├── RSA_3072_RSAPRIVATEBLOB.bin │ │ ├── RSA_3072_RSAPUBLICBLOB.bin │ │ ├── RSA_4096_CAPIPRIVATEBLOB.bin │ │ ├── RSA_4096_CAPIPUBLICBLOB.bin │ │ ├── RSA_4096_RSAFULLPRIVATEBLOB.bin │ │ ├── RSA_4096_RSAPRIVATEBLOB.bin │ │ ├── RSA_4096_RSAPUBLICBLOB.bin │ │ ├── RSA_512_CAPIPRIVATEBLOB.bin │ │ ├── RSA_512_CAPIPUBLICBLOB.bin │ │ ├── RSA_512_RSAFULLPRIVATEBLOB.bin │ │ ├── RSA_512_RSAPRIVATEBLOB.bin │ │ └── RSA_512_RSAPUBLICBLOB.bin ├── ResourcesProvider.cs ├── XbfsTests.cs ├── XvdFileTests.cs └── XvdHashBlockTests.cs ├── LibXboxOne ├── AppDirs.cs ├── Certificates │ ├── BootCapability.cs │ ├── BootCapabilityCert.cs │ └── PspConsoleCert.cs ├── Common.cs ├── Crypto │ ├── AesXts.cs │ ├── BCryptRsaImport.cs │ └── HashUtils.cs ├── Keys │ ├── DurangoKeys.cs │ ├── KeyEntry.cs │ ├── KeyType.cs │ └── OdkIndex.cs ├── LibXboxOne.csproj ├── MiscStructs.cs ├── NAND │ ├── XbfsEntry.cs │ ├── XbfsFile.cs │ └── XbfsHeader.cs ├── Shared.cs ├── ThirdParty │ ├── ProgressBar.cs │ └── TableParserExtensions.cs └── XVD │ ├── XVDEnums.cs │ ├── XVDFile.cs │ ├── XVDLicenseBlock.cs │ ├── XVDMount.cs │ ├── XVDStructs.cs │ ├── XvcConstants.cs │ ├── XvcRegionId.cs │ ├── XvdFilesystem.cs │ ├── XvdFilesystemStream.cs │ └── XvdMath.cs ├── README.md ├── XBFSTool ├── Program.cs └── XBFSTool.csproj ├── XVDTool.sln ├── XVDTool ├── Program.cs └── XVDTool.csproj └── xvd_info.md /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | prepare: 7 | runs-on: ubuntu-latest 8 | outputs: 9 | AssemblySemFileVer: ${{ steps.gitversion.outputs.assemblySemFileVer }} 10 | AssemblySemVer: ${{ steps.gitversion.outputs.assemblySemVer }} 11 | SemVer: ${{ steps.gitversion.outputs.semVer }} 12 | ShortSha: ${{ steps.gitversion.outputs.shortSha }} 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: Install GitVersion 18 | uses: gittools/actions/gitversion/setup@v1.1.1 19 | with: 20 | versionSpec: x # Latest 21 | - name: Use GitVersion 22 | id: gitversion 23 | uses: gittools/actions/gitversion/execute@v1.1.1 24 | 25 | build_and_test: 26 | runs-on: ubuntu-latest 27 | outputs: 28 | artifact_name: ${{ steps.prepare_artifacts.outputs.artifact_name }} 29 | needs: prepare 30 | steps: 31 | - uses: actions/setup-dotnet@v4 32 | with: 33 | dotnet-version: 8.0 34 | - uses: actions/checkout@v4 35 | - name: Build 36 | run: dotnet build -c Release 37 | - name: Test 38 | run: dotnet test --no-restore 39 | - name: Publish 40 | run: dotnet publish --no-restore -c Release -o release /p:"AssemblyVersion=${{ steps.gitversion.outputs.assemblySemFileVer }};FileVersion=${{ steps.gitversion.outputs.assemblySemFileVer }};InformationalVersion=${{ steps.gitversion.outputs.SemVer }}-${{ steps.gitversion.outputs.ShortSha }}" 41 | - name: Prepare artifacts 42 | id: prepare_artifacts 43 | run: | 44 | cp {README,xvd_info,CHANGELOG}.md release 45 | echo "artifact_name=XVDTool-${{ needs.prepare.outputs.SemVer }}-${{ needs.prepare.outputs.ShortSha }}" >> $GITHUB_OUTPUT 46 | - name: Upload artifacts 47 | uses: actions/upload-artifact@v4 48 | with: 49 | name: ${{ steps.prepare_artifacts.outputs.artifact_name }} 50 | path: release 51 | if-no-files-found: error 52 | 53 | create_release: 54 | runs-on: ubuntu-latest 55 | needs: [prepare, build_and_test] 56 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 57 | steps: 58 | - name: Download artifacts 59 | uses: actions/download-artifact@v3 60 | - name: Generate zip 61 | run: zip -j XVDTool-${{ needs.prepare.outputs.AssemblySemVer }}.zip ${{ needs.build_and_test.outputs.artifact_name }}/* 62 | - name: Create release (on tag) 63 | uses: softprops/action-gh-release@v2 64 | with: 65 | name: Release ${{ github.ref }} 66 | tag_name: ${{ github.ref }} 67 | files: 'XVDTool-*.zip' 68 | fail_on_unmatched_files: true 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | # User-specific files 6 | *.rsuser 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # macOS 16 | .DS_Store 17 | ._.DS_Store 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015/2017 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # Visual Studio 2017 auto generated files 39 | Generated\ Files/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUNIT 46 | *.VisualState.xml 47 | TestResult.xml 48 | 49 | # Build Results of an ATL Project 50 | [Dd]ebugPS/ 51 | [Rr]eleasePS/ 52 | dlldata.c 53 | 54 | # Benchmark Results 55 | BenchmarkDotNet.Artifacts/ 56 | 57 | # .NET Core 58 | project.lock.json 59 | project.fragment.lock.json 60 | artifacts/ 61 | 62 | # StyleCop 63 | StyleCopReport.xml 64 | 65 | # Files built by Visual Studio 66 | *_i.c 67 | *_p.c 68 | *_h.h 69 | *.ilk 70 | *.meta 71 | *.obj 72 | *.iobj 73 | *.pch 74 | *.pdb 75 | *.ipdb 76 | *.pgc 77 | *.pgd 78 | *.rsp 79 | *.sbr 80 | *.tlb 81 | *.tli 82 | *.tlh 83 | *.tmp 84 | *.tmp_proj 85 | *_wpftmp.csproj 86 | *.log 87 | *.vspscc 88 | *.vssscc 89 | .builds 90 | *.pidb 91 | *.svclog 92 | *.scc 93 | 94 | # Chutzpah Test files 95 | _Chutzpah* 96 | 97 | # Visual C++ cache files 98 | ipch/ 99 | *.aps 100 | *.ncb 101 | *.opendb 102 | *.opensdf 103 | *.sdf 104 | *.cachefile 105 | *.VC.db 106 | *.VC.VC.opendb 107 | 108 | # Visual Studio profiler 109 | *.psess 110 | *.vsp 111 | *.vspx 112 | *.sap 113 | 114 | # Visual Studio Trace Files 115 | *.e2e 116 | 117 | # TFS 2012 Local Workspace 118 | $tf/ 119 | 120 | # Guidance Automation Toolkit 121 | *.gpState 122 | 123 | # ReSharper is a .NET coding add-in 124 | _ReSharper*/ 125 | *.[Rr]e[Ss]harper 126 | *.DotSettings.user 127 | 128 | # JustCode is a .NET coding add-in 129 | .JustCode 130 | 131 | # TeamCity is a build add-in 132 | _TeamCity* 133 | 134 | # DotCover is a Code Coverage Tool 135 | *.dotCover 136 | 137 | # AxoCover is a Code Coverage Tool 138 | .axoCover/* 139 | !.axoCover/settings.json 140 | 141 | # Visual Studio code coverage results 142 | *.coverage 143 | *.coveragexml 144 | 145 | # NCrunch 146 | _NCrunch_* 147 | .*crunch*.local.xml 148 | nCrunchTemp_* 149 | 150 | # MightyMoose 151 | *.mm.* 152 | AutoTest.Net/ 153 | 154 | # Web workbench (sass) 155 | .sass-cache/ 156 | 157 | # Installshield output folder 158 | [Ee]xpress/ 159 | 160 | # DocProject is a documentation generator add-in 161 | DocProject/buildhelp/ 162 | DocProject/Help/*.HxT 163 | DocProject/Help/*.HxC 164 | DocProject/Help/*.hhc 165 | DocProject/Help/*.hhk 166 | DocProject/Help/*.hhp 167 | DocProject/Help/Html2 168 | DocProject/Help/html 169 | 170 | # Click-Once directory 171 | publish/ 172 | 173 | # Publish Web Output 174 | *.[Pp]ublish.xml 175 | *.azurePubxml 176 | # Note: Comment the next line if you want to checkin your web deploy settings, 177 | # but database connection strings (with potential passwords) will be unencrypted 178 | *.pubxml 179 | *.publishproj 180 | 181 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 182 | # checkin your Azure Web App publish settings, but sensitive information contained 183 | # in these scripts will be unencrypted 184 | PublishScripts/ 185 | 186 | # NuGet Packages 187 | *.nupkg 188 | # The packages folder can be ignored because of Package Restore 189 | **/[Pp]ackages/* 190 | # except build/, which is used as an MSBuild target. 191 | !**/[Pp]ackages/build/ 192 | # Uncomment if necessary however generally it will be regenerated when needed 193 | #!**/[Pp]ackages/repositories.config 194 | # NuGet v3's project.json files produces more ignorable files 195 | *.nuget.props 196 | *.nuget.targets 197 | 198 | # Microsoft Azure Build Output 199 | csx/ 200 | *.build.csdef 201 | 202 | # Microsoft Azure Emulator 203 | ecf/ 204 | rcf/ 205 | 206 | # Windows Store app package directories and files 207 | AppPackages/ 208 | BundleArtifacts/ 209 | Package.StoreAssociation.xml 210 | _pkginfo.txt 211 | *.appx 212 | 213 | # Visual Studio cache files 214 | # files ending in .cache can be ignored 215 | *.[Cc]ache 216 | # but keep track of directories ending in .cache 217 | !?*.[Cc]ache/ 218 | 219 | # Others 220 | ClientBin/ 221 | ~$* 222 | *~ 223 | *.dbmdl 224 | *.dbproj.schemaview 225 | *.jfm 226 | *.pfx 227 | *.publishsettings 228 | orleans.codegen.cs 229 | 230 | # Including strong name files can present a security risk 231 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 232 | #*.snk 233 | 234 | # Since there are multiple workflows, uncomment next line to ignore bower_components 235 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 236 | #bower_components/ 237 | # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true 238 | **/wwwroot/lib/ 239 | 240 | # RIA/Silverlight projects 241 | Generated_Code/ 242 | 243 | # Backup & report files from converting an old project file 244 | # to a newer Visual Studio version. Backup files are not needed, 245 | # because we have git ;-) 246 | _UpgradeReport_Files/ 247 | Backup*/ 248 | UpgradeLog*.XML 249 | UpgradeLog*.htm 250 | ServiceFabricBackup/ 251 | *.rptproj.bak 252 | 253 | # SQL Server files 254 | *.mdf 255 | *.ldf 256 | *.ndf 257 | 258 | # Business Intelligence projects 259 | *.rdl.data 260 | *.bim.layout 261 | *.bim_*.settings 262 | *.rptproj.rsuser 263 | *- Backup*.rdl 264 | 265 | # Microsoft Fakes 266 | FakesAssemblies/ 267 | 268 | # GhostDoc plugin setting file 269 | *.GhostDoc.xml 270 | 271 | # Node.js Tools for Visual Studio 272 | .ntvs_analysis.dat 273 | node_modules/ 274 | 275 | # Visual Studio 6 build log 276 | *.plg 277 | 278 | # Visual Studio 6 workspace options file 279 | *.opt 280 | 281 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 282 | *.vbw 283 | 284 | # Visual Studio LightSwitch build output 285 | **/*.HTMLClient/GeneratedArtifacts 286 | **/*.DesktopClient/GeneratedArtifacts 287 | **/*.DesktopClient/ModelManifest.xml 288 | **/*.Server/GeneratedArtifacts 289 | **/*.Server/ModelManifest.xml 290 | _Pvt_Extensions 291 | 292 | # Paket dependency manager 293 | .paket/paket.exe 294 | paket-files/ 295 | 296 | # FAKE - F# Make 297 | .fake/ 298 | 299 | # JetBrains Rider 300 | .idea/ 301 | *.sln.iml 302 | 303 | # CodeRush personal settings 304 | .cr/personal 305 | 306 | # Python Tools for Visual Studio (PTVS) 307 | __pycache__/ 308 | *.pyc 309 | 310 | # Cake - Uncomment if you are using it 311 | # tools/** 312 | # !tools/packages.config 313 | 314 | # Tabs Studio 315 | *.tss 316 | 317 | # Telerik's JustMock configuration file 318 | *.jmconfig 319 | 320 | # BizTalk build output 321 | *.btp.cs 322 | *.btm.cs 323 | *.odx.cs 324 | *.xsd.cs 325 | 326 | # OpenCover UI analysis results 327 | OpenCover/ 328 | 329 | # Azure Stream Analytics local run output 330 | ASALocalRun/ 331 | 332 | # MSBuild Binary and Structured Log 333 | *.binlog 334 | 335 | # NVidia Nsight GPU debugger configuration file 336 | *.nvuser 337 | 338 | # MFractors (Xamarin productivity tool) working folder 339 | .mfractor/ 340 | 341 | # Local History for Visual Studio 342 | .localhistory/ 343 | 344 | # BeatPulse healthcheck temp database 345 | healthchecksdb 346 | 347 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 348 | MigrationBackup/ 349 | 350 | # Application specific 351 | *.rsa 352 | *.odk 353 | *.cik 354 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Added 9 | - General: 10 | 11 | ## [0.53] - 2020-09-11 12 | ### Fixed 13 | - XVDTool: OverflowError on "-xf" / extract files for big files 14 | 15 | ## [0.52] - 2020-08-10 16 | ### Added 17 | - General: Start using CHANGELOG.md 18 | - XBFSTool: Add printing of certificate info via -c switch 19 | - XVDTool: AddMutableData function, -addmdu cmdline switch 20 | - XVDTool: For XVC decryption, iterate through all loaded CIKs to find a matching one 21 | - XVDTool: Support supplying mountpoint to xvd mounting (-mp / -mountpoint) 22 | - XvdStructs: Add new XvdHeader fields -> resilient data 23 | - XvdStructs: Skip signature verification on console-signed xvds 24 | - XvdFilesystem: Implement XvdFilesystem operations via XvdFilesystemStream 25 | - XvdFilesystem: Utilize DiscUtils for vhd conversion 26 | - XvdFile: Print filesystem info 27 | - XvdFile: Add Add/RemoveMutableData function & -addmdu/-removemdu tool option 28 | - XvdFile: Move data-removal logic from RemoveHashTree into seperate RemoveData function 29 | - XvdFile: Allow fetching dataUnit from hashtables during CryptSectionXts 30 | 31 | ### Changed 32 | - General: Bump to netcoreapp3.1 / netstandard2.1 33 | - XvdFile: Move duplicated "get hash entry offset" code to its own function 34 | - XvdFile: Remove unneeded copy constructors for structs 35 | - XVDTool: Make changes so that Save() is only called once in the session 36 | 37 | ### Fixed 38 | - XvdFile: Add fixups for XvdUpdateSegments 39 | - XvdMath: Fix InBlockOffset calculation 40 | - XvdFile: Improve VerifyXvcHash & fix AddHashTree 41 | - XvdFile: Print UpdateSegments/RegionSpecifiers as table, fix reading RegionSpecifiers 42 | - XVDTool: Fix AppDirs path creation for Linux 43 | 44 | ## [0.51] - 2019-03-21 45 | ### Added 46 | - General: Ship release archives with README.md 47 | 48 | ### Changed 49 | - General: Release archive is packed without a subfolder 50 | 51 | ## [0.5] - 2019-03-21 52 | ### Added 53 | - DurangoKeyExtractor: Add tool to extract known keys from binaries 54 | - Tests: Added tests for hash block related functions 55 | - XVDTool: Add Xvc enums / structs 56 | - XVDTool: Enable raw filesystem extraction, rework VHD extraction 57 | - XVDTool: Add XvdMountFlags 58 | - XVDTool: Name previously unknown XvdMount function arguments 59 | - XVDTool: Add additional xsapi.dll XvdMount interop 60 | - XVDTool: Enable loading keys from global/local config directory 61 | - XVDTool: Enable supplying CIK/ODK/SignKey via cmdline 62 | - XVDTool: Helper functions to calculate data offsets 63 | - XBFSTool: Updated filetable 64 | - XBFSTool: Enable rehashing XBFS table 65 | - XBFSTool: Parsing of console certificate 66 | 67 | ### Changed 68 | - General: Retarget solution to .NET Core 2.0 69 | - XVDTool: Use BouncyCastle for RSA signature calculation/verification 70 | 71 | ### Fixed 72 | - XVDTool: Parsing of MSIXVC header (XvcInfo version 2) 73 | 74 | ### Removed 75 | - XVDTool: The -nn switch to disable native functions, only xvd mounting requires windows specific interop 76 | - Tests: Temporarily disable xvd file tests that rely on big binary blobs 77 | 78 | ## [0.4] - 2015-01-08 79 | ### Added 80 | - Initial release 81 | -------------------------------------------------------------------------------- /DurangoKeyExtractor/DurangoKeyExtractor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /DurangoKeyExtractor/Extractor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using LibXboxOne; 4 | using LibXboxOne.Keys; 5 | 6 | namespace DurangoKeyExtractor 7 | { 8 | public class KeyExtractor 9 | { 10 | public string FilePath { get; } 11 | 12 | public KeyExtractor(string filePath) 13 | { 14 | if (!File.Exists(filePath)) 15 | throw new InvalidOperationException($"File {filePath} does not exist"); 16 | 17 | FilePath = filePath; 18 | } 19 | 20 | public int PullKeysFromFile() 21 | { 22 | var exeData = File.ReadAllBytes(FilePath); 23 | 24 | int foundCount = 0; 25 | for (int i = 0; i < exeData.Length - 32; i++) 26 | { 27 | byte[] hash32 = HashUtils.ComputeSha256(exeData, i, 32); 28 | DurangoKeyEntry keyEntry; 29 | foreach(var kvp in DurangoKeys.GetAllXvdSigningKeys()) 30 | { 31 | string keyName = kvp.Key; 32 | keyEntry = kvp.Value; 33 | 34 | if (keyEntry.HasKeyData) 35 | continue; 36 | if (keyEntry.DataSize > exeData.Length - i) 37 | continue; 38 | 39 | byte[] signKeyHash = HashUtils.ComputeSha256(exeData, i, keyEntry.DataSize); 40 | 41 | if (!keyEntry.SHA256Hash.IsEqualTo(signKeyHash)) 42 | continue; 43 | 44 | Console.WriteLine($"Found {keyEntry.KeyType} \"{keyName}\" at offset 0x{i:X}"); 45 | 46 | byte[] keyData = new byte[keyEntry.DataSize]; 47 | Array.Copy(exeData, i, keyData, 0, keyData.Length); 48 | 49 | DurangoKeys.LoadSignKey(keyName, keyData, out bool newKey, out keyName); 50 | foundCount++; 51 | } 52 | 53 | foreach(var kvp in DurangoKeys.GetAllODK()) 54 | { 55 | OdkIndex keyId = kvp.Key; 56 | keyEntry = kvp.Value; 57 | 58 | if (keyEntry.HasKeyData) 59 | continue; 60 | 61 | if (!hash32.IsEqualTo(keyEntry.SHA256Hash)) 62 | continue; 63 | 64 | Console.WriteLine($"Found {keyEntry.KeyType} \"{keyId}\" at offset 0x{i:X}"); 65 | 66 | byte[] keyData = new byte[keyEntry.DataSize]; 67 | Array.Copy(exeData, i, keyData, 0, keyData.Length); 68 | 69 | DurangoKeys.LoadOdkKey(keyId, keyData, out bool newKey); 70 | foundCount++; 71 | } 72 | 73 | foreach(var kvp in DurangoKeys.GetAllCIK()) 74 | { 75 | Guid keyId = kvp.Key; 76 | keyEntry = kvp.Value; 77 | 78 | if (keyEntry.HasKeyData) 79 | continue; 80 | 81 | if (!hash32.IsEqualTo(keyEntry.SHA256Hash)) 82 | continue; 83 | 84 | Console.WriteLine($"Found {keyEntry.KeyType} \"{keyId}\" at offset 0x{i:X}"); 85 | 86 | byte[] keyData = new byte[0x10 + keyEntry.DataSize]; 87 | Array.Copy(keyId.ToByteArray(), 0, keyData, 0, 0x10); 88 | Array.Copy(exeData, i, keyData, 0x10, keyEntry.DataSize); 89 | 90 | DurangoKeys.LoadCikKeys(keyData, out Guid[] keyGuid); 91 | foundCount++; 92 | } 93 | } 94 | 95 | return foundCount; 96 | } 97 | 98 | public bool SaveFoundKeys(string destinationDirectory) 99 | { 100 | string path; 101 | 102 | foreach (var keyType in Enum.GetNames(typeof(KeyType))) 103 | { 104 | path = Path.Combine(destinationDirectory, $"{keyType}"); 105 | if (!Directory.Exists(path)) 106 | { 107 | Directory.CreateDirectory(path); 108 | } 109 | } 110 | 111 | /* Xvd signing keys */ 112 | foreach(var kvp in DurangoKeys.GetAllXvdSigningKeys()) 113 | { 114 | IKeyEntry keyEntry = kvp.Value; 115 | 116 | if (!keyEntry.HasKeyData) 117 | continue; 118 | 119 | path = Path.Combine(destinationDirectory, $"{KeyType.XvdSigningKey}", $"{kvp.Key}.rsa"); 120 | File.WriteAllBytes(path, keyEntry.KeyData); 121 | } 122 | 123 | /* ODK */ 124 | foreach(var kvp in DurangoKeys.GetAllODK()) 125 | { 126 | IKeyEntry keyEntry = kvp.Value; 127 | 128 | if (!keyEntry.HasKeyData) 129 | continue; 130 | 131 | path = Path.Combine(destinationDirectory, $"{KeyType.Odk}", $"{kvp.Key}.odk"); 132 | File.WriteAllBytes(path, keyEntry.KeyData); 133 | } 134 | 135 | /* CIK */ 136 | foreach(var kvp in DurangoKeys.GetAllCIK()) 137 | { 138 | IKeyEntry keyEntry = kvp.Value; 139 | 140 | if (!keyEntry.HasKeyData) 141 | continue; 142 | 143 | path = Path.Combine(destinationDirectory, $"{KeyType.Cik}", $"{kvp.Key}.cik"); 144 | 145 | byte[] keyData = new byte[0x30]; 146 | Array.Copy(kvp.Key.ToByteArray(), 0, keyData, 0, 0x10); 147 | Array.Copy(keyEntry.KeyData, 0, keyData, 0x10, 0x20); 148 | 149 | File.WriteAllBytes(path, keyData); 150 | } 151 | 152 | return true; 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /DurangoKeyExtractor/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using NDesk.Options; 5 | 6 | namespace DurangoKeyExtractor 7 | { 8 | class Program 9 | { 10 | static string AppVersion => LibXboxOne.Common.AppVersion; 11 | 12 | static void Main(string[] args) 13 | { 14 | var printHelp = false; 15 | var outputFolder = String.Empty; 16 | 17 | var p = new OptionSet { 18 | { "h|?|help", "Show this help and exit", v => printHelp = v != null }, 19 | { "o|output=", "Specify {OUTPUT DIRECTORY} for extracted keys", 20 | v => outputFolder = v} 21 | }; 22 | 23 | List extraArgs; 24 | try 25 | { 26 | extraArgs = p.Parse(args); 27 | } 28 | catch (OptionException e) 29 | { 30 | Console.WriteLine($"Failed parsing parameter \'{e.OptionName}\': {e.Message}"); 31 | Console.WriteLine("Try 'durangokeyextractor --help' for more information"); 32 | return; 33 | } 34 | 35 | if(extraArgs.Count <= 0) 36 | { 37 | Console.WriteLine("ERROR: Missing filepath!"); 38 | Console.WriteLine(); 39 | } 40 | 41 | Console.WriteLine($"DurangoKeyExtractor {AppVersion}: Durango key extractor"); 42 | if (printHelp || extraArgs.Count <= 0) 43 | { 44 | Console.WriteLine("Usage : durangokeyextractor.exe [parameters] [filepath]"); 45 | Console.WriteLine(); 46 | Console.WriteLine("Parameters:"); 47 | p.WriteOptionDescriptions(Console.Out); 48 | return; 49 | } 50 | 51 | if (outputFolder == String.Empty) 52 | { 53 | outputFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "extracted"); 54 | } 55 | 56 | var filePath = extraArgs[0]; 57 | KeyExtractor extractor; 58 | 59 | try 60 | { 61 | extractor = new KeyExtractor(filePath); 62 | } 63 | catch (Exception e) 64 | { 65 | Console.WriteLine($"Error setting up KeyExtractor: {e.Message}"); 66 | return; 67 | } 68 | 69 | Console.WriteLine($"Scanning {filePath} for known keys..."); 70 | int foundCount = extractor.PullKeysFromFile(); 71 | if (foundCount == 0) 72 | { 73 | Console.WriteLine("No keys found :("); 74 | return; 75 | } 76 | 77 | Console.WriteLine($"Found {foundCount} keys :)"); 78 | Console.WriteLine($"Saving keys to \"{outputFolder}\"..."); 79 | extractor.SaveFoundKeys(outputFolder); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /LibXboxOne.Tests/AesCipherTests.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using Xunit; 3 | 4 | namespace LibXboxOne.Tests 5 | { 6 | public static class AesChipherData 7 | { 8 | public static byte[] AesKey => new byte[]{ 9 | 0x58,0x51,0x28,0xbc,0xcb,0xdb,0x45,0x73,0xdb,0x92,0x18,0xc5,0x64,0x92,0x86,0x15, 10 | 0xde,0xdb,0xc8,0x29,0x99,0x32,0x81,0xd9,0x77,0xa0,0xf9,0xc9,0x4b,0xbb,0x7f,0x62}; 11 | public static byte[] PlaintextData => new byte[]{ 12 | 0x48,0x45,0x4c,0x4c,0x4f,0x2c,0x20,0x49,0x54,0x53,0x20,0x4d,0x45,0x2c,0x20,0x54, 13 | 0x45,0x53,0x54,0x44,0x41,0x54,0x41,0x0a,0x01,0x11,0x21,0x31,0x41,0x51,0x61,0x71}; 14 | 15 | public static byte[] EncryptedData => new byte[]{ 16 | 0x50,0x88,0xED,0x6D,0x2B,0xD6,0xDE,0xA9,0x23,0x45,0x29,0x97,0x8A,0xD1,0x8C,0xE9, 17 | 0xED,0x65,0x53,0x0A,0xB9,0x72,0x46,0xF0,0xA7,0x49,0xE3,0x19,0x5D,0x13,0x15,0x82}; 18 | } 19 | 20 | public class AesCipherTests 21 | { 22 | [Fact] 23 | public void TestAesEncryption() 24 | { 25 | var data = AesChipherData.PlaintextData; 26 | 27 | byte[] nullIv = new byte[16]; 28 | var cipher = Aes.Create(); 29 | cipher.Mode = CipherMode.ECB; 30 | cipher.Padding = PaddingMode.None; 31 | 32 | ICryptoTransform transform = cipher.CreateEncryptor(AesChipherData.AesKey, nullIv); 33 | transform.TransformBlock(data, 0, data.Length, data, 0); 34 | 35 | Assert.Equal(AesChipherData.EncryptedData, data); 36 | } 37 | 38 | [Fact] 39 | public void TestAesDecryption() 40 | { 41 | var data = AesChipherData.EncryptedData; 42 | 43 | byte[] nullIv = new byte[16]; 44 | var cipher = Aes.Create(); 45 | cipher.Mode = CipherMode.ECB; 46 | cipher.Padding = PaddingMode.None; 47 | 48 | ICryptoTransform transform = cipher.CreateDecryptor(AesChipherData.AesKey, nullIv); 49 | transform.TransformBlock(data, 0, data.Length, data, 0); 50 | 51 | Assert.Equal(AesChipherData.PlaintextData, data); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /LibXboxOne.Tests/AesXtsTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace LibXboxOne.Tests 4 | { 5 | public static class AesXtsData 6 | { 7 | public static byte[] WholeKey => new byte[]{ 8 | 0x82,0x21,0xd4,0xac,0xf4,0x31,0x9d,0x95,0xe2,0x8e,0x72,0x2e,0x82,0xa3,0xb5,0xe2, 9 | 0x32,0xba,0x06,0xdd,0x96,0x86,0x15,0x91,0x3f,0x6b,0xf8,0x10,0xd0,0x89,0x16,0xa0}; 10 | public static byte[] TweakKey => new byte[]{ 11 | 0x82,0x21,0xd4,0xac,0xf4,0x31,0x9d,0x95,0xe2,0x8e,0x72,0x2e,0x82,0xa3,0xb5,0xe2}; 12 | 13 | public static byte[] DataKey => new byte[]{ 14 | 0x32,0xba,0x06,0xdd,0x96,0x86,0x15,0x91,0x3f,0x6b,0xf8,0x10,0xd0,0x89,0x16,0xa0}; 15 | 16 | public static byte[] Tweak => new byte[]{ 17 | 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x34,0xFF,0x87,0x90,0xCA,0xDB,0x14,0x22}; 18 | 19 | public static byte[] PlainData => ResourcesProvider.GetBytes("xts_plain.bin", ResourceType.DataBlobs); 20 | public static byte[] CipherData => ResourcesProvider.GetBytes("xts_cipher.bin", ResourceType.DataBlobs); 21 | 22 | public static uint HeaderId => 0x1; 23 | } 24 | 25 | public class AesXtsTests 26 | { 27 | [Fact] 28 | public void TestEncrypt() 29 | { 30 | var cipher = new AesXtsTransform(AesXtsData.Tweak, AesXtsData.DataKey, AesXtsData.TweakKey, 31 | encrypt: true); 32 | 33 | var plaintext = AesXtsData.PlainData; 34 | var ciphertext = AesXtsData.CipherData; 35 | byte[] result = new byte[0x3000]; 36 | int transformedBytes = 0; 37 | 38 | for (int dataUnit=0; dataUnit < 3; dataUnit++) 39 | { 40 | transformedBytes += cipher.TransformDataUnit(plaintext, dataUnit * 0x1000, 0x1000, 41 | result, dataUnit * 0x1000, (uint)dataUnit); 42 | } 43 | 44 | Assert.Equal(0x3000, transformedBytes); 45 | Assert.Equal(ciphertext, result); 46 | Assert.NotEqual(plaintext, result); 47 | } 48 | 49 | [Fact] 50 | public void TestDecrypt() 51 | { 52 | var cipher = new AesXtsTransform(AesXtsData.Tweak, AesXtsData.DataKey, AesXtsData.TweakKey, 53 | encrypt: false); 54 | 55 | var plaintext = AesXtsData.PlainData; 56 | var ciphertext = AesXtsData.CipherData; 57 | byte[] result = new byte[0x3000]; 58 | int transformedBytes = 0; 59 | 60 | for (int dataUnit=0; dataUnit < 3; dataUnit++) 61 | { 62 | transformedBytes += cipher.TransformDataUnit(ciphertext, dataUnit * 0x1000, 0x1000, 63 | result, dataUnit * 0x1000, (uint)dataUnit); 64 | } 65 | 66 | Assert.Equal(0x3000, transformedBytes); 67 | Assert.Equal(plaintext, result); 68 | Assert.NotEqual(ciphertext, result); 69 | } 70 | 71 | [Fact] 72 | public void TestEncryptFail() 73 | { 74 | var cipher = new AesXtsTransform(AesXtsData.Tweak, AesXtsData.DataKey, AesXtsData.TweakKey, 75 | encrypt: true); 76 | 77 | var plaintext = AesXtsData.PlainData; 78 | var ciphertext = AesXtsData.CipherData; 79 | byte[] result = new byte[0x3000]; 80 | int transformedBytes = 0; 81 | 82 | for (int dataUnit=0; dataUnit < 3; dataUnit++) 83 | { 84 | transformedBytes += cipher.TransformDataUnit(plaintext, dataUnit * 0x1000, 0x1000, 85 | result, dataUnit * 0x1000, 86 | (uint)dataUnit + 1); // <- Invalid data unit 87 | } 88 | 89 | Assert.Equal(0x3000, transformedBytes); 90 | Assert.NotEqual(plaintext, result); 91 | Assert.NotEqual(ciphertext, result); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /LibXboxOne.Tests/BCryptImportTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Security.Cryptography; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace LibXboxOne.Tests 7 | { 8 | public static class BCryptRsaHelper 9 | { 10 | static async Task GetRsa(int keyStrength, string keyIdentifier) 11 | { 12 | var blob = await ResourcesProvider.GetBytesAsync($"RSA_{keyStrength}_{keyIdentifier}.bin", 13 | ResourceType.RsaKeys); 14 | var rsaParams = BCryptRsaImport.BlobToParameters(blob, out int resultBitLength, out bool isPrivate); 15 | if (keyStrength != resultBitLength) 16 | throw new InvalidDataException("Desired keyStrength does not match parsed data"); 17 | 18 | return rsaParams; 19 | } 20 | 21 | public static Task GetRsaPublic(int keyStrength) 22 | { 23 | return GetRsa(keyStrength, "RSAPUBLICBLOB"); 24 | } 25 | 26 | public static Task GetRsaPrivate(int keyStrength) 27 | { 28 | return GetRsa(keyStrength, "RSAPRIVATEBLOB"); 29 | } 30 | 31 | public static Task GetRsaFullPrivate(int keyStrength) 32 | { 33 | return GetRsa(keyStrength, "RSAFULLPRIVATEBLOB"); 34 | } 35 | 36 | public static Task GetRsaPublicCAPI(int keyStrength) 37 | { 38 | return GetRsa(keyStrength, "CAPIPUBLICBLOB"); 39 | } 40 | 41 | public static Task GetRsaPrivateCAPI(int keyStrength) 42 | { 43 | return GetRsa(keyStrength, "CAPIPRIVATEBLOB"); 44 | } 45 | } 46 | 47 | public class BCryptImportTests 48 | { 49 | [Theory] 50 | [InlineData(512)] 51 | [InlineData(1024)] 52 | [InlineData(2048)] 53 | [InlineData(3072)] 54 | [InlineData(4096)] 55 | public async void ImportRsaPublicBlob(int keyStrength) 56 | { 57 | RSAParameters rsaParams = await BCryptRsaHelper.GetRsaPublic(keyStrength); 58 | 59 | Assert.NotEmpty(rsaParams.Exponent); 60 | Assert.NotEmpty(rsaParams.Modulus); 61 | } 62 | 63 | [Theory] 64 | [InlineData(512)] 65 | [InlineData(1024)] 66 | [InlineData(2048)] 67 | [InlineData(3072)] 68 | [InlineData(4096)] 69 | public async void ImportRsaPrivateBlob(int keyStrength) 70 | { 71 | RSAParameters rsaParams = await BCryptRsaHelper.GetRsaPrivate(keyStrength); 72 | 73 | Assert.NotEmpty(rsaParams.Exponent); 74 | Assert.NotEmpty(rsaParams.Modulus); 75 | 76 | Assert.NotEmpty(rsaParams.P); 77 | Assert.NotEmpty(rsaParams.Q); 78 | } 79 | 80 | [Theory] 81 | [InlineData(512)] 82 | [InlineData(1024)] 83 | [InlineData(2048)] 84 | [InlineData(3072)] 85 | [InlineData(4096)] 86 | public async void ImportRsaFullPrivateBlob(int keyStrength) 87 | { 88 | RSAParameters rsaParams = await BCryptRsaHelper.GetRsaFullPrivate(keyStrength); 89 | 90 | Assert.NotEmpty(rsaParams.Exponent); 91 | Assert.NotEmpty(rsaParams.Modulus); 92 | 93 | Assert.NotEmpty(rsaParams.P); 94 | Assert.NotEmpty(rsaParams.Q); 95 | 96 | Assert.NotEmpty(rsaParams.DP); 97 | Assert.NotEmpty(rsaParams.DQ); 98 | Assert.NotEmpty(rsaParams.InverseQ); 99 | Assert.NotEmpty(rsaParams.D); 100 | } 101 | 102 | [Theory] 103 | [InlineData(512)] 104 | [InlineData(1024)] 105 | [InlineData(2048)] 106 | [InlineData(3072)] 107 | [InlineData(4096)] 108 | public async void ImportAndComparePrivateBlobs(int keyStrength) 109 | { 110 | RSAParameters rsaParams = await BCryptRsaHelper.GetRsaPrivate(keyStrength); 111 | RSAParameters rsaParamsFull = await BCryptRsaHelper.GetRsaFullPrivate(keyStrength); 112 | 113 | Assert.NotEmpty(rsaParams.Exponent); 114 | Assert.NotEmpty(rsaParams.Modulus); 115 | 116 | Assert.Equal(rsaParams.Exponent, rsaParamsFull.Exponent); 117 | Assert.Equal(rsaParams.Modulus, rsaParamsFull.Modulus); 118 | 119 | Assert.Equal(rsaParams.P, rsaParamsFull.P); 120 | Assert.Equal(rsaParams.Q, rsaParamsFull.Q); 121 | } 122 | 123 | [Theory] 124 | [InlineData(512)] 125 | [InlineData(1024)] 126 | [InlineData(2048)] 127 | [InlineData(3072)] 128 | [InlineData(4096)] 129 | public async void ImportAndComparePublicBlobs(int keyStrength) 130 | { 131 | RSAParameters rsaParamsPublic = await BCryptRsaHelper.GetRsaPublic(keyStrength); 132 | RSAParameters rsaParamsFullprivate = await BCryptRsaHelper.GetRsaFullPrivate(keyStrength); 133 | 134 | Assert.NotEmpty(rsaParamsPublic.Exponent); 135 | Assert.NotEmpty(rsaParamsPublic.Modulus); 136 | 137 | Assert.Equal(rsaParamsPublic.Exponent, rsaParamsFullprivate.Exponent); 138 | Assert.Equal(rsaParamsPublic.Modulus, rsaParamsFullprivate.Modulus); 139 | } 140 | 141 | [Theory] 142 | [InlineData(512)] 143 | [InlineData(1024)] 144 | [InlineData(2048)] 145 | [InlineData(3072)] 146 | [InlineData(4096)] 147 | public void ImportInvalidPublicBlobCAPI(int keyStrength) 148 | { 149 | Assert.ThrowsAsync(async () => 150 | await BCryptRsaHelper.GetRsaPublicCAPI(keyStrength) 151 | ); 152 | } 153 | 154 | [Theory] 155 | [InlineData(512)] 156 | [InlineData(1024)] 157 | [InlineData(2048)] 158 | [InlineData(3072)] 159 | [InlineData(4096)] 160 | public void ImportInvalidPrivateBlobCAPI(int keyStrength) 161 | { 162 | Assert.ThrowsAsync(async () => 163 | await BCryptRsaHelper.GetRsaPrivateCAPI(keyStrength) 164 | ); 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /LibXboxOne.Tests/DurangoKeysTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using LibXboxOne.Keys; 3 | 4 | namespace LibXboxOne.Tests 5 | { 6 | public class DurangoKeysTests 7 | { 8 | [Fact] 9 | public void TestOdkIndexEnumConversion() 10 | { 11 | var redSuccess0 = DurangoKeys.GetOdkIndexFromString("RedOdk", out OdkIndex redOdk); 12 | var redSuccess1 = DurangoKeys.GetOdkIndexFromString("redodk", out OdkIndex redOdkLower); 13 | var redSuccess2 = DurangoKeys.GetOdkIndexFromString("2", out OdkIndex redOdkNumber); 14 | var standardSuccess = DurangoKeys.GetOdkIndexFromString("0", out OdkIndex standardOdkNumber); 15 | var unknownNumberSuccess = DurangoKeys.GetOdkIndexFromString("42", out OdkIndex unknownOdkNumber); 16 | 17 | var invalidNameFail = DurangoKeys.GetOdkIndexFromString("redodkblabla", out OdkIndex nameFail); 18 | 19 | Assert.True(redSuccess0); 20 | Assert.True(redSuccess1); 21 | Assert.True(redSuccess2); 22 | Assert.True(standardSuccess); 23 | Assert.True(unknownNumberSuccess); 24 | 25 | Assert.False(invalidNameFail); 26 | 27 | Assert.Equal(OdkIndex.RedOdk, redOdk); 28 | Assert.Equal(OdkIndex.RedOdk, redOdkLower); 29 | Assert.Equal(OdkIndex.RedOdk, redOdkNumber); 30 | Assert.Equal((OdkIndex)42, unknownOdkNumber); 31 | 32 | Assert.Equal(OdkIndex.Invalid, nameFail); 33 | 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /LibXboxOne.Tests/HashUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using Xunit; 3 | 4 | namespace LibXboxOne.Tests 5 | { 6 | public static class HashUtilsData 7 | { 8 | public static byte[] Data => new byte[]{ 9 | 0x48,0x45,0x4c,0x4c,0x4f,0x2c,0x20,0x49,0x54,0x53,0x20,0x4d,0x45,0x2c,0x20,0x54, 10 | 0x45,0x53,0x54,0x44,0x41,0x54,0x41,0x0a,0x01,0x11,0x21,0x31,0x41,0x51,0x61,0x71}; 11 | 12 | public static byte[] Sha256Hash => new byte[]{ 13 | 0x3C,0x47,0xF6,0x32,0x57,0x72,0xCC,0x80,0xAE,0x72,0x0D,0xA2,0x4C,0x60,0xD8,0x1A, 14 | 0xBF,0xD1,0x7E,0x8A,0x63,0xA8,0xCC,0xDE,0x4B,0xD9,0xF2,0xB8,0xBB,0xC3,0x82,0x0F}; 15 | 16 | public static byte[] Sha1Hash => new byte[]{ 17 | 0xD7,0x3E,0x77,0x3C,0xE0,0x56,0x82,0x03,0xE8,0x92,0xFD,0xDD,0xBC,0xD8,0xAA,0x3C, 18 | 0x94,0xE2,0x37,0xC2}; 19 | 20 | public static byte[] RsaSignature => new byte[]{ 21 | 0x52,0xDA,0x68,0x6B,0x69,0x32,0x48,0x01,0x28,0x7A,0x38,0x24,0x84,0xA7,0xC2,0xCD, 22 | 0x9F,0xDC,0xDC,0xA5,0x98,0x59,0x35,0x9D,0x5E,0x76,0xCF,0x4A,0xA7,0xDB,0xF0,0xEE, 23 | 0x12,0x19,0xA6,0x87,0x0A,0x6F,0xF6,0xE1,0x44,0x57,0xC7,0xBE,0x0E,0x36,0x77,0x70, 24 | 0xA3,0xD3,0x95,0xA0,0x07,0xA3,0x59,0x7F,0xCD,0xEF,0xC2,0x8D,0x79,0x4D,0x7A,0x9C, 25 | 0xC0,0xB8,0x12,0xEC,0x1D,0xF1,0x58,0x4C,0x46,0xC5,0x0A,0xEA,0x8D,0x6C,0x51,0xFC, 26 | 0x21,0xFA,0x25,0x76,0xE7,0xAC,0x51,0xD0,0xDC,0x87,0x82,0x89,0x70,0x41,0x79,0x0D, 27 | 0x04,0xF5,0x75,0xD6,0x6B,0x3D,0xE0,0x77,0x79,0x86,0xF4,0xD5,0x72,0x3B,0x0F,0xC5, 28 | 0xDF,0x68,0xC6,0x88,0x08,0x71,0x98,0x64,0x20,0xAC,0xE1,0x4A,0x4A,0xE7,0xD9,0xF4}; 29 | } 30 | 31 | public class HashUtilsTests 32 | { 33 | [Fact] 34 | public void TestComputeSha256() 35 | { 36 | var result = HashUtils.ComputeSha256(HashUtilsData.Data); 37 | 38 | Assert.Equal(HashUtilsData.Sha256Hash.Length, result.Length); 39 | Assert.Equal(HashUtilsData.Sha256Hash, result); 40 | } 41 | 42 | [Fact] 43 | public void TestComputeSha1() 44 | { 45 | var result = HashUtils.ComputeSha1(HashUtilsData.Data); 46 | 47 | Assert.Equal(HashUtilsData.Sha1Hash.Length, result.Length); 48 | Assert.Equal(HashUtilsData.Sha1Hash, result); 49 | } 50 | 51 | byte[] GetRsaBlob(string blobType, int bits) 52 | { 53 | return ResourcesProvider.GetBytes($"RSA_{bits}_{blobType}.bin", ResourceType.RsaKeys); 54 | } 55 | 56 | [Theory] 57 | // [InlineData(1024, "RSAPRIVATEBLOB")] 58 | [InlineData(1024, "RSAFULLPRIVATEBLOB")] 59 | public void TestSignData(int bits, string blobType) 60 | { 61 | var rsaBlob = GetRsaBlob(blobType, bits); 62 | bool result = HashUtils.SignData(rsaBlob, blobType, HashUtilsData.Data, 63 | out byte[] signature); 64 | Assert.True(result); 65 | Assert.NotEqual(HashUtilsData.RsaSignature, signature); // Cant be same, due to PSS 66 | 67 | 68 | result = HashUtils.VerifySignature(rsaBlob, signature, 69 | HashUtilsData.Data); 70 | Assert.True(result); 71 | } 72 | 73 | [Fact] 74 | public void TestSignDataFailPubKey() 75 | { 76 | var rsaBlob = GetRsaBlob("RSAPUBLICBLOB", 1024); 77 | 78 | Assert.Throws(() => 79 | { 80 | HashUtils.SignData(rsaBlob, "RSAPUBLICBLOB", HashUtilsData.Data, 81 | out byte[] signature); 82 | }); 83 | } 84 | 85 | [Theory] 86 | [InlineData(1024, "RSAPUBLICBLOB")] 87 | [InlineData(1024, "RSAPRIVATEBLOB")] 88 | [InlineData(1024, "RSAFULLPRIVATEBLOB")] 89 | public void TestVerifySignature(int bits, string blobType) 90 | { 91 | var rsaBlob = GetRsaBlob(blobType, bits); 92 | bool success = HashUtils.VerifySignature(rsaBlob, HashUtilsData.RsaSignature, 93 | HashUtilsData.Data); 94 | 95 | Assert.True(success); 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /LibXboxOne.Tests/LibXboxOne.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | true 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/DataBlobs/xbfs_header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/DataBlobs/xbfs_header.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/DataBlobs/xts_cipher.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/DataBlobs/xts_cipher.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/DataBlobs/xts_plain.bin: -------------------------------------------------------------------------------- 1 | SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION SOME PLAINTEXT DATA BEFORE XTS TRANSFORMATION -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_CAPIPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_CAPIPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_CAPIPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_CAPIPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_RSAFULLPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_RSAFULLPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_RSAPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_RSAPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_RSAPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_1024_RSAPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_CAPIPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_CAPIPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_CAPIPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_CAPIPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_RSAFULLPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_RSAFULLPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_RSAPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_RSAPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_RSAPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_2048_RSAPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_CAPIPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_CAPIPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_CAPIPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_CAPIPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_RSAFULLPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_RSAFULLPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_RSAPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_RSAPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_RSAPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_3072_RSAPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_CAPIPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_CAPIPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_CAPIPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_CAPIPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_RSAFULLPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_RSAFULLPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_RSAPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_RSAPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_RSAPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_4096_RSAPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_512_CAPIPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_512_CAPIPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_512_CAPIPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_512_CAPIPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_512_RSAFULLPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_512_RSAFULLPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_512_RSAPRIVATEBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_512_RSAPRIVATEBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/Resources/RsaKeys/RSA_512_RSAPUBLICBLOB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xboxoneresearch/xvdtool/aeaba7484a70cbe4dce4a66786932286c1cb6a40/LibXboxOne.Tests/Resources/RsaKeys/RSA_512_RSAPUBLICBLOB.bin -------------------------------------------------------------------------------- /LibXboxOne.Tests/ResourcesProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | 5 | namespace LibXboxOne.Tests 6 | { 7 | public enum ResourceType 8 | { 9 | RsaKeys, 10 | AesKeys, 11 | Rc4Keys, 12 | DataBlobs, 13 | TestFiles, 14 | Misc 15 | } 16 | 17 | public class ResourcesProvider 18 | { 19 | static readonly string ResourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../Resources"); 20 | 21 | public static byte[] GetBytes(string fileName, ResourceType type = ResourceType.Misc) 22 | { 23 | var file = $"{ResourcePath}/{type}/{fileName}"; 24 | if (File.Exists(file)) 25 | { 26 | return File.ReadAllBytes(file); 27 | } 28 | throw new FileNotFoundException(file); 29 | } 30 | 31 | public static async Task GetBytesAsync(string fileName, ResourceType type = ResourceType.Misc) 32 | { 33 | var file = $"{ResourcePath}/{type}/{fileName}"; 34 | if (File.Exists(file)) 35 | { 36 | using (FileStream stream = File.OpenRead(file)) 37 | { 38 | byte[] result = new byte[stream.Length]; 39 | await stream.ReadAsync(result, 0, (int)stream.Length); 40 | return result; 41 | } 42 | } 43 | throw new FileNotFoundException(file); 44 | } 45 | public static string GetString(string fileName, ResourceType type = ResourceType.Misc) 46 | { 47 | var file = $"{ResourcePath}/{type}/{fileName}"; 48 | if (File.Exists(file)) 49 | { 50 | return System.Text.Encoding.UTF8.GetString( 51 | File.ReadAllBytes(file) 52 | ); 53 | } 54 | throw new FileNotFoundException(file); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /LibXboxOne.Tests/XbfsTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using LibXboxOne.Nand; 3 | 4 | namespace LibXboxOne.Tests 5 | { 6 | public class XbfsTests 7 | { 8 | public static XbfsHeader GetHeader() 9 | { 10 | var data = ResourcesProvider.GetBytes("xbfs_header.bin", ResourceType.DataBlobs); 11 | return Shared.BytesToStruct(data); 12 | } 13 | 14 | [Fact] 15 | public void TestXbfsHeaderParsing() 16 | { 17 | XbfsHeader header = GetHeader(); 18 | 19 | Assert.True(header.IsValid); 20 | Assert.True(header.IsHashValid); 21 | Assert.Equal(1, header.FormatVersion); 22 | Assert.Equal(1, header.SequenceNumber); 23 | Assert.Equal(9, header.LayoutVersion); 24 | Assert.Equal((ulong)0, header.Reserved08); 25 | Assert.Equal((ulong)0, header.Reserved10); 26 | Assert.Equal((ulong)0, header.Reserved18); 27 | } 28 | 29 | [Fact] 30 | 31 | public void TestXbfsHeaderRehash() 32 | { 33 | XbfsHeader header = GetHeader(); 34 | header.Files[0].BlockCount = 123; 35 | 36 | Assert.False(header.IsHashValid); 37 | header.Rehash(); 38 | Assert.True(header.IsHashValid); 39 | } 40 | 41 | [Fact] 42 | public void TestXbfsOutOfBounds() 43 | { 44 | XbfsHeader header = GetHeader(); 45 | 46 | // Write a file entry past the known filenames array 47 | header.Files[XbfsFile.XbfsFilenames.Length + 2] = new XbfsEntry(){ 48 | LBA=0, BlockCount=1, Reserved=0 49 | }; 50 | 51 | // Call to ToString will print filenames and should 52 | // encouter a file that we don't know the filename of 53 | header.ToString(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /LibXboxOne.Tests/XvdFileTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Xunit; 3 | 4 | namespace LibXboxOne.Tests 5 | { 6 | public class XvdFileTests 7 | { 8 | [Fact(Skip="Relies on xvd data blob")] 9 | public void Dev_Signed_ValidHash_Test() 10 | { 11 | using (var file = new XvdFile(@"F:\XBone\XVDs\TestXVDs\xvd1")) 12 | { 13 | Assert.True(file.Load()); 14 | /* 15 | if(XvdFile.SignKeyLoaded) 16 | Assert.True(file.Header.IsSignedWithRedKey); 17 | */ 18 | Assert.True(file.IsEncrypted); 19 | Assert.True(file.IsDataIntegrityEnabled); 20 | Assert.True(file.HashTreeValid); 21 | Assert.True(file.DataHashTreeValid); 22 | } 23 | } 24 | 25 | [Fact(Skip="Relies on xvd data blob")] 26 | public void Dev_Signed_InvalidHashTree_Test() 27 | { 28 | using (var file = new XvdFile(@"F:\XBone\XVDs\TestXVDs\xvd1_brokehash")) 29 | { 30 | Assert.True(file.Load()); 31 | /* 32 | if (XvdFile.SignKeyLoaded) 33 | Assert.True(file.Header.IsSignedWithRedKey); 34 | */ 35 | Assert.True(file.IsEncrypted); 36 | Assert.True(file.IsDataIntegrityEnabled); 37 | Assert.False(file.HashTreeValid); 38 | } 39 | } 40 | 41 | [Fact(Skip="Relies on xvd data blob")] 42 | public void Dev_Signed_InvalidDataHashTree_Test() 43 | { 44 | using (var file = new XvdFile(@"F:\XBone\XVDs\TestXVDs\xvd1_brokedatahash")) 45 | { 46 | Assert.True(file.Load()); 47 | /* 48 | if (XvdFile.SignKeyLoaded) 49 | Assert.True(file.Header.IsSignedWithRedKey); 50 | */ 51 | Assert.True(file.IsEncrypted); 52 | Assert.True(file.IsDataIntegrityEnabled); 53 | Assert.True(file.HashTreeValid); 54 | Assert.False(file.DataHashTreeValid); 55 | } 56 | } 57 | 58 | [Fact(Skip="Relies on xvd data blob")] 59 | public void Dev_Signed_XVC_Decrypt_Test() 60 | { 61 | const string dest = @"F:\XBone\XVDs\TestXVDs\xvd1_decrypted_temp"; 62 | const string fileToCompare = @"F:\XBone\XVDs\TestXVDs\xvd1_decrypted"; 63 | if (File.Exists(dest)) 64 | File.Delete(dest); 65 | 66 | File.Copy(@"F:\XBone\XVDs\TestXVDs\xvd1", dest); 67 | using (var file = new XvdFile(dest)) 68 | { 69 | Assert.True(file.Load()); 70 | /* 71 | Assert.True(file.Header.IsSignedWithRedKey); 72 | */ 73 | Assert.True(file.IsEncrypted); 74 | Assert.True(file.IsDataIntegrityEnabled); 75 | Assert.True(file.HashTreeValid); 76 | Assert.True(file.DataHashTreeValid); 77 | Assert.True(file.Decrypt()); 78 | Assert.False(file.IsEncrypted); 79 | ulong[] invalid = file.VerifyDataHashTree(); 80 | Assert.True(invalid.Length == 0); 81 | Assert.True(file.VerifyHashTree()); 82 | 83 | byte[] ntfsString = file.ReadBytes(0x87003, 4); 84 | byte[] expectedString = { 0x4E, 0x54, 0x46, 0x53 }; 85 | Assert.True(ntfsString.IsEqualTo(expectedString)); 86 | } 87 | 88 | byte[] generatedHash; 89 | using (FileStream stream = File.OpenRead(dest)) 90 | { 91 | generatedHash = HashUtils.ComputeSha256(stream); 92 | } 93 | 94 | File.Delete(dest); 95 | 96 | byte[] expectedHash; 97 | using (FileStream stream = File.OpenRead(fileToCompare)) 98 | { 99 | expectedHash = HashUtils.ComputeSha256(stream); 100 | } 101 | 102 | Assert.True(generatedHash.IsEqualTo(expectedHash)); 103 | } 104 | 105 | [Fact(Skip="Relies on xvd data blob")] 106 | public void Dev_Signed_XVC_Encrypt_Test() 107 | { 108 | const string dest = @"F:\XBone\XVDs\TestXVDs\xvd1_encrypted_temp"; 109 | const string fileToCompare = @"F:\XBone\XVDs\TestXVDs\xvd1"; 110 | if (File.Exists(dest)) 111 | File.Delete(dest); 112 | 113 | File.Copy(@"F:\XBone\XVDs\TestXVDs\xvd1_decrypted", dest); 114 | using (var file = new XvdFile(dest)) 115 | { 116 | Assert.True(file.Load()); 117 | Assert.False(file.IsEncrypted); 118 | Assert.True(file.IsDataIntegrityEnabled); 119 | Assert.True(file.HashTreeValid); 120 | Assert.True(file.DataHashTreeValid); 121 | // Assert.True(file.Encrypt()); 122 | Assert.True(file.IsEncrypted); 123 | 124 | ulong[] invalid = file.VerifyDataHashTree(); 125 | Assert.True(invalid.Length == 0); 126 | Assert.True(file.VerifyHashTree()); 127 | } 128 | 129 | byte[] generatedHash; 130 | using (FileStream stream = File.OpenRead(dest)) 131 | { 132 | generatedHash = HashUtils.ComputeSha256(stream); 133 | } 134 | 135 | File.Delete(dest); 136 | 137 | byte[] expectedHash; 138 | using (FileStream stream = File.OpenRead(fileToCompare)) 139 | { 140 | expectedHash = HashUtils.ComputeSha256(stream); 141 | } 142 | 143 | Assert.True(generatedHash.IsEqualTo(expectedHash)); 144 | } 145 | 146 | [Fact(Skip="Relies on xvd data blob")] 147 | public void Unsigned_XVD_Decrypt_Test() 148 | { 149 | const string dest = @"F:\XBone\XVDs\TestXVDs\xvd2_decrypted_temp"; 150 | const string fileToCompare = @"F:\XBone\XVDs\TestXVDs\xvd2_decrypted"; 151 | if (File.Exists(dest)) 152 | File.Delete(dest); 153 | 154 | File.Copy(@"F:\XBone\XVDs\TestXVDs\xvd2", dest); 155 | using (var file = new XvdFile(dest)) 156 | { 157 | Assert.True(file.Load()); 158 | /* 159 | Assert.True(file.Header.IsSignedWithRedKey); 160 | */ 161 | Assert.True(file.IsEncrypted); 162 | Assert.True(file.IsDataIntegrityEnabled); 163 | Assert.True(file.HashTreeValid); 164 | Assert.True(file.DataHashTreeValid); 165 | Assert.True(file.Decrypt()); 166 | Assert.False(file.IsEncrypted); 167 | 168 | ulong[] invalid = file.VerifyDataHashTree(); 169 | Assert.True(invalid.Length == 0); 170 | Assert.True(file.VerifyHashTree()); 171 | 172 | byte[] ntfsString = file.ReadBytes(0x75003, 4); 173 | byte[] expectedString = { 0x4E, 0x54, 0x46, 0x53 }; 174 | Assert.True(ntfsString.IsEqualTo(expectedString)); 175 | } 176 | 177 | byte[] generatedHash; 178 | using (FileStream stream = File.OpenRead(dest)) 179 | { 180 | generatedHash = HashUtils.ComputeSha256(stream); 181 | } 182 | 183 | File.Delete(dest); 184 | 185 | byte[] expectedHash; 186 | using (FileStream stream = File.OpenRead(fileToCompare)) 187 | { 188 | expectedHash = HashUtils.ComputeSha256(stream); 189 | } 190 | 191 | Assert.True(generatedHash.IsEqualTo(expectedHash)); 192 | } 193 | 194 | [Fact(Skip="Relies on xvd data blob")] 195 | public void Unsigned_XVD_Encrypt_Test() 196 | { 197 | const string dest = @"F:\XBone\XVDs\TestXVDs\xvd2_encrypted_temp"; 198 | const string fileToCompare = @"F:\XBone\XVDs\TestXVDs\xvd2"; 199 | if (File.Exists(dest)) 200 | File.Delete(dest); 201 | 202 | File.Copy(@"F:\XBone\XVDs\TestXVDs\xvd2_decrypted_orig_mod", dest); // modded with CIK used in xvd2 203 | using (var file = new XvdFile(dest)) 204 | { 205 | Assert.True(file.Load()); 206 | /* 207 | Assert.False(file.Header.IsSignedWithRedKey); 208 | */ 209 | Assert.False(file.IsEncrypted); 210 | Assert.False(file.IsDataIntegrityEnabled); 211 | // Assert.True(file.Encrypt()); 212 | Assert.True(file.IsEncrypted); 213 | 214 | // copy values from file being compared so the hashes match 215 | file.Header.PDUID = new byte[] {0xEA, 0xC8, 0xE2, 0x82, 0x2F, 0x58, 0x32, 0x4F, 0x92, 0x29, 0xE1, 0xAB, 0x6E, 0x8F, 0x91, 0x63}; 216 | using (FileStream stream = File.OpenRead(fileToCompare)) 217 | { 218 | stream.Position = 0; 219 | stream.Read(file.Header.Signature, 0, 0x200); 220 | } 221 | 222 | Assert.True(file.AddHashTree()); 223 | 224 | ulong[] invalid = file.VerifyDataHashTree(); 225 | Assert.True(invalid.Length == 0); 226 | Assert.True(file.VerifyHashTree()); 227 | } 228 | 229 | byte[] generatedHash; 230 | using (FileStream stream = File.OpenRead(dest)) 231 | { 232 | generatedHash = HashUtils.ComputeSha256(stream); 233 | } 234 | 235 | File.Delete(dest); 236 | 237 | byte[] expectedHash; 238 | using (FileStream stream = File.OpenRead(fileToCompare)) 239 | { 240 | expectedHash = HashUtils.ComputeSha256(stream); 241 | } 242 | 243 | Assert.True(generatedHash.IsEqualTo(expectedHash)); 244 | } 245 | 246 | /* 247 | [Fact(Skip="Relies on xvd data blob")] 248 | public void XvdSign_Key_Extract() 249 | { 250 | var sdkVersions = new List { "XDK_11785" }; 251 | var versionsTextFile = @"F:\Xbone\Research\xdk_versions.txt"; 252 | if (File.Exists(versionsTextFile)) 253 | { 254 | string[] sdkVersionArr = File.ReadAllLines(versionsTextFile); 255 | foreach (string ver in sdkVersionArr) 256 | if (!sdkVersions.Contains(ver.ToUpper())) 257 | sdkVersions.Add(ver.ToUpper()); 258 | } 259 | 260 | foreach (string ver in sdkVersions) 261 | { 262 | string path = Path.Combine(@"F:\Xbone\Research\", ver); 263 | string binPath = Path.Combine(path, "bin"); 264 | if (Directory.Exists(binPath)) 265 | path = binPath; 266 | if (!File.Exists(Path.Combine(path, "xvdsign.exe"))) 267 | continue; 268 | 269 | XvdFile.CikFileLoaded = false; 270 | XvdFile.OdkKeyLoaded = false; 271 | XvdFile.SignKeyLoaded = false; 272 | XvdFile.LoadKeysFromSdk(path); 273 | Assert.True(XvdFile.CikFileLoaded && XvdFile.OdkKeyLoaded && XvdFile.SignKeyLoaded); 274 | } 275 | 276 | XvdFile.CikFileLoaded = false; 277 | XvdFile.OdkKeyLoaded = false; 278 | XvdFile.SignKeyLoaded = false; 279 | } 280 | */ 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /LibXboxOne.Tests/XvdHashBlockTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace LibXboxOne.Tests 4 | { 5 | public class XvdHashBlockTests 6 | { 7 | [Theory] 8 | [InlineData(0x4000, 1, 0x1)] 9 | [InlineData(0x5427, 1, 0x1)] 10 | [InlineData(0x20001, 1, 0x5)] 11 | [InlineData(0x20001, 2, 0x1)] 12 | [InlineData(0xA43E3, 1, 0x18)] 13 | [InlineData(0xA43E3, 2, 0x1)] 14 | [InlineData(0xC2BFF, 1, 0x1C)] 15 | [InlineData(0xC2BFF, 2, 0x1)] 16 | public void TestNumHashBlockCalculation(ulong size, ulong index, ulong expected) 17 | { 18 | ulong actual = XvdMath.CalculateNumHashBlocksInLevel(size, index, false); 19 | 20 | Assert.Equal(expected, actual); 21 | } 22 | 23 | [Theory] 24 | [InlineData((XvdType)0, 0x2, 0x4000, 0xE02, 0x0, 0x10, 0x16)] 25 | [InlineData((XvdType)1, 0x3, 0xC001, 0x150A, 0x0, 0x74, 0x22)] 26 | [InlineData((XvdType)1, 0x3, 0x20001, 0x3772, 0x0, 0x54, 0x59)] 27 | [InlineData((XvdType)1, 0x3, 0x5F653, 0x5F604, 0x0, 0x0, 0x909)] 28 | [InlineData((XvdType)1, 0x3, 0x5F653, 0x5F604, 0x1, 0x58, 0xE)] 29 | [InlineData((XvdType)1, 0x3, 0x5F653, 0x5BB94, 0x1, 0x0, 0xE)] 30 | [InlineData((XvdType)1, 0x3, 0x5F653, 0x5BB94, 0x2, 0xD, 0x0)] 31 | [InlineData((XvdType)1, 0x3, 0x5F653, 0xAB2, 0x0, 0x12, 0x1F)] 32 | [InlineData((XvdType)1, 0x3, 0x5F653, 0xF20B, 0x0, 0x53, 0x17B)] 33 | [InlineData((XvdType)1, 0x3, 0x5F653, 0xF60A, 0x0, 0x56, 0x181)] 34 | public void TestCalculateHashBlockNumForBlockNum(XvdType xvdType, ulong hashTreeLevels, ulong xvdDataBlockCount, 35 | ulong blockNum, uint index, 36 | ulong expectedEntryNum, ulong expectedResult) 37 | { 38 | ulong result = XvdMath.CalculateHashBlockNumForBlockNum(xvdType, hashTreeLevels, xvdDataBlockCount, 39 | blockNum, index, out ulong entryNumInBlock); 40 | 41 | Assert.Equal(expectedEntryNum, entryNumInBlock); 42 | Assert.Equal(expectedResult, result); 43 | } 44 | 45 | [Theory] 46 | [InlineData(0x6401, 0x97)] 47 | [InlineData(0x5427, 0x7F)] 48 | [InlineData(0x20001, 0x304)] 49 | [InlineData(0x5F653, 0x8FB)] 50 | public void TestCalculateHashTreeBlockCount(ulong xvdDataBlockCount, ulong expected) 51 | { 52 | ulong result = XvdMath.PagesToBlocks(xvdDataBlockCount); 53 | 54 | Assert.Equal(expected, result); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /LibXboxOne/AppDirs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace LibXboxOne 6 | { 7 | public static class AppDirs 8 | { 9 | internal static string GetApplicationBaseDirectory() 10 | { 11 | string baseDir; 12 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 13 | { 14 | /* 15 | * Windows 16 | * Result: C:\Users\\AppData\Local 17 | */ 18 | baseDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 19 | } 20 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 21 | { 22 | /* 23 | * Mac OS X 24 | * Result: /Users//.config 25 | */ 26 | baseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 27 | } 28 | else 29 | { 30 | /* 31 | * Linux 32 | * Assemble application config directory manually, SpecialFolder.* is not 33 | * really prepared for Linux yet it seems... 34 | * 35 | * Result: /home//.config 36 | */ 37 | baseDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 38 | baseDir = Path.Combine(baseDir, ".config"); 39 | } 40 | 41 | return baseDir; 42 | } 43 | 44 | public static string GetApplicationConfigDirectory(string appName) 45 | { 46 | /* 47 | * Windows: C:\Users\\AppData\Local\ 48 | * Linux: /home//.config/ 49 | * Mac OS X: /Users//.config/ 50 | */ 51 | return Path.Combine(GetApplicationBaseDirectory(), appName); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /LibXboxOne/Certificates/BootCapability.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace LibXboxOne.Certificates 5 | { 6 | public enum BootCapability : ushort 7 | { 8 | CERT_CAP_NONE = 0x0000, 9 | CERT_CAP_DELETED = 0xFFFF, 10 | 11 | CERT_CAP_SRA_DEVKIT = 0x2001, 12 | CERT_CAP_SRA_DEVKIT_DEBUG = 0x2002, 13 | CERT_CAP_SRA_FILE_IO = 0x2003, 14 | CERT_CAP_SRA_STREAM = 0x2004, 15 | CERT_CAP_SRA_PUSH_DEPLOY = 0x2005, 16 | CERT_CAP_SRA_PULL_DEPLOY = 0x2006, 17 | CERT_CAP_SRA_PROFILING = 0x2007, 18 | CERT_CAP_SRA_JS_PROFILING = 0x2008, 19 | CERT_CAP_RECOVERY = 0x3001, 20 | CERT_CAP_VS_CRASH_DUMP = 0x3002, 21 | CERT_CAP_CRASH_DUMP = 0x3003, 22 | CERT_CAP_REMOTE_MGMT = 0x3004, 23 | CERT_CAP_VIEW_TRACING = 0x3005, 24 | CERT_CAP_TCR_TOOL = 0x3006, 25 | CERT_CAP_XSTUDIO = 0x3007, 26 | CERT_CAP_GESTURE_BUILDER = 0x3008, 27 | CERT_CAP_SPEECH_LAB = 0x3009, 28 | CERT_CAP_SMARTGLASS_STUDIO = 0x300A, 29 | CERT_CAP_NETWORK_FIDDLER = 0x300B, 30 | CERT_CAP_ERA_DEVKIT = 0x4001, 31 | CERT_CAP_HW_BERSINGSEA_DEBUG = 0x4002, 32 | CERT_CAP_ERA_DEVKIT_DEBUG = 0x4003, 33 | CERT_CAP_ERA_FILE_IO = 0x4004, 34 | CERT_CAP_ERA_STREAM = 0x4005, 35 | CERT_CAP_ERA_PUSH_DEPLOY = 0x4006, 36 | CERT_CAP_ERA_PULL_DEPLOY = 0x4007, 37 | CERT_CAP_ERA_EXTRA_MEM = 0x4008, 38 | CERT_CAP_ERA_PROFILING = 0x4009, 39 | CERT_CAP_MS_DEVKIT = 0x6001, 40 | CERT_CAP_HW_CPU_DEBUG = 0x6002, 41 | CERT_CAP_HW_FW_DEBUG = 0x6003, 42 | CERT_CAP_HW_POWER_DEBUG = 0x6004, 43 | CERT_CAP_MEMENC_DISABLED = 0x6005, 44 | CERT_CAP_MEMENC_FIXED_KEY = 0x6006, 45 | CERT_CAP_MEMENC_KEY_0 = 0x6007, 46 | CERT_CAP_CERT_MTE_BOOST = 0x6008, 47 | CERT_CAP_CERT_RIO_BOOST = 0x6009, 48 | CERT_CAP_CERT_TEST_BOOST = 0x600A, 49 | CERT_CAP_UPDATE_TESTER = 0x600B, 50 | CERT_CAP_RED_CPU_CODE = 0x600C, 51 | CERT_CAP_OS_PREVIEW = 0x600D, 52 | CERT_CAP_RETAIL_DEBUGGER = 0x600E, 53 | CERT_CAP_OFFLINE = 0x600F, 54 | CERT_CAP_IGNORE_UPDATESEQUENCE = 0x6010, 55 | CERT_CAP_CERT_QASLT = 0x6011, 56 | CERT_CAP_GPU_FENCE_DEBUG = 0x6013, 57 | CERT_CAP_HW_EN_MEM_SPEED_RETEST = 0x6014, 58 | CERT_CAP_HW_EN_MEM_1GB_RETEST = 0x6015, 59 | CERT_CAP_HW_EN_FUSE_READ = 0x6016, 60 | CERT_CAP_HW_EN_POST_CODE_SECURE = 0x6017, 61 | CERT_CAP_HW_EN_FUSE_OVR_RETEST = 0x6018, 62 | CERT_CAP_HW_EN_MEM_UNLIM_RETEST = 0x6019, 63 | CERT_CAP_ICT_TESTER = 0x601A, 64 | CERT_CAP_LOADXVD_TESTER = 0x601B, 65 | CERT_CAP_WIDE_THERMAL_THRESHOLDS = 0x601C, 66 | CERT_CAP_MS_TESTLAB = 0x601D, 67 | CERT_CAP_ALLOW_DISK_LICENSE = 0x601E, 68 | CERT_CAP_ALLOW_SYSTEM_DOWNGRADE = 0x601F, 69 | CERT_CAP_WIFI_TESTER = 0x6020, 70 | CERT_CAP_GREEN_FIDDLER = 0x6021, 71 | CERT_CAP_KIOSK_MODE = 0x6022, 72 | CERT_CAP_FULL_MEDIA_AUTH = 0x6023, 73 | CERT_CAP_HW_DEVTEST = 0x6024, 74 | CERT_CAP_ALLOW_FUSE_FA = 0x6025, 75 | CERT_CAP_ALLOW_SERIAL_CERT_UPLOAD = 0x6026, 76 | CERT_CAP_ALLOW_INSTRUMENTATION = 0x6027, 77 | CERT_CAP_WIFI_TESTER_DFS = 0x6028, 78 | CERT_CAP_HOSTOS_HW_TEST = 0x6029, 79 | CERT_CAP_HOSTOS_ODD_TEST = 0x602A, 80 | CERT_CAP_REDUCE_MODE_TESTER = 0x7002, 81 | CERT_CAP_SP_DEVKIT = 0x8001, 82 | CERT_CAP_HW_SP_DEBUG = 0x8002, 83 | CERT_CAP_SCP_DEBUG = 0x8003, 84 | CERT_CAP_HW_SDF = 0x8004, 85 | CERT_CAP_HW_ALL_DEBUG = 0x8005, 86 | CERT_CAP_HW_AEB_DEBUG = 0x8006, 87 | CERT_CAP_HW_BTG = 0x8007, 88 | CERT_CAP_SP_TESTER = 0x8008, 89 | CERT_CAP_SP_DEBUG_BUILD = 0x8009, 90 | CERT_CAP_NO_FUSE_BLOW = 0x800A, 91 | CERT_CAP_HW_DIS_N_CALIB_RETEST = 0x800B, 92 | CERT_CAP_HW_DIS_CRIT_FUSE_CHK_RETEST = 0x800C, 93 | CERT_CAP_GREEN_SRA_DEBUG = 0x800D, 94 | CERT_CAP_GREEN_ERA_DEBUG = 0x800E, 95 | CERT_CAP_HW_DIS_RNG_CHK_SECURE = 0x800F, 96 | CERT_CAP_HW_EN_FUSE_OVRD_SECURE = 0x8010, 97 | CERT_CAP_GREEN_HOST_DEBUG = 0x8011, 98 | CERT_CAP_GREEN_ALLOW_DISK_LICENSES = 0x8012, 99 | CERT_CAP_VIRT_CAP_DEVKIT_ANY_EQUIV = 0xF001, 100 | CERT_CAP_VIRT_CAP_DEVKIT_INTERNAL_EQUIV = 0xF002, 101 | CERT_CAP_VIRT_CAP_SP_DEVKIT_EQUIV = 0xF003 102 | } 103 | } -------------------------------------------------------------------------------- /LibXboxOne/Certificates/BootCapabilityCert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace LibXboxOne.Certificates 6 | { 7 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 8 | public struct BootCapabilityCert 9 | { 10 | public ushort StructId; 11 | public ushort Size; 12 | public ushort ProtocolVersion; 13 | public ushort IssuerKeyId; 14 | 15 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] 16 | public byte[] IssueDate; 17 | 18 | public DateTime IssueDateTime 19 | { 20 | get 21 | { 22 | var filetime = BitConverter.ToInt64(IssueDate, 0); 23 | return DateTime.FromFileTime(filetime); 24 | } 25 | } 26 | 27 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] 28 | public byte[] SocId; 29 | 30 | public ushort GenerationId; 31 | public byte AllowedStates; 32 | public byte LastCapability; 33 | public uint Flags; 34 | public byte ExpireCentury; 35 | public byte ExpireYear; 36 | public byte ExpireMonth; 37 | public byte ExpireDayOfMonth; 38 | public byte ExpireHour; 39 | public byte ExpireMinute; 40 | public byte ExpireSecond; 41 | public byte MinimumSpVersion; 42 | public ulong Minimum2blVersion; 43 | 44 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] 45 | public byte[] Nonce; 46 | 47 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x38)] 48 | public byte[] Reserved; 49 | 50 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] 51 | public BootCapability[] Capabilities; 52 | 53 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x180)] 54 | public byte[] RsaSignature; 55 | 56 | public override string ToString() 57 | { 58 | return ToString(false); 59 | } 60 | 61 | public string ToString(bool formatted) 62 | { 63 | string fmt = formatted ? " " : ""; 64 | 65 | var b = new StringBuilder(); 66 | b.AppendLineSpace("BootCapabilityCert:"); 67 | 68 | if (StructId == 0x00) 69 | { 70 | b.AppendLineSpace(fmt + "! No or invalid certificate (StructId: 0)\n"); 71 | return b.ToString(); 72 | } 73 | 74 | b.AppendLineSpace(fmt + $"StructId: 0x{StructId:X}"); 75 | b.AppendLineSpace(fmt + $"Size: {Size} (0x{Size:X})"); 76 | b.AppendLineSpace(fmt + $"IssuerKeyId: {IssuerKeyId} (0x{IssuerKeyId:X})"); 77 | b.AppendLineSpace(fmt + $"IssueDate: {IssueDateTime}"); 78 | b.AppendLineSpace(fmt + $"SocId: {Environment.NewLine}{fmt}{SocId.ToHexString()}"); 79 | b.AppendLineSpace(fmt + $"GenerationId: {GenerationId} (0x{GenerationId:X})"); 80 | b.AppendLineSpace(fmt + $"AllowedStates: {AllowedStates} (0x{AllowedStates:X})"); 81 | b.AppendLineSpace(fmt + $"LastCapability: {LastCapability} (0x{LastCapability:X})"); 82 | b.AppendLineSpace(fmt + $"Flags: {Flags} (0x{Flags:X})"); 83 | 84 | var expiry = $"{ExpireCentury}{ExpireYear:00}-{ExpireMonth:00}-{ExpireDayOfMonth:00} {ExpireHour:00}:{ExpireMinute:00}:{ExpireSecond:00}"; 85 | if (ExpireCentury == 255) 86 | expiry = "never!"; 87 | b.AppendLineSpace(fmt + $"Expiration date: {expiry}"); 88 | 89 | b.AppendLineSpace(fmt + $"MinimumSpVersion: {MinimumSpVersion} (0x{MinimumSpVersion:X})"); 90 | b.AppendLineSpace(fmt + $"Minimum2blVersion: {Minimum2blVersion} (0x{Minimum2blVersion:X})"); 91 | 92 | b.AppendLineSpace(fmt + $"Nonce: {Environment.NewLine}{fmt}{Nonce.ToHexString()}"); 93 | b.AppendLineSpace(fmt + $"Reserved: {Environment.NewLine}{fmt}{Reserved.ToHexString()}"); 94 | b.AppendLineSpace(fmt + $"RsaSignature: {Environment.NewLine}{fmt}{RsaSignature.ToHexString()}"); 95 | 96 | b.AppendLineSpace(fmt + "Boot Capabilities:"); 97 | for(int i = 0; i < Capabilities.Length; i++) 98 | { 99 | BootCapability cap = Capabilities[i]; 100 | 101 | if (cap == BootCapability.CERT_CAP_NONE 102 | || cap == BootCapability.CERT_CAP_DELETED) 103 | { 104 | continue; 105 | } 106 | b.AppendLine($"{fmt}-{cap}"); 107 | } 108 | 109 | b.AppendLine(); 110 | return b.ToString(); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /LibXboxOne/Certificates/PspConsoleCert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace LibXboxOne.Certificates 6 | { 7 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 8 | public struct PspConsoleCert 9 | { 10 | public UInt16 StructID; // 0x4343 (ASCII: CC = ConsoleCert?) 11 | 12 | public UInt16 Size; 13 | 14 | public UInt16 IssuerKeyId; // Key Version 15 | 16 | public UInt16 ProtocolVer; // unknown 17 | 18 | public UInt32 IssueDate; // POSIX time 19 | 20 | public DateTime IssueDateTime 21 | { 22 | get 23 | { 24 | return DateTime.UnixEpoch.AddSeconds(IssueDate); 25 | } 26 | } 27 | 28 | public UInt32 PspRevisionId; // PSP Version 29 | 30 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] 31 | public byte[] SocId; 32 | 33 | public UInt16 GenerationId; 34 | 35 | public byte ConsoleRegion; 36 | 37 | public byte ReservedByte; // 0 38 | 39 | public UInt32 ReservedDword; // 0 40 | 41 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x8)] 42 | public byte[] VendorId; // size of 8 43 | 44 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] 45 | public byte[] AttestationPubKey; // Public key that is used by the Xbox One ChalResp system 46 | 47 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] 48 | public byte[] ReservedPublicKey; 49 | 50 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xC)] 51 | public char[] ConsoleSerialNumber; 52 | 53 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x8)] 54 | public byte[] ConsoleSku; 55 | 56 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)] 57 | public byte[] ConsoleSettingsHash; // Hash of factory settings (SHA-256) 58 | 59 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xC)] 60 | public char[] ConsolePartNumber; 61 | 62 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] 63 | public byte[] SomeData; // unknown 64 | 65 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x180)] 66 | public byte[] RsaSignature; 67 | 68 | public string ConsoleSerialNumberString 69 | { 70 | get 71 | { 72 | return new string(ConsoleSerialNumber); 73 | } 74 | } 75 | 76 | public string ConsolePartNumberString 77 | { 78 | get 79 | { 80 | return new string(ConsolePartNumber); 81 | } 82 | } 83 | 84 | public override string ToString() 85 | { 86 | return ToString(false); 87 | } 88 | 89 | public string ToString(bool formatted) 90 | { 91 | string fmt = formatted ? " " : ""; 92 | 93 | var b = new StringBuilder(); 94 | b.AppendLineSpace("PspConsoleCert:"); 95 | b.AppendLineSpace(fmt + $"StructId: 0x{StructID:X}"); 96 | b.AppendLineSpace(fmt + $"Size: {Size} (0x{Size:X})"); 97 | b.AppendLineSpace(fmt + $"IssuerKeyId: {IssuerKeyId} (0x{IssuerKeyId:X})"); 98 | b.AppendLineSpace(fmt + $"ProtocolVer: {ProtocolVer} (0x{ProtocolVer:X})"); 99 | b.AppendLineSpace(fmt + $"IssueDateTime: {IssueDateTime} ({IssueDate})"); 100 | b.AppendLineSpace(fmt + $"PspRevisionId: {PspRevisionId} (0x{PspRevisionId:X})"); 101 | b.AppendLineSpace(fmt + $"SocId: {SocId.ToHexString()}"); 102 | b.AppendLineSpace(fmt + $"GenerationId: {GenerationId} (0x{GenerationId:X})"); 103 | b.AppendLineSpace(fmt + $"ConsoleRegion: {ConsoleRegion} (0x{ConsoleRegion:X})"); 104 | b.AppendLineSpace(fmt + $"ReservedByte: {ReservedByte} (0x{ReservedByte:X})"); 105 | b.AppendLineSpace(fmt + $"ReservedDword: {ReservedDword} (0x{ReservedDword:X})"); 106 | b.AppendLineSpace(fmt + $"VendorId: {VendorId.ToHexString()}"); 107 | b.AppendLineSpace(fmt + $"AttestationPubKey: {Environment.NewLine}{fmt}{AttestationPubKey.ToHexString()}"); 108 | b.AppendLineSpace(fmt + $"ReservedPublicKey: {Environment.NewLine}{fmt}{ReservedPublicKey.ToHexString()}"); 109 | b.AppendLineSpace(fmt + $"ConsoleSerialNumberString: {ConsoleSerialNumberString}"); 110 | b.AppendLineSpace(fmt + $"ConsoleSku: {ConsoleSku.ToHexString()}"); 111 | b.AppendLineSpace(fmt + $"ConsoleSettingsHash: {ConsoleSettingsHash.ToHexString(String.Empty)}"); 112 | b.AppendLineSpace(fmt + $"ConsolePartNumberString: {ConsolePartNumberString}"); 113 | b.AppendLineSpace(fmt + $"SomeData: {SomeData.ToHexString()}"); 114 | b.AppendLineSpace(fmt + $"RsaSignature: {Environment.NewLine}{fmt}{RsaSignature.ToHexString()}"); 115 | 116 | b.AppendLine(); 117 | return b.ToString(); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /LibXboxOne/Common.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace LibXboxOne 5 | { 6 | public static class Common 7 | { 8 | public static string AppVersion => 9 | Assembly.GetExecutingAssembly() 10 | .GetCustomAttribute() 11 | .InformationalVersion; 12 | } 13 | } -------------------------------------------------------------------------------- /LibXboxOne/Crypto/AesXts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | 5 | namespace LibXboxOne 6 | { 7 | class AesXtsTransform : IDisposable 8 | { 9 | public int BlockSize; 10 | private readonly byte[] _tweakBytes; 11 | private readonly ICryptoTransform _tweakEncryptor; 12 | private readonly ICryptoTransform _dataTransform; 13 | private readonly SymmetricAlgorithm _symmetricAlgorithm; 14 | 15 | public AesXtsTransform(byte[] tweakBytes, byte[] dataAesKey, byte[] tweakAesKey, bool encrypt) 16 | { 17 | if (tweakBytes == null) throw new InvalidDataException("Tweak bytes not provided"); 18 | if (dataAesKey == null) throw new InvalidDataException("Data AES key not provided"); 19 | if (tweakAesKey == null) throw new InvalidDataException("Tweak AES key not provided"); 20 | if (tweakBytes.Length != 16) throw new InvalidDataException("Tweak bytes not 16 bytes"); 21 | if (dataAesKey.Length != 16) throw new InvalidDataException("Data AES key not 16 bytes"); 22 | if (tweakAesKey.Length != 16) throw new InvalidDataException("Tweak AES not 16 bytes"); 23 | 24 | _tweakBytes = tweakBytes; 25 | 26 | _symmetricAlgorithm = Aes.Create(); 27 | _symmetricAlgorithm.Padding = PaddingMode.None; 28 | _symmetricAlgorithm.Mode = CipherMode.ECB; 29 | 30 | byte[] nullIv = new byte[16]; 31 | _tweakEncryptor = _symmetricAlgorithm.CreateEncryptor(tweakAesKey, nullIv); 32 | _dataTransform = encrypt ? _symmetricAlgorithm.CreateEncryptor(dataAesKey, nullIv) : 33 | _symmetricAlgorithm.CreateDecryptor(dataAesKey, nullIv); 34 | 35 | BlockSize = _symmetricAlgorithm.BlockSize / 8; 36 | } 37 | 38 | internal static byte[] MultiplyTweak(byte[] tweak) 39 | { 40 | byte dl = 0; 41 | var newTweak = new byte[0x10]; 42 | 43 | for (int i = 0; i < 0x10; i++) 44 | { 45 | byte cl = tweak[i]; 46 | byte al = cl; 47 | al = (byte)(al + al); 48 | al = (byte)(al | dl); 49 | dl = cl; 50 | newTweak[i] = al; 51 | dl = (byte)(dl >> 7); 52 | } 53 | if (dl != 0) 54 | newTweak[0] = (byte)(newTweak[0] ^ 0x87); 55 | return newTweak; 56 | } 57 | 58 | public int TransformDataUnit(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset, uint dataUnit) 59 | { 60 | byte[] encryptedTweak = new byte[0x10]; 61 | byte[] tweak = _tweakBytes; 62 | 63 | // Update tweak with data unit number 64 | Array.Copy(BitConverter.GetBytes(dataUnit), tweak, 4); 65 | 66 | // Encrypt tweak 67 | _tweakEncryptor.TransformBlock(tweak, 0, tweak.Length, encryptedTweak, 0); 68 | 69 | byte[] encryptedTweakOrig = new byte[0x10]; 70 | Array.Copy(encryptedTweak, encryptedTweakOrig, 0x10); 71 | 72 | int blocks = inputCount / BlockSize; 73 | 74 | // Apply first part of tweak (input-tweak) to input buffer all at once 75 | for (int i = 0; i < blocks; i++) 76 | { 77 | for (int y = 0; y < BlockSize; y++) 78 | outputBuffer[outputOffset + (i * BlockSize) + y] = (byte)(inputBuffer[inputOffset + (i * BlockSize) + y] ^ encryptedTweak[y % encryptedTweak.Length]); 79 | 80 | encryptedTweak = MultiplyTweak(encryptedTweak); 81 | } 82 | 83 | // AES transform the data... 84 | var transformedBytes = _dataTransform.TransformBlock(outputBuffer, outputOffset, inputCount, outputBuffer, outputOffset); 85 | 86 | // Reset tweak back to original encrypted tweak and then apply output-tweak 87 | Array.Copy(encryptedTweakOrig, encryptedTweak, 0x10); 88 | for (int i = 0; i < blocks; i++) 89 | { 90 | for (int y = 0; y < BlockSize; y++) 91 | outputBuffer[outputOffset + (i * BlockSize) + y] = (byte)(outputBuffer[outputOffset + (i * BlockSize) + y] ^ encryptedTweak[y % encryptedTweak.Length]); 92 | 93 | encryptedTweak = MultiplyTweak(encryptedTweak); 94 | } 95 | 96 | return transformedBytes; 97 | } 98 | 99 | public void Dispose() 100 | { 101 | _tweakEncryptor.Dispose(); 102 | _dataTransform.Dispose(); 103 | _symmetricAlgorithm.Dispose(); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /LibXboxOne/Crypto/BCryptRsaImport.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.InteropServices; 3 | using System.Security.Cryptography; 4 | 5 | namespace LibXboxOne 6 | { 7 | public enum BCRYPT_RSABLOB_MAGIC : uint 8 | { 9 | // The key is an RSA public key. 10 | RSAPUBLIC = 0x31415352, 11 | // The key is an RSA private key. 12 | RSAPRIVATE = 0x32415352, 13 | // The key is a full RSA private key. 14 | RSAFULLPRIVATE = 0x33415352 15 | }; 16 | 17 | [StructLayout(LayoutKind.Sequential)] 18 | public struct BCRYPT_RSAKEY_BLOB 19 | { 20 | public BCRYPT_RSABLOB_MAGIC Magic; 21 | public uint BitLength; 22 | public uint cbPublicExp; 23 | public uint cbModulus; 24 | public uint cbPrime1; 25 | public uint cbPrime2; 26 | }; 27 | 28 | public sealed class BCryptRsaImport 29 | { 30 | public static RSAParameters BlobToParameters(byte[] blobData, out int bitLength, out bool isPrivate) 31 | { 32 | var parameters = new RSAParameters(); 33 | var reader = new BinaryReader(new MemoryStream(blobData)); 34 | 35 | BCRYPT_RSAKEY_BLOB header = reader.ReadStruct(); 36 | if (header.Magic == BCRYPT_RSABLOB_MAGIC.RSAPUBLIC) 37 | isPrivate = false; 38 | else if (header.Magic == BCRYPT_RSABLOB_MAGIC.RSAPRIVATE || 39 | header.Magic == BCRYPT_RSABLOB_MAGIC.RSAFULLPRIVATE) 40 | isPrivate = true; 41 | else 42 | throw new InvalidDataException("Unexpected RSA keyblob"); 43 | 44 | bitLength = (int)header.BitLength; 45 | 46 | parameters.Exponent = reader.ReadBytes((int)header.cbPublicExp); 47 | parameters.Modulus = reader.ReadBytes((int)header.cbModulus); 48 | 49 | if (header.Magic == BCRYPT_RSABLOB_MAGIC.RSAPUBLIC) 50 | return parameters; 51 | 52 | // RSAPRIVATEBLOB 53 | parameters.P = reader.ReadBytes((int)header.cbPrime1); 54 | parameters.Q = reader.ReadBytes((int)header.cbPrime2); 55 | 56 | if (header.Magic == BCRYPT_RSABLOB_MAGIC.RSAPRIVATE) 57 | return parameters; 58 | 59 | // RSAFULLPRIVATEBLOB 60 | parameters.DP = reader.ReadBytes((int)header.cbPrime1); 61 | parameters.DQ = reader.ReadBytes((int)header.cbPrime2); 62 | parameters.InverseQ = reader.ReadBytes((int)header.cbPrime1); 63 | parameters.D = reader.ReadBytes((int)header.cbModulus); 64 | 65 | return parameters; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /LibXboxOne/Crypto/HashUtils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Security.Cryptography; 3 | using Org.BouncyCastle.Crypto; 4 | using Org.BouncyCastle.Crypto.Parameters; 5 | using Org.BouncyCastle.Security; 6 | 7 | namespace LibXboxOne 8 | { 9 | public static class HashUtils 10 | { 11 | public static byte[] ComputeSha256(byte[] data) 12 | { 13 | return ComputeSha256(data, 0, data.Length); 14 | } 15 | 16 | public static byte[] ComputeSha256(Stream stream) 17 | { 18 | return SHA256.Create().ComputeHash(stream); 19 | } 20 | 21 | public static byte[] ComputeSha256(byte[] data, int offset, int length) 22 | { 23 | return SHA256.Create().ComputeHash(data, offset, length); 24 | } 25 | 26 | public static byte[] ComputeSha1(byte[] data) 27 | { 28 | return ComputeSha1(data, 0, data.Length); 29 | } 30 | 31 | public static byte[] ComputeSha1(Stream stream) 32 | { 33 | return SHA1.Create().ComputeHash(stream); 34 | } 35 | 36 | public static byte[] ComputeSha1(byte[] data, int offset, int length) 37 | { 38 | return SHA1.Create().ComputeHash(data, offset, length); 39 | } 40 | 41 | public static bool SignData(byte[] key, string keyType, byte[] data, out byte[] signature) // keyType = RSAFULLPRIVATEBLOB, RSAPRIVATEBLOB, RSAPUBLICBLOB 42 | { 43 | if (keyType != "RSAFULLPRIVATEBLOB") 44 | throw new CryptographicException("Only RSAFULLPRIVATEBLOB can be used for signing"); 45 | 46 | var rsaParams = BCryptRsaImport.BlobToParameters(key, out int bitLength, out bool isPrivate); 47 | var rsaKey = DotNetUtilities.GetRsaKeyPair(rsaParams).Private; 48 | ISigner s = SignerUtilities.GetSigner("SHA256withRSA/PSS"); 49 | 50 | s.Init(true, new ParametersWithRandom(rsaKey)); 51 | s.BlockUpdate(data, 0, data.Length); 52 | 53 | signature = s.GenerateSignature(); 54 | return true; 55 | } 56 | 57 | public static bool VerifySignature(byte[] key, byte[] signature, byte[] data) // keyType = RSAFULLPRIVATEBLOB, RSAPRIVATEBLOB, RSAPUBLICBLOB 58 | { 59 | var rsaParams = BCryptRsaImport.BlobToParameters(key, out int bitLength, out bool isPrivate); 60 | var rsaKey = DotNetUtilities.GetRsaPublicKey(rsaParams); 61 | ISigner s = SignerUtilities.GetSigner("SHA256withRSA/PSS"); 62 | 63 | s.Init(false, new ParametersWithRandom(rsaKey)); 64 | s.BlockUpdate(data, 0, data.Length); 65 | 66 | return s.VerifySignature(signature); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /LibXboxOne/Keys/DurangoKeys.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace LibXboxOne.Keys 7 | { 8 | public static class DurangoKeys 9 | { 10 | public static Guid TestCIK => new Guid("33EC8436-5A0E-4F0D-B1CE-3F29C3955039"); 11 | public static string RedXvdPrivateKey => "RedXvdPrivateKey"; 12 | 13 | static readonly Dictionary SignkeyStorage = new Dictionary(){ 14 | // Xvd signing keys 15 | {"RedXvdPrivateKey", new DurangoKeyEntry(KeyType.XvdSigningKey, sha256Hash: "8E2B60377006D87EE850334C42FC200081386A838C65D96D1EA52032AA9628C5", dataSize: 0x91B)}, 16 | {"GreenXvdPublicKey", new DurangoKeyEntry(KeyType.XvdSigningKey, sha256Hash: "618C5FB1193040AF8BC1C0199B850B4B5C42E43CE388129180284E4EF0B18082", dataSize: 0x21B)}, 17 | {"GreenGamesPublicKey", new DurangoKeyEntry(KeyType.XvdSigningKey, sha256Hash: "183F0AE05431E4AD91554E88946967C872997227DBE6C85116F5FD2FD2D1229E", dataSize: 0x21B)}, 18 | }; 19 | 20 | static readonly Dictionary OdkStorage = new Dictionary(){ 21 | {OdkIndex.RedOdk, new DurangoKeyEntry(KeyType.Odk, sha256Hash: "CA37132DFB4B811506AE4DC45F45970FED8FE5E58C1BACB259F1B96145B0EBC6", dataSize: 0x20)} 22 | }; 23 | 24 | static readonly Dictionary CikStorage = new Dictionary() 25 | { 26 | {new Guid("33EC8436-5A0E-4F0D-B1CE-3F29C3955039"), new DurangoKeyEntry(KeyType.Cik, sha256Hash: "6786C11B788ED5CCE3C7695425CB82970347180650893D1B5613B2EFB33F9F4E", dataSize: 0x20)}, // TestCIK 27 | {new Guid("F0522B7C-7FC1-D806-43E3-68A5DAAB06DA"), new DurangoKeyEntry(KeyType.Cik, sha256Hash: "B767CE5F83224375E663A1E01044EA05E8022C033D96BED952475D87F0566642", dataSize: 0x20)}, 28 | }; 29 | 30 | static DurangoKeys() 31 | { 32 | } 33 | 34 | public static KeyValuePair[] GetAllXvdSigningKeys() 35 | { 36 | return SignkeyStorage.ToArray(); 37 | } 38 | 39 | public static KeyValuePair[] GetAllODK() 40 | { 41 | return OdkStorage.ToArray(); 42 | } 43 | 44 | public static KeyValuePair[] GetAllCIK() 45 | { 46 | return CikStorage.ToArray(); 47 | } 48 | 49 | public static bool KnowsSignKeySHA256(byte[] sha256Hash, out string keyName) 50 | { 51 | foreach (var kvp in SignkeyStorage) 52 | { 53 | if (!kvp.Value.SHA256Hash.IsEqualTo(sha256Hash)) 54 | continue; 55 | 56 | keyName = kvp.Key; 57 | return true; 58 | } 59 | keyName = ""; 60 | return false; 61 | } 62 | 63 | public static bool KnowsOdkSHA256(byte[] sha256Hash, out OdkIndex keyId) 64 | { 65 | foreach (var kvp in OdkStorage) 66 | { 67 | if (!kvp.Value.SHA256Hash.IsEqualTo(sha256Hash)) 68 | continue; 69 | 70 | keyId = kvp.Key; 71 | return true; 72 | } 73 | keyId = OdkIndex.Invalid; 74 | return false; 75 | } 76 | 77 | public static bool KnowsCikSHA256(byte[] sha256Hash, out Guid keyGuid) 78 | { 79 | foreach (var kvp in CikStorage) 80 | { 81 | if (!kvp.Value.SHA256Hash.IsEqualTo(sha256Hash)) 82 | continue; 83 | 84 | keyGuid = kvp.Key; 85 | return true; 86 | } 87 | keyGuid = Guid.Empty; 88 | return false; 89 | } 90 | 91 | public static bool GetOdkIndexFromString(string name, out OdkIndex odkIndex) 92 | { 93 | // First, try to convert to know Enum values 94 | var success = Enum.TryParse(name, true, out odkIndex); 95 | if (success) 96 | return true; 97 | 98 | odkIndex = OdkIndex.Invalid; 99 | success = UInt32.TryParse(name, out uint odkUint); 100 | if (success) 101 | // Odk Id is valid uint but we don't know its Enum name yet 102 | odkIndex = (OdkIndex)odkUint; 103 | 104 | return success; 105 | } 106 | 107 | public static int LoadCikKeys(byte[] keyData, out Guid[] loadedKeys) 108 | { 109 | int foundCount = 0; 110 | if (keyData.Length < 0x30 || keyData.Length % 0x30 != 0) 111 | throw new Exception("Misaligned CIK, expecting array of 0x30 bytes: 0x10 bytes: GUID, 0x20 bytes: Key"); 112 | 113 | int cikKeyCount = keyData.Length / 0x30; 114 | loadedKeys = new Guid[cikKeyCount]; 115 | using (BinaryReader br = new BinaryReader(new MemoryStream(keyData))) 116 | { 117 | for (int keyIndex = 0; keyIndex < cikKeyCount; keyIndex++) 118 | { 119 | var guid = new Guid(br.ReadBytes(0x10)); 120 | var cikKeyData = br.ReadBytes(0x20); 121 | var sha256Hash = HashUtils.ComputeSha256(cikKeyData); 122 | bool hashMatches = KnowsCikSHA256(sha256Hash, out Guid verifyGuid); 123 | var hashString = sha256Hash.ToHexString(""); 124 | 125 | if (hashMatches && verifyGuid != guid) 126 | { 127 | Console.WriteLine($"CIK {guid} with hash {hashString} is known as {verifyGuid}"); 128 | continue; 129 | } 130 | 131 | if (hashMatches && CikStorage[guid].HasKeyData) 132 | { 133 | // Duplicate key, already loaded 134 | Console.WriteLine($"CIK {guid} is already loaded"); 135 | continue; 136 | } 137 | 138 | CikStorage[guid] = new DurangoKeyEntry(KeyType.Cik, cikKeyData); 139 | foundCount++; 140 | } 141 | } 142 | return foundCount; 143 | } 144 | 145 | public static bool LoadOdkKey(OdkIndex keyId, byte[] keyData, out bool isNewKey) 146 | { 147 | isNewKey = false; 148 | byte[] sha256Hash = HashUtils.ComputeSha256(keyData); 149 | DurangoKeyEntry existingKey = GetOdkById(keyId); 150 | 151 | if (existingKey != null) 152 | { 153 | bool hashMatches = KnowsOdkSHA256(sha256Hash, out OdkIndex verifyKeyId); 154 | if (hashMatches && verifyKeyId != keyId) 155 | { 156 | var hashString = sha256Hash.ToHexString(""); 157 | Console.WriteLine($"ODK {keyId} with hash {hashString} is known as ODK {verifyKeyId}"); 158 | return false; 159 | } 160 | 161 | if (hashMatches && OdkStorage[keyId].HasKeyData) 162 | { 163 | // Duplicate key, already loaded 164 | Console.WriteLine($"ODK {keyId} is already loaded"); 165 | return false; 166 | } 167 | 168 | if (!hashMatches) 169 | { 170 | Console.WriteLine($"ODK {keyId} does not match expected hash"); 171 | return false; 172 | } 173 | 174 | OdkStorage[keyId].SetKey(keyData); 175 | return true; 176 | } 177 | 178 | isNewKey = true; 179 | OdkStorage[keyId] = new DurangoKeyEntry(KeyType.Odk, keyData); 180 | return true; 181 | } 182 | 183 | public static bool LoadSignKey(string desiredKeyName, byte[] keyData, out bool isNewKey, out string keyName) 184 | { 185 | isNewKey = false; 186 | byte[] sha256Hash = HashUtils.ComputeSha256(keyData); 187 | 188 | bool hashMatches = KnowsSignKeySHA256(sha256Hash, out keyName); 189 | if (hashMatches && SignkeyStorage[keyName].HasKeyData) 190 | { 191 | // Duplicate key, already loaded 192 | Console.WriteLine($"SignKey {keyName} is already loaded"); 193 | return false; 194 | } 195 | 196 | if (hashMatches) 197 | { 198 | SignkeyStorage[keyName].SetKey(keyData); 199 | return true; 200 | } 201 | 202 | // New key, using user-set keyname 203 | isNewKey = true; 204 | SignkeyStorage[desiredKeyName] = new DurangoKeyEntry(KeyType.Odk, keyData); 205 | return true; 206 | } 207 | 208 | public static bool LoadKey(KeyType keyType, string keyFilePath) 209 | { 210 | if (keyFilePath == String.Empty) 211 | return false; 212 | 213 | bool success; 214 | var isNewKey = false; 215 | var keyName = String.Empty; 216 | var filename = Path.GetFileNameWithoutExtension(keyFilePath); 217 | var keyBytes = File.ReadAllBytes(keyFilePath); 218 | 219 | switch(keyType) 220 | { 221 | case KeyType.XvdSigningKey: 222 | success = LoadSignKey(filename, keyBytes, out isNewKey, out keyName); 223 | break; 224 | case KeyType.Odk: 225 | success = GetOdkIndexFromString(filename, out OdkIndex odkIndex); 226 | if (!success) 227 | { 228 | Console.WriteLine($"Could not get OdkIndex from filename: {filename}"); 229 | break; 230 | } 231 | success = LoadOdkKey(odkIndex, keyBytes, out isNewKey); 232 | break; 233 | case KeyType.Cik: 234 | var keyCount = LoadCikKeys(keyBytes, out Guid[] loadedCiks); 235 | success = keyCount > 0; 236 | break; 237 | default: 238 | throw new InvalidOperationException("Invalid KeyType supplied"); 239 | } 240 | 241 | return success; 242 | } 243 | 244 | public static int LoadKeysRecursive(string basePath) 245 | { 246 | int foundCount = 0; 247 | foreach (KeyType keyType in Enum.GetValues(typeof(KeyType))) 248 | { 249 | var keyDirectory = Path.Combine(basePath, keyType.ToString()); 250 | if (!Directory.Exists(keyDirectory)) 251 | { 252 | Console.WriteLine($"Key directory \"{keyDirectory}\" was not found!"); 253 | continue; 254 | } 255 | 256 | var keyFiles = Directory.GetFiles(keyDirectory); 257 | foreach (var keyFilePath in keyFiles) 258 | { 259 | if (LoadKey(keyType, keyFilePath)) 260 | { 261 | Console.WriteLine($"Loaded key {keyType} from {keyFilePath}"); 262 | foundCount++; 263 | } 264 | else 265 | Console.WriteLine($"Unable to load key from \"{keyFilePath}\""); 266 | } 267 | } 268 | return foundCount; 269 | } 270 | 271 | public static DurangoKeyEntry GetSignkeyByName(string keyName) 272 | { 273 | return SignkeyStorage.ContainsKey(keyName) ? SignkeyStorage[keyName] : null; 274 | } 275 | 276 | public static DurangoKeyEntry GetCikByGuid(Guid keyId) 277 | { 278 | return CikStorage.ContainsKey(keyId) ? CikStorage[keyId] : null; 279 | } 280 | 281 | public static DurangoKeyEntry GetOdkById(OdkIndex keyId) 282 | { 283 | return OdkStorage.ContainsKey(keyId) ? OdkStorage[keyId] : null; 284 | } 285 | 286 | public static bool IsSignkeyLoaded(string keyName) 287 | { 288 | var key = GetSignkeyByName(keyName); 289 | return key != null && key.HasKeyData; 290 | } 291 | 292 | public static bool IsCikLoaded(Guid keyId) 293 | { 294 | var key = GetCikByGuid(keyId); 295 | return key != null && key.HasKeyData; 296 | } 297 | 298 | public static bool IsOdkLoaded(OdkIndex keyId) 299 | { 300 | var key = GetOdkById(keyId); 301 | return key != null && key.HasKeyData; 302 | } 303 | } 304 | } -------------------------------------------------------------------------------- /LibXboxOne/Keys/KeyEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LibXboxOne.Keys 4 | { 5 | public interface IKeyEntry 6 | { 7 | bool HasKeyData { get; } 8 | 9 | int DataSize { get; } 10 | byte[] SHA256Hash { get; } 11 | byte[] KeyData { get; } 12 | KeyType KeyType { get; } 13 | 14 | void SetKey(byte[] keyData); 15 | } 16 | 17 | public class DurangoKeyEntry : IKeyEntry 18 | { 19 | public bool HasKeyData => KeyData != null && KeyData.Length == DataSize; 20 | public byte[] SHA256Hash { get; } 21 | public int DataSize { get; } 22 | public byte[] KeyData { get; private set; } 23 | public KeyType KeyType { get; } 24 | 25 | public DurangoKeyEntry(KeyType keyType, string sha256Hash, int dataSize) 26 | { 27 | KeyType = keyType; 28 | SHA256Hash = sha256Hash.ToBytes(); 29 | DataSize = dataSize; 30 | if (SHA256Hash.Length != 0x20) 31 | throw new DataMisalignedException("Invalid length for SHA256Hash"); 32 | } 33 | 34 | public DurangoKeyEntry(KeyType keyType, byte[] keyData) 35 | { 36 | KeyType = keyType; 37 | SHA256Hash = HashUtils.ComputeSha256(keyData); 38 | DataSize = keyData.Length; 39 | KeyData = keyData; 40 | } 41 | 42 | public void SetKey(byte[] newKeyData) 43 | { 44 | if (HasKeyData) 45 | throw new InvalidOperationException("KeyData is already filled!"); 46 | if (newKeyData.Length != DataSize) 47 | throw new InvalidProgramException($"Unexpected keydata of length: {newKeyData.Length} bytes"); 48 | 49 | KeyData = newKeyData; 50 | } 51 | 52 | public override string ToString() 53 | { 54 | return $"Loaded: {HasKeyData} Hash: {SHA256Hash.ToHexString("")} Size: {DataSize}"; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /LibXboxOne/Keys/KeyType.cs: -------------------------------------------------------------------------------- 1 | namespace LibXboxOne.Keys 2 | { 3 | public enum KeyType 4 | { 5 | XvdSigningKey, 6 | Odk, // Offline Distribution Key, AES256 (32 bytes) key 7 | Cik // Content Instance Key, Guid (16 bytes) + AES256 (32 bytes) key 8 | } 9 | } -------------------------------------------------------------------------------- /LibXboxOne/Keys/OdkIndex.cs: -------------------------------------------------------------------------------- 1 | namespace LibXboxOne.Keys 2 | { 3 | public enum OdkIndex : uint 4 | { 5 | StandardOdk = 0, 6 | GreenOdk = 1, 7 | RedOdk = 2, 8 | Invalid = 0xFFFFFFFF 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LibXboxOne/LibXboxOne.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <_Parameter1>$(MSBuildProjectName).Tests 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /LibXboxOne/MiscStructs.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace LibXboxOne 4 | { 5 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 6 | public struct XviHeader 7 | { 8 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x240)] 9 | /* 0x0 */ 10 | public byte[] Unknown1; 11 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] 12 | /* 0x240 */ 13 | public byte[] ContentId; 14 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] 15 | /* 0x250 */ 16 | public byte[] VDUID; 17 | /* 0x260 */ 18 | public ulong Unknown4; 19 | /* 0x268 */ 20 | public uint Unknown5; // should be 1 21 | /* 0x26C */ 22 | public uint Unknown6; // should be r9d 23 | } 24 | 25 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 26 | public struct EkbFile 27 | { // EKBs also seem to follow the XvcLicenseBlock format, but with 2 byte block ids instead of 4 byte. 28 | // note: this is just here for reference as EKB files are written sequentially like this, but EKB files should be loaded with the XvcLicenseBlock class now since it's assumed that's how the xbox reads them 29 | 30 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x4)] 31 | /* 0x0 */ 32 | public byte[] Magic; // EKB1 33 | 34 | /* 0x4 */ 35 | public uint HeaderLength; // (length of data proceeding this) 36 | /* 0x8 */ 37 | public ushort Unknown1; // 1? 38 | /* 0xA */ 39 | public uint Unknown2; // 2? 40 | /* 0xE */ 41 | public ushort Unknown3; // 5? 42 | /* 0x10 */ 43 | public ushort Unknown4; // 2? 44 | /* 0x12 */ 45 | public uint Unknown5; // 0x20? 46 | 47 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)] 48 | /* 0x16 */ 49 | public byte[] KeyIdHexChars; 50 | 51 | /* 0x36 */ 52 | public ushort Unknown7; // 3? 53 | /* 0x38 */ 54 | public uint Unknown8; // 2? 55 | /* 0x3C */ 56 | public ushort Unknown9; // 6? lots of calcs for this 57 | /* 0x3E */ 58 | public ushort Unknown10; // 4? 59 | /* 0x40 */ 60 | public uint Unknown11; // 1? 61 | /* 0x44 */ 62 | public byte Unknown12; // 0x31? 63 | /* 0x45 */ 64 | public ushort Unknown13; // 5? 65 | /* 0x47 */ 66 | public uint Unknown14; // 2? 67 | /* 0x4B */ 68 | public ushort Unknown15; // 1? 69 | /* 0x4D */ 70 | public ushort Unknown16; // 7? 71 | /* 0x4F */ 72 | public uint EncryptedDataLength; // EncryptedDataLength 73 | 74 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x180)] 75 | /* 0x53 */ 76 | public byte[] EncryptedData; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /LibXboxOne/NAND/XbfsEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | 4 | namespace LibXboxOne.Nand 5 | { 6 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 7 | public struct XbfsEntry 8 | { 9 | /* 0x0 */ public uint LBA; 10 | /* 0x4 */ public uint BlockCount; 11 | /* 0x8 */ public ulong Reserved; 12 | 13 | public long Length => BlockCount * XbfsFile.BlockSize; 14 | 15 | public long Offset(XbfsFlavor flavor) { 16 | var offset = LBA * XbfsFile.BlockSize; 17 | if (flavor == XbfsFlavor.XboxSeries && offset >= XbfsFile.SeriesOffsetDiff) 18 | offset -= XbfsFile.SeriesOffsetDiff; 19 | return offset; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return ToString(false); 25 | } 26 | 27 | public string ToString(bool formatted) 28 | { 29 | var b = new StringBuilder(); 30 | b.Append($"LBA: 0x{LBA:X} (One: 0x{Offset(XbfsFlavor.XboxOne):X} Series: 0x{Offset(XbfsFlavor.XboxSeries):X}), "); 31 | b.Append($"Length: 0x{BlockCount:X} (0x{Length:X}), "); 32 | b.Append($"Reserved: 0x{Reserved:X}"); 33 | 34 | return b.ToString(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /LibXboxOne/NAND/XbfsFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Linq; 6 | 7 | namespace LibXboxOne.Nand 8 | { 9 | public enum XbfsFlavor { 10 | XboxOne, 11 | XboxSeries, 12 | Invalid, 13 | } 14 | 15 | public enum NandSize: ulong 16 | { 17 | EMMC_LOGICAL = 0x13B00_0000, 18 | EMMC_PHYSICAL = 0x13C00_0000, 19 | NVME_SERIES = 0x4000_0000, 20 | } 21 | 22 | public class XbfsFile 23 | { 24 | public static readonly int SeriesOffsetDiff = 0x6000; 25 | public static readonly int BlockSize = 0x1000; 26 | public static readonly int[] XbfsOffsetsXboxOne = { 0x1_0000, 0x81_0000, 0x82_0000 }; 27 | public static readonly int[] XbfsOffsetsXboxSeries = { 0x0, 0x1800_8000 }; 28 | public static string[] XbfsFilenames = 29 | { 30 | "1smcbl_a.bin", // 0 31 | "header.bin", // 1 32 | "devkit.ini", // 2 33 | "mtedata.cfg", // 3 34 | "certkeys.bin", // 4 35 | "smcerr.log", // 5 36 | "system.xvd", // 6 37 | "$sospf.xvd", // 7, formerly $sosrst.xvd 38 | "download.xvd", // 8 39 | "smc_s.cfg", // 9 40 | "sp_s.cfg", // 10, keyvault? has serial/partnum/osig, handled by psp.sys (/Device/psp) 41 | "os_s.cfg", // 11 42 | "smc_d.cfg", // 12 43 | "sp_d.cfg", // 13 44 | "os_d.cfg", // 14 45 | "smcfw.bin", // 15 46 | "boot.bin", // 16 47 | "host.xvd", // 17 48 | "settings.xvd", // 18 49 | "1smcbl_b.bin", // 19 50 | "bootanim.dat", // 20, this entry and ones below it are only in retail 97xx and above? 51 | "obsolete.001", // 21, formerly sostmpl.xvd 52 | "update.cfg", // 22 53 | "obsolete.002", // 23, formerly sosinit.xvd 54 | "hwinit.cfg", // 24 55 | "qaslt.xvd", // 25 56 | "sp_s.bak", // 26, keyvault backup? has serial/partnum/osig 57 | "update2.cfg", // 27 58 | "obsolete.003", // 28 59 | "dump.lng", // 29 60 | "os_d_dev.cfg", // 30 61 | "os_glob.cfg", // 31 62 | "sp_s.alt", // 32 63 | "sysauxf.xvd", // 33 64 | }; 65 | 66 | private readonly IO _io; 67 | 68 | public XbfsFlavor Flavor = XbfsFlavor.Invalid; 69 | public int[] HeaderOffsets; 70 | public List XbfsHeaders; 71 | 72 | public readonly string FilePath; 73 | public long FileSize => _io.Stream.Length; 74 | 75 | public XbfsFile(string path) 76 | { 77 | FilePath = path; 78 | _io = new IO(path); 79 | } 80 | 81 | public static XbfsFlavor FlavorFromSize(long size) { 82 | return size switch 83 | { 84 | (long)NandSize.EMMC_LOGICAL or (long)NandSize.EMMC_PHYSICAL => XbfsFlavor.XboxOne, 85 | (long)NandSize.NVME_SERIES => XbfsFlavor.XboxSeries, 86 | _ => XbfsFlavor.Invalid, 87 | }; 88 | } 89 | 90 | /// 91 | /// Get Filename for XBFS filename from index 92 | /// 93 | /// Index of file 94 | /// 95 | public static string GetFilenameForIndex(int index) 96 | { 97 | if (index >= XbfsFilenames.Length) { 98 | return null; 99 | } 100 | 101 | return XbfsFilenames[index]; 102 | } 103 | 104 | /// 105 | /// Get index for XBFS filename from filename 106 | /// 107 | /// Filename 108 | /// Returns >= 0 if file is found, -1 otherwise 109 | public static int GetFileindexForName(string name) 110 | { 111 | return Array.IndexOf(XbfsFilenames, name); 112 | } 113 | 114 | public Certificates.PspConsoleCert? ReadPspConsoleCertificate() 115 | { 116 | if (XbfsHeaders == null || XbfsHeaders.Count == 0) 117 | return null; 118 | 119 | long spDataSize = SeekToFile("sp_s.cfg"); 120 | if (spDataSize <= 0) 121 | return null; 122 | 123 | // SP_S.cfg: (secure processor secured config? there's also a blank sp_d.cfg which is probably secure processor decrypted config) 124 | // 0x0 - 0x200 - signature? 125 | // 0x200 - 0x5200 - encrypted data? maybe loaded and decrypted into PSP memory? 126 | // 0x5200 - 0x5400 - blank 127 | // 0x5400 - 0x5800 - console certificate 128 | // 0x5800 - 0x6000 - unknown data, looks like it has some hashes and the OSIG of the BR drive 129 | // 0x6000 - 0x6600 - encrypted data? 130 | // 0x6600 - 0x7400 - blank 131 | // 0x7400 - 0x7410 - unknown data, hash maybe 132 | // 0x7410 - 0x40000 - blank 133 | 134 | _io.Stream.Position += 0x5400; // seek to start of unencrypted data in sp_s (console certificate) 135 | return _io.Reader.ReadStruct(); 136 | } 137 | 138 | public Certificates.BootCapabilityCert? ReadBootcapCertificate() 139 | { 140 | if (XbfsHeaders == null || XbfsHeaders.Count == 0) 141 | return null; 142 | 143 | long spDataSize = SeekToFile("certkeys.bin"); 144 | if (spDataSize <= 0) 145 | return null; 146 | 147 | return _io.Reader.ReadStruct(); 148 | } 149 | 150 | public bool Load() 151 | { 152 | Flavor = FlavorFromSize(FileSize); 153 | 154 | HeaderOffsets = Flavor switch { 155 | XbfsFlavor.XboxOne => XbfsOffsetsXboxOne, 156 | XbfsFlavor.XboxSeries => XbfsOffsetsXboxSeries, 157 | _ => throw new InvalidDataException($"Invalid xbfs filesize: {FileSize:X}"), 158 | }; 159 | 160 | // read each XBFS header 161 | XbfsHeaders = new List(); 162 | foreach (int offset in HeaderOffsets) 163 | { 164 | _io.Stream.Position = offset; 165 | var header = _io.Reader.ReadStruct(); 166 | XbfsHeaders.Add(header); 167 | } 168 | return true; 169 | } 170 | 171 | // returns the size of the file if found 172 | public long SeekToFile(string fileName) 173 | { 174 | int idx = GetFileindexForName(fileName); 175 | if (idx < 0) 176 | return 0; 177 | 178 | long size = 0; 179 | for (int i = 0; i < XbfsHeaders.Count; i++) 180 | { 181 | if (!XbfsHeaders[i].IsValid) 182 | continue; 183 | if (idx >= XbfsHeaders[i].Files.Length) 184 | continue; 185 | var ent = XbfsHeaders[i].Files[idx]; 186 | if (ent.BlockCount == 0) 187 | continue; 188 | _io.Stream.Position = ent.Offset(Flavor); 189 | size = ent.Length; 190 | } 191 | return size; 192 | } 193 | 194 | public string GetXbfsInfo() 195 | { 196 | var info = new Dictionary(); 197 | for (int i = 0; i < XbfsHeaders.Count; i++) 198 | { 199 | if (!XbfsHeaders[i].IsValid) 200 | continue; 201 | for (int y = 0; y < XbfsHeaders[i].Files.Length; y++) 202 | { 203 | var ent = XbfsHeaders[i].Files[y]; 204 | if (ent.Length == 0) 205 | continue; 206 | long start = ent.Offset(Flavor); 207 | long length = ent.Length; 208 | long end = start + length; 209 | string addInfo = $"{end:X} {i}_{y}"; 210 | if (info.ContainsKey(start)) 211 | info[start] += $" {addInfo}"; 212 | else 213 | info.Add(start, addInfo); 214 | } 215 | } 216 | string infoStr = String.Empty; 217 | var keys = info.Keys.ToList(); 218 | keys.Sort(); 219 | foreach (var key in keys) 220 | infoStr += $"{key:X} - {info[key]}{Environment.NewLine}"; 221 | 222 | return infoStr; 223 | } 224 | 225 | public void ExtractXbfsData(string folderPath) 226 | { 227 | if (!Directory.Exists(folderPath)) { 228 | Console.WriteLine($"Creating output folder for xbfs extraction '{folderPath}'"); 229 | Directory.CreateDirectory(folderPath); 230 | } 231 | 232 | var doneAddrs = new List(); 233 | for (int i = 0; i < XbfsHeaders.Count; i++) 234 | { 235 | if (!XbfsHeaders[i].IsValid) 236 | continue; 237 | for (int y = 0; y < XbfsHeaders[i].Files.Length; y++) 238 | { 239 | var ent = XbfsHeaders[i].Files[y]; 240 | if (ent.BlockCount == 0) 241 | continue; 242 | 243 | var xbfsFilename = GetFilenameForIndex(y); 244 | string fileName = $"{ent.Offset(Flavor):X}_{ent.Length:X}_{i}_{y}_{xbfsFilename ?? "unknown"}"; 245 | 246 | long read = 0; 247 | long total = ent.Length; 248 | _io.Stream.Position = ent.Offset(Flavor); 249 | 250 | bool writeFile = true; 251 | if (doneAddrs.Contains(_io.Stream.Position)) 252 | { 253 | writeFile = false; 254 | fileName = $"DUPE_{fileName}"; 255 | } 256 | doneAddrs.Add(_io.Stream.Position); 257 | 258 | 259 | if (_io.Stream.Position + total > _io.Stream.Length) 260 | continue; 261 | 262 | using (var fileIo = new IO(Path.Combine(folderPath, fileName), FileMode.Create)) 263 | { 264 | if (!writeFile) // create empty file for DUPE_* files 265 | continue; 266 | 267 | while (read < total) 268 | { 269 | int toRead = 0x4000; 270 | if (total - read < toRead) 271 | toRead = (int) (total - read); 272 | byte[] data = _io.Reader.ReadBytes(toRead); 273 | fileIo.Writer.Write(data); 274 | read += toRead; 275 | } 276 | } 277 | } 278 | } 279 | } 280 | 281 | public override string ToString() 282 | { 283 | return ToString(false); 284 | } 285 | 286 | public string ToString(bool formatted) 287 | { 288 | var b = new StringBuilder(); 289 | b.AppendLine("XbfsFile"); 290 | b.AppendLine($"Flavor: {Flavor}"); 291 | b.AppendLine($"Size: 0x{FileSize:X} ({FileSize / 1024 / 1024} MB)"); 292 | b.AppendLine(); 293 | for (int i = 0; i < XbfsHeaders.Count; i++) 294 | { 295 | if(!XbfsHeaders[i].IsValid) 296 | continue; 297 | 298 | b.AppendLine($"XbfsHeader slot {i}: (0x{HeaderOffsets[i]:X})"); 299 | b.Append(XbfsHeaders[i].ToString(formatted)); 300 | } 301 | return b.ToString(); 302 | } 303 | } 304 | } -------------------------------------------------------------------------------- /LibXboxOne/NAND/XbfsHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace LibXboxOne.Nand 6 | { 7 | // XBFS header, can be at 0x10000, 0x810000 or 0x820000 8 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 9 | public struct XbfsHeader 10 | { 11 | public static readonly int DataToHash = 0x3E0; 12 | public static readonly string XbfsMagic = "SFBX"; 13 | 14 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 15 | /* 0x0 */ public char[] Magic; // SFBX 16 | 17 | /* 0x4 */ public byte FormatVersion; 18 | /* 0x5 */ public byte SequenceNumber; // Indicates latest filesystem, wraps around: 0xFF -> 0x00 19 | /* 0x6 */ public ushort LayoutVersion; // 3 20 | /* 0x8 */ public ulong Reserved08; // 0 21 | /* 0x10 */ public ulong Reserved10; // 0 22 | /* 0x18 */ public ulong Reserved18; // 0 23 | 24 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3A)] 25 | /* 0x20 */ public XbfsEntry[] Files; 26 | 27 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] 28 | /* 0x3C0 */ public byte[] Reserved3C0; 29 | 30 | /* 0x3D0 */ public Guid SystemXVID; // GUID 31 | 32 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)] 33 | /* 0x3E0 */ public byte[] XbfsHash; // SHA256 hash of 0x0 - 0x3E0 34 | 35 | public string MagicString => new string(Magic); 36 | 37 | public bool IsValid => MagicString == XbfsMagic; 38 | 39 | public bool IsHashValid => XbfsHash.IsEqualTo(CalculateHash()); 40 | 41 | byte[] CalculateHash() 42 | { 43 | byte[] data = Shared.StructToBytes(this); 44 | return HashUtils.ComputeSha256(data, 0, DataToHash); 45 | } 46 | 47 | public void Rehash() 48 | { 49 | XbfsHash = CalculateHash(); 50 | } 51 | 52 | public override string ToString() 53 | { 54 | return ToString(false); 55 | } 56 | 57 | public string ToString(bool formatted) 58 | { 59 | string fmt = formatted ? " " : ""; 60 | 61 | var b = new StringBuilder(); 62 | b.AppendLineSpace(fmt + $"Magic: {new string(Magic)}"); 63 | b.AppendLineSpace(fmt + $"Format Version: 0x{FormatVersion:X}"); 64 | b.AppendLineSpace(fmt + $"Sequence Number: 0x{SequenceNumber:X}"); 65 | b.AppendLineSpace(fmt + $"Layout Version: 0x{LayoutVersion:X}"); 66 | b.AppendLineSpace(fmt + $"Reserved08: 0x{Reserved08:X}"); 67 | b.AppendLineSpace(fmt + $"Reserved10: 0x{Reserved10:X}"); 68 | b.AppendLineSpace(fmt + $"Reserved18: 0x{Reserved18:X}"); 69 | b.AppendLineSpace(fmt + $"Reserved3C0: {Reserved3C0.ToHexString()}"); 70 | b.AppendLineSpace(fmt + $"System XVID: {SystemXVID}"); 71 | b.AppendLineSpace(fmt + $"XBFS header hash: {Environment.NewLine}{fmt}{XbfsHash.ToHexString()} (Valid: {IsHashValid})"); 72 | b.AppendLine(); 73 | 74 | for(int i = 0; i < Files.Length; i++) 75 | { 76 | XbfsEntry entry = Files[i]; 77 | if (entry.Length == 0) 78 | continue; 79 | b.AppendLine($"File {i}: {XbfsFile.GetFilenameForIndex(i) ?? ""} {entry.ToString(formatted)}"); 80 | } 81 | 82 | return b.ToString(); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /LibXboxOne/Shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | namespace LibXboxOne 8 | { 9 | public class Natives 10 | { 11 | [DllImport("xsapi.dll", SetLastError = true)] 12 | public static extern uint XvdMount(out IntPtr hDiskHandle, 13 | out int mountedDiskNum, 14 | IntPtr hXvdHandle, 15 | [MarshalAs(UnmanagedType.LPWStr)] string pszXvdPath, 16 | long unknown, 17 | [MarshalAs(UnmanagedType.LPWStr)] string pszMountPoint, 18 | int mountFlags); 19 | 20 | [DllImport("xsapi.dll", SetLastError = true)] 21 | public static extern uint XvdMountContentType(out IntPtr hDiskHandle, 22 | out int mountedDiskNum, 23 | IntPtr hXvdHandle, 24 | [MarshalAs(UnmanagedType.LPWStr)] string pszXvdPath, 25 | long xvdContentType, 26 | long unknown, 27 | [MarshalAs(UnmanagedType.LPWStr)] string pszMountPoint, 28 | int mountFlags); 29 | 30 | [DllImport("xsapi.dll", SetLastError = true)] 31 | public static extern uint XvdVmMount(out IntPtr hDiskHandle, 32 | IntPtr hXvdHandle, 33 | long vmNumber, 34 | [MarshalAs(UnmanagedType.LPWStr)] string pszXvdPath, 35 | long unknown, 36 | [MarshalAs(UnmanagedType.LPWStr)] string pszMountPoint, 37 | int mountFlags); 38 | 39 | [DllImport("xsapi.dll", SetLastError = true)] 40 | public static extern uint XvdUnmountDiskNumber(IntPtr hXvdHandle, 41 | int diskNum); 42 | 43 | [DllImport("xsapi.dll", SetLastError = true)] 44 | public static extern uint XvdUnmountFile(IntPtr hXvdHandle, 45 | [MarshalAs(UnmanagedType.LPWStr)] string pszXvdPath); 46 | 47 | [DllImport("xsapi.dll", SetLastError = true)] 48 | public static extern uint XvdVmUnmountFile(IntPtr hXvdHandle, 49 | long vmId, 50 | [MarshalAs(UnmanagedType.LPWStr)] string pszXvdPath); 51 | 52 | [DllImport("xsapi.dll", SetLastError = true)] 53 | public static extern uint XvdOpenAdapter(out IntPtr phXvdHandle); 54 | 55 | [DllImport("xsapi.dll", SetLastError = true)] 56 | public static extern uint XvdCloseAdapter(IntPtr phXvdHandle); 57 | 58 | [DllImport("kernel32.dll")] 59 | public static extern int DeviceIoControl(IntPtr hDevice, int 60 | dwIoControlCode, ref short lpInBuffer, int nInBufferSize, IntPtr 61 | lpOutBuffer, int nOutBufferSize, ref int lpBytesReturned, IntPtr 62 | lpOverlapped); 63 | } 64 | // ReSharper disable once InconsistentNaming 65 | public class IO : IDisposable 66 | { 67 | public BinaryReader Reader; 68 | public BinaryWriter Writer; 69 | public Stream Stream; 70 | 71 | public IO(string filePath) 72 | { 73 | Stream = new FileStream(filePath, FileMode.Open); 74 | InitIo(); 75 | } 76 | 77 | public IO(string filePath, FileMode mode) 78 | { 79 | Stream = new FileStream(filePath, mode); 80 | InitIo(); 81 | } 82 | 83 | public IO(Stream baseStream) 84 | { 85 | Stream = baseStream; 86 | InitIo(); 87 | } 88 | 89 | public void Dispose() 90 | { 91 | Stream.Dispose(); 92 | Reader.Dispose(); 93 | Writer.Dispose(); 94 | } 95 | 96 | public bool AddBytes(long numBytes) 97 | { 98 | const int blockSize = 0x1000; 99 | 100 | long startPos = Stream.Position; 101 | long startSize = Stream.Length; 102 | long endPos = startPos + numBytes; 103 | long endSize = Stream.Length + numBytes; 104 | 105 | Stream.SetLength(endSize); 106 | 107 | long totalWrite = startSize - startPos; 108 | 109 | while (totalWrite > 0) 110 | { 111 | int toRead = totalWrite < blockSize ? (int)totalWrite : blockSize; 112 | 113 | Stream.Position = startPos + (totalWrite - toRead); 114 | var data = Reader.ReadBytes(toRead); 115 | 116 | Stream.Position = startPos + (totalWrite - toRead); 117 | var blankData = new byte[toRead]; 118 | Writer.Write(blankData); 119 | 120 | Stream.Position = endPos + (totalWrite - toRead); 121 | Writer.Write(data); 122 | 123 | totalWrite -= toRead; 124 | } 125 | 126 | Stream.Position = startPos; 127 | 128 | return true; 129 | } 130 | 131 | public bool DeleteBytes(long numBytes) 132 | { 133 | if (Stream.Position + numBytes > Stream.Length) 134 | return false; 135 | 136 | const int blockSize = 0x1000; 137 | 138 | long startPos = Stream.Position; 139 | long endPos = startPos + numBytes; 140 | long endSize = Stream.Length - numBytes; 141 | long i = 0; 142 | 143 | while (i < endSize) 144 | { 145 | long totalRemaining = endSize - i; 146 | int toRead = totalRemaining < blockSize ? (int)totalRemaining : blockSize; 147 | 148 | Stream.Position = endPos + i; 149 | byte[] data = Reader.ReadBytes(toRead); 150 | 151 | Stream.Position = startPos + i; 152 | Writer.Write(data); 153 | 154 | i += toRead; 155 | } 156 | 157 | Stream.SetLength(endSize); 158 | return true; 159 | } 160 | 161 | private void InitIo() 162 | { 163 | Reader = new BinaryReader(Stream); 164 | Writer = new BinaryWriter(Stream); 165 | } 166 | } 167 | public static class Shared 168 | { 169 | public static string FindFile(string fileName) 170 | { 171 | string test = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); 172 | if (File.Exists(test)) 173 | return test; 174 | string[] drives = Directory.GetLogicalDrives(); 175 | foreach (string drive in drives) 176 | { 177 | test = Path.Combine(drive, fileName); 178 | if (File.Exists(test)) 179 | return test; 180 | } 181 | return String.Empty; 182 | } 183 | 184 | /// 185 | /// Reads in a block from a file and converts it to the struct 186 | /// type specified by the template parameter 187 | /// 188 | /// 189 | /// 190 | /// 191 | public static T ReadStruct(this BinaryReader reader) 192 | { 193 | var size = Marshal.SizeOf(typeof (T)); 194 | // Read in a byte array 195 | var bytes = reader.ReadBytes(size); 196 | 197 | return BytesToStruct(bytes); 198 | } 199 | 200 | public static bool WriteStruct(this BinaryWriter writer, T structure) 201 | { 202 | byte[] bytes = StructToBytes(structure); 203 | 204 | writer.Write(bytes); 205 | 206 | return true; 207 | } 208 | 209 | public static T BytesToStruct(byte[] bytes) 210 | { 211 | // Pin the managed memory while, copy it out the data, then unpin it 212 | var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); 213 | var theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); 214 | handle.Free(); 215 | 216 | return theStructure; 217 | } 218 | 219 | public static byte[] StructToBytes(T structure) 220 | { 221 | var bytes = new byte[Marshal.SizeOf(typeof(T))]; 222 | 223 | // Pin the managed memory while, copy in the data, then unpin it 224 | var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); 225 | Marshal.StructureToPtr(structure, handle.AddrOfPinnedObject(), true); 226 | handle.Free(); 227 | 228 | return bytes; 229 | } 230 | 231 | public static string ToHexString(this byte[] bytes, string seperator = " ") 232 | { 233 | return bytes.Aggregate("", (current, b) => $"{current}{b:X2}{seperator}"); 234 | } 235 | 236 | public static string ToHexString(this uint[] array, string seperator = " ") 237 | { 238 | return array.Aggregate("", (current, b) => $"{current}0x{b:X8}{seperator}"); 239 | } 240 | 241 | public static string ToHexString(this ushort[] array, string seperator = " ") 242 | { 243 | return array.Aggregate("", (current, b) => $"{current}0x{b:X4}{seperator}"); 244 | } 245 | 246 | public static byte[] ToBytes(this string hexString) 247 | { 248 | hexString = hexString.Replace(" ", ""); 249 | 250 | byte[] retval = new byte[hexString.Length / 2]; 251 | for (int i = 0; i < hexString.Length; i += 2) 252 | retval[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); 253 | return retval; 254 | } 255 | 256 | public static bool IsArrayEmpty(this byte[] bytes) 257 | { 258 | return bytes.All(b => b == 0); 259 | } 260 | public static bool IsEqualTo(this byte[] byte1, byte[] byte2) 261 | { 262 | if (byte1.Length != byte2.Length) 263 | return false; 264 | 265 | for (int i = 0; i < byte1.Length; i++) 266 | if (byte1[i] != byte2[i]) 267 | return false; 268 | 269 | return true; 270 | } 271 | 272 | public static void AppendLineSpace(this StringBuilder b, string str) 273 | { 274 | b.AppendLine(str + " "); 275 | } 276 | 277 | public static ushort EndianSwap(this ushort num) 278 | { 279 | byte[] data = BitConverter.GetBytes(num); 280 | Array.Reverse(data); 281 | return BitConverter.ToUInt16(data, 0); 282 | } 283 | 284 | public static uint EndianSwap(this uint num) 285 | { 286 | byte[] data = BitConverter.GetBytes(num); 287 | Array.Reverse(data); 288 | return BitConverter.ToUInt32(data, 0); 289 | } 290 | 291 | public static ulong EndianSwap(this ulong num) 292 | { 293 | byte[] data = BitConverter.GetBytes(num); 294 | Array.Reverse(data); 295 | return BitConverter.ToUInt64(data, 0); 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /LibXboxOne/ThirdParty/ProgressBar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading; 4 | 5 | /// 6 | /// An ASCII progress bar 7 | /// 8 | namespace LibXboxOne 9 | { 10 | // Source: https://gist.github.com/DanielSWolf/0ab6a96899cc5377bf54 11 | // License: MIT 12 | public class ProgressBar : IDisposable, IProgress 13 | { 14 | private const int blockCount = 30; 15 | private readonly TimeSpan animationInterval = TimeSpan.FromSeconds(1.0 / 8); 16 | private const string animation = @"|/-\"; 17 | 18 | private readonly Timer timer; 19 | 20 | private long totalItems = 0; 21 | private long currentItem = 0; 22 | private string description = String.Empty; 23 | private string currentText = string.Empty; 24 | private bool disposed = false; 25 | private int animationIndex = 0; 26 | 27 | public ProgressBar(long items, string unitDescription="") { 28 | totalItems = items; 29 | description = unitDescription; 30 | timer = new Timer(TimerHandler); 31 | 32 | // A progress bar is only for temporary display in a console window. 33 | // If the console output is redirected to a file, draw nothing. 34 | // Otherwise, we'll end up with a lot of garbage in the target file. 35 | if (!Console.IsOutputRedirected) { 36 | ResetTimer(); 37 | } 38 | } 39 | 40 | public void Report(long current) { 41 | Interlocked.Exchange(ref currentItem, current); 42 | } 43 | 44 | private void TimerHandler(object state) { 45 | lock (timer) { 46 | if (disposed) return; 47 | 48 | double progress = (double)(currentItem + 1) / totalItems; 49 | int progressBlockCount = (int) (progress * blockCount); 50 | int percent = (int) (progress * 100); 51 | string text = string.Format("{0,3}% [{1}{2}] {3}/{4} {5} {6}", 52 | percent, 53 | new string('#', progressBlockCount), new string('-', blockCount - progressBlockCount), 54 | currentItem, 55 | totalItems, 56 | description, 57 | animation[animationIndex++ % animation.Length]); 58 | UpdateText(text); 59 | 60 | ResetTimer(); 61 | } 62 | } 63 | 64 | private void UpdateText(string text) { 65 | // Get length of common portion 66 | int commonPrefixLength = 0; 67 | int commonLength = Math.Min(currentText.Length, text.Length); 68 | while (commonPrefixLength < commonLength && text[commonPrefixLength] == currentText[commonPrefixLength]) { 69 | commonPrefixLength++; 70 | } 71 | 72 | // Backtrack to the first differing character 73 | StringBuilder outputBuilder = new StringBuilder(); 74 | outputBuilder.Append('\b', currentText.Length - commonPrefixLength); 75 | 76 | // Output new suffix 77 | outputBuilder.Append(text.Substring(commonPrefixLength)); 78 | 79 | // If the new text is shorter than the old one: delete overlapping characters 80 | int overlapCount = currentText.Length - text.Length; 81 | if (overlapCount > 0) { 82 | outputBuilder.Append(' ', overlapCount); 83 | outputBuilder.Append('\b', overlapCount); 84 | } 85 | 86 | Console.Write(outputBuilder); 87 | currentText = text; 88 | } 89 | 90 | private void ResetTimer() { 91 | timer.Change(animationInterval, TimeSpan.FromMilliseconds(-1)); 92 | } 93 | 94 | public void Dispose() { 95 | lock (timer) { 96 | disposed = true; 97 | UpdateText(string.Empty); 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /LibXboxOne/ThirdParty/TableParserExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | namespace LibXboxOne.ThirdParty 10 | { 11 | // TableParserExtensions class from https://github.com/Robert-McGinley/TableParser 12 | public static class TableParserExtensions 13 | { 14 | public static string ToStringTable(this IEnumerable values, string[] columnHeaders, params Func[] valueSelectors) 15 | { 16 | return ToStringTable(values.ToArray(), columnHeaders, valueSelectors); 17 | } 18 | 19 | public static string ToStringTable(this T[] values, string[] columnHeaders, params Func[] valueSelectors) 20 | { 21 | Debug.Assert(columnHeaders.Length == valueSelectors.Length); 22 | 23 | var arrValues = new string[values.Length + 1, valueSelectors.Length]; 24 | 25 | // Fill headers 26 | for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++) 27 | { 28 | arrValues[0, colIndex] = columnHeaders[colIndex]; 29 | } 30 | 31 | // Fill table rows 32 | for (int rowIndex = 1; rowIndex < arrValues.GetLength(0); rowIndex++) 33 | { 34 | for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++) 35 | { 36 | object value = valueSelectors[colIndex].Invoke(values[rowIndex - 1]); 37 | 38 | arrValues[rowIndex, colIndex] = value != null ? value.ToString() : "null"; 39 | } 40 | } 41 | 42 | return ToStringTable(arrValues); 43 | } 44 | 45 | public static string ToStringTable(this string[,] arrValues) 46 | { 47 | int[] maxColumnsWidth = GetMaxColumnsWidth(arrValues); 48 | var headerSpliter = new string('-', maxColumnsWidth.Sum(i => i + 3) - 1); 49 | 50 | var sb = new StringBuilder(); 51 | for (int rowIndex = 0; rowIndex < arrValues.GetLength(0); rowIndex++) 52 | { 53 | for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++) 54 | { 55 | // Print cell 56 | string cell = arrValues[rowIndex, colIndex]; 57 | cell = cell.PadRight(maxColumnsWidth[colIndex]); 58 | sb.Append(" | "); 59 | sb.Append(cell); 60 | } 61 | 62 | // Print end of line 63 | sb.Append(" | "); 64 | sb.AppendLine(); 65 | 66 | // Print splitter 67 | if (rowIndex == 0) 68 | { 69 | sb.AppendFormat(" |{0}| ", headerSpliter); 70 | sb.AppendLine(); 71 | } 72 | } 73 | 74 | return sb.ToString(); 75 | } 76 | 77 | private static int[] GetMaxColumnsWidth(string[,] arrValues) 78 | { 79 | var maxColumnsWidth = new int[arrValues.GetLength(1)]; 80 | for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++) 81 | { 82 | for (int rowIndex = 0; rowIndex < arrValues.GetLength(0); rowIndex++) 83 | { 84 | int newLength = arrValues[rowIndex, colIndex].Length; 85 | int oldLength = maxColumnsWidth[colIndex]; 86 | 87 | if (newLength > oldLength) 88 | { 89 | maxColumnsWidth[colIndex] = newLength; 90 | } 91 | } 92 | } 93 | 94 | return maxColumnsWidth; 95 | } 96 | 97 | public static string ToStringTable(this IEnumerable values, params Expression>[] valueSelectors) 98 | { 99 | var headers = valueSelectors.Select(func => GetProperty(func).Name).ToArray(); 100 | var selectors = valueSelectors.Select(exp => exp.Compile()).ToArray(); 101 | return ToStringTable(values, headers, selectors); 102 | } 103 | 104 | private static PropertyInfo GetProperty(Expression> expresstion) 105 | { 106 | switch (expresstion.Body) 107 | { 108 | case UnaryExpression expression when expression.Operand is MemberExpression memberExpression: 109 | return memberExpression.Member as PropertyInfo; 110 | case MemberExpression expression1: 111 | return expression1.Member as PropertyInfo; 112 | default: 113 | return null; 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /LibXboxOne/XVD/XVDEnums.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LibXboxOne 4 | { 5 | public enum XvdType : uint 6 | { 7 | Fixed = 0, 8 | Dynamic = 1 9 | } 10 | 11 | public enum XvdContentType : uint 12 | { 13 | Data = 0, 14 | Title = 1, 15 | SystemOS = 2, 16 | EraOS = 3, 17 | Scratch = 4, 18 | ResetData = 5, 19 | Application = 6, 20 | HostOS = 7, 21 | X360STFS = 8, 22 | X360FATX = 9, 23 | X360GDFX = 0xA, 24 | Updater = 0xB, 25 | OfflineUpdater = 0xC, 26 | Template = 0xD, 27 | MteHost = 0xE, 28 | MteApp = 0xF, 29 | MteTitle = 0x10, 30 | MteEraOS = 0x11, 31 | EraTools = 0x12, 32 | SystemTools = 0x13, 33 | SystemAux = 0x14, 34 | AcousticModel = 0x15, 35 | SystemCodecsVolume = 0x16, 36 | QasltPackage = 0x17, 37 | AppDlc = 0x18, 38 | TitleDlc = 0x19, 39 | UniversalDlc = 0x1A, 40 | SystemDataVolume = 0x1B, 41 | TestVolume = 0x1C, 42 | HardwareTestVolume = 0x1D, 43 | KioskContent = 0x1E, 44 | HostProfiler = 0x20, 45 | Uwa = 0x21, 46 | Unknown22 = 0x22, 47 | Unknown23 = 0x23, 48 | Unknown24 = 0x24, 49 | ServerAgent = 0x25 50 | } 51 | 52 | [Flags] 53 | public enum XvcRegionFlags : uint 54 | { 55 | Resident = 1, 56 | InitialPlay = 2, 57 | Preview = 4, 58 | FileSystemMetadata = 8, 59 | Present = 0x10, 60 | OnDemand = 0x20, 61 | Available = 0x40, 62 | } 63 | 64 | [Flags] 65 | public enum XvdVolumeFlags : uint 66 | { 67 | ReadOnly = 1, 68 | EncryptionDisabled = 2, // data decrypted, no encrypted CIKs 69 | DataIntegrityDisabled = 4, // unsigned and unhashed 70 | LegacySectorSize = 8, 71 | ResiliencyEnabled = 0x10, 72 | SraReadOnly = 0x20, 73 | RegionIdInXts = 0x40, 74 | EraSpecific = 0x80 75 | } 76 | 77 | [Flags] 78 | public enum XvcRegionPresenceInfo : byte 79 | { 80 | IsPresent = 1, // not set = "not present" 81 | IsAvailable = 2, // not set = "unavailable" 82 | 83 | //value >> 4 = discnum 84 | Disc1 = 0x10, 85 | Disc2 = 0x20, 86 | Disc3 = 0x30, 87 | Disc4 = 0x40, 88 | Disc5 = 0x50, 89 | Disc6 = 0x60, 90 | Disc7 = 0x70, 91 | Disc8 = 0x80, 92 | Disc9 = 0x90, 93 | Disc10 = 0xA0, 94 | Disc11 = 0xB0, 95 | Disc12 = 0xC0, 96 | Disc13 = 0xD0, 97 | Disc14 = 0xE0, 98 | Disc15 = 0xF0 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /LibXboxOne/XVD/XVDLicenseBlock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LibXboxOne 4 | { 5 | public enum XvcLicenseBlockId : uint 6 | { 7 | // EKB block IDs follow 8 | 9 | EkbUnknown1 = 1, // length 0x2, value 0x5 10 | EkbKeyIdString = 2, // length 0x20 11 | EkbUnknown2 = 3, // length 0x2, value 0x7 12 | EkbUnknown4 = 4, // length 0x1, value 0x31 13 | EkbUnknown5 = 5, // length 0x2, value 0x1 14 | EkbEncryptedCik = 7, // length 0x180 15 | 16 | // SPLicenseBlock block IDs follow 17 | 18 | LicenseSection = 0x14, 19 | Unknown1 = 0x15, 20 | Unknown2 = 0x16, 21 | UplinkKeyId = 0x18, 22 | KeyId = 0x1A, 23 | EncryptedCik = 0x1B, 24 | DiscIdSection = 0x1C, 25 | Unknown3 = 0x1E, 26 | Unknown4 = 0x24, 27 | DiscId = 0x25, 28 | SignatureSection = 0x28, 29 | Unknown5 = 0x29, 30 | Unknown6 = 0x2C, 31 | PossibleHash = 0x2A, 32 | PossibleSignature = 0x2B, // length 0x100, could be the encrypted CIK if it's encrypted in the same way as EKB files 33 | } 34 | 35 | // XvcLicenseBlock can load in SPLicenseBlock data and EKB data 36 | public class XvcLicenseBlock 37 | { 38 | public XvcLicenseBlockId BlockId; 39 | public uint BlockSize; 40 | public byte[] BlockData; 41 | 42 | public XvcLicenseBlock BlockDataAsBlock => BlockData.Length < MinBlockLength ? null : new XvcLicenseBlock(BlockData, IsEkbFile); 43 | 44 | public byte[] NextBlockData; 45 | public XvcLicenseBlock NextBlockDataAsBlock => NextBlockData.Length < MinBlockLength ? null : new XvcLicenseBlock(NextBlockData, IsEkbFile); 46 | 47 | public bool IsEkbFile; 48 | 49 | public int MinBlockLength = 8; 50 | 51 | public XvcLicenseBlock(byte[] data, bool isEkbFile = false) 52 | { 53 | IsEkbFile = isEkbFile; 54 | int idLen = IsEkbFile ? 2 : 4; 55 | int szLen = 4; 56 | 57 | if (data.Length >= idLen) 58 | BlockId = (XvcLicenseBlockId)(isEkbFile ? BitConverter.ToUInt16(data, 0) : BitConverter.ToUInt32(data, 0)); 59 | 60 | if (isEkbFile && BlockId == (XvcLicenseBlockId)0x4b45) 61 | { 62 | // if its an ekb file and we read the magic of the EKB skip 2 bytes 63 | idLen = 4; 64 | BlockId = (XvcLicenseBlockId)BitConverter.ToUInt16(data, 2); 65 | } 66 | 67 | if (!isEkbFile && BlockId == (XvcLicenseBlockId)0x31424b45) 68 | { 69 | // if we're not an EKB file but we read the EKB magic act like its an EKB 70 | IsEkbFile = true; 71 | idLen = 4; 72 | BlockId = (XvcLicenseBlockId)BitConverter.ToUInt16(data, 2); 73 | } 74 | 75 | if (data.Length >= idLen + szLen) 76 | BlockSize = BitConverter.ToUInt32(data, idLen); 77 | 78 | if (data.Length < BlockSize + (idLen + szLen)) 79 | return; 80 | 81 | BlockData = new byte[BlockSize]; 82 | Array.Copy(data, idLen + szLen, BlockData, 0, BlockSize); 83 | 84 | if (data.Length - (BlockSize + (idLen + szLen)) <= 0) 85 | return; 86 | 87 | NextBlockData = new byte[data.Length - (BlockSize + (idLen + szLen))]; 88 | Array.Copy(data, BlockSize + (idLen + szLen), NextBlockData, 0, NextBlockData.Length); 89 | 90 | idLen = IsEkbFile ? 2 : 4; 91 | szLen = 4; 92 | MinBlockLength = idLen + szLen; 93 | } 94 | public XvcLicenseBlock GetBlockWithId(XvcLicenseBlockId id) 95 | { 96 | int idLen = IsEkbFile ? 2 : 4; 97 | int szLen = 4; 98 | 99 | if (BlockId == id) 100 | return this; 101 | 102 | XvcLicenseBlock block; 103 | if (BlockSize > idLen + szLen && BlockSize >= idLen + szLen + BlockDataAsBlock.BlockSize) 104 | { 105 | block = BlockDataAsBlock.GetBlockWithId(id); 106 | if (block != null) 107 | return block; 108 | } 109 | 110 | if (NextBlockData.Length <= idLen + szLen || NextBlockData.Length < idLen + szLen + NextBlockDataAsBlock.BlockSize) 111 | return null; 112 | 113 | block = NextBlockDataAsBlock.GetBlockWithId(id); 114 | return block; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /LibXboxOne/XVD/XVDMount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LibXboxOne 4 | { 5 | [Flags] 6 | public enum XvdMountFlags 7 | { 8 | None = 0x0, 9 | ReadOnly = 0x1, 10 | Boot = 0x2, // Used with XvdVmMount 11 | MountEmbeddedXvd = 0x8, 12 | AsRemovable = 0x20 13 | } 14 | 15 | public class XvdMount 16 | { 17 | public static bool MountXvd(string filepath, string mountPoint, XvdMountFlags flags=0) 18 | { 19 | // Setup Xvd handle 20 | ulong result = Natives.XvdOpenAdapter(out var pHandle); 21 | 22 | if (result != 0x10000000) 23 | { 24 | Console.WriteLine("XvdOpenAdapter failed. Result: 0x{0:X}", result); 25 | return false; 26 | } 27 | 28 | result = Natives.XvdMount(out var pDiskHandle, out var diskNum, pHandle, filepath, 0, mountPoint, (int)flags); 29 | 30 | // Check for errors 31 | if (result == 0x80070002) 32 | { 33 | Console.WriteLine("Failed to find XVD file!"); 34 | } 35 | else if (result == 0xC0000043) 36 | { 37 | Console.WriteLine("Xvd file is already mounted or being used by another process!"); 38 | } 39 | else if (result != 0) 40 | { 41 | Console.WriteLine("XvdMount error: 0x{0:X}", result); 42 | } 43 | else 44 | { 45 | Console.WriteLine($"Package {filepath} attached as disk number {diskNum}"); 46 | } 47 | 48 | Natives.XvdCloseAdapter(pHandle); 49 | return result == 0; 50 | } 51 | 52 | public static bool UnmountXvd(string filepath) 53 | { 54 | // Setup XVD Handle 55 | ulong result = Natives.XvdOpenAdapter(out var pHandle); 56 | 57 | if (result != 0x10000000) 58 | { 59 | Console.WriteLine("XvdOpenAdapter failed. Result: 0x{0:X}", result); 60 | return false; 61 | } 62 | 63 | // UnMount XVD file 64 | result = Natives.XvdUnmountFile(pHandle, filepath); 65 | 66 | //Check for errors 67 | if (result == 0x80070002) 68 | { 69 | Console.WriteLine("Failed to find XVD file!"); 70 | } 71 | else if (result != 0) 72 | { 73 | Console.WriteLine("XvdMount error: 0x{0:X}", result); 74 | } 75 | 76 | Natives.XvdCloseAdapter(pHandle); 77 | return result == 0; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /LibXboxOne/XVD/XvcConstants.cs: -------------------------------------------------------------------------------- 1 | namespace LibXboxOne 2 | { 3 | public static class XvcConstants 4 | { 5 | public const ushort XVC_KEY_NONE = 0xFFFF; 6 | } 7 | } -------------------------------------------------------------------------------- /LibXboxOne/XVD/XvcRegionId.cs: -------------------------------------------------------------------------------- 1 | namespace LibXboxOne 2 | { 3 | public enum XvcRegionId : uint 4 | { 5 | MetadataXvc = 0x40000001, 6 | MetadataFilesystem = 0x40000002, 7 | Unknown = 0x40000003, 8 | EmbeddedXvd = 0x40000004, 9 | Header = 0x40000005, 10 | MutableData = 0x40000006 11 | } 12 | } -------------------------------------------------------------------------------- /LibXboxOne/XVD/XvdFilesystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Diagnostics; 6 | using DiscUtils; 7 | using DiscUtils.Ntfs; 8 | using DiscUtils.Partitions; 9 | using DiscUtils.Streams; 10 | 11 | namespace LibXboxOne 12 | { 13 | public class XvdFilesystem 14 | { 15 | XvdFile _xvdFile { get; } 16 | XvdFilesystemStream _fs { get; } 17 | 18 | XvdType XvdFsType => _xvdFile.Header.Type; 19 | uint SectorSize => (uint)_xvdFile.Header.SectorSize; 20 | ulong FilesystemSize => _xvdFile.Header.DriveSize; 21 | Geometry DiskGeometry => 22 | Geometry.FromCapacity((long)FilesystemSize, (int)SectorSize); 23 | 24 | const long CHUNK_SIZE = (16 * 1024 * 1024); // 16 MB 25 | 26 | public XvdFilesystem(XvdFile file) 27 | { 28 | _xvdFile = file; 29 | _fs = new XvdFilesystemStream(_xvdFile); 30 | } 31 | 32 | bool GetGptPartitionTable(out GuidPartitionTable partitionTable) 33 | { 34 | try 35 | { 36 | partitionTable = new GuidPartitionTable(_fs, DiskGeometry); 37 | } 38 | catch (Exception) 39 | { 40 | partitionTable = null; 41 | } 42 | 43 | // CHECKME: Count refers to the number of PARTITION TABLES, not partitions? 44 | if (partitionTable == null || partitionTable.Count <= 0) 45 | { 46 | Debug.WriteLine("No GPT partition table detected"); 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | bool GetMbrPartitionTable(out BiosPartitionTable partitionTable) 54 | { 55 | try 56 | { 57 | partitionTable = new BiosPartitionTable(_fs, DiskGeometry); 58 | } 59 | catch (Exception) 60 | { 61 | partitionTable = null; 62 | } 63 | 64 | // CHECKME: Count refers to the number of PARTITION TABLES, not partitions? 65 | if (partitionTable == null || partitionTable.Count <= 0) 66 | { 67 | Debug.WriteLine("No MBR partition table detected"); 68 | return false; 69 | } 70 | 71 | return true; 72 | } 73 | 74 | PartitionTable OpenDisk() 75 | { 76 | // Gathering partitions manually so Geometry can be provided 77 | // explicitly. This ensures that 4k sectors are properly set. 78 | PartitionTable partitionTable; 79 | if (GetGptPartitionTable(out GuidPartitionTable gptTable)) 80 | { 81 | partitionTable = (PartitionTable)gptTable; 82 | } 83 | else if (GetMbrPartitionTable(out BiosPartitionTable mbrTable)) 84 | { 85 | partitionTable = (PartitionTable)mbrTable; 86 | } 87 | else 88 | { 89 | Debug.WriteLine("No valid partition table detected"); 90 | return null; 91 | } 92 | 93 | if (partitionTable.Partitions == null || partitionTable.Partitions.Count <= 0) 94 | { 95 | Debug.WriteLine("Partition table holds no partitions"); 96 | return null; 97 | } 98 | 99 | return partitionTable; 100 | } 101 | 102 | IEnumerable IterateFilesystem(int partitionNumber) 103 | { 104 | IEnumerable IterateSubdir(DiscUtils.DiscDirectoryInfo subdir) 105 | { 106 | foreach(var dir in subdir.GetDirectories()) 107 | { 108 | foreach(var subfile in IterateSubdir(dir)) 109 | { 110 | yield return subfile; 111 | } 112 | } 113 | 114 | var files = subdir.GetFiles(); 115 | foreach(var f in files) 116 | { 117 | yield return f; 118 | } 119 | } 120 | 121 | PartitionTable disk = OpenDisk(); 122 | if (disk == null) 123 | { 124 | Debug.WriteLine("IterateFilesystem: Failed to open disk"); 125 | yield break; 126 | } 127 | else if (disk.Partitions.Count - 1 < partitionNumber) 128 | { 129 | Debug.WriteLine($"IterateFilesystem: Partition {partitionNumber} does not exist"); 130 | yield break; 131 | } 132 | 133 | using (var partitionStream = disk.Partitions[partitionNumber].Open()) 134 | { 135 | NtfsFileSystem fs = new DiscUtils.Ntfs.NtfsFileSystem(partitionStream); 136 | 137 | foreach(var file in IterateSubdir(fs.Root)) 138 | { 139 | yield return file; 140 | } 141 | 142 | fs.Dispose(); 143 | partitionStream.Dispose(); 144 | } 145 | } 146 | 147 | public bool ExtractFilesystem(string outputDirectory, int partitionNumber=0) 148 | { 149 | foreach (var file in IterateFilesystem(partitionNumber)) 150 | { 151 | Console.WriteLine(file.DirectoryName + file.Name); 152 | /* Assemble destination path and create directory */ 153 | var destDir = outputDirectory; 154 | var parentDirs = file.DirectoryName.Split('\\'); 155 | foreach(var pd in parentDirs) 156 | { 157 | destDir = Path.Combine(destDir, pd); 158 | } 159 | Directory.CreateDirectory(destDir); 160 | 161 | /* Write out file */ 162 | var destFile = Path.Combine(destDir, file.Name); 163 | using(var srcFile = file.OpenRead()) 164 | { 165 | using(var dstFs = File.Create(destFile)) 166 | { 167 | while(dstFs.Length < srcFile.Length) 168 | { 169 | var readSize = Math.Min(CHUNK_SIZE, srcFile.Length - srcFile.Position); 170 | var data = new byte[readSize]; 171 | if (srcFile.Read(data, 0, data.Length) != data.Length) 172 | throw new InvalidOperationException( 173 | $"Failed to read {srcFile} from raw image"); 174 | 175 | dstFs.Write(data, 0, data.Length); 176 | } 177 | } 178 | } 179 | } 180 | 181 | return true; 182 | } 183 | 184 | public bool ExtractFilesystemImage(string targetFile, bool createVhd) 185 | { 186 | using (var destFs = File.Open(targetFile, FileMode.Create)) 187 | { 188 | byte[] buffer = new byte[XvdFile.PAGE_SIZE]; 189 | 190 | for (long offset = 0; offset < _fs.Length; offset += XvdFile.PAGE_SIZE) 191 | { 192 | _fs.Read(buffer, 0, buffer.Length); 193 | destFs.Write(buffer, 0, buffer.Length); 194 | } 195 | } 196 | return true; 197 | } 198 | 199 | void InitializeVhdManually(DiscUtils.Vhd.Disk vhdDisk) 200 | { 201 | BiosPartitionTable.Initialize(vhdDisk, WellKnownPartitionType.WindowsNtfs); 202 | // GuidPartitionTable.Initialize(vhdDisk, WellKnownPartitionType.WindowsNtfs); 203 | 204 | var volMgr = new VolumeManager(vhdDisk); 205 | var logicalVolume = volMgr.GetLogicalVolumes()[0]; 206 | 207 | var label = $"XVDTool conversion"; 208 | 209 | using (var destNtfs = NtfsFileSystem.Format(logicalVolume, label, new NtfsFormatOptions())) 210 | { 211 | destNtfs.NtfsOptions.ShortNameCreation = ShortFileNameOption.Disabled; 212 | 213 | // NOTE: For VHD creation we just assume a single partition 214 | foreach (var file in IterateFilesystem(partitionNumber: 0)) 215 | { 216 | var fh = file.OpenRead(); 217 | 218 | if (!destNtfs.Exists(file.DirectoryName)) 219 | { 220 | destNtfs.CreateDirectory(file.DirectoryName); 221 | } 222 | 223 | using (Stream dest = destNtfs.OpenFile(file.FullName, FileMode.Create, 224 | FileAccess.ReadWrite)) 225 | { 226 | fh.CopyTo(dest); 227 | dest.Flush(); 228 | } 229 | 230 | fh.Close(); 231 | } 232 | } 233 | } 234 | 235 | void InitializeVhdViaPump(DiscUtils.Vhd.Disk vhdDisk) 236 | { 237 | if (SectorSize != XvdFile.LEGACY_SECTOR_SIZE) 238 | { 239 | throw new InvalidOperationException( 240 | "Initializing VHD via pump is only supported for 512 byte sectors"); 241 | } 242 | 243 | var pump = new StreamPump(_fs, vhdDisk.Content, (int)XvdFile.LEGACY_SECTOR_SIZE); 244 | pump.Run(); 245 | } 246 | 247 | // Source: https://github.com/DiscUtils/DiscUtils/issues/137 248 | public bool ConvertToVhd(string outputFile) 249 | { 250 | using (FileStream destVhdFs = File.Open(outputFile, FileMode.Create, FileAccess.ReadWrite)) 251 | { 252 | DiscUtils.Vhd.Disk vhdDisk; 253 | if (XvdFsType == XvdType.Fixed) 254 | { 255 | Console.WriteLine("Initializing fixed VHD..."); 256 | vhdDisk = DiscUtils.Vhd.Disk.InitializeFixed(destVhdFs, 257 | Ownership.None, 258 | (long)FilesystemSize 259 | + (long)(FilesystemSize / 10)); 260 | } 261 | else if (XvdFsType == XvdType.Dynamic) 262 | { 263 | Console.WriteLine("Initializing dynamic VHD..."); 264 | vhdDisk = DiscUtils.Vhd.Disk.InitializeDynamic(destVhdFs, 265 | Ownership.None, 266 | (long)FilesystemSize 267 | + (long)(FilesystemSize / 10)); 268 | } 269 | else 270 | throw new InvalidOperationException(); 271 | 272 | if (SectorSize == XvdFile.LEGACY_SECTOR_SIZE) 273 | { 274 | Console.WriteLine("Pumping data as-is to vhd (legacy sector size)"); 275 | InitializeVhdViaPump(vhdDisk); 276 | } 277 | else 278 | { 279 | Console.WriteLine("Creating vhd manually (4k sectors)"); 280 | InitializeVhdManually(vhdDisk); 281 | } 282 | } 283 | return true; 284 | } 285 | 286 | public string FileInfoToString(DiscFileInfo info) 287 | { 288 | return $"{info.FullName} ({info.Length} bytes)"; 289 | } 290 | 291 | public override string ToString() 292 | { 293 | return ToString(true); 294 | } 295 | 296 | public string ToString(bool formatted) 297 | { 298 | var b = new StringBuilder(); 299 | b.AppendLine("XvdFilesystem:"); 300 | 301 | string fmt = formatted ? " " : ""; 302 | 303 | if (_xvdFile.IsEncrypted) 304 | { 305 | b.AppendLineSpace(fmt + "Cannot get XvdFilesystem info from encrypted package"); 306 | return b.ToString(); 307 | } 308 | 309 | var disk = OpenDisk(); 310 | if (disk == null) 311 | { 312 | b.AppendLineSpace(fmt + "No partition table found on disk!"); 313 | return b.ToString(); 314 | } 315 | 316 | b.AppendLineSpace(fmt + "Partitions:"); 317 | var partitions = disk.Partitions; 318 | for (int i = 0; i < partitions.Count; i++) 319 | { 320 | var part = partitions[i]; 321 | b.AppendLineSpace(fmt + fmt + $"- Partition {i}:"); 322 | 323 | b.AppendLineSpace(fmt + fmt + fmt + $" BIOS-type: {part.TypeAsString} ({part.BiosType} / 0x{part.BiosType:X})"); 324 | b.AppendLineSpace(fmt + fmt + fmt + $" GUID-type: {part.GuidType}"); 325 | b.AppendLineSpace(fmt + fmt + fmt + $" First sector: {part.FirstSector} (0x{part.FirstSector:X})"); 326 | b.AppendLineSpace(fmt + fmt + fmt + $" Last sector: {part.LastSector} (0x{part.LastSector:X})"); 327 | b.AppendLineSpace(fmt + fmt + fmt + $" Sector count: {part.SectorCount} (0x{part.SectorCount:X})"); 328 | b.AppendLine(); 329 | } 330 | 331 | b.AppendLineSpace(fmt + "Filesystem content:"); 332 | try 333 | { 334 | for (int partitionNumber = 0; partitionNumber < partitions.Count; partitionNumber++) 335 | { 336 | b.AppendLineSpace(fmt + fmt + $":: Partition {partitionNumber}:"); 337 | foreach (var file in IterateFilesystem(partitionNumber)) 338 | { 339 | b.AppendLineSpace(fmt + fmt + FileInfoToString(file)); 340 | } 341 | } 342 | } 343 | catch (Exception e) 344 | { 345 | b.AppendLineSpace(fmt + fmt + $"Failed to list filesystem content. Error: {e}"); 346 | } 347 | 348 | return b.ToString(); 349 | } 350 | } 351 | } -------------------------------------------------------------------------------- /LibXboxOne/XVD/XvdFilesystemStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | 5 | namespace LibXboxOne 6 | { 7 | public class XvdFilesystemStream : Stream 8 | { 9 | readonly XvdFile _xvdFile; 10 | 11 | public override bool CanRead => true; 12 | public override bool CanSeek => true; 13 | // Disable writing for now 14 | public override bool CanWrite => false; 15 | 16 | public override long Length => (long)_xvdFile.Header.DriveSize; 17 | public override long Position { get; set; } 18 | 19 | // Absolute offset in Xvd of DriveData start 20 | long DriveBaseOffset => (long)_xvdFile.DriveDataOffset; 21 | // Absolute offset to use for calculation BAT target offset 22 | long DynamicBaseOffset => (long)_xvdFile.DynamicBaseOffset; 23 | // Length of static data for XvdType.Dynamic 24 | long StaticDataLength => (long)_xvdFile.StaticDataLength; 25 | 26 | public XvdFilesystemStream(XvdFile file) 27 | { 28 | _xvdFile = file; 29 | Position = 0; 30 | } 31 | 32 | public override void Flush() 33 | { 34 | } 35 | 36 | byte[] InternalRead(int count) 37 | { 38 | var offset = DriveBaseOffset + Position; 39 | return InternalReadAbsolute(offset, count); 40 | } 41 | 42 | byte[] InternalReadAbsolute(long offset, int count) 43 | { 44 | Debug.WriteLine($"InternalReadAbsolute: Reading 0x{count:X} @ 0x{offset:X}"); 45 | var data = _xvdFile.ReadBytes(offset, count); 46 | if (data.Length <= 0) 47 | throw new IOException("InternalReadAbsolute got nothing..."); 48 | 49 | // Debug.WriteLine($"Got {data.Length:X} bytes: {data.ToHexString("")}"); 50 | Position += data.Length; 51 | return data; 52 | } 53 | 54 | byte[] ReadDynamic(int count) 55 | { 56 | int positionInBuffer = 0; 57 | int bytesRemaining = count; 58 | byte[] destBuffer = new byte[count]; 59 | 60 | while (positionInBuffer < count) 61 | { 62 | byte[] data = new byte[0]; 63 | if (Position < StaticDataLength) 64 | { 65 | // Read a chunk from non-dynamic area, next iteration will read dynamic data 66 | int maxReadLength = (int)(StaticDataLength - Position); 67 | int length = bytesRemaining > maxReadLength ? maxReadLength : bytesRemaining; 68 | data = InternalRead(length); 69 | } 70 | else 71 | { 72 | // Lookup block allocation table for real data offset 73 | var targetVirtualOffset = (ulong)(Position - StaticDataLength); 74 | ulong blockNumber = XvdMath.OffsetToBlockNumber(targetVirtualOffset); 75 | long inBlockOffset = (long)XvdMath.InBlockOffset(targetVirtualOffset); 76 | int maxReadLength = (int)(XvdFile.BLOCK_SIZE - inBlockOffset); 77 | int length = bytesRemaining > maxReadLength ? maxReadLength : bytesRemaining; 78 | 79 | var targetPage = _xvdFile.ReadBat(blockNumber); 80 | if (targetPage == XvdFile.INVALID_SECTOR) 81 | { 82 | data = new byte[length]; 83 | // Advance stream position cause we are not actually reading data 84 | Position += length; 85 | } 86 | else 87 | { 88 | long targetPhysicalOffset = DynamicBaseOffset 89 | + (long)XvdMath.PageNumberToOffset(targetPage) 90 | + inBlockOffset; 91 | 92 | data = InternalReadAbsolute(targetPhysicalOffset, length); 93 | } 94 | } 95 | 96 | Array.Copy(data, 0, destBuffer, positionInBuffer, data.Length); 97 | positionInBuffer += data.Length; 98 | bytesRemaining -= data.Length; 99 | } 100 | 101 | return destBuffer; 102 | } 103 | 104 | public override int Read(byte[] buffer, int offset, int count) 105 | { 106 | byte[] dataRead; 107 | 108 | if (Position + count > Length) 109 | throw new IOException("Desired range out-of-bounds for stream"); 110 | else if (offset + count > buffer.Length) 111 | throw new IOException("Target buffer to small to hold read data"); 112 | 113 | else if (_xvdFile.Header.Type == XvdType.Fixed) 114 | dataRead = InternalRead(count); 115 | else if (_xvdFile.Header.Type == XvdType.Dynamic) 116 | dataRead = ReadDynamic(count); 117 | 118 | else 119 | throw new IOException($"Unsupported XvdType: {_xvdFile.Header.Type}"); 120 | 121 | Array.Copy(dataRead, 0, buffer, offset, count); 122 | return dataRead.Length; 123 | } 124 | 125 | public override long Seek(long offset, SeekOrigin origin) 126 | { 127 | switch (origin) 128 | { 129 | case SeekOrigin.Begin: 130 | Position = offset; 131 | break; 132 | case SeekOrigin.Current: 133 | Position += offset; 134 | break; 135 | case SeekOrigin.End: 136 | Position = Length + offset; 137 | break; 138 | } 139 | return Position; 140 | } 141 | 142 | public override void SetLength(long value) 143 | { 144 | throw new NotImplementedException(); 145 | } 146 | 147 | public override void Write(byte[] buffer, int offset, int count) 148 | { 149 | throw new NotImplementedException(); 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /LibXboxOne/XVD/XvdMath.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LibXboxOne 4 | { 5 | public static class XvdMath 6 | { 7 | public static bool PagesAligned(ulong page) 8 | { 9 | return (page & (XvdFile.PAGE_SIZE - 1)) == 0; 10 | } 11 | 12 | public static ulong PageAlign(ulong offset) 13 | { 14 | return offset & 0xFFFFFFFFFFFFF000; 15 | } 16 | 17 | public static ulong InBlockOffset(ulong offset) 18 | { 19 | return offset - ((offset / XvdFile.BLOCK_SIZE) * XvdFile.BLOCK_SIZE); 20 | } 21 | 22 | public static ulong InPageOffset(ulong offset) 23 | { 24 | return offset & (XvdFile.PAGE_SIZE - 1); 25 | } 26 | 27 | public static ulong BlockNumberToOffset(ulong blockNumber) 28 | { 29 | return blockNumber * XvdFile.BLOCK_SIZE; 30 | } 31 | 32 | public static ulong PageNumberToOffset(ulong pageNumber) 33 | { 34 | return pageNumber * XvdFile.PAGE_SIZE; 35 | } 36 | 37 | public static ulong BytesToBlocks(ulong bytes) 38 | { 39 | return (bytes + XvdFile.BLOCK_SIZE - 1) / XvdFile.BLOCK_SIZE; 40 | } 41 | 42 | public static ulong PagesToBlocks(ulong pages) 43 | { 44 | return (pages + XvdFile.PAGES_PER_BLOCK - 1) / XvdFile.PAGES_PER_BLOCK; 45 | } 46 | 47 | public static ulong BytesToPages(ulong bytes) 48 | { 49 | return (bytes + XvdFile.PAGE_SIZE - 1) / XvdFile.PAGE_SIZE; 50 | } 51 | 52 | public static ulong OffsetToBlockNumber(ulong offset) 53 | { 54 | return offset / XvdFile.BLOCK_SIZE; 55 | } 56 | 57 | public static ulong OffsetToPageNumber(ulong offset) 58 | { 59 | return offset / XvdFile.PAGE_SIZE; 60 | } 61 | 62 | public static ulong SectorsToBytes(ulong sectors) 63 | { 64 | return sectors * XvdFile.SECTOR_SIZE; 65 | } 66 | 67 | public static ulong LegacySectorsToBytes(ulong sectors) 68 | { 69 | return sectors * XvdFile.LEGACY_SECTOR_SIZE; 70 | } 71 | 72 | public static ulong ComputePagesSpanned(ulong startOffset, ulong lengthBytes) 73 | { 74 | return OffsetToPageNumber(startOffset + lengthBytes - 1) - 75 | OffsetToPageNumber(lengthBytes) + 1; 76 | } 77 | 78 | public static ulong QueryFirstDynamicPage(ulong metaDataPagesCount) 79 | { 80 | return XvdFile.PAGES_PER_BLOCK * PagesToBlocks(metaDataPagesCount); 81 | } 82 | 83 | public static ulong ComputeDataBackingPageNumber(XvdType type, ulong numHashLevels, ulong hashPageCount, ulong dataPageNumber) 84 | { 85 | if (type > XvdType.Dynamic) // Invalid Xvd Type! 86 | return dataPageNumber; 87 | 88 | return dataPageNumber + hashPageCount; 89 | } 90 | 91 | public static ulong CalculateHashBlockNumForBlockNum(XvdType type, ulong hashTreeLevels, ulong numberOfHashedPages, 92 | ulong blockNum, uint hashLevel, out ulong entryNumInBlock, 93 | bool resilient=false, bool unknown=false) 94 | { 95 | ulong HashBlockExponent(ulong blockCount) 96 | { 97 | return (ulong)Math.Pow(0xAA, blockCount); 98 | } 99 | 100 | ulong result = 0xFFFF; 101 | entryNumInBlock = 0; 102 | 103 | if ((uint)type > 1 || hashLevel > 3) 104 | return result; // Invalid data 105 | 106 | if (hashLevel == 0) 107 | entryNumInBlock = blockNum % 0xAA; 108 | else 109 | entryNumInBlock = blockNum / HashBlockExponent(hashLevel) % 0xAA; 110 | 111 | if (hashLevel == 3) 112 | return 0; 113 | 114 | result = blockNum / HashBlockExponent(hashLevel + 1); 115 | hashTreeLevels -= hashLevel + 1; 116 | 117 | if (hashLevel == 0 && hashTreeLevels > 0) 118 | { 119 | result += (numberOfHashedPages + HashBlockExponent(2) - 1) / HashBlockExponent(2); 120 | hashTreeLevels--; 121 | } 122 | 123 | if ((hashLevel == 0 || hashLevel == 1) && hashTreeLevels > 0) 124 | { 125 | result += (numberOfHashedPages + HashBlockExponent(3) - 1) / HashBlockExponent(3); 126 | hashTreeLevels--; 127 | } 128 | 129 | if (hashTreeLevels > 0) 130 | result += (numberOfHashedPages + HashBlockExponent(4) - 1) / HashBlockExponent(4); 131 | 132 | if (resilient) 133 | result *= 2; 134 | if (unknown) 135 | result++; 136 | 137 | return result; 138 | } 139 | 140 | public static ulong CalculateNumHashBlocksInLevel(ulong size, ulong hashLevel, bool resilient) 141 | { 142 | ulong hashBlocks = 0; 143 | 144 | switch (hashLevel) 145 | { 146 | case 0: 147 | hashBlocks = (size + XvdFile.DATA_BLOCKS_IN_LEVEL0_HASHTREE - 1) / XvdFile.DATA_BLOCKS_IN_LEVEL0_HASHTREE; 148 | break; 149 | case 1: 150 | hashBlocks = (size + XvdFile.DATA_BLOCKS_IN_LEVEL1_HASHTREE - 1) / XvdFile.DATA_BLOCKS_IN_LEVEL1_HASHTREE; 151 | break; 152 | case 2: 153 | hashBlocks = (size + XvdFile.DATA_BLOCKS_IN_LEVEL2_HASHTREE - 1) / XvdFile.DATA_BLOCKS_IN_LEVEL2_HASHTREE; 154 | break; 155 | case 3: 156 | hashBlocks = (size + XvdFile.DATA_BLOCKS_IN_LEVEL3_HASHTREE - 1) / XvdFile.DATA_BLOCKS_IN_LEVEL3_HASHTREE; 157 | break; 158 | } 159 | 160 | if (resilient) 161 | hashBlocks *= 2; 162 | 163 | return hashBlocks; 164 | } 165 | 166 | public static ulong CalculateNumberHashPages(out ulong hashTreeLevels, ulong hashedPagesCount, bool resilient) 167 | { 168 | ulong hashTreePageCount = (hashedPagesCount + XvdFile.HASH_ENTRIES_IN_PAGE - 1) / XvdFile.HASH_ENTRIES_IN_PAGE; 169 | hashTreeLevels = 1; 170 | 171 | if (hashTreePageCount > 1) 172 | { 173 | ulong result = 2; 174 | while (result > 1) 175 | { 176 | result = CalculateNumHashBlocksInLevel(hashedPagesCount, hashTreeLevels, false); 177 | hashTreeLevels += 1; 178 | hashTreePageCount += result; 179 | } 180 | } 181 | 182 | if (resilient) 183 | hashTreePageCount *= 2; 184 | 185 | return hashTreePageCount; 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xvdtool 2 | 3 | [![GitHub Workflow - Build](https://img.shields.io/github/actions/workflow/status/emoose/xvdtool/build.yml?branch=master)](https://github.com/emoose/xvdtool/actions?query=workflow%3Abuild) 4 | 5 | ⚠️ No support for leaked files or copyrighted source code is provided, issues or pull requests will be closed without further comment. ⚠️ 6 | 7 | 8 | xvdtool is a C# command-line utility for manipulating Xbox One XVD/XVC packages. It can print detailed info about package headers, resign, rehash, en/decrypt and verify data integrity of a package, it can also convert decrypted XVD files to VHD or extract the filesystem itself. 9 | 10 | So far it's only been tested with dev-crypted packages (which use a different 256-bit **Offline Distribution Key (ODK)** to retail packages), as the retail key is still unknown. **This currently makes the tool useless for 90% of people**, but developers looking into how XVD files work will find a detailed mapping of the XVD structures and near-complete methods for manipulating them. 11 | 12 | However **no encryption keys are provided with this tool**, you'll have to find them yourself. Hashes for the dev keys are provided below. 13 | If you have an Xbox One development kit or GamingServices framework (Windows10-exclusive) installed, you can use DurangoKeyExtractor to extract the keys from there. 14 | 15 | Also included is a tool for extracting files from the XBFS (Xbox Boot File System) inside the Xbox One NAND, based on tuxuser's original [NANDOne](https://github.com/tuxuser/NANDOne) work with a few small additions. 16 | Thanks Kebob for providing [OpenXvd](https://github.com/Kebob/OpenXvd). 17 | 18 | ## Usage 19 | ``` 20 | Usage : xvdtool.exe [parameters] [filename] 21 | 22 | Parameters: 23 | -h (-help) - print xvdtool usage 24 | -i (-info) - print info about package 25 | -wi (-writeinfo) - write info about package to [filename].txt 26 | -o (-output) - specify output filename 27 | 28 | -m (-mount) - mount package 29 | -um (-unmount) - unmount package 30 | -mp (-mountpoint) - Mount point for package (e.g. "X:") 31 | 32 | -lk (-listkeys) - List known keys including their hashes / availability 33 | 34 | -signfile - Path to xvd sign key (RSA) 35 | -odkfile - Path to Offline Distribution key 36 | -cikfile - Path to Content Instance key 37 | 38 | -sk (-signkey) - Name of xvd sign key to use 39 | -odk (-odkid) - Id of Offline Distribution key to use (uint) 40 | -cik (-cikguid) - Guid of Content Instance key to use 41 | 42 | -nd (-nodatahash) - disable data hash checking, speeds up -l and -f 43 | -ne (-noextract) - disable data (embedded XVD/user data) extraction, speeds up -l and -f 44 | 45 | -eu (-decrypt) - decrypt output xvd 46 | -ee (-encrypt) - encrypt output xvd 47 | XVDs will have a new CIK generated (if CIK in XVD header is empty), which will be encrypted with the ODK and stored in the XVD header 48 | 49 | -hd (-removehash) - remove hash tree/data integrity from package 50 | -he (-addhash) - add hash tree/data integrity to package 51 | 52 | -md (-removemdu) - remove mutable data (MDU) from package 53 | 54 | -r (-rehash) - fix data integrity hashes inside package 55 | -rs (-resign) - sign package using the private key from rsa3_key.bin 56 | 57 | -xe (-extractembedded) - extract embedded XVD from package 58 | -xu (-extractuserdata) - extract user data from package 59 | -xv (-extractvhd) - extracts filesystem from XVD into a VHD file 60 | -xi (-extractimage) - extract raw filesystem image 61 | -xf (-extractfiles) - extract files from XVD filesystem 62 | 63 | The next two commands will write info about each package found to [filename].txt 64 | also extracts embedded XVD and user data to [filename].exvd.bin / [filename].userdata.bin 65 | -l (-filelist) - use each XVD specified in the list 66 | -f (-folder) - scan folder for XVD files 67 | 68 | To mount a package in Windows you'll have to decrypt it and remove the hash tables & mutable data first (-eu -hd -md) 69 | ``` 70 | 71 | To decrypt non-XVC packages you'll need the correct ODK. The devkit ODK is "widely known" and a hashes are provided below, but as mentioned above the retail key is currently unknown. 72 | 73 | Decrypting XVC packages is a different matter, XVC packages use a **Content Instance Key (CIK)** which appears to be stored somewhere outside the package, however where and how it's stored is currently unknown. If you have the correct deobfuscated CIK for a given package you should be able to use it to to decrypt the package. 74 | 75 | Devkit/test-signed XVC packages use a static CIK which is also "widely known" (Hash provided below). 76 | 77 | ## Required Files 78 | To make full use of this tool you'll need the following files, which **are not included**. The tool will work fine without them, but some functions might not work. 79 | 80 | You can use the included tool "DurangoKeyExtractor" to extract these keys from the Microsoft.GamingServices framework available on Windows 10. 81 | Just check some DLL / SYS / EXE files - you might find them. 82 | 83 | - 33ec8436-5a0e-4f0d-b1ce-3f29c3955039.cik: CIK keys for XVC crypto. 84 | First entry should be the key used by SDK tools/devkits. 85 | Format: `[16 byte encryption key GUID][32 byte CIK]` 86 | ~~~ 87 | MD5: C9E58F4E1DC611E110A849648DADCC9B 88 | SHA256: 855CCA97C85558AE8E5FF87D8EEDB44AE6B8510601EB71423178B80EF1A7FF7F 89 | ~~~ 90 | - RedOdk.odk: ODK key used by SDK tools/devkits 91 | Format: `[32 byte ODK]` 92 | ~~~ 93 | MD5: A2BCFA87F6F83A560BD5739586A5D516 94 | SHA256: CA37132DFB4B811506AE4DC45F45970FED8FE5E58C1BACB259F1B96145B0EBC6 95 | ~~~ 96 | - RedXvdPrivateKey.rsa: Private RSA key used by SDK tools to verify/sign packages. 97 | Format: `RSAFULLPRIVATEBLOB` struct 98 | ~~~ 99 | MD5: 2DC371F46B67E29FFCC514C5B134BF73 100 | SHA256: 8E2B60377006D87EE850334C42FC200081386A838C65D96D1EA52032AA9628C5 101 | ~~~ 102 | 103 | For other known keys and their hashes use the `-listkeys` cmdline switch. 104 | To chose a specific key use the following cmdline switches: 105 | ``` 106 | -sk (-signkey) - Name of xvd sign key to use 107 | -odk (-odkid) - Id of Offline Distribution key to use (uint) 108 | -cik (-cikguid) - Guid of Content Instance key to use 109 | ``` 110 | 111 | ### Mounting XVDs 112 | 113 | For mounting of XVD/XVC files, you require DLLs from [GamingServices](https://www.microsoft.com/en-us/p/gaming-services/9mwpm2cqnlhn?activetab=pivot:overviewtab) component. 114 | Download & install it via the Microsoft Store and you should be good to go. 115 | 116 | ## Possible locations to store keys 117 | XVDTool will create configuration/keys folders on first start - Global and local to the app. 118 | 119 | Global configuration folder: 120 | * Windows: `C:\Users\\AppData\Local\xvdtool` 121 | * Linux: `/home//.config/xvdtool` 122 | * Mac OS X: `/Users//.config/xvdtool` 123 | 124 | Local configuration folder is the current directory of the executable. 125 | 126 | Inside these folders you can can store your keys to be autoloaded. 127 | 128 | * Xvd Signing keys: `/XvdSigningKey/` 129 | * Content Instance keys: `/Cik/` 130 | * Offline distribution keys: `/Odk/` 131 | 132 | Additionally, you can provide keys from arbitrary filesystem locations via the respective cmdline switches: `-signfile, -odkfile, -cikfile` 133 | 134 | ### Naming the keys 135 | For CIK it is not important how the keys are named if they have the binary structure of `[16 byte encryption key GUID][32 byte CIK]`. 136 | XVD signing keys should have a distinct identifier so you can refer to them via the `-sk (-signkey)` cmdline switch. 137 | ODK needs to be named either by OdkIndex (`.odk`) or by its identifier: `RedOdk.odk, StandardOdk.odk etc.` 138 | For detailed up-to-date info refer to: `LibXboxOne/Keys/` 139 | 140 | ## What are XVDs? 141 | XVD packages are a secured file format used by the Xbox One to store data, an analogue to the Xbox 360's STFS packages. XVD files are usually used to store system images/data while XVCs (a slightly modified variant of XVDs) are used to store game data. 142 | 143 | For a more detailed explanation of XVD files see xvd_info.md 144 | 145 | ## Third party libraries used 146 | * BouncyCastle (https://www.bouncycastle.org/csharp/) 147 | * NDesk.Options (http://www.ndesk.org/Options) 148 | * DiscUtils (https://github.com/DiscUtils/DiscUtils) 149 | 150 | ## Building from source 151 | 152 | ### Requirements 153 | 154 | - [.NET 7.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) - Choose Installer x64 for ease of use 155 | 156 | ### Building 157 | 158 | - After installing the SDK, open up a new powershell window 159 | - Clone the repository 160 | ``` 161 | git clone https://github.com/emoose/xvdtool 162 | ``` 163 | - Navigate into the directory 164 | ``` 165 | cd xvdtool 166 | ``` 167 | - Build 168 | ``` 169 | dotnet build -c Release 170 | ``` 171 | 172 | NOTE: If you want to build as DEBUG, either omit `-c Release` or supply `-c Debug` instead. 173 | 174 | ## Help / Support 175 | xvdtool has been tested on Windows and MacOS but it should work on all systems supported by .NET Core. 176 | 177 | There's no help given for this tool besides this readme, it's also currently **very** experimental and **very** likely to blow up in your face. If you do encounter any bugs please submit a description of what happened to the issue tracker. 178 | 179 | If you want to help out with development feel free, just make a fork of this repo, make your changes in a new branch of that fork and then submit a pull request from that branch to the master branch of this repo. 180 | -------------------------------------------------------------------------------- /XBFSTool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using LibXboxOne.Nand; 4 | using NDesk.Options; 5 | 6 | namespace XBFSTool 7 | { 8 | class Program 9 | { 10 | static string AppVersion => LibXboxOne.Common.AppVersion; 11 | 12 | static void Main(string[] args) 13 | { 14 | var printHelp = false; 15 | var printInfo = false; 16 | var printCertInfo = false; 17 | var outputFolder = String.Empty; 18 | 19 | var p = new OptionSet { 20 | { "h|?|help", "Show this help and exit", v => printHelp = v != null }, 21 | { "i|info", "Print info about nand dump", v => printInfo = v != null }, 22 | { "c|certinfo", "Print certificate info", v => printCertInfo = v != null }, 23 | { "x|extract=", "Specify {OUTPUT DIRECTORY} for extracted files", v => outputFolder = v } 24 | }; 25 | 26 | List extraArgs; 27 | try 28 | { 29 | extraArgs = p.Parse(args); 30 | } 31 | catch (OptionException e) 32 | { 33 | Console.WriteLine($"Failed parsing parameter \'{e.OptionName}\': {e.Message}"); 34 | Console.WriteLine("Try 'xbfstool --help' for more information"); 35 | return; 36 | } 37 | 38 | if(extraArgs.Count <= 0) 39 | { 40 | Console.WriteLine("ERROR: Missing filepath!"); 41 | Console.WriteLine(); 42 | } 43 | 44 | Console.WriteLine($"XBFSTool {AppVersion}: Xbox boot filesystem tool"); 45 | Console.WriteLine(); 46 | if (printHelp || extraArgs.Count <= 0) 47 | { 48 | Console.WriteLine("Usage : xbfstool.exe [parameters] [filepath]"); 49 | Console.WriteLine("Parse Xbox boot filesystem"); 50 | Console.WriteLine(); 51 | Console.WriteLine("Parameters:"); 52 | p.WriteOptionDescriptions(Console.Out); 53 | return; 54 | } 55 | 56 | var filePath = extraArgs[0]; 57 | var xbfs = new XbfsFile(filePath); 58 | xbfs.Load(); 59 | 60 | if (printCertInfo) 61 | { 62 | var consoleCert = xbfs.ReadPspConsoleCertificate(); 63 | var bootcapCert = xbfs.ReadBootcapCertificate(); 64 | 65 | Console.WriteLine(consoleCert != null ? consoleCert.ToString() : "No PspConsoleCertificate available"); 66 | Console.WriteLine(bootcapCert != null ? bootcapCert.ToString() : "No BootCapabilityCertificate available"); 67 | } 68 | 69 | if (printInfo) 70 | { 71 | var infoString = xbfs.ToString(); 72 | Console.WriteLine(infoString); 73 | } 74 | 75 | if (outputFolder != String.Empty) 76 | { 77 | Console.WriteLine("Extracting boot filesystem..."); 78 | xbfs.ExtractXbfsData(outputFolder); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /XBFSTool/XBFSTool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /XVDTool.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XVDTool", "XVDTool\XVDTool.csproj", "{5E7AB577-6CFF-421C-AE65-F9AB4BB19DB0}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XBFSTool", "XBFSTool\XBFSTool.csproj", "{DB3A4E68-469C-492B-B7A8-2AA93BD3B1A6}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibXboxOne", "LibXboxOne\LibXboxOne.csproj", "{7C009949-7098-42DB-9F0B-9A0BA68EED92}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibXboxOne.Tests", "LibXboxOne.Tests\LibXboxOne.Tests.csproj", "{960C5852-85E7-49BA-AF9E-818DF1C29F9F}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurangoKeyExtractor", "DurangoKeyExtractor\DurangoKeyExtractor.csproj", "{F6CF120B-6836-477D-884D-C596EB769858}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|x64 = Debug|x64 20 | Release|Any CPU = Release|Any CPU 21 | Release|x64 = Release|x64 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {5E7AB577-6CFF-421C-AE65-F9AB4BB19DB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {5E7AB577-6CFF-421C-AE65-F9AB4BB19DB0}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {5E7AB577-6CFF-421C-AE65-F9AB4BB19DB0}.Debug|x64.ActiveCfg = Debug|Any CPU 27 | {5E7AB577-6CFF-421C-AE65-F9AB4BB19DB0}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {5E7AB577-6CFF-421C-AE65-F9AB4BB19DB0}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {5E7AB577-6CFF-421C-AE65-F9AB4BB19DB0}.Release|x64.ActiveCfg = Release|Any CPU 30 | {DB3A4E68-469C-492B-B7A8-2AA93BD3B1A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {DB3A4E68-469C-492B-B7A8-2AA93BD3B1A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {DB3A4E68-469C-492B-B7A8-2AA93BD3B1A6}.Debug|x64.ActiveCfg = Debug|Any CPU 33 | {DB3A4E68-469C-492B-B7A8-2AA93BD3B1A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {DB3A4E68-469C-492B-B7A8-2AA93BD3B1A6}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {DB3A4E68-469C-492B-B7A8-2AA93BD3B1A6}.Release|x64.ActiveCfg = Release|Any CPU 36 | {7C009949-7098-42DB-9F0B-9A0BA68EED92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {7C009949-7098-42DB-9F0B-9A0BA68EED92}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {7C009949-7098-42DB-9F0B-9A0BA68EED92}.Debug|x64.ActiveCfg = Debug|Any CPU 39 | {7C009949-7098-42DB-9F0B-9A0BA68EED92}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {7C009949-7098-42DB-9F0B-9A0BA68EED92}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {7C009949-7098-42DB-9F0B-9A0BA68EED92}.Release|x64.ActiveCfg = Release|Any CPU 42 | {960C5852-85E7-49BA-AF9E-818DF1C29F9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {960C5852-85E7-49BA-AF9E-818DF1C29F9F}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {960C5852-85E7-49BA-AF9E-818DF1C29F9F}.Debug|x64.ActiveCfg = Debug|Any CPU 45 | {960C5852-85E7-49BA-AF9E-818DF1C29F9F}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {960C5852-85E7-49BA-AF9E-818DF1C29F9F}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {960C5852-85E7-49BA-AF9E-818DF1C29F9F}.Release|x64.ActiveCfg = Release|Any CPU 48 | {F6CF120B-6836-477D-884D-C596EB769858}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {F6CF120B-6836-477D-884D-C596EB769858}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {F6CF120B-6836-477D-884D-C596EB769858}.Debug|x64.ActiveCfg = Debug|Any CPU 51 | {F6CF120B-6836-477D-884D-C596EB769858}.Debug|x64.Build.0 = Debug|Any CPU 52 | {F6CF120B-6836-477D-884D-C596EB769858}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {F6CF120B-6836-477D-884D-C596EB769858}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {F6CF120B-6836-477D-884D-C596EB769858}.Release|x64.ActiveCfg = Release|Any CPU 55 | {F6CF120B-6836-477D-884D-C596EB769858}.Release|x64.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | EndGlobal 61 | -------------------------------------------------------------------------------- /XVDTool/XVDTool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /xvd_info.md: -------------------------------------------------------------------------------- 1 | ### What are XVDs? 2 | XVD packages are a secured file format used by the Xbox One to store data, similar to the Xbox 360's STFS packages. XVD files are usually used to store system images/data while XVCs (a slightly modified version of XVDs) are used to store game data. 3 | 4 | An XVD file consists of a header containing the hash of the top level hash table, certain metadata about the file (such as content ID, content type, the sandbox ID the package was created for, the product ID that the package belongs to, etc) and also a signature of the header itself which is stored at the beginning of the XVD file, from 0h to 200h. 5 | 6 | After the header there's space for an optional embedded XVD (which is usually the GameOS partition that the game inside the XVC is coded against). This embedded XVD is just a copy-paste of the XVD being embedded with no changes made to it. 7 | 8 | After the embedded XVD comes an (optional) area for the hash tree: the tree is a multilevel array of hashes, the topmost levels contain the hashes of the blocks in the levels underneath it, with the lowest level of the tree containing hashes of the data blocks (the blocks following the hash tree). The hashes in the hash tree are computed using SHA-256, with the result usually being resized to 18h bytes (but for data block hashes can be slightly modified depending on flags in the XVD header and the XVC region that the data being hashed is located). The full 256-bit hash of the top-most level is stored in the XVD header. 9 | 10 | Following the hash tree is another optional area reserved for user data (also known as Persistent Local Storage). This area is for games to store local-only data, although some system XVD packages seem to store data here too. 11 | 12 | Finally after the user data comes the actual XVD data. If the file is an XVC the first 3 blocks are reserved for an XVC descriptor (which is never encrypted). This specifies the content ID, any encryption keys used, the chunks used to update packages (if the XVC is using chunk-based updates) and offsets/lengths/keyIDs of the different XVC regions in the file, along with other metadata. XVC regions can be encrypted with any of the keys specified in the XVC descriptor. The region-based encryption also includes the XVC region ID as part of the AES-128-XTS 'tweak' value. 13 | 14 | Then comes the actual filesystem data. This data is encrypted with the CIK (can either be the decrypted value of the encrypted CIK in the XVD header if it's an XVD, or the key corresponding to the XVC key GUID) 15 | 16 | The filesystem data is just a normal NTFS filesystem containing the files inside the package, other filesystems may be possible but NTFS is the only one observed so far. 17 | 18 | ### Security Overview 19 | From a security standpoint XVDs seem very secure: 20 | 21 | - Each block of data is hashed, with the hash stored in the bottom-most hash tree level 22 | - Each block in that hash tree level is then hashed with the hash result stored in the level above it 23 | - This continues, until eventually the number of hashes in the level can fit into one hash block 24 | - That hash block is then hashed with the result stored in the XVD header 25 | - The XVD header is then hashed, and the hash signed with Microsoft's private key, the signature is then stored at the beginning of the file. 26 | 27 | To make sure the package is authenticated by Microsoft and not tampered with the console just needs to verify the signature of the header-hash, verify the top-most hash tree hash and then verify that each hash in the hash tree matches up with the actual hash. This is similar to the way STFS packages were secured on the Xbox 360, however instead of having the hash tables scattered around the file (as with STFS) they're instead stored before the data actually begins. 28 | 29 | The data blocks inside XVD files are also secured with customized AES-128-XTS encryption (the encrypted data is then used for computing the hashes), with XVC packages the Xbox One either retrieves the encryption key over Xbox Live or retrieves it from the game disc, however it seems that the keys from these methods don't work as CIK keys. It's assumed that these keys are obfuscated/encrypted in some way (possibly with the retail ODK in the same way that the encrypted CIK in non-XVC files is encrypted?) 30 | 31 | Non-XVC files use an ODK which appears to be static for all XVDs (but differs between retail/devkits), this key is used to decrypt the encrypted CIK in the XVD header, the decrypted CIK is then used to decrypt the XVD data. 32 | 33 | ### Platform Security Processor 34 | The PSP is a self-contained core located on the Xbox CPU die. From official AMD documentation its described as an ARM core, however instead of running TrustZone per AMD's spec it appears to be running MS customized code. 35 | 36 | It seems that the PSP handles crypto for certain things, and also may use keyslots in a way similar to the Xbox 360's keyvault, except instead of the actual console OS having access to these keys only code running on the PSP can use them. 37 | 38 | The Xbox One HostOS contacts the PSP through the psp.sys driver. For decrypting XVDs it appears to send the header of the XVD to the PSP, which then (assumably) decrypts the CIK field in the header and sends it back, with the OS performing the rest of the decryption. 39 | 40 | psp.sys also has commands which seem to read memory from the PSP instead of sending commands to it, the contents of this memory are unknown, but it's possible (albeit unlikely) that the ODK may be in this area of memory. 41 | 42 | ### Acronyms 43 | - CIK: Content Instance Key, used to encrypt the XVD data, is either stored encrypted in the header or stored outside the package in a license file 44 | 45 | - ODK: Offline Distribution Key, used to decrypt the header's encrypted CIK, and likely any CIK stored outside the package 46 | --------------------------------------------------------------------------------