Skip to content

Commit 472115f

Browse files
committed
feat: Administrator Protection (SMAA) readiness — resolve real user SID for HKCU and LocalAppData
1 parent 38daaa7 commit 472115f

3 files changed

Lines changed: 121 additions & 3 deletions

File tree

src/DeepPurge.Core/App/DataPaths.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private static string ResolveRoot()
7272
Directory.CreateDirectory(dir);
7373
return dir;
7474
}
75-
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
75+
var appData = UserIdentity.RealLocalAppData;
7676
var root = Path.Combine(appData, "DeepPurge");
7777
Directory.CreateDirectory(root);
7878
return root;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System.Security.Principal;
2+
using DeepPurge.Core.Diagnostics;
3+
4+
namespace DeepPurge.Core.App;
5+
6+
public static class UserIdentity
7+
{
8+
private static readonly Lazy<string> _realUserSid = new(ResolveRealUserSid);
9+
private static readonly Lazy<string> _realLocalAppData = new(ResolveRealLocalAppData);
10+
private static readonly Lazy<bool> _isSmaaElevated = new(DetectSmaaElevation);
11+
12+
public static string RealUserSid => _realUserSid.Value;
13+
public static string RealLocalAppData => _realLocalAppData.Value;
14+
public static bool IsSmaaElevated => _isSmaaElevated.Value;
15+
16+
private static bool DetectSmaaElevation()
17+
{
18+
try
19+
{
20+
var currentIdentity = WindowsIdentity.GetCurrent();
21+
var currentSid = currentIdentity.User?.Value;
22+
if (string.IsNullOrEmpty(currentSid)) return false;
23+
24+
var consoleSid = GetConsoleSessionUserSid();
25+
if (string.IsNullOrEmpty(consoleSid)) return false;
26+
27+
return !string.Equals(currentSid, consoleSid, StringComparison.OrdinalIgnoreCase);
28+
}
29+
catch { return false; }
30+
}
31+
32+
private static string ResolveRealUserSid()
33+
{
34+
try
35+
{
36+
var consoleSid = GetConsoleSessionUserSid();
37+
if (!string.IsNullOrEmpty(consoleSid)) return consoleSid;
38+
}
39+
catch (Exception ex) { Log.Warn($"Console SID resolution failed: {ex.Message}"); }
40+
41+
try { return WindowsIdentity.GetCurrent().User?.Value ?? ""; }
42+
catch { return ""; }
43+
}
44+
45+
private static string ResolveRealLocalAppData()
46+
{
47+
var fallback = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
48+
if (!IsSmaaElevated) return fallback;
49+
50+
try
51+
{
52+
var sid = RealUserSid;
53+
if (string.IsNullOrEmpty(sid)) return fallback;
54+
55+
using var profileKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
56+
$@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\{sid}");
57+
var profilePath = profileKey?.GetValue("ProfileImagePath") as string;
58+
if (!string.IsNullOrEmpty(profilePath) && Directory.Exists(profilePath))
59+
{
60+
var localAppData = Path.Combine(profilePath, "AppData", "Local");
61+
if (Directory.Exists(localAppData)) return localAppData;
62+
}
63+
}
64+
catch (Exception ex) { Log.Warn($"SMAA LocalAppData resolution failed: {ex.Message}"); }
65+
66+
return fallback;
67+
}
68+
69+
private static string? GetConsoleSessionUserSid()
70+
{
71+
try
72+
{
73+
var explorerProcesses = System.Diagnostics.Process.GetProcessesByName("explorer");
74+
if (explorerProcesses.Length == 0) return null;
75+
76+
foreach (var explorer in explorerProcesses)
77+
{
78+
try
79+
{
80+
var handle = explorer.Handle;
81+
if (!OpenProcessToken(handle, TOKEN_QUERY, out var tokenHandle)) continue;
82+
try
83+
{
84+
using var identity = new WindowsIdentity(tokenHandle);
85+
return identity.User?.Value;
86+
}
87+
finally { CloseHandle(tokenHandle); }
88+
}
89+
catch { continue; }
90+
finally { explorer.Dispose(); }
91+
}
92+
}
93+
catch { }
94+
return null;
95+
}
96+
97+
public static Microsoft.Win32.RegistryKey? OpenRealUserHive(string subKey)
98+
{
99+
var sid = RealUserSid;
100+
if (string.IsNullOrEmpty(sid)) return null;
101+
try
102+
{
103+
return Microsoft.Win32.Registry.Users.OpenSubKey($@"{sid}\{subKey}");
104+
}
105+
catch { return null; }
106+
}
107+
108+
private const uint TOKEN_QUERY = 0x0008;
109+
110+
[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
111+
private static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle);
112+
113+
[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
114+
private static extern bool CloseHandle(IntPtr handle);
115+
}

src/DeepPurge.Core/Registry/InstalledProgramScanner.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using global::Microsoft.Win32;
2+
using DeepPurge.Core.App;
23
using DeepPurge.Core.Models;
34

45
namespace DeepPurge.Core.Registry;
@@ -30,10 +31,12 @@ public static List<InstalledProgram> GetAllInstalledPrograms(bool includeSystemC
3031
catch { /* Access denied or other registry errors */ }
3132
}
3233

33-
// Scan HKCU
34+
// Scan HKCU — use real user's hive when running under SMAA elevation
3435
try
3536
{
36-
using var hkcuKey = global::Microsoft.Win32.Registry.CurrentUser.OpenSubKey(HkcuUninstallPath);
37+
using var hkcuKey = UserIdentity.IsSmaaElevated
38+
? UserIdentity.OpenRealUserHive(HkcuUninstallPath)
39+
: global::Microsoft.Win32.Registry.CurrentUser.OpenSubKey(HkcuUninstallPath);
3740
if (hkcuKey != null)
3841
{
3942
ScanRegistryKey(hkcuKey, HkcuUninstallPath, RegistrySource.HKCU_Uninstall,

0 commit comments

Comments
 (0)