├── .gitattributes
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── CikExtractor.sln
├── CikExtractor
├── CikExtractor.csproj
├── ConsoleLogger.cs
├── Crypto.cs
├── DeviceKeyDumper.cs
├── DeviceKeyParameters.cs
├── Emulation
│ ├── clep_vault.py
│ └── x8664_windows
│ │ └── Windows
│ │ └── registry
│ │ ├── HARDWARE
│ │ ├── NTUSER.DAT
│ │ ├── SAM
│ │ ├── SECURITY
│ │ ├── SOFTWARE
│ │ └── SYSTEM
├── Models
│ ├── BasicPolicies.cs
│ ├── LicenseType.cs
│ ├── SpLicense.cs
│ └── SpLicenseBlocks.cs
├── Program.cs
└── RegistryInterface.cs
├── LICENSE.txt
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | permissions:
3 | contents: write
4 |
5 | on:
6 | push:
7 | tags:
8 | - "*"
9 | workflow_dispatch:
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | dotnet-version: [ '8.0.x' ]
17 | rid: ['win-x64']
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 | - name: Setup .NET SDK ${{ matrix.dotnet-version }}
22 | uses: actions/setup-dotnet@v4
23 | with:
24 | dotnet-version: ${{ matrix.dotnet-version }}
25 | - name: Install dependencies
26 | run: dotnet restore -r ${{ matrix.rid }}
27 | - name: Publish
28 | run: |
29 | dotnet publish CikExtractor/CikExtractor.csproj -c Release --no-restore -o ./${{ matrix.rid }} -r ${{ matrix.rid }} --no-self-contained
30 | zip -r ${{ matrix.rid }}.zip ./${{ matrix.rid }}/*
31 | - name: Upload artifacts
32 | uses: actions/upload-artifact@v4
33 | with:
34 | name: ${{ matrix.rid }}.zip
35 | path: ./${{ matrix.rid }}.zip
36 |
37 | make-release:
38 | needs: build
39 | runs-on: ubuntu-latest
40 | steps:
41 | - uses: actions/checkout@v3
42 | - name: Download artifacts
43 | uses: actions/download-artifact@v4
44 | with:
45 | path: ./artifacts/
46 | - name: Make release
47 | uses: softprops/action-gh-release@v2
48 | with:
49 | files: ./artifacts/**/*
--------------------------------------------------------------------------------
/.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 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/CikExtractor.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32519.111
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CikExtractor", "CikExtractor\CikExtractor.csproj", "{4E00EDDE-E181-423A-9524-102AAFE7A129}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {4E00EDDE-E181-423A-9524-102AAFE7A129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {4E00EDDE-E181-423A-9524-102AAFE7A129}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {4E00EDDE-E181-423A-9524-102AAFE7A129}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {4E00EDDE-E181-423A-9524-102AAFE7A129}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {5F71FFF6-B4DB-4838-BB99-685A0F258FF6}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/CikExtractor/CikExtractor.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0-windows7.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Always
24 |
25 |
26 | PreserveNewest
27 |
28 |
29 | PreserveNewest
30 |
31 |
32 | PreserveNewest
33 |
34 |
35 | PreserveNewest
36 |
37 |
38 | PreserveNewest
39 |
40 |
41 | PreserveNewest
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/CikExtractor/ConsoleLogger.cs:
--------------------------------------------------------------------------------
1 | using Spectre.Console;
2 |
3 | namespace CikExtractor;
4 |
5 | public static class ConsoleLogger
6 | {
7 | public static void WriteInfoLine(string line)
8 | {
9 | AnsiConsole.MarkupLine($"[white]INFO:[/] {line}");
10 | }
11 |
12 | public static void WriteWarnLine(string line)
13 | {
14 | AnsiConsole.MarkupLine($"[orange1]WARN:[/] {line}");
15 | }
16 |
17 | public static void WriteErrLine(string line)
18 | {
19 | AnsiConsole.MarkupLine($"[red]ERR:[/] {line}");
20 | }
21 | }
--------------------------------------------------------------------------------
/CikExtractor/Crypto.cs:
--------------------------------------------------------------------------------
1 | using Org.BouncyCastle.Crypto.Engines;
2 | using Org.BouncyCastle.Crypto.Parameters;
3 |
4 | namespace CikExtractor;
5 |
6 | internal static class Crypto
7 | {
8 | internal static byte[] DecryptContentKey(byte[] deviceKey, byte[] encryptedContentKey)
9 | {
10 | var engine = new AesWrapEngine();
11 | engine.Init(false, new KeyParameter(deviceKey));
12 |
13 | return engine.Unwrap(encryptedContentKey, 0, encryptedContentKey.Length);
14 | }
15 | }
--------------------------------------------------------------------------------
/CikExtractor/DeviceKeyDumper.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace CikExtractor;
4 |
5 | internal static class DeviceKeyDumper
6 | {
7 | private const string Python = "python";
8 | private const string ErrorPrefix = "Error";
9 |
10 | private const string EmulationDir = "Emulation";
11 |
12 | private const string DllName = "clipsp.sys";
13 | private const string DllTargetPath = $"{EmulationDir}/{DllName}";
14 | private const string DllSourcePath = $"C:/Windows/System32/drivers/{DllName}";
15 |
16 | private const string KernelName = "ntoskrnl.exe";
17 | private const string KernelSourcePath = $"C:/Windows/System32/{KernelName}";
18 | private const string KernelTargetDirectory = $"{EmulationDir}/x8664_windows/Windows/System32";
19 | private const string KernelTargetPath = $"{KernelTargetDirectory}/{KernelName}";
20 |
21 | public static byte[]? DeriveDeviceKey(DeviceKeyParameters parameters)
22 | {
23 | if (!File.Exists(KernelTargetPath))
24 | {
25 | Directory.CreateDirectory(KernelTargetDirectory);
26 | File.Copy(KernelSourcePath, KernelTargetPath);
27 | }
28 |
29 | if (!File.Exists(DllTargetPath))
30 | File.Copy(DllSourcePath, DllTargetPath);
31 |
32 | using var process = new Process();
33 | process.StartInfo.FileName = Python;
34 | process.StartInfo.CreateNoWindow = false;
35 | process.StartInfo.RedirectStandardOutput = true;
36 | process.StartInfo.Arguments = parameters.ToCommand();
37 | process.StartInfo.WorkingDirectory = Path.GetFullPath(EmulationDir);
38 |
39 | process.Start();
40 | process.WaitForExit();
41 |
42 | var deviceKey = process.StandardOutput.ReadLine()!.Trim();
43 |
44 | if (deviceKey.Contains(ErrorPrefix))
45 | {
46 | Console.WriteLine(deviceKey);
47 | return null;
48 | }
49 |
50 | return Convert.FromHexString(deviceKey.Trim());
51 | }
52 | }
--------------------------------------------------------------------------------
/CikExtractor/DeviceKeyParameters.cs:
--------------------------------------------------------------------------------
1 | using System.Management;
2 | using System.Text;
3 | using CikExtractor.Models;
4 |
5 | namespace CikExtractor;
6 |
7 | internal record DeviceKeyParameters(byte[] Smbios, byte[] DriveSerial, byte[] EncryptedLicense)
8 | {
9 | public static DeviceKeyParameters? DumpParameters(RegistryManager registryManager)
10 | {
11 | var deviceLicenses = registryManager.Licenses.Where(x => x.LicenseType == LicenseType.Device).ToList();
12 |
13 | if (0 >= deviceLicenses.Count)
14 | {
15 | ConsoleLogger.WriteErrLine("No device license found.");
16 | return null;
17 | }
18 |
19 | if (deviceLicenses.Count > 1)
20 | {
21 | ConsoleLogger.WriteErrLine("More than one device license found.");
22 | return null;
23 | }
24 |
25 | var license = deviceLicenses.First();
26 |
27 | if (license.EncryptedDeviceKey == null)
28 | {
29 | ConsoleLogger.WriteErrLine("Device license did not contain an encrypted device key.");
30 | return null;
31 | }
32 |
33 | var smbios = DumpSmbios();
34 |
35 | if (smbios == null)
36 | {
37 | ConsoleLogger.WriteErrLine("Failed to dump SMBIOS system struct.");
38 | return null;
39 | }
40 |
41 | var driveSerial = DumpDriveSerial();
42 |
43 | if (driveSerial == null)
44 | {
45 | ConsoleLogger.WriteErrLine("Failed to read the root drive serial number.");
46 | return null;
47 | }
48 |
49 | return new DeviceKeyParameters(smbios, driveSerial, license.EncryptedDeviceKey);
50 | }
51 |
52 | private static byte[]? DumpSmbios()
53 | {
54 | var mgmtScope = new ManagementScope(@"\\localhost\root\WMI");
55 | mgmtScope.Connect();
56 |
57 | var query = new ObjectQuery("SELECT * FROM MSSmBios_RawSMBiosTables");
58 | var searcher = new ManagementObjectSearcher(mgmtScope, query);
59 | var collection = searcher.Get();
60 | foreach (var entry in collection)
61 | {
62 | if (entry?["SMBiosData"] is byte[] rawData)
63 | return GetSystemStructFromRawSmbios(rawData);
64 | }
65 |
66 | return null;
67 | }
68 |
69 | private static byte[]? GetSystemStructFromRawSmbios(ReadOnlySpan smbios)
70 | {
71 | var length = smbios.Length;
72 |
73 | var current = 0;
74 | byte[]? systemStructData = null;
75 |
76 | while (current < length)
77 | {
78 | var tableId = smbios[current];
79 | var formattedLen = smbios[current + 1];
80 |
81 | if (tableId == 0x1)
82 | {
83 | systemStructData = smbios.Slice(current, formattedLen).ToArray();
84 | }
85 |
86 | current += formattedLen;
87 |
88 | var unformattedLen = current;
89 |
90 | while (smbios[unformattedLen] != 0x0 || smbios[unformattedLen + 1] != 0x0)
91 | unformattedLen++;
92 |
93 | unformattedLen += 2;
94 |
95 | if (unformattedLen - current != 0 && systemStructData != null)
96 | {
97 | var unformattedData = smbios[current..unformattedLen].ToArray();
98 |
99 | var newBuf = new byte[systemStructData.Length + unformattedData.Length];
100 | Buffer.BlockCopy(systemStructData, 0, newBuf, 0, systemStructData.Length);
101 | Buffer.BlockCopy(unformattedData, 0, newBuf, systemStructData.Length, unformattedData.Length);
102 |
103 | return newBuf.Length > 256 ? newBuf.Take(256).ToArray() : newBuf;
104 | }
105 |
106 | current = unformattedLen;
107 | }
108 |
109 | ConsoleLogger.WriteErrLine("Could not find system struct in SMBIOS.");
110 | return null;
111 | }
112 |
113 | //Technically, this is also used for deriving/decrypting the device key. But testing showed that is not actually used, at least in all cases I've observed.
114 | private static byte[]? DumpDriveSerial()
115 | {
116 | var searcher = new ManagementObjectSearcher(@"SELECT * FROM Win32_DiskDrive WHERE DeviceID = ""\\\\.\\PHYSICALDRIVE0""");
117 |
118 | foreach (var entry in searcher.Get())
119 | {
120 | var serialNo = entry["SerialNumber"] as string;
121 | var serialNoBuf = Encoding.UTF8.GetBytes(serialNo + '\x00');
122 | return serialNoBuf.Length > 64 ? serialNoBuf.Take(64).ToArray() : serialNoBuf;
123 | }
124 |
125 | ConsoleLogger.WriteErrLine("Could not get root drive serial number.");
126 | return null;
127 | }
128 |
129 | private const string CommandTemplate = "clep_vault.py --license \"{0}\" --smbios \"{1}\" --driveser \"{2}\"";
130 |
131 | public string ToCommand()
132 | {
133 | return string.Format(CommandTemplate, Convert.ToBase64String(EncryptedLicense), Convert.ToBase64String(Smbios),
134 | Convert.ToBase64String(DriveSerial));
135 | }
136 | }
--------------------------------------------------------------------------------
/CikExtractor/Emulation/clep_vault.py:
--------------------------------------------------------------------------------
1 | import binascii
2 | from base64 import b64decode
3 | import struct
4 |
5 | from qiling.os.windows.fncc import *
6 | from qiling import *
7 |
8 | import argparse
9 |
10 |
11 | @winsdkapi(cc=STDCALL)
12 | def hook_chkstk(ql, addr, params):
13 | return ql.arch.regs.rax
14 |
15 |
16 | def parse_args():
17 | parser = argparse.ArgumentParser(
18 | description="decrypts a ClepV4 encrypted device key."
19 | )
20 | parser.add_argument(
21 | "--license",
22 | required=True,
23 | help="base64 encoded encrypted device license (Required length: 4094)",
24 | )
25 | parser.add_argument(
26 | "--smbios", required=True, help="base64 encoded SMBIOS system struct."
27 | )
28 | parser.add_argument(
29 | "--driveser",
30 | required=True,
31 | help="base64 encoded null-terminated root drive serial number.",
32 | )
33 | args = parser.parse_args()
34 | return (args.license, args.smbios, args.driveser)
35 |
36 |
37 | if __name__ == "__main__":
38 | enc_license_b64, smbios_b64, driveser_b64 = parse_args()
39 | encrypted_device_license = b64decode(enc_license_b64)
40 | smbiosSystem = b64decode(smbios_b64)
41 | driveSer = b64decode(driveser_b64)
42 |
43 | assert len(encrypted_device_license) == 4094, (
44 | "Error: Encrypted Device License length mismatch. (Expected: 4094)"
45 | )
46 |
47 | max_smbios = 256
48 | max_driveser = 64
49 | max_tpminfo = 901
50 |
51 | if len(smbiosSystem) > max_smbios:
52 | smbiosSystem = smbiosSystem[:max_smbios]
53 |
54 | if len(driveSer) > max_driveser:
55 | driveSer = driveSer[:max_driveser]
56 |
57 | ql = Qiling(["./clipsp.sys"], ".\\x8664_windows", libcache=True, console=False)
58 |
59 | ql.os.set_api("__chkstk", hook_chkstk)
60 |
61 | clep_vault_func_pattern = b"\x4c\x8b\xdc\x49\x89\x4b\x08"
62 | clep_vault_size = 0x4E
63 | clep_vault_func = ql.mem.search(
64 | clep_vault_func_pattern, begin=0x1C0000000, end=0x1D0000000
65 | )[0]
66 | if clep_vault_func is None:
67 | print("Error: Failed to find vault function using pattern.")
68 | exit()
69 |
70 | # Gets the cached request memory location through a bit of trickery:
71 | # Basically pattern matching a part of a vault function, navigating to the lea request opcode, then reading the operand for the offset
72 | # tuple of (pattern, offset to address)
73 | patterns_to_try = [
74 | # actually 0x4b, but + 3 to get the operand directly
75 | (
76 | b"\xc6\x45\x00\x19\x8a\x45\x00\x8b\x04\x24\x48\x83\xec\x10\x8b\x04\x24\x8b\x04\x24\x48\x83\xec\x50\x48\x8d\x4c\x24\x20\x8b\x01\x41\x0f\x10\x02\x33\xc0\x48\x8d\x59\x0f\x48\x83\xe3\xf0\xf3\x0f\x7f\x43\x28",
77 | 0x4E,
78 | ),
79 | (b"\xc6\x45\x00\x19\x0f\xb6\x45", 0x50),
80 | ]
81 |
82 | clep_request_ptr = 0
83 |
84 | for pattern, offset in patterns_to_try:
85 | results = ql.mem.search(pattern, begin=0x1C0000000, end=0x1D0000000)
86 | if len(results) == 0:
87 | continue
88 |
89 | if len(results) != 1:
90 | print(
91 | "Error: Ambiguous request function references found: "
92 | + str(hex(x) for x in results)
93 | )
94 | continue
95 |
96 | clep_request_opcode = results[0] + offset
97 | clep_request_ptr_offset = struct.unpack(
98 | " EntryIds = new();
25 | public readonly Dictionary PackedContentKeys = new();
26 | public readonly byte[]? EncryptedDeviceKey;
27 |
28 | public readonly byte[]? LicensePolicies;
29 | public readonly byte[]? KeyholderPolicies;
30 |
31 | public readonly ushort SignatureOrigin;
32 | public readonly byte[]? SignatureBlock;
33 | public readonly byte[]? ClepSignState;
34 |
35 | public readonly byte[]? UnknownBlock2;
36 |
37 | public SpLicense(byte[] licenseBlob)
38 | {
39 | using var reader = new BinaryReader(new MemoryStream(licenseBlob));
40 |
41 | var header = reader.ReadBytes(4);
42 | var sigBlockOffset = reader.ReadUInt32();
43 |
44 | while (reader.BaseStream.Position != reader.BaseStream.Length)
45 | {
46 | var blockId = (SpLicenseBlocks) reader.ReadInt32();
47 |
48 | var blockLength = reader.ReadInt32();
49 |
50 | switch (blockId)
51 | {
52 | case SpLicenseBlocks.LicenseId:
53 | LicenseId = new Guid(reader.ReadBytes(16));
54 | break;
55 |
56 | case SpLicenseBlocks.ClepSignState:
57 | ClepSignState = reader.ReadBytes(blockLength);
58 | break;
59 |
60 | case SpLicenseBlocks.KeyholderKeyLicenseId:
61 | KeyholderKeyLicenseId = new Guid(reader.ReadBytes(16));
62 | break;
63 |
64 | case SpLicenseBlocks.KeyholderPublicSigningKey:
65 | KeyholderPublicSigningKey = reader.ReadBytes(blockLength);
66 | break;
67 |
68 | case SpLicenseBlocks.KeyholderPolicies:
69 | KeyholderPolicies = reader.ReadBytes(blockLength);
70 | break;
71 |
72 | case SpLicenseBlocks.LicensePolicies:
73 | LicensePolicies = reader.ReadBytes(blockLength);
74 | break;
75 |
76 | case SpLicenseBlocks.UplinkKeyId:
77 | UplinkKeyId = reader.ReadBytes(blockLength);
78 | break;
79 |
80 | case SpLicenseBlocks.DeviceLicenseDeviceId: // 0xa
81 | case SpLicenseBlocks.LicenseDeviceId: // 0x8
82 | DeviceId = reader.ReadBytes(blockLength);
83 | break;
84 |
85 | case SpLicenseBlocks.UnkBlock2:
86 | UnknownBlock2 = reader.ReadBytes(blockLength);
87 | break;
88 |
89 | case SpLicenseBlocks.HardwareId:
90 | HardwareId = reader.ReadBytes(blockLength);
91 | break;
92 |
93 | case SpLicenseBlocks.PackageFullName:
94 | PackageName = Encoding.Unicode.GetString(reader.ReadBytes(blockLength))[..^1];
95 | break;
96 |
97 | case SpLicenseBlocks.SignatureBlock:
98 | var unk = reader.ReadUInt16();
99 | SignatureOrigin = reader.ReadUInt16();
100 | SignatureBlock = reader.ReadBytes(blockLength);
101 | break;
102 |
103 | case SpLicenseBlocks.EncryptedDeviceKey:
104 | reader.BaseStream.Seek(2, SeekOrigin.Current);
105 | EncryptedDeviceKey = reader.ReadBytes(blockLength - 2);
106 | break;
107 |
108 | case SpLicenseBlocks.LicenseExpirationTime:
109 | case SpLicenseBlocks.DeviceLicenseExpirationTime:
110 | ExpirationTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadInt32());
111 | break;
112 |
113 | case SpLicenseBlocks.LicenseInformation:
114 | LicenseVersion = reader.ReadUInt16();
115 | LicenseType = (LicenseType) reader.ReadUInt16();
116 | IssuedTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadInt32());
117 | BasicPolicies = (BasicPolicies) reader.ReadUInt16();
118 | break;
119 |
120 | case SpLicenseBlocks.LicenseEntryIds:
121 | var count = reader.ReadUInt16();
122 | for (int i = 0; i < count; i++)
123 | EntryIds.Add(reader.ReadBytes(32));
124 | break;
125 |
126 | case SpLicenseBlocks.PackedContentKeys:
127 | var currentOffset = 0;
128 | while (currentOffset != blockLength)
129 | {
130 | var keyIdLen = reader.ReadUInt16(); // 0x20
131 | var packedKeyLen = reader.ReadUInt16(); // 0x28
132 |
133 | var keyId = reader.ReadBytes(keyIdLen);
134 | var packedKey = reader.ReadBytes(packedKeyLen);
135 |
136 | PackedContentKeys.Add(new Guid(keyId[..16]), packedKey);
137 |
138 | currentOffset += 4 + keyIdLen + packedKeyLen;
139 | }
140 | break;
141 |
142 | case SpLicenseBlocks.PollingTime:
143 | PollingTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadInt32());
144 | break;
145 |
146 | case SpLicenseBlocks.UnkBlock0:
147 | case SpLicenseBlocks.UnkBlock1:
148 | case SpLicenseBlocks.UnkBlock3:
149 | case SpLicenseBlocks.UnkBlock4:
150 | case SpLicenseBlocks.UnkBlock5:
151 | default:
152 | Console.WriteLine($"Parsing block id {blockId} ({blockId:X}) is not implemented.");
153 | reader.BaseStream.Seek(blockLength, SeekOrigin.Current);
154 | break;
155 | }
156 | }
157 | }
158 | }
--------------------------------------------------------------------------------
/CikExtractor/Models/SpLicenseBlocks.cs:
--------------------------------------------------------------------------------
1 | namespace CikExtractor.Models;
2 |
3 | public enum SpLicenseBlocks
4 | {
5 | UnkBlock0 = 0x14,
6 | DeviceLicenseExpirationTime = 0x1f,
7 | PollingTime = 0xd3,
8 | LicenseExpirationTime = 0x20,
9 | ClepSignState = 0x12d,
10 | LicenseDeviceId = 0xd2,
11 | UnkBlock1 = 0xd1,
12 | LicenseId = 0xcb,
13 | HardwareId = 0xd0,
14 | UnkBlock2 = 0xcf,
15 | UplinkKeyId = 0x18,
16 | UnkBlock3 = 0x0,
17 | UnkBlock4 = 0x12e,
18 | UnkBlock5 = 0xd5,
19 | PackageFullName = 0xce,
20 | LicenseInformation = 0xc9,
21 | PackedContentKeys = 0xca,
22 | EncryptedDeviceKey = 0x1,
23 | DeviceLicenseDeviceId = 0x2,
24 | LicenseEntryIds = 0xcd,
25 | LicensePolicies = 0xd4,
26 | KeyholderPublicSigningKey = 0xdc,
27 | KeyholderPolicies = 0xdd,
28 | KeyholderKeyLicenseId = 0xde,
29 | SignatureBlock = 0xcc,
30 | }
--------------------------------------------------------------------------------
/CikExtractor/Program.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Diagnostics;
3 | using System.Text.Json;
4 | using Spectre.Console;
5 | using Spectre.Console.Cli;
6 |
7 | namespace CikExtractor;
8 |
9 | internal class Program
10 | {
11 | static void Main(string[] args)
12 | {
13 | var app = new CommandApp();
14 |
15 | app.Configure(ctx =>
16 | {
17 | ctx.TrimTrailingPeriods(false);
18 |
19 | ctx.AddCommand("dump")
20 | .WithDescription("Derives the device key and decrypts all CIKs stored in the registry. Default command.");
21 |
22 | ctx.AddCommand("export-hive")
23 | .WithDescription("Export the registry hive containing the CIKs into a file.");
24 |
25 | ctx.AddCommand("export-params")
26 | .WithDescription(
27 | "Export the parameters needed to derive a device key.\nUseful if you want to run the key derivation on another device.");
28 |
29 | ctx.AddCommand("derive")
30 | .WithDescription("Derives a device key from the exported parameters of the 'export-params' command.");
31 | });
32 |
33 | app.Run(args);
34 | }
35 | }
36 |
37 | internal sealed class DumpCommand : Command
38 | {
39 | public sealed class Settings : CommandSettings
40 | {
41 | [DefaultValue("Cik")]
42 | [Description("Folder to extract CIKs into.")]
43 | [CommandOption("-c|--export-cik-path")]
44 | public string? CikExtractionFolder { get; init; }
45 |
46 | [DefaultValue("deviceKey.txt")]
47 | [Description("File to read/write device key from/into.")]
48 | [CommandOption("-d|--device-key-path")]
49 | public string? DeviceKeyFile { get; init; }
50 |
51 | [Description("Path to an already exported registry hive exported by the 'export-hive' command.")]
52 | [CommandOption("-r|--reg-hive")]
53 | public string? RegistryHiveFile { get; init; }
54 | }
55 |
56 | public override int Execute(CommandContext context, Settings settings)
57 | {
58 | Debug.Assert(settings.CikExtractionFolder != null, "settings.CikExtractionFolder != null");
59 | Debug.Assert(settings.DeviceKeyFile != null, "settings.DeviceKeyFile != null");
60 |
61 | byte[] deviceKey;
62 |
63 | var manager = new RegistryManager();
64 |
65 | string? registryPath;
66 |
67 | if (settings.RegistryHiveFile != null)
68 | {
69 | registryPath = Path.GetFullPath(settings.RegistryHiveFile);
70 |
71 | ConsoleLogger.WriteInfoLine($"Loading entries from [white bold]{registryPath}[/]...");
72 | }
73 | else
74 | {
75 | registryPath = null;
76 |
77 | ConsoleLogger.WriteInfoLine("Loading entries from registry...");
78 | }
79 |
80 | if (manager.LoadLicenses(registryPath))
81 | {
82 | ConsoleLogger.WriteInfoLine("Registry [green bold]loaded[/].");
83 | }
84 | else
85 | {
86 | ConsoleLogger.WriteErrLine("Failed to load registry.");
87 | return -1;
88 | }
89 |
90 | var deviceKeyPath = Path.GetFullPath(settings.DeviceKeyFile);
91 |
92 | if (File.Exists(deviceKeyPath))
93 | {
94 | ConsoleLogger.WriteInfoLine($"Reading device key from [white]{deviceKeyPath}[/].");
95 | var deviceKeyFile = File.ReadAllText(deviceKeyPath);
96 |
97 | Debug.Assert(deviceKeyFile.Length == 32, "deviceKeyFile.Length == 32");
98 | Debug.Assert(deviceKeyFile.All("0123456789ABCDEFabcdef".Contains), "Device key is not a valid hex string");
99 |
100 | deviceKey = Convert.FromHexString(deviceKeyFile);
101 | ConsoleLogger.WriteInfoLine("[green bold]Successfully[/] loaded device key from file.");
102 | }
103 | else
104 | {
105 | ConsoleLogger.WriteInfoLine("Deriving device key...");
106 | var parameters = DeviceKeyParameters.DumpParameters(manager);
107 | if (parameters == null)
108 | {
109 | ConsoleLogger.WriteErrLine("Failed to dump device key parameters.");
110 | return -1;
111 | }
112 |
113 | var key = DeviceKeyDumper.DeriveDeviceKey(parameters);
114 | if (key == null)
115 | {
116 | ConsoleLogger.WriteErrLine("Failed to derive device key.");
117 | return -1;
118 | }
119 |
120 | deviceKey = key;
121 |
122 | var hexDeviceKey = Convert.ToHexString(deviceKey);
123 |
124 | ConsoleLogger.WriteInfoLine($"[green bold]Successfully[/] derived device key [white bold]{hexDeviceKey}[/].");
125 |
126 | File.WriteAllText(deviceKeyPath, hexDeviceKey);
127 | }
128 |
129 | var cikFolderPath = Path.GetFullPath(settings.CikExtractionFolder);
130 | Directory.CreateDirectory(cikFolderPath);
131 |
132 | var tree = new Tree(":post_office:");
133 | foreach (var license in manager.Licenses.Where(x => x.PackedContentKeys.Count != 0))
134 | {
135 | var packageNode = tree.AddNode($":package: [blue]{license.PackageName}[/]");
136 | packageNode.AddNode($"[white bold]License Type[/]: [green bold]{license.LicenseType}[/]");
137 |
138 | foreach (var pair in license.PackedContentKeys)
139 | {
140 | var contentKey = Crypto.DecryptContentKey(deviceKey, pair.Value);
141 | var filePath = Path.Join(cikFolderPath, $"{pair.Key}.cik");
142 |
143 | File.WriteAllBytes(filePath,
144 | pair.Key
145 | .ToByteArray()
146 | .Concat(contentKey)
147 | .ToArray());
148 |
149 | var keyNode = packageNode.AddNode($":key: [blue]{pair.Key}[/]");
150 | keyNode.AddNode($"[white bold]Key[/]: [green bold]{Convert.ToHexString(contentKey)}[/]");
151 | }
152 | }
153 |
154 | AnsiConsole.Write(tree);
155 |
156 | return 0;
157 | }
158 |
159 | public override ValidationResult Validate(CommandContext context, Settings settings)
160 | {
161 | if (File.Exists(settings.CikExtractionFolder))
162 | return ValidationResult.Error("CIK extraction path must be a folder.");
163 |
164 | if (File.Exists(settings.DeviceKeyFile))
165 | {
166 | var deviceKey = File.ReadAllText(settings.DeviceKeyFile);
167 | if (deviceKey.Length != 32 || !deviceKey.All("0123456789ABCDEFabcdef".Contains))
168 | return ValidationResult.Error("Provided device key in file is invalid. Must be 32 hex characters long.");
169 | }
170 |
171 | if (settings.RegistryHiveFile != null && !File.Exists(settings.RegistryHiveFile))
172 | return ValidationResult.Error("Supplied registry hive does not exist.");
173 |
174 | return ValidationResult.Success();
175 | }
176 | }
177 |
178 | internal sealed class ExportHiveCommand : Command
179 | {
180 | public sealed class Settings : CommandSettings
181 | {
182 | [Description("Path to export the registry hive into.")]
183 | [CommandArgument(0, "")]
184 | public string? ExportPath { get; init; }
185 | }
186 |
187 | public override int Execute(CommandContext context, Settings settings)
188 | {
189 | Debug.Assert(settings.ExportPath != null, "settings.ExportPath != null");
190 |
191 | var manager = new RegistryManager();
192 |
193 | var fullPath = Path.GetFullPath(settings.ExportPath);
194 |
195 | manager.ExportHive(fullPath);
196 |
197 | ConsoleLogger.WriteInfoLine($"[green bold]Successfully[/] exported registry hive into [white]{fullPath}[/]");
198 |
199 | return 0;
200 | }
201 | }
202 |
203 | internal sealed class ExportParametersCommand : Command
204 | {
205 | public sealed class Settings : CommandSettings
206 | {
207 | [Description("Path to export the parameters into.")]
208 | [CommandArgument(0, "")]
209 | public string? ExportPath { get; init; }
210 | }
211 |
212 | public override int Execute(CommandContext context, Settings settings)
213 | {
214 | Debug.Assert(settings.ExportPath != null, "settings.ExportPath != null");
215 |
216 | var manager = new RegistryManager();
217 | ConsoleLogger.WriteInfoLine("Parsing registry entries...");
218 | if (manager.LoadLicenses())
219 | {
220 | ConsoleLogger.WriteInfoLine("Registry [green bold]loaded[/].");
221 | }
222 | else
223 | {
224 | ConsoleLogger.WriteErrLine("Failed to load registry.");
225 | return -1;
226 | }
227 |
228 | var parameters = DeviceKeyParameters.DumpParameters(manager);
229 |
230 | var fullPath = Path.GetFullPath(settings.ExportPath);
231 | var directory = Path.GetDirectoryName(fullPath);
232 | if (directory != null)
233 | Directory.CreateDirectory(directory);
234 |
235 | File.WriteAllText(fullPath, JsonSerializer.Serialize(parameters));
236 |
237 | ConsoleLogger.WriteInfoLine($"[green bold]Successfully[/] exported parameters into [white]{fullPath}[/]");
238 |
239 | return 0;
240 | }
241 | }
242 |
243 | internal sealed class DeriveKeyCommand : Command
244 | {
245 | public sealed class Settings : CommandSettings
246 | {
247 | [Description("Path to the exported parameters.")]
248 | [CommandArgument(0, "")]
249 | public string? ParametersPath { get; init; }
250 |
251 | [DefaultValue("deviceKey.txt")]
252 | [Description("Path to write the derived device key into.")]
253 | [CommandArgument(1, "[output path]")]
254 | public string? DeviceKeyPath { get; init; }
255 | }
256 |
257 | public override int Execute(CommandContext context, Settings settings)
258 | {
259 | Debug.Assert(settings.ParametersPath != null, "settings.ParametersPath != null");
260 | Debug.Assert(settings.DeviceKeyPath != null, "settings.DeviceKeyPath != null");
261 |
262 | var parameters = JsonSerializer.Deserialize(File.ReadAllText(settings.ParametersPath));
263 | if (parameters == null)
264 | {
265 | ConsoleLogger.WriteErrLine("Failed to deserialize key parameters.");
266 | return -1;
267 | }
268 |
269 | ConsoleLogger.WriteInfoLine("Imported parameters.");
270 | ConsoleLogger.WriteInfoLine("Deriving device key...");
271 |
272 | var key = DeviceKeyDumper.DeriveDeviceKey(parameters);
273 | if (key == null)
274 | {
275 | ConsoleLogger.WriteErrLine("Failed to derive device key.");
276 | return -1;
277 | }
278 |
279 | var hexDeviceKey = Convert.ToHexString(key);
280 |
281 | ConsoleLogger.WriteInfoLine($"[green bold]Successfully[/] derived device key [white bold]{hexDeviceKey}[/].");
282 |
283 | var fullKeyPath = Path.GetFullPath(settings.DeviceKeyPath);
284 |
285 | File.WriteAllText(fullKeyPath, hexDeviceKey);
286 |
287 | ConsoleLogger.WriteInfoLine($"[green bold]Successfully[/] wrote device key into [white bold]{fullKeyPath}[/].");
288 |
289 | return 0;
290 | }
291 |
292 | public override ValidationResult Validate(CommandContext context, Settings settings)
293 | {
294 | if (settings.ParametersPath != null && !File.Exists(settings.ParametersPath))
295 | return ValidationResult.Error("Parameters file does not exist.");
296 |
297 | return ValidationResult.Success();
298 | }
299 | }
--------------------------------------------------------------------------------
/CikExtractor/RegistryInterface.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using CikExtractor.Models;
3 | using Registry;
4 |
5 | namespace CikExtractor;
6 |
7 | internal sealed class RegistryManager
8 | {
9 | private const string RegExe = "reg";
10 | private const string ControlRegKey = @"HKLM\SYSTEM\ControlSet001\Control\";
11 |
12 | private const string ExportCommand = "save {0} \"{1}\" /Y";
13 |
14 | // Registry Constants
15 | private const string RootClipStoragePath = "{7746D80F-97E0-4E26-9543-26B41FC22F79}";
16 | private const string LicenseBlobStorage = $"{RootClipStoragePath}\\{{A25AE4F2-1B96-4CED-8007-AA30E9B1A218}}";
17 |
18 | // Unused here, just for reference
19 | private const string LicenseContentKeyIdStorage = $"{RootClipStoragePath}\\{{D73E01AC-F5A0-4D80-928B-33C1920C38BA}}";
20 | private const string LicencePolicyKeyStorage = $"{RootClipStoragePath}\\{{59AEE675-B203-4D61-9A1F-04518A20F359}}";
21 | private const string LicenseEntryIdStorage = $"{RootClipStoragePath}\\{{FB9F5B62-B48B-45F5-8586-E514958C92E2}}";
22 | private const string OptionalInfoStorage = $"{RootClipStoragePath}\\{{221601AB-48C7-4970-B0EC-96E66F578407}}";
23 |
24 | private RegistryHive? _loadedRegistry;
25 |
26 | public IEnumerable Licenses
27 | {
28 | get
29 | {
30 | Debug.Assert(_loadedRegistry != null, "_loadedRegistry != null");
31 | return _licenses ??= ParseLicenses();
32 | }
33 | }
34 |
35 | private List? _licenses;
36 |
37 | public bool LoadLicenses(string? hivePath = null)
38 | {
39 | if (hivePath == null)
40 | {
41 | hivePath = Path.GetTempFileName();
42 |
43 | ExportRegistryHive(ControlRegKey, hivePath);
44 | }
45 |
46 | return LoadHive(hivePath);
47 | }
48 |
49 | public void ExportHive(string path)
50 | {
51 | ExportRegistryHive(ControlRegKey, path);
52 | }
53 |
54 | private bool LoadHive(string hivePath)
55 | {
56 | _loadedRegistry = new RegistryHive(hivePath);
57 | return _loadedRegistry.ParseHive();
58 | }
59 |
60 | private static void ExportRegistryHive(string hiveName, string outputPath)
61 | {
62 | using var process = new Process();
63 | process.StartInfo.Verb = "runas";
64 | process.StartInfo.FileName = RegExe;
65 | process.StartInfo.CreateNoWindow = true;
66 | process.StartInfo.UseShellExecute = true;
67 | process.StartInfo.Arguments = string.Format(ExportCommand, hiveName, outputPath);
68 |
69 | process.Start();
70 | process.WaitForExit();
71 | }
72 |
73 | private List ParseLicenses()
74 | {
75 | Debug.Assert(_loadedRegistry != null, "_loadedRegistry != null");
76 |
77 | var licenseBlobValues = _loadedRegistry.GetKey(LicenseBlobStorage).Values;
78 | return licenseBlobValues
79 | .Where(value => value.ValueDataRaw.Length > 8)
80 | .Select(licenseBlobValue => licenseBlobValue.ValueDataRaw)
81 | .Select(licenseBlob => new SpLicense(licenseBlob))
82 | .ToList();
83 | }
84 | }
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 LukeFZ
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CikExtractor
2 |
3 | *Utility to dump stored packed CIK (Content Integrity Key) data for MSIXVC packages from the registry. Additionally leverages emulation to derive your device encryption key to decrypt the CIKs for normal usage.*
4 |
5 | ## Disclaimer
6 |
7 | **Warning: All keys derived and decrypted by this tool are sensitive information. You should never share a derived key with anyone, especially not your unique device key. This tool is for educational and research purposes only.**
8 |
9 | ## Requirements
10 |
11 | - Windows 10+
12 | - [.NET 6.0.x](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) installed
13 | - Python 3 added to your PATH, along with the **[Qiling](https://github.com/qilingframework/qiling)** package installed
14 | - Administrator privileges (for dumping the required registry keys)
15 |
16 | ## Usage
17 |
18 | - Download the [latest release](https://github.com/LukeFZ/CikExtractor/releases/latest).
19 | - Extract the downloaded zip file.
20 | - Run *CikExtractor.exe*.
21 |
22 | The derived device key will be printed to the console, and will also be saved to *deviceKey.txt* in the app directory.
23 | Decrypted CIKs will be saved in the *Cik* subfolder.
24 |
25 | ## Advanced Usage
26 |
27 | ```
28 | USAGE:
29 | CikExtractor.exe [OPTIONS]
30 |
31 | OPTIONS:
32 | DEFAULT
33 | -h, --help Prints help information
34 | -c, --export-cik-path Cik Folder to extract CIKs into
35 | -d, --device-key-path deviceKey.txt File to read/write device key from/into
36 | -r, --reg-hive Path to an already exported registry hive exported by the 'export-hive' command
37 |
38 | COMMANDS:
39 | dump Derives the device key and decrypts all CIKs stored in the registry. Default command.
40 | export-hive Export the registry hive containing the CIKs into a file.
41 | export-params Export the parameters needed to derive a device key.
42 | Useful if you want to run the key derivation on another device.
43 | derive Derives a device key from the exported parameters of the 'export-params' command.
44 | ```
45 |
46 | ## How to use the keys for decryption
47 |
48 | You can use the generated *Cik* directory and the keys within with [xvdtool, by emoose.](https://github.com/emoose/xvdtool)
49 | Example command:
50 | ```
51 | // To decrypt (Note: will in-place-decrypt, so replacing the existing file):
52 | ./xvdtool.exe -nd -eu -cik "" -cikfile ""
53 |
54 | // Then, to extract the files within:
55 | ./xvdtool.exe -nd -xf ""
56 | ```
57 |
58 | The keys can also be used with [XvdTool.Streaming.](https://github.com/LukeFZ/XvdTool.Streaming)
59 |
60 | You can also use the derived device key to decrypt local XML licenses that contain keys directly, but that is not currently implemented.
61 |
62 | ## Special Thanks
63 |
64 | - [XvddKeyslotUtil](https://github.com/billyhulbert/XvddKeyslotUtil), by billyhulbert
65 | - Inspired me to also look into how Windows handles CIKs and their storage/derivation.
66 | - [Information about SystemPolicyInfo](https://github.com/KiFilterFiberContext/windows-software-policy), by KiFilterFiberContext
67 | - Their findings helped me to better understand the licensing flow and how all of the different parts work together, and their unpacking script inspired me to also use Qiling for the vault emulation.
68 | - [xvdtool](https://github.com/emoose/xvdtool) and [this SPLicenseBlock struct](https://github.com/emoose/xbox-reversing/blob/master/templates/SPLicenseBlock.bt), both by emoose & tuxuser
69 | -
70 |
71 | ## Third party libraries used
72 | - [BouncyCastle](https://bouncycastle.org)
73 | - [Spectre.Console](https://spectreconsole.net)
74 | - [Registry](https://github.com/EricZimmerman/Registry)
--------------------------------------------------------------------------------