using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.IO.MemoryMappedFiles; using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.InteropServices; using System.ServiceProcess; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; using TrustedUninstaller.Shared.Actions; using TrustedUninstaller.Shared.Parser; using TrustedUninstaller.Shared.Tasks; namespace TrustedUninstaller.Shared { public static class AmeliorationUtil { private static readonly ConfigParser Parser = new ConfigParser(); private static readonly HttpClient Client = new HttpClient(); public static Playbook Playbook { set; get; } public static readonly List ErrorDisplayList = new List(); public static int GetProgressMaximum() { return Parser.Tasks.Sum(task => task.Actions.Sum(action => action.GetProgressWeight())); } public static bool AddTasks(string configPath, string file) { try { //This allows for a proper detection of if any error occurred, and if so the CLI will relay an :AME-Fatal Error: //This is important, as we want the process to stop immediately if a YAML syntax error was detected. bool hadError = false; //Adds the config file to the parser's task list Parser.Add(Path.Combine(configPath, file)); var currentTask = Parser.Tasks[Parser.Tasks.Count - 1]; if (File.Exists("TasksAdded.txt")) { var doneTasks = File.ReadAllText("TasksAdded.txt").Split(new[] { "\r\n" }, StringSplitOptions.None); if (doneTasks.Contains(currentTask.Title)) { Parser.Tasks.Remove(currentTask); return true; } } //Get the features of the last added task (the task that was just added from the config file) var features = currentTask.Features; //Each feature would reference a directory that has a YAML file, we take those directories and then run the //AddTasks function again, until we reach a file that doesn't reference any other YAML files, and add them //all to the parser's tasks list. if (features == null) return true; foreach (var feature in features) { var subResult = AddTasks(configPath, feature); // We could return false here, however we want to output ALL detected YAML errors, // which is why we continue here. if (!subResult) hadError = true; } return hadError ? false : true; } catch (Exception e) { Console.WriteLine($"Error adding tasks in {configPath + "\\" + file}:\r\n{e.Message}"); ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"Error adding tasks in {configPath + "\\" + file}."); return false; } } public static async Task DoActions(UninstallTask task, UninstallTaskPrivilege privilege) { try { //If the privilege is admin and the program is running as TI, do not do the action. if (privilege == UninstallTaskPrivilege.Admin && WinUtil.IsTrustedInstaller()) { return 0; } if (privilege == UninstallTaskPrivilege.TrustedInstaller && !WinUtil.IsTrustedInstaller()) { Console.WriteLine("Relaunching as Trusted Installer!"); var mmf = MemoryMappedFile.CreateNew("ImgA", 5000000); WinUtil.RelaunchAsTrustedInstaller(); if (NativeProcess.Process == null) { ErrorLogger.WriteToErrorLog($"Could not launch TrustedInstaller process. Return output was null.", Environment.StackTrace, "Error while attempting to sync with TrustedInstaller process."); Console.WriteLine(":AME-Fatal Error: Could not launch TrustedInstaller process."); Environment.Exit(-1); } var delay = 20; while (!NativeProcess.Process.HasExited) { if (delay > 3500) { NativeProcess.Process.Kill(); ErrorLogger.WriteToErrorLog($"Could not initialize memory data exchange. Timeframe exceeded.", Environment.StackTrace, "Error while attempting to sync with TrustedInstaller process."); Console.WriteLine(":AME-Fatal Error: Could not initialize memory data exchange."); Environment.Exit(-1); } Task.Delay(delay).Wait(); // Kind of inefficient looping this, however it's likely to cause access errors otherwise using var stream = mmf.CreateViewStream(); using BinaryReader binReader = new BinaryReader(stream); { var res = binReader.ReadBytes((int)stream.Length); var data = Encoding.UTF8.GetString(res); var end = data.IndexOf('\0'); if (end == 0) { delay += 200; } else { break; } } } var offset = 0; var read = false; using (var stream = mmf.CreateViewStream()) { while (!NativeProcess.Process.HasExited || read) { read = false; BinaryReader binReader = new BinaryReader(stream); binReader.BaseStream.Seek(offset, SeekOrigin.Begin); var res = binReader.ReadBytes((int)stream.Length - offset); var data = Encoding.UTF8.GetString(res); var end = data.IndexOf("\0"); var content = data.Substring(0, end); offset += Encoding.UTF8.GetBytes(content).Length; var output = content.Split(new [] {Environment.NewLine}, StringSplitOptions.None); if (output.Length > 0) output = output.Take(output.Length - 1).ToArray(); foreach (var line in output) { Console.WriteLine(line); read = true; // Introducing ANY delay here makes it lag behind, which isn't ideal //Task.Delay(5).Wait(); } Task.Delay(20).Wait(); } } mmf.Dispose(); return 0; //Only returns after TI is done } //Goes through the list of tasks that are inside the parser class, //and runs the task using the RunTask method //Check the Actions folder inside the Shared folder for reference. foreach (ITaskAction action in task.Actions) { int i = 0; //var actionType = action.GetType().ToString().Replace("TrustedUninstaller.Shared.Actions.", ""); try { do { //Console.WriteLine($"Running {actionType}"); Console.WriteLine(); try { await action.RunTask(); action.ResetProgress(); } catch (Exception e) { action.ResetProgress(); if (e.InnerException != null) { ErrorLogger.WriteToErrorLog(e.InnerException.Message, e.InnerException.StackTrace, e.Message); } else { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, action.ErrorString()); List ExceptionBreakList = new List() { "System.ArgumentException", "System.SecurityException", "System.UnauthorizedAccessException", "System.UnauthorizedAccessException", "System.TimeoutException" }; if (ExceptionBreakList.Any(x => x.Equals(e.GetType().ToString()))) { i = 10; break; } } } Console.WriteLine($"Status: {action.GetStatus()}"); if (i > 0) Thread.Sleep(50); i++; } while (action.GetStatus() != UninstallTaskStatus.Completed && i < 10); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "Critical error while running action."); Console.WriteLine($":AME-ERROR: Critical error while running action: " + e.Message); } if (i == 10) { var errorString = action.ErrorString(); ErrorLogger.WriteToErrorLog(errorString, Environment.StackTrace, "Action failed to complete."); // AmeliorationUtil.ErrorDisplayList.Add(errorString) would NOT work here since this // might be a separate process, and thus has to be forwarded via the console Console.WriteLine($":AME-ERROR: {errorString}"); //Environment.Exit(-2); Console.WriteLine($"Action completed. Weight:{action.GetProgressWeight()}"); continue; } Console.WriteLine($"Action completed. Weight:{action.GetProgressWeight()}"); } Console.WriteLine("Task completed."); File.AppendAllText("TasksAdded.txt", task.Title + Environment.NewLine); } catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "Encountered an error while doing task actions."); } return 0; } public static Task DeserializePlaybook(string dir) { Playbook pb; XmlSerializer serializer = new XmlSerializer(typeof(Playbook)); using (XmlReader reader = XmlReader.Create($"{dir}\\playbook.conf")) { pb = (Playbook)serializer.Deserialize(reader); } pb.Path = dir; return Task.FromResult(pb); } public static async Task StartAmelioration() { //Needed after defender removal's reboot, the "current directory" will be set to System32 //After the auto start up. Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); if (File.Exists("TasksAdded.txt") && !WinUtil.IsTrustedInstaller()) { File.Delete("TasksAdded.txt"); } if (Directory.Exists("Logs") && !WinUtil.IsTrustedInstaller()) { if (File.Exists("Logs\\AdminOutput.txt")) { File.Delete("Logs\\AdminOutput.txt"); } if (File.Exists("Logs\\TIOutput.txt")) { File.Delete("Logs\\TIOutput.txt"); } if (File.Exists("Logs\\FileChecklist.txt")) { File.Delete("Logs\\FileChecklist.txt"); } } //Check if KPH is installed. ServiceController service = ServiceController.GetDevices() .FirstOrDefault(s => s.DisplayName == "KProcessHacker2"); if (service == null) { //Installs KPH await WinUtil.RemoveProtectionAsync(); } var langsFile = Path.Combine($"{Playbook.Path}\\Configuration", "langs.txt"); //Download language packs that were selected by the user if (!File.Exists(langsFile)) { File.Create(langsFile); } //var langsSelected = File.ReadLines(langsFile); //await DownloadLanguagesAsync(langsSelected); //Start adding tasks from the top level configuration folder. if (!AddTasks($"{Playbook.Path}\\Configuration", "custom.yml")) { Console.WriteLine($":AME-Fatal Error: Error adding tasks."); Environment.Exit(1); } if (!Parser.Tasks.Any()) { Console.Error.WriteLine($"Couldn't find any tasks."); return -1; } //Sort the list based on the priority value. if (Parser.Tasks.Any(x => x.Priority != Parser.Tasks.First().Priority)) Parser.Tasks.Sort(new TaskComparer()); UninstallTaskPrivilege prevPriv = UninstallTaskPrivilege.Admin; foreach (var task in Parser.Tasks.Where(task => task.Actions.Count != 0)) { try { if (prevPriv == UninstallTaskPrivilege.TrustedInstaller && task.Privilege == UninstallTaskPrivilege.TrustedInstaller && !WinUtil.IsTrustedInstaller()) { continue; } await DoActions(task, task.Privilege); prevPriv = task.Privilege; } catch (Exception ex) { ErrorLogger.WriteToErrorLog(ex.Message, ex.StackTrace, "Error during DoAction loop."); } } if (WinUtil.IsTrustedInstaller()) return 0; WinUtil.RegistryManager.UnhookUserHives(); //Check how many files were successfully and unsuccessfully deleted. var deletedItemsCount = 0; var failedDeletedItemsCount = 0; if (File.Exists("Logs\\FileChecklist.txt")) { using (var reader = new StreamReader("Logs\\FileChecklist.txt")) { var data = reader.ReadToEnd(); var listData = data.Split(new [] { Environment.NewLine }, StringSplitOptions.None).ToList(); deletedItemsCount = listData.FindAll(s => s == "Deleted: True").Count(); failedDeletedItemsCount = listData.FindAll(s => s == "Deleted: False").Count(); } using (var writer = new StreamWriter("Logs\\FileChecklist.txt", true)) { writer.WriteLine($"{deletedItemsCount} files were deleted successfully. " + $"{failedDeletedItemsCount} files couldn't be deleted."); } } Console.WriteLine($"{deletedItemsCount} files were deleted successfully. " + $"{failedDeletedItemsCount} files couldn't be deleted."); //Check if the kernel driver is installed. //service = ServiceController.GetDevices() //.FirstOrDefault(s => s.DisplayName == "KProcessHacker2"); if (true) { //Remove Process Hacker's kernel driver. await WinUtil.UninstallDriver(); } await AmeliorationUtil.SafeRunAction(new RegistryKeyAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\KProcessHacker2", }); File.Delete("TasksAdded.txt"); Console.WriteLine(); Console.WriteLine("Playbook finished."); return 0; } public static async Task DownloadLanguagesAsync(IEnumerable langsSelected) { foreach (var lang in langsSelected) { var lowerLang = lang.ToLower(); var arch = RuntimeInformation.OSArchitecture; var winVersion = Environment.OSVersion.Version.Build; var convertedArch = ""; switch (arch) { case Architecture.X64: convertedArch = "amd64"; break; case Architecture.Arm64: convertedArch = "arm64"; break; case Architecture.X86: convertedArch = "x86"; break; } var uuidOfWindowsVersion = ""; var uuidResponse = await Client.GetAsync( $"https://api.uupdump.net/listid.php?search={winVersion}%20{convertedArch}&sortByDate=1"); switch (uuidResponse.StatusCode) { //200 Status code case HttpStatusCode.OK: { var result = uuidResponse.Content.ReadAsStringAsync().Result; //Gets the UUID of the first build object in the response, we take the first since it's the newest. uuidOfWindowsVersion = (string)(JToken.Parse(result)["response"]?["builds"]?.Children().First() .Children().First().Last()); break; } //400 Status code case HttpStatusCode.BadRequest: { var result = uuidResponse.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Bad request.\r\nError:{data["response"]["error"]}"); break; } //429 Status code case (HttpStatusCode)429: { var result = uuidResponse.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Too many requests, try again later.\r\nError:{data["response"]["error"]}"); break; } //500 Status code case HttpStatusCode.InternalServerError: { var result = uuidResponse.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Internal Server Error.\r\nError:{data["response"]["error"]}"); break; } default: throw new ArgumentOutOfRangeException(); } var responseString = await Client.GetAsync( $"https://api.uupdump.net/get.php?id={uuidOfWindowsVersion}&lang={lowerLang}"); switch (responseString.StatusCode) { //200 Status code case HttpStatusCode.OK: { var result = responseString.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); //Add different urls to different packages to a list var urls = new Dictionary { { "basic", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-basic-{lowerLang}-package-{convertedArch}.cab"] [ "url"] }, { "hw", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-handwriting-{lowerLang}-package-{convertedArch}.cab"] [ "url"] }, { "ocr", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-ocr-{lowerLang}-package-{convertedArch}.cab"][ "url"] }, { "speech", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-speech-{lowerLang}-package-{convertedArch}.cab"] [ "url"] }, { "tts", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-texttospeech-{lowerLang}-package-{convertedArch}.cab"] [ "url"] } }; var amePath = Path.Combine(Path.GetTempPath(), "AME\\"); //Create the directory if it doesn't exist. var file = new FileInfo(amePath); file.Directory?.Create(); //Does nothing if the directory already exists //Final result being "temp\AME\Languages\file.cab" var downloadPath = Path.Combine(amePath, "Languages\\"); file = new FileInfo(downloadPath); file.Directory?.Create(); using (var webClient = new WebClient()) { Console.WriteLine($"Downloading {lowerLang}.cab file, please wait.."); foreach (var url in urls) { //Check if the file exists, if it does exist, skip it. if (File.Exists(Path.Combine(downloadPath, $"{url.Key}_{lowerLang}.cab"))) { Console.WriteLine($"{url.Key}_{lowerLang} already exists, skipping."); continue; } //Output file format: featureName_languageCode.cab: speech_de-de.cab webClient.DownloadFile(url.Value, $@"{downloadPath}\{url.Key}_{lowerLang}.cab"); } } break; } //400 Status code case HttpStatusCode.BadRequest: { var result = responseString.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Bad request.\r\nError:{data["response"]["error"]}"); break; } //429 Status code case (HttpStatusCode)429: { var result = responseString.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Too many requests, try again later.\r\nError:{data["response"]["error"]}"); break; } //500 Status code case HttpStatusCode.InternalServerError: { var result = responseString.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Internal Server Error.\r\nError:{data["response"]["error"]}"); break; } default: throw new ArgumentOutOfRangeException(); } } } public static async Task SafeRunAction(ITaskAction action) { try { return await action.RunTask(); } catch (Exception e) { action.ResetProgress(); if (e.InnerException != null) { ErrorLogger.WriteToErrorLog(e.InnerException.Message, e.InnerException.StackTrace, e.Message); } else { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, action.ErrorString()); } } return false; } } }