-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathForm1.cs
More file actions
497 lines (429 loc) · 17 KB
/
Copy pathForm1.cs
File metadata and controls
497 lines (429 loc) · 17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace SecureBootInspector;
public partial class Form1 : Form
{
private readonly TextBox _summary = new();
private readonly ListView _certificates = new();
private readonly Button _refreshButton = new();
private readonly Button _copyButton = new();
private readonly Button _saveButton = new();
private readonly Label _status = new();
private string _lastReport = string.Empty;
public Form1()
{
InitializeComponent();
BuildUi();
Load += (_, _) => RefreshChecks();
}
private void BuildUi()
{
Text = "SecureBoot Inspector";
MinimumSize = new Size(980, 680);
Size = new Size(1120, 760);
StartPosition = FormStartPosition.CenterScreen;
Font = new Font("Segoe UI", 10F);
TryLoadWindowIcon();
var header = new Label
{
Text = "SecureBoot Inspector",
Dock = DockStyle.Top,
Height = 44,
Font = new Font("Segoe UI Semibold", 18F),
Padding = new Padding(12, 8, 12, 0)
};
var copyright = new Label
{
Text = "Copyright by Dr. René Bäder (PhDs) | GPL-3.0-or-later | Portable Windows 11 Tool",
Dock = DockStyle.Top,
Height = 30,
Padding = new Padding(14, 0, 12, 6),
ForeColor = Color.FromArgb(74, 85, 104)
};
var toolbar = new FlowLayoutPanel
{
Dock = DockStyle.Top,
Height = 48,
Padding = new Padding(12, 6, 12, 6),
FlowDirection = FlowDirection.LeftToRight,
WrapContents = false
};
_refreshButton.Text = "Neu prüfen";
_refreshButton.AutoSize = true;
_refreshButton.Click += (_, _) => RefreshChecks();
toolbar.Controls.Add(_refreshButton);
_copyButton.Text = "Bericht kopieren";
_copyButton.AutoSize = true;
_copyButton.Click += (_, _) => Clipboard.SetText(_lastReport);
toolbar.Controls.Add(_copyButton);
_saveButton.Text = "Bericht speichern";
_saveButton.AutoSize = true;
_saveButton.Click += (_, _) => SaveReport();
toolbar.Controls.Add(_saveButton);
_status.AutoSize = true;
_status.Padding = new Padding(18, 8, 0, 0);
toolbar.Controls.Add(_status);
var split = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Horizontal
};
_summary.Dock = DockStyle.Fill;
_summary.Multiline = true;
_summary.ReadOnly = true;
_summary.ScrollBars = ScrollBars.Vertical;
_summary.Font = new Font("Cascadia Mono", 10F);
_summary.BackColor = Color.White;
split.Panel1.Controls.Add(_summary);
_certificates.Dock = DockStyle.Fill;
_certificates.View = View.Details;
_certificates.FullRowSelect = true;
_certificates.GridLines = true;
_certificates.Columns.Add("Datenbank", 90);
_certificates.Columns.Add("Typ", 110);
_certificates.Columns.Add("Betreff / Hash", 330);
_certificates.Columns.Add("Aussteller", 260);
_certificates.Columns.Add("Gültig bis", 120);
_certificates.Columns.Add("Fingerprint", 320);
split.Panel2.Controls.Add(_certificates);
Controls.Add(split);
Controls.Add(toolbar);
Controls.Add(copyright);
Controls.Add(header);
Shown += (_, _) => ConfigureSplitContainer(split);
split.SizeChanged += (_, _) => ConfigureSplitContainer(split);
}
private static void ConfigureSplitContainer(SplitContainer split)
{
const int panel1MinSize = 180;
const int panel2MinSize = 260;
if (split.Height <= split.SplitterWidth)
{
return;
}
var availableHeight = split.Height - split.SplitterWidth;
var safePanel1MinSize = Math.Min(panel1MinSize, availableHeight);
var safePanel2MinSize = Math.Min(panel2MinSize, Math.Max(0, availableHeight - safePanel1MinSize));
split.Panel1MinSize = safePanel1MinSize;
split.Panel2MinSize = safePanel2MinSize;
var minDistance = split.Panel1MinSize;
var maxDistance = split.Height - split.SplitterWidth - split.Panel2MinSize;
if (maxDistance < minDistance)
{
return;
}
var targetDistance = Math.Max(minDistance, availableHeight / 3);
split.SplitterDistance = Math.Min(targetDistance, maxDistance);
}
private void TryLoadWindowIcon()
{
var iconPath = Path.Combine(AppContext.BaseDirectory, "SecureBootInspector.ico");
if (File.Exists(iconPath))
{
Icon = new Icon(iconPath);
}
}
private void RefreshChecks()
{
_refreshButton.Enabled = false;
_certificates.Items.Clear();
try
{
FirmwareSecurity.EnableSystemEnvironmentPrivilege();
var report = SecureBootReport.Create();
_lastReport = report.ToText();
_summary.Text = _lastReport;
_status.Text = report.SecureBootEnabled ? "Secure Boot aktiv" : "Secure Boot nicht aktiv oder nicht lesbar";
_status.ForeColor = report.SecureBootEnabled ? Color.ForestGreen : Color.Firebrick;
foreach (var item in report.Signatures)
{
var row = new ListViewItem(item.Database);
row.SubItems.Add(item.Type);
row.SubItems.Add(item.SubjectOrHash);
row.SubItems.Add(item.Issuer);
row.SubItems.Add(item.NotAfter);
row.SubItems.Add(item.Thumbprint);
_certificates.Items.Add(row);
}
}
catch (Exception ex)
{
_lastReport = "Fehler beim Prüfen:\r\n" + ex;
_summary.Text = _lastReport;
_status.Text = "Fehler";
_status.ForeColor = Color.Firebrick;
}
finally
{
_refreshButton.Enabled = true;
}
}
private void SaveReport()
{
using var dialog = new SaveFileDialog
{
Filter = "Textbericht (*.txt)|*.txt|Alle Dateien (*.*)|*.*",
FileName = "secureboot-report.txt"
};
if (dialog.ShowDialog(this) == DialogResult.OK)
{
File.WriteAllText(dialog.FileName, _lastReport, Encoding.UTF8);
}
}
}
internal sealed record SecureBootSignature(string Database, string Type, string SubjectOrHash, string Issuer, string NotAfter, string Thumbprint);
internal sealed class SecureBootReport
{
public required bool SecureBootEnabled { get; init; }
public required string SecureBootRaw { get; init; }
public required string SetupModeRaw { get; init; }
public required string AuditModeRaw { get; init; }
public required string DeployedModeRaw { get; init; }
public required string FirmwareType { get; init; }
public required string Machine { get; init; }
public required string User { get; init; }
public required DateTimeOffset CreatedAt { get; init; }
public required List<SecureBootSignature> Signatures { get; init; }
public required List<string> Warnings { get; init; }
public static SecureBootReport Create()
{
var warnings = new List<string>();
var signatures = new List<SecureBootSignature>();
var secureBoot = FirmwareSecurity.ReadUefiByte("SecureBoot", warnings);
var setupMode = FirmwareSecurity.ReadUefiByte("SetupMode", warnings);
var auditMode = FirmwareSecurity.ReadUefiByte("AuditMode", warnings);
var deployedMode = FirmwareSecurity.ReadUefiByte("DeployedMode", warnings);
foreach (var database in new[] { "PK", "KEK", "db", "dbx" })
{
var data = FirmwareSecurity.ReadUefiVariable(database, warnings);
if (data.Length > 0)
{
signatures.AddRange(EfiSignatureParser.Parse(database, data, warnings));
}
}
return new SecureBootReport
{
SecureBootEnabled = secureBoot == 1,
SecureBootRaw = FormatByteState(secureBoot),
SetupModeRaw = FormatByteState(setupMode),
AuditModeRaw = FormatByteState(auditMode),
DeployedModeRaw = FormatByteState(deployedMode),
FirmwareType = FirmwareSecurity.GetFirmwareTypeName(),
Machine = Environment.MachineName,
User = Environment.UserName,
CreatedAt = DateTimeOffset.Now,
Signatures = signatures,
Warnings = warnings.Distinct().ToList()
};
}
public string ToText()
{
var sb = new StringBuilder();
sb.AppendLine("SecureBoot Inspector");
sb.AppendLine("Copyright by Dr. René Bäder (PhDs)");
sb.AppendLine("License: GNU General Public License v3.0 or later");
sb.AppendLine();
sb.AppendLine($"Zeitpunkt: {CreatedAt:yyyy-MM-dd HH:mm:ss zzz}");
sb.AppendLine($"Computer: {Machine}");
sb.AppendLine($"Benutzer: {User}");
sb.AppendLine($"Firmware-Typ: {FirmwareType}");
sb.AppendLine($"Secure Boot: {SecureBootRaw}");
sb.AppendLine($"Setup Mode: {SetupModeRaw}");
sb.AppendLine($"Audit Mode: {AuditModeRaw}");
sb.AppendLine($"Deployed Mode: {DeployedModeRaw}");
sb.AppendLine($"Signaturen: {Signatures.Count}");
sb.AppendLine();
if (Warnings.Count > 0)
{
sb.AppendLine("Hinweise:");
foreach (var warning in Warnings)
{
sb.AppendLine($"- {warning}");
}
sb.AppendLine();
}
sb.AppendLine("Ausgelesene Secure-Boot-Datenbanken:");
foreach (var group in Signatures.GroupBy(s => s.Database))
{
sb.AppendLine($"- {group.Key}: {group.Count()} Einträge");
}
return sb.ToString();
}
private static string FormatByteState(byte? value) => value switch
{
0 => "0 (Nein)",
1 => "1 (Ja)",
null => "Nicht lesbar",
_ => $"{value} (unbekannter Wert)"
};
}
internal static class FirmwareSecurity
{
private const string GlobalVariable = "{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}";
private const int ErrorNoAccess = 998;
private const int ErrorInsufficientBuffer = 122;
public static void EnableSystemEnvironmentPrivilege()
{
if (!OpenProcessToken(Process.GetCurrentProcess().Handle, 0x20 | 0x8, out var token))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
try
{
if (!LookupPrivilegeValue(null, "SeSystemEnvironmentPrivilege", out var luid))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
var privileges = new TokenPrivileges
{
PrivilegeCount = 1,
Luid = luid,
Attributes = 0x2
};
if (!AdjustTokenPrivileges(token, false, ref privileges, 0, IntPtr.Zero, IntPtr.Zero))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
finally
{
CloseHandle(token);
}
}
public static byte? ReadUefiByte(string name, List<string> warnings)
{
var data = ReadUefiVariable(name, warnings);
return data.Length > 0 ? data[0] : null;
}
public static byte[] ReadUefiVariable(string name, List<string> warnings)
{
var buffer = new byte[1024 * 1024];
var read = GetFirmwareEnvironmentVariable(name, GlobalVariable, buffer, buffer.Length);
if (read > 0)
{
return buffer[..read];
}
var error = Marshal.GetLastWin32Error();
if (error == ErrorInsufficientBuffer)
{
warnings.Add($"{name}: UEFI-Variable ist größer als 1 MB und wurde ausgelassen.");
}
else if (error == ErrorNoAccess)
{
warnings.Add($"{name}: Zugriff verweigert. Starte das Tool als Administrator, falls Daten fehlen.");
}
else
{
warnings.Add($"{name}: nicht lesbar (Win32-Fehler {error}).");
}
return Array.Empty<byte>();
}
public static string GetFirmwareTypeName()
{
return GetFirmwareType(out var type) ? type.ToString() : "Unbekannt";
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int GetFirmwareEnvironmentVariable(string lpName, string lpGuid, byte[] pBuffer, int nSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetFirmwareType(out FirmwareType firmwareType);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LookupPrivilegeValue(string? systemName, string name, out Luid luid);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool AdjustTokenPrivileges(IntPtr tokenHandle, bool disableAllPrivileges, ref TokenPrivileges newState, uint bufferLength, IntPtr previousState, IntPtr returnLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr handle);
private enum FirmwareType
{
Unknown = 0,
Bios = 1,
Uefi = 2,
Max = 3
}
[StructLayout(LayoutKind.Sequential)]
private struct Luid
{
public uint LowPart;
public int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
private struct TokenPrivileges
{
public uint PrivilegeCount;
public Luid Luid;
public uint Attributes;
}
}
internal static class EfiSignatureParser
{
private static readonly Guid X509Guid = new("A5C059A1-94E4-4AA7-87B5-AB155C2BF072");
private static readonly Guid Sha256Guid = new("C1C41626-504C-4092-ACA9-41F936934328");
public static IEnumerable<SecureBootSignature> Parse(string database, byte[] data, List<string> warnings)
{
var offset = 0;
while (offset + 28 <= data.Length)
{
var type = new Guid(data.AsSpan(offset, 16));
var listSize = ReadUInt32(data, offset + 16);
var headerSize = ReadUInt32(data, offset + 20);
var signatureSize = ReadUInt32(data, offset + 24);
if (listSize < 28 || signatureSize < 16 || offset + listSize > data.Length)
{
warnings.Add($"{database}: Signaturliste bei Offset {offset} ist ungültig.");
yield break;
}
var entriesStart = offset + 28 + (int)headerSize;
var entriesEnd = offset + (int)listSize;
if (entriesStart > entriesEnd)
{
warnings.Add($"{database}: Signaturlisten-Header ist ungültig.");
yield break;
}
for (var entry = entriesStart; entry + signatureSize <= entriesEnd; entry += (int)signatureSize)
{
var payloadStart = entry + 16;
var payloadLength = (int)signatureSize - 16;
var payload = data.AsSpan(payloadStart, payloadLength);
if (type == X509Guid)
{
yield return ParseCertificate(database, payload, warnings);
}
else if (type == Sha256Guid && payloadLength >= 32)
{
yield return new SecureBootSignature(database, "SHA-256", Convert.ToHexString(payload[..32].ToArray()), "", "", "");
}
else
{
yield return new SecureBootSignature(database, ShortGuid(type), $"Rohdaten: {payloadLength} Bytes", "", "", "");
}
}
offset += (int)listSize;
}
}
private static SecureBootSignature ParseCertificate(string database, ReadOnlySpan<byte> payload, List<string> warnings)
{
try
{
var cert = new X509Certificate2(payload.ToArray());
return new SecureBootSignature(
database,
"X.509",
cert.GetNameInfo(X509NameType.SimpleName, false),
cert.GetNameInfo(X509NameType.SimpleName, true),
cert.NotAfter.ToString("yyyy-MM-dd"),
cert.Thumbprint ?? "");
}
catch (Exception ex)
{
warnings.Add($"{database}: X.509-Zertifikat konnte nicht gelesen werden ({ex.Message}).");
return new SecureBootSignature(database, "X.509", $"Nicht dekodierbar: {payload.Length} Bytes", "", "", "");
}
}
private static uint ReadUInt32(byte[] data, int offset) => BitConverter.ToUInt32(data, offset);
private static string ShortGuid(Guid guid) => guid.ToString("D")[..8];
}