From 1f909256af0a98d86d17ff8028e7a2238411b3de Mon Sep 17 00:00:00 2001 From: NaolShow Date: Thu, 30 Apr 2020 23:28:56 +0200 Subject: [PATCH] First Trou commit ! Please note that I am currently writing the whole wiki. It will take some time, so be patient :) And contact me on discord if you have any questions! --- .gitignore | 547 ++++++++++++++++ README.md | 97 ++- Trou.sln | 31 + Trou/.editorconfig | 4 + Trou/Services/PrivoxyProxy.cs | 137 +++++ Trou/Services/TorController.cs | 202 ++++++ Trou/Services/TorProxy.cs | 350 +++++++++++ Trou/ServicesSettings/PrivoxyProxySettings.cs | 88 +++ .../ServicesSettings/TorControllerSettings.cs | 34 + Trou/ServicesSettings/TorProxySettings.cs | 201 ++++++ Trou/Tools/ChildProcessTracker.cs | 135 ++++ Trou/Tools/HiddenProcess.cs | 353 +++++++++++ Trou/Trou.csproj | 35 ++ Trou/Trou.xml | 582 ++++++++++++++++++ Trou/TrouProxy.cs | 78 +++ TrouExample/Program.cs | 51 ++ TrouExample/TrouExample.csproj | 12 + logo.png | Bin 0 -> 35204 bytes 18 files changed, 2935 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 Trou.sln create mode 100644 Trou/.editorconfig create mode 100644 Trou/Services/PrivoxyProxy.cs create mode 100644 Trou/Services/TorController.cs create mode 100644 Trou/Services/TorProxy.cs create mode 100644 Trou/ServicesSettings/PrivoxyProxySettings.cs create mode 100644 Trou/ServicesSettings/TorControllerSettings.cs create mode 100644 Trou/ServicesSettings/TorProxySettings.cs create mode 100644 Trou/Tools/ChildProcessTracker.cs create mode 100644 Trou/Tools/HiddenProcess.cs create mode 100644 Trou/Trou.csproj create mode 100644 Trou/Trou.xml create mode 100644 Trou/TrouProxy.cs create mode 100644 TrouExample/Program.cs create mode 100644 TrouExample/TrouExample.csproj create mode 100644 logo.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f604126 --- /dev/null +++ b/.gitignore @@ -0,0 +1,547 @@ + +# Created by https://www.gitignore.io/api/csharp,windows,dotnetcore,visualstudio +# Edit at https://www.gitignore.io/?templates=csharp,windows,dotnetcore,visualstudio + +### Csharp ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +### DotnetCore ### +# .NET Core build folders +/bin +/obj + +# Common node modules locations +/node_modules +/wwwroot/node_modules + + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### VisualStudio ### + +# User-specific files + +# User-specific files (MonoDevelop/Xamarin Studio) + +# Mono auto generated files + +# Build results + +# Visual Studio 2015/2017 cache/options directory +# Uncomment if you have tasks that create the project's static files in wwwroot + +# Visual Studio 2017 auto generated files + +# MSTest test Results + +# NUnit + +# Build Results of an ATL Project + +# Benchmark Results + +# .NET Core + +# StyleCop + +# Files built by Visual Studio + +# Chutzpah Test files + +# Visual C++ cache files + +# Visual Studio profiler + +# Visual Studio Trace Files + +# TFS 2012 Local Workspace + +# Guidance Automation Toolkit + +# ReSharper is a .NET coding add-in + +# JustCode is a .NET coding add-in + +# TeamCity is a build add-in + +# DotCover is a Code Coverage Tool + +# AxoCover is a Code Coverage Tool + +# Visual Studio code coverage results + +# NCrunch + +# MightyMoose + +# Web workbench (sass) + +# Installshield output folder + +# DocProject is a documentation generator add-in + +# Click-Once directory + +# Publish Web Output +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted + +# NuGet Packages +# NuGet Symbol Packages +# The packages folder can be ignored because of Package Restore +# except build/, which is used as an MSBuild target. +# Uncomment if necessary however generally it will be regenerated when needed +# NuGet v3's project.json files produces more ignorable files + +# Microsoft Azure Build Output + +# Microsoft Azure Emulator + +# Windows Store app package directories and files + +# Visual Studio cache files +# files ending in .cache can be ignored +# but keep track of directories ending in .cache + +# Others + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) + +# RIA/Silverlight projects + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) + +# SQL Server files + +# Business Intelligence projects + +# Microsoft Fakes + +# GhostDoc plugin setting file + +# Node.js Tools for Visual Studio + +# Visual Studio 6 build log + +# Visual Studio 6 workspace options file + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) + +# Visual Studio LightSwitch build output + +# Paket dependency manager + +# FAKE - F# Make + +# CodeRush personal settings + +# Python Tools for Visual Studio (PTVS) + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio + +# Telerik's JustMock configuration file + +# BizTalk build output + +# OpenCover UI analysis results + +# Azure Stream Analytics local run output + +# MSBuild Binary and Structured Log + +# NVidia Nsight GPU debugger configuration file + +# MFractors (Xamarin productivity tool) working folder + +# Local History for Visual Studio + +# BeatPulse healthcheck temp database + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 + +# End of https://www.gitignore.io/api/csharp,windows,dotnetcore,visualstudio \ No newline at end of file diff --git a/README.md b/README.md index 0affa43..bbe0165 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,95 @@ -# Trou -Tor + Privoxy for the best anonymous HTTP proxy implementation on C# +# +--- + +> Join **ToWolf** server to get latest news about **Trou** - https://discord.gg/m7CZ6md + +> Tor + Privoxy for the best anonymous HTTP proxy implementation on C# + +Trou is a complete Tor (and Tor Controller) and Privoxy implementation on C# +You can use every services separately or combined to have a local anonymous proxy ! + +**You could use Trou with**: +- HttpClients +- WebClients +- WebBrowsers +- (and everything that support HTTP or Socks5 proxy..) + +## Compatibility + +**Trou is made using .NET Core 3.1 and it's working currently working on:** +- Windows 8 and higher (you could go up to windows 7, just check the project wiki) + +/!\ **Linux and Mac OS compatibility is planned** /!\ + +# :rocket: Quick example +--- + +Here's a quick example on how to use Trou. +This example is very minimalist, and it doesn't even care about errors/warnings/exceptions.. + +You can also get this [example project](https://github.com/NaolShow/Trou/blob/master/TrouExample) + +```cs + +// - Instantiate Trou proxy + +TrouProxy proxy = new TrouProxy(new TorProxySettings() { + TorBundlePath = @"C:\AnyFolder\TorProxy" +}, new TorControllerSettings() { + +}, new PrivoxyProxySettings() { + PrivoxyBundlePath = @"C:\AnyFolder\PrivoxyProxy" +}); + +// - Start + +proxy.Start(); + +// - Check IP + +// Create client connected to Tor using Trou +WebClient client = new WebClient() { + Proxy = new WebProxy("127.0.0.1:8118") +}; + +// Write TOR IP address +Console.WriteLine(client.DownloadString("http://api.ipify.org")); + +// - Stop +Console.ReadLine(); +client.Dispose(); +proxy.Dispose(); + +``` + +# :books: Documentation +--- + +**The complete Trou documentation [can be found here](https://github.com/NaolShow/Trou/wiki)** + +# :question: Help +--- + +If you need help, or if you want to contact me in general, just make a github issue ticket ! +You can also contact me on my discord server or in private messages: [NaolShow#7243](#) + +# :wrench: Installation +--- + +You have two ways to install Trou, the first one is by far the most simplest one: +``` +// With the package manager (Nuget) +PM> Install-Package Trou + +// With .NET CLI +dotnet add package Trou +``` +You can also go in your project top bar menu in visual studio > Manage Nuget packages > Search for "Trou" > Install + +The second way is to go in the [release](https://github.com/NaolShow/Trou/releases) tab in the github project, +and download the last .dll, and then just reference it in your project! + +# :newspaper: Licence +--- + +Distributed under the GNU General Public Licence v3.0. See LICENSE for more information. \ No newline at end of file diff --git a/Trou.sln b/Trou.sln new file mode 100644 index 0000000..1a949bd --- /dev/null +++ b/Trou.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29519.87 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Trou", "Trou\Trou.csproj", "{818BC883-C5C2-4638-844F-48A68CEECE8D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrouExample", "TrouExample\TrouExample.csproj", "{D5124A10-86EE-4904-B266-88F3A4AAD32A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {818BC883-C5C2-4638-844F-48A68CEECE8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {818BC883-C5C2-4638-844F-48A68CEECE8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {818BC883-C5C2-4638-844F-48A68CEECE8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {818BC883-C5C2-4638-844F-48A68CEECE8D}.Release|Any CPU.Build.0 = Release|Any CPU + {D5124A10-86EE-4904-B266-88F3A4AAD32A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5124A10-86EE-4904-B266-88F3A4AAD32A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5124A10-86EE-4904-B266-88F3A4AAD32A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5124A10-86EE-4904-B266-88F3A4AAD32A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6C833C61-2A95-4808-A4FD-C44E78365717} + EndGlobalSection +EndGlobal diff --git a/Trou/.editorconfig b/Trou/.editorconfig new file mode 100644 index 0000000..8f0432e --- /dev/null +++ b/Trou/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1591.severity = none diff --git a/Trou/Services/PrivoxyProxy.cs b/Trou/Services/PrivoxyProxy.cs new file mode 100644 index 0000000..492da58 --- /dev/null +++ b/Trou/Services/PrivoxyProxy.cs @@ -0,0 +1,137 @@ +using System; +using System.IO; +using System.Linq; +using Trou.ServicesSettings; +using Trou.Tools; + +namespace Trou.Services { + + /// + /// Allows to start a which will listen to an IP and a Port for HTTP requests + /// + public class PrivoxyProxy : IDisposable { + + #region Variables + + #region PrivoxyProxy + + /// + /// hidden process + /// + public HiddenProcess Process { get; private set; } + /// + /// process executable path + /// + public string PrivoxyExe { get; private set; } + + /// + /// settings + /// + public PrivoxyProxySettings Settings { get; set; } + + #endregion + + #endregion + + #region Constructors + + /// + /// Initializes a new intance of the service + /// + public PrivoxyProxy(PrivoxyProxySettings settings) { + + // Save + Settings = settings; + + } + + #endregion + + /// + /// Refreshes every Arguments and Configuration keys from the before starting the process + /// + private void RefreshArguments() { + + #region Paths + + // Privoxy executable path + PrivoxyExe = Settings.PrivoxyFile ?? Path.Combine(Settings.PrivoxyBundlePath, "privoxy.exe"); + + #endregion + + #region Network + + // Listening address & port + Settings.Configuration["listen-address"] = string.Format("{0}:{1}", Settings.ListeningAddress, Settings.ListeningPort); + + #region Forward + + // Forward to SOCKS 5 proxy + Settings.Configuration["forward-socks5t"] = string.Format("/ {0}:{1} .", Settings.ForwardAddress, Settings.ForwardPort); + + #endregion + + #endregion + + #region Arguments + + // Set process as not a daemon + Settings.Arguments["--no-daemon"] = string.Empty; + + // Set configuration file in arguments + Settings.Arguments["configfile"] = Path.Combine(Path.GetDirectoryName(PrivoxyExe), "trou_privoxy_config"); + + #endregion + + } + + #region Start & Stop + + /// + /// Starts the service + /// + public void Start() { + + // Privoxy process is already started + if (Process != null) { + + // Stop Privoxy process + Dispose(); + + } + + // Refresh arguments and get them as a single string + RefreshArguments(); + string arguments = string.Join(" ", Settings.Arguments.ToList().Select(a => string.Format("{0} {1}", a.Key, a.Value))); + + // Write the configuration file + File.WriteAllText(Path.Combine(Path.GetDirectoryName(PrivoxyExe), "trou_privoxy_config"), string.Join(Environment.NewLine, Settings.Configuration.ToList().Select(a => string.Format("{0} {1}", a.Key, a.Value)))); + + // Create hidden process + Process = new HiddenProcess(Path.GetDirectoryName(PrivoxyExe), PrivoxyExe, arguments, HiddenProcess.PriorityClass.NORMAL_PRIORITY_CLASS, true); + + // Start hidden process + if (!Process.Start()) { + + // Throw exception + throw new Exception("start_failed: Privoxy process start failed"); + + } + + } + + /// + /// Dispose the service + /// + public void Dispose() { + + // Dispose process + Process?.Dispose(); + + } + + #endregion + + } + +} diff --git a/Trou/Services/TorController.cs b/Trou/Services/TorController.cs new file mode 100644 index 0000000..28f622f --- /dev/null +++ b/Trou/Services/TorController.cs @@ -0,0 +1,202 @@ +using System; +using System.IO; +using System.Net.Sockets; +using System.Text; +using Trou.ServicesSettings; + +namespace Trou.Services { + + /// + /// Allows to start a which can interact with a + /// + public class TorController : IDisposable { + + #region Variables + + /// + /// settings + /// + public TorControllerSettings Settings { get; set; } + + #region TCPClient + + /// + /// tcp client + /// + private TcpClient ControlClient; + + /// + /// writer + /// + private StreamWriter ControlWriter; + /// + /// reader + /// + private StreamReader ControlReader; + + #endregion + + #endregion + + #region Constructors + + /// + /// Initializes a new intance of the service + /// + public TorController(TorControllerSettings settings) { + + // Save + Settings = settings; + + } + + #endregion + + /// + /// Refresh the circuit of (changes every nodes so get new IP Address)
+ /// Returns true if the refresh was successful + ///
+ public bool RefreshCircuit() { + return Send("SIGNAL NEWNYM"); + } + + /// + /// Authenticates to
+ /// Returns true if the authentication was successful + ///
+ public bool Authenticate() { + return Send(string.Format("AUTHENTICATE \"{0}\"", Settings.ControlPassword)); + } + + #region Quit, Shutdown, Dormant/Active + + /// + /// Quit/Log-Off from
+ /// Returns true if the log-off was successful + ///
+ public bool Quit() { + return Send("QUIT"); + } + + /// + /// Stops completely the
+ /// Returns true if the shutdown was successful + ///
+ public bool Shutdown() { + return Send("SIGNAL SHUTDOWN"); + } + + /// + /// Sets to a "sleep mode"
+ /// Returns true if the sleep mode set was successful + ///
+ public bool Dormant() { + return Send("SIGNAL DORMANT"); + } + + /// + /// Removes the "sleep mode" (Dormant) from + /// Returns true if the sleep mode remove was successful + /// + public bool Active() { + return Send("SIGNAL ACTIVE"); + } + + #endregion + + #region TcpClient + + /// + /// Sends a text message to
+ /// Returns true if the sending was successful + ///
+ public bool Send(string text) { + + try { + + // Send text to tor controller + ControlWriter.WriteLine(text); + ControlWriter.Flush(); + + // Return response state (valid or not) + return (ControlReader.ReadLine()).Contains("250"); + + } catch { return false; } + + } + + #endregion + + #region Start & Stop + + /// + /// Starts the service + /// + public void Start() { + + // Control client is already started + if (ControlClient != null) { + + // Stop TorController + Dispose(); + + } + + // Create ControlClient + ControlClient = new TcpClient(); + + // Connect the client to Tor and check if the connection was successful + ControlClient.Connect(Settings.TorAddress, Settings.TorPort); + if (ControlClient.Connected) { + + // Save network stream + NetworkStream stream = ControlClient.GetStream(); + + // Create stream writer & reader + ControlWriter = new StreamWriter(stream, Encoding.ASCII, 4096, true); + ControlReader = new StreamReader(stream, Encoding.ASCII, false, 4096, true); + + // If the authentification to Tor didn't work (due to wrong password) + if (!Authenticate()) { + + // Throw exception + throw new Exception("authentification_failed: Control client authentification failed"); + + } + + } else { + + // Throw exception + throw new Exception("connection_failed: Control client connection to Tor service failed"); + + } + + + } + + /// + /// Dispose the service + /// + public void Dispose() { + + // If ControlClient is connected + if (ControlClient != null && ControlClient.Connected) { + + // Quit Tor connection + Quit(); + + } + + // Dispose client & streams + ControlWriter?.Dispose(); + ControlReader?.Dispose(); + + ControlClient?.Dispose(); + + } + + #endregion + + } + +} diff --git a/Trou/Services/TorProxy.cs b/Trou/Services/TorProxy.cs new file mode 100644 index 0000000..0301589 --- /dev/null +++ b/Trou/Services/TorProxy.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using Trou.ServicesSettings; +using Trou.Tools; + +namespace Trou.Services { + + /// + /// Allows to start a which will listen to an IP and a Port for SOCKS 5 requests + /// + public class TorProxy : IDisposable { + + #region Variables + + #region TorProxy + + /// + /// hidden process + /// + public HiddenProcess Process { get; private set; } + /// + /// process executable path + /// + public string TorExe { get; private set; } + + /// + /// settings + /// + public TorProxySettings Settings { get; set; } + + #endregion + + #region Events + + /// + /// Triggered when process output informations + /// + public event EventHandler OnOutput; + /// + /// Triggered when process output warns or errors + /// + public event EventHandler OnErrorOutput; + + #endregion + + #endregion + + #region Constructors + + /// + /// Initializes a new intance of the service + /// + public TorProxy(TorProxySettings settings) { + + // Save + Settings = settings; + + } + + #endregion + + /// + /// Refreshes every Arguments from the before starting the process + /// + private void RefreshArguments() { + + #region Paths + + // Tor executable path + TorExe = Settings.TorFile ?? Path.Combine(Settings.TorBundlePath, "Tor", "tor.exe"); + // Tor cache path + SetPathArgument("CacheDirectory", Settings.CachePath); + + #region GeoIPFiles + + // Geo IP File (IPV4) + SetPathArgument("GeoIPFile", Settings.GeoIPFile ?? Path.Combine(Settings.TorBundlePath, "Data", "Tor", "geoip")); + // Geo IPv6 File (IPV6) + SetPathArgument("GeoIPv6File", Settings.GeoIPv6File ?? Path.Combine(Settings.TorBundlePath, "Data", "Tor", "geoip6")); + + #endregion + + #endregion + + #region Network + + // Listening address & port + SetArgument("SocksPort", string.Format("{0}:{1}", Settings.ListeningAddress, Settings.ListeningPort)); + + #region Controller + + // Listening address & port + SetArgument("ControlPort", string.Format("{0}:{1}", Settings.ListeningControlAddress, Settings.ListeningControlPort), Settings.UseController); + + // Set the control password, use the hashed one if it has been set, or hash the non hashed one + SetArgument("HashedControlPassword", Settings.HashedControlPassword ?? HashPassword(Settings.ControlPassword), Settings.ControlPassword != null || Settings.HashedControlPassword != null); + + #endregion + + #region Nodes filtering + + // If Tor should respect the nodes filtering + SetArgument("StrictNodes", (Settings.StrictNodes) ? "1" : "0"); + + #region Whitelists + + SetListArgument("ExitNodes", Settings.ExitNodes); + SetListArgument("MiddleNodes", Settings.MiddleNodes); + SetListArgument("EntryNodes", Settings.EntryNodes); + + #endregion + #region Blacklists + + SetListArgument("ExcludeNodes", Settings.ExcludeNodes); + SetListArgument("ExcludeExitNodes", Settings.ExcludeExitNodes); + + #endregion + + #endregion + #region Traffic + + // No traffic to .onion websites + SetToggleArgument("NoOnionTraffic", Settings.NoOnionTraffic); + // Accept traffic only to .onion websites + SetToggleArgument("OnionTrafficOnly", Settings.OnionTrafficOnly); + + #endregion + + #endregion + + } + + /// + /// Triggered when we receive process output that needs to be forwarded to events + /// + private void ForwardOutput(string message) { + + // If message is a normal output + if (message.Contains("[notice]")) { + + // Trigger event + OnOutput?.Invoke(this, message); + + } else { + + // Trigger event + OnErrorOutput?.Invoke(this, message); + + } + + } + + #region Start & Stop + + /// + /// Starts the service + /// + public void Start() { + + // Tor process is already started + if (Process != null) { + + // Stop Tor process + Dispose(); + + } + + // Refresh arguments and get them as a single string + RefreshArguments(); + string arguments = string.Join(" ", Settings.Arguments.ToList().Select(a => string.Format("{0} {1}", a.Key, a.Value))); + + // Create hidden process + Process = new HiddenProcess(Path.GetDirectoryName(TorExe), TorExe, arguments, HiddenProcess.PriorityClass.NORMAL_PRIORITY_CLASS, true); + + // Set output event (forward to ForwardOuput function) + Process.OnOutput += (sender, message) => ForwardOutput(message); + + // Start hidden process + if (Process.Start()) { + + // Process already stopped (this is due to a critical error, like port already used..) + if (Process.Process != null && Process.Process.HasExited) { + + // Throw exception + throw new Exception("start_critical_error: Tor process start failed due to a critical error, check if the port isn't already used"); + + } + + } else { + + // Throw exception + throw new Exception("start_failed: Tor process start failed, try increasing the privileges"); + + } + + } + + /// + /// Dispose the service + /// + public void Dispose() { + + // Dispose process + Process?.Dispose(); + + } + + #endregion + + #region Arguments utility + + /// + /// Sets an argument that can be active or not
+ /// (If the argument isn't active it will be removed) + ///
+ public void SetArgument(string name, string value, bool active = true) { + + // We accept nullable arguments or the value isn't empty + if (active) { + + // Set argument + Settings.Arguments[name] = value; + + } else { + + // Remove argument + Settings.Arguments.Remove(name); + + } + + } + + /// + /// Sets a toggle argument and remove it if the value is false + /// + public void SetToggleArgument(string name, bool value) { + + // We have to set the argument + if (value) { + + // Set argument + Settings.Arguments[name] = string.Empty; + + } else { + + // Clear entry with key + Settings.Arguments.Remove(name); + + } + + } + + /// + /// Sets an argument to a list and remove it if the list is empty + /// + public void SetListArgument(string name, List value) { + + // The list isn't empty + if (value.Count > 0) { + + // Set argument + Settings.Arguments[name] = string.Join(",", value.Select(a => "{" + a + "}")); + + } else { + + // Remove argument + Settings.Arguments.Remove(name); + + } + + + + } + + /// + /// Sets a path argument, throw an exception if the path isn't valid + /// + public void SetPathArgument(string name, string path) { + + // If path isn't null + if (path != null) { + + // Path exists + if (File.Exists(path) || Directory.Exists(path)) { + + // Set argument + Settings.Arguments[name] = path; + + } else { + + // Throw exception + throw new Exception(string.Format("invalid_path: Path '{0}' assigned to '{1}' isn't valid", path, name)); + + } + + } + + } + + #endregion + + /// + /// Hash a password using the process + /// + public string HashPassword(string password) { + + // Password isn't empty + if (!string.IsNullOrEmpty(password)) { + + // Create process runner (the process will stop itself) + HiddenProcess runner = new HiddenProcess(null, TorExe, string.Format("--hash-password \"{0}\"", password), HiddenProcess.PriorityClass.NORMAL_PRIORITY_CLASS, false); + + // Process on output event (get password hash) + string passwordHash = null; + runner.OnOutput += (sender, message) => { + + // If it's the password hash + if (message.StartsWith("16:")) { + + // Save password hash + passwordHash = message; + + } + + }; + + // Start process + runner.Start(); + + // Wait to get the password hash or wait for the end of the timeout + Stopwatch timeout = Stopwatch.StartNew(); + while (timeout.ElapsedMilliseconds < 5000 && passwordHash == null) { Thread.Sleep(500); } + + // Stop process + runner.Dispose(); + + return passwordHash; + + } + return null; + + } + + } + +} diff --git a/Trou/ServicesSettings/PrivoxyProxySettings.cs b/Trou/ServicesSettings/PrivoxyProxySettings.cs new file mode 100644 index 0000000..175249e --- /dev/null +++ b/Trou/ServicesSettings/PrivoxyProxySettings.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using Trou.Services; + +namespace Trou.ServicesSettings { + + /// + /// Allows to configure a + /// + public class PrivoxyProxySettings { + + #region Paths + + /// + /// Path to the extracted Privoxy Bundle folder (folder that contains the privoxy.exe file). This is the fastest way of configuring paths.
+ /// For a more advanced paths configuration, you can set every paths manually (if those paths are set, they will not be recovered from path):
+ /// + ///
+ public string PrivoxyBundlePath { get; set; } = null; + + /// + /// Path to the privoxy.exe file
+ /// If this value isn't set, will try to recover it using
+ /// (Default: null) + ///
+ public string PrivoxyFile { get; set; } = null; + + #endregion + + #region Process Arguments + + /// + /// Arguments that are used when starting the process
+ /// Useful when you want to add arguments that are not natively supported by Trou
+ /// -> List of the supported Privoxy arguments here + ///
+ public readonly Dictionary Arguments = new Dictionary(); + + #endregion + #region Configuration + + /// + /// Configuration pairs that are used when starting the process
+ /// Useful when you want to add configuration pairs that are not natively supported by Trou
+ /// -> List of the supported Tor arguments here + ///
+ public readonly Dictionary Configuration = new Dictionary(); + + #region Network + + /// + /// Address that the is listening at (HTTP)
+ /// -> Documentation here
+ /// (Default: 127.0.0.1) + ///
+ public string ListeningAddress { get; set; } = "127.0.0.1"; + + /// + /// Port that the is listening at (HTTP)
+ /// -> Documentation here
+ /// (Default: 8118) + ///
+ public int ListeningPort { get; set; } = 8118; + + #region Forward + + /// + /// Address that the will forward requests to (SOCKS 5)
+ /// -> Documentation here
+ /// (Default: 127.0.0.1) + ///
+ public string ForwardAddress { get; set; } = "127.0.0.1"; + + /// + /// Port that the will forward requests to (SOCKS 5)
+ /// -> Documentation here
+ /// (Default: 9050) + ///
+ public int ForwardPort { get; set; } = 9050; + + #endregion + + #endregion + + #endregion + + } + +} diff --git a/Trou/ServicesSettings/TorControllerSettings.cs b/Trou/ServicesSettings/TorControllerSettings.cs new file mode 100644 index 0000000..c2cd575 --- /dev/null +++ b/Trou/ServicesSettings/TorControllerSettings.cs @@ -0,0 +1,34 @@ +using Trou.Services; + +namespace Trou.ServicesSettings { + + /// + /// Allows to configure a + /// + public class TorControllerSettings { + + #region Network + + /// + /// Address that the will connect to
+ /// (Default: 127.0.0.1) + ///
+ public string TorAddress { get; set; } = "127.0.0.1"; + + /// + /// Port that the will connect to
+ /// (Default: 9051) + ///
+ public int TorPort { get; set; } = 9051; + + /// + /// Password that the will use to authenticate to the
+ /// (Default: null) + ///
+ public string ControlPassword { get; set; } = null; + + #endregion + + } + +} diff --git a/Trou/ServicesSettings/TorProxySettings.cs b/Trou/ServicesSettings/TorProxySettings.cs new file mode 100644 index 0000000..71b60fa --- /dev/null +++ b/Trou/ServicesSettings/TorProxySettings.cs @@ -0,0 +1,201 @@ +using System.Collections.Generic; +using Trou.Services; + +namespace Trou.ServicesSettings { + + /// + /// Allows to configure a + /// + public class TorProxySettings { + + #region Paths + + /// + /// Path to the extracted Tor Bundle folder (folder that contains the Tor and Data folders). This is the fastest way of configuring paths
+ /// For a more advanced paths configuration, you can set every paths manually (if those paths are set, they will not be recovered from path):
+ /// , , , + ///
+ public string TorBundlePath { get; set; } = null; + + /// + /// Path to the tor.exe file
+ /// If this value isn't set, will try to recover it using
+ /// (Default: null) + ///
+ public string TorFile { get; set; } = null; + + /// + /// Path where the cache will be stored
+ /// If this value isn't set, will use your ApplicationData folder
+ /// -> Documentation here
+ /// (Default: null) + ///
+ public string CachePath { get; set; } = null; + + #region GeoIPFiles + + /// + /// Path to the geoip file (this file is used when using nodes filtering) + /// If this value isn't set, will try to recover it using
+ /// -> Documentation here
+ /// (Default: null) + ///
+ public string GeoIPFile { get; set; } = null; + + /// + /// Path to the geoip6 file (this file is used when using nodes filtering) + /// If this value isn't set, will try to recover it using
+ /// -> Documentation here
+ /// (Default: null) + ///
+ public string GeoIPv6File { get; set; } = null; + + #endregion + + #endregion + + #region Process Arguments + + /// + /// Arguments that are used when starting the process
+ /// Useful when you want to add arguments that are not natively supported by Trou
+ /// -> List of the supported Tor arguments here + ///
+ public readonly Dictionary Arguments = new Dictionary(); + + #region Network + + /// + /// Address that the is listening at (SOCKS 5)
+ /// -> Documentation here
+ /// (Default: 127.0.0.1) + ///
+ public string ListeningAddress { get; set; } = "127.0.0.1"; + + /// + /// Port that the is listening at (SOCKS 5)
+ /// -> Documentation here
+ /// (Default: 9050) + ///
+ public int ListeningPort { get; set; } = 9050; + + #region Controller + + /// + /// Determines if should listen to
+ /// (Default: true) + ///
+ public bool UseController { get; set; } = true; + + /// + /// Address that the is listening at (for the )
+ /// -> Documentation here
+ /// (Default: 127.0.0.1) + ///
+ public string ListeningControlAddress { get; set; } = "127.0.0.1"; + + /// + /// Port that the is listening at (for the )
+ /// -> Documentation here
+ /// (Default: 9051) + ///
+ public int ListeningControlPort { get; set; } = 9051; + + #region Password + + /// + /// Password that is required for the when he wants to control the
+ /// If the is set, will ignore
+ /// -> Documentation here
+ /// (Default: null) + ///
+ public string ControlPassword { get; set; } = null; + + /// + /// Password that is required for the when he wants to control the
+ /// If the is set, will ignore
+ /// -> Documentation here
+ /// (Default: null) + ///
+ public string HashedControlPassword { get; set; } = null; + + #endregion + + #endregion + + #region Nodes filtering + + /// + /// Determines if should strictly respect the nodes filtering
+ /// (Default: true) + ///
+ public bool StrictNodes { get; set; } = false; + + #region Whitelists + + /// + /// List of accepted exit nodes
+ /// -> Documentation here
+ /// (Default: empty) + ///
+ public readonly List ExitNodes = new List(); + + /// + /// List of accepted middle nodes
+ /// -> Documentation here
+ /// (Default: empty) + ///
+ public readonly List MiddleNodes = new List(); + + /// + /// List of accepted entry nodes
+ /// -> Documentation here
+ /// (Default: empty) + ///
+ public readonly List EntryNodes = new List(); + + #endregion + #region Blacklists + + /// + /// List of non-accepted nodes
+ /// -> Documentation here
+ /// (Default: empty) + ///
+ public readonly List ExcludeNodes = new List(); + + /// + /// List of non-accepted exit nodes
+ /// -> Documentation here
+ /// (Default: empty) + ///
+ public readonly List ExcludeExitNodes = new List(); + + #endregion + + #endregion + #region Traffic + + /// + /// Determines if the shouldn't accept connections to .onion websites
+ /// -> Documentation here
+ /// (Default: false) + ///
+ public bool NoOnionTraffic { get; set; } = false; + + /// + /// Determines if the should only accept connections to .onion websites
+ /// -> Documentation here
+ /// (Default: false) + ///
+ public bool OnionTrafficOnly { get; set; } = false; + + #endregion + + #endregion + + #endregion + + } + +} \ No newline at end of file diff --git a/Trou/Tools/ChildProcessTracker.cs b/Trou/Tools/ChildProcessTracker.cs new file mode 100644 index 0000000..ba4d127 --- /dev/null +++ b/Trou/Tools/ChildProcessTracker.cs @@ -0,0 +1,135 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Trou.Tools { + + /// + /// Allows processes to be automatically killed if this parent process unexpectedly quits. + /// This feature requires Windows 8 or greater. On Windows 7, nothing is done. + /// References: + /// http://stackoverflow.com/a/4657392/386091 + /// http://stackoverflow.com/a/9164742/386091 + public static class ChildProcessTracker { + + /// + /// Add the process to be tracked. If our current process is killed, the child processes + /// that we are tracking will be automatically killed, too. If the child process terminates + /// first, that's fine, too. + /// + public static void AddProcess(Process process) { + if (s_jobHandle != IntPtr.Zero) { + bool success = AssignProcessToJobObject(s_jobHandle, process.Handle); + if (!success) + throw new Win32Exception(); + } + } + + static ChildProcessTracker() { + // This feature requires Windows 8 or later. To support Windows 7 requires + // registry settings to be added if you are using Visual Studio plus an + // app.manifest change. + // http://stackoverflow.com/a/4232259/386091 + // http://stackoverflow.com/a/9507862/386091 + if (Environment.OSVersion.Version < new Version(6, 2)) + return; + + // The job name is optional (and can be null) but it helps with diagnostics. + // If it's not null, it has to be unique. Use SysInternals' Handle command-line + // utility: handle -a ChildProcessTracker + string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id; + s_jobHandle = CreateJobObject(IntPtr.Zero, jobName); + + var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION { + + // This is the key flag. When our process is killed, Windows will automatically + // close the job handle, and when that happens, we want the child processes to + // be killed, too. + LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + + }; + + var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION { + BasicLimitInformation = info + }; + + int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); + try { + Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); + + if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation, + extendedInfoPtr, (uint)length)) { + throw new Win32Exception(); + } + } finally { + Marshal.FreeHGlobal(extendedInfoPtr); + } + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name); + + [DllImport("kernel32.dll")] + static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType, + IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); + + // Windows will automatically close any open job handles when our process terminates. + // This can be verified by using SysInternals' Handle utility. When the job handle + // is closed, the child processes will be killed. + private static readonly IntPtr s_jobHandle; + } + + public enum JobObjectInfoType { + AssociateCompletionPortInformation = 7, + BasicLimitInformation = 2, + BasicUIRestrictions = 4, + EndOfJobTimeInformation = 6, + ExtendedLimitInformation = 9, + SecurityLimitInformation = 5, + GroupInformation = 11 + } + + [StructLayout(LayoutKind.Sequential)] + public struct JOBOBJECT_BASIC_LIMIT_INFORMATION { + public Int64 PerProcessUserTimeLimit; + public Int64 PerJobUserTimeLimit; + public JOBOBJECTLIMIT LimitFlags; + public UIntPtr MinimumWorkingSetSize; + public UIntPtr MaximumWorkingSetSize; + public UInt32 ActiveProcessLimit; + public Int64 Affinity; + public UInt32 PriorityClass; + public UInt32 SchedulingClass; + } + + [Flags] + public enum JOBOBJECTLIMIT : uint { + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000 + } + + [StructLayout(LayoutKind.Sequential)] + public struct IO_COUNTERS { + public UInt64 ReadOperationCount; + public UInt64 WriteOperationCount; + public UInt64 OtherOperationCount; + public UInt64 ReadTransferCount; + public UInt64 WriteTransferCount; + public UInt64 OtherTransferCount; + } + + [StructLayout(LayoutKind.Sequential)] + public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { + public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + public IO_COUNTERS IoInfo; + public UIntPtr ProcessMemoryLimit; + public UIntPtr JobMemoryLimit; + public UIntPtr PeakProcessMemoryUsed; + public UIntPtr PeakJobMemoryUsed; + } + +} diff --git a/Trou/Tools/HiddenProcess.cs b/Trou/Tools/HiddenProcess.cs new file mode 100644 index 0000000..a1ed93f --- /dev/null +++ b/Trou/Tools/HiddenProcess.cs @@ -0,0 +1,353 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Trou.Tools { + + /// + /// Allows to start processes, they are placed in a virtual desktop in order to be hidden + /// + public class HiddenProcess : IDisposable { + + #region Variables + + #region Path + + /// + /// working directory + /// + public string WorkingDirectory; + + /// + /// executable file path + /// + public string FileName; + + #endregion + + #region Process + + /// + /// process instance + /// + public Process Process; + + /// + /// Determines if the should close when his parent process close + /// + public readonly bool EndAsParent; + + /// + /// arguments + /// + public string Arguments; + + /// + /// priority + /// + public PriorityClass Priority; + + /// + /// read buffer size + /// + public uint ReadBufferSize = 4096; + + #endregion + + #region Events + + /// + /// Triggered when the process has completely started + /// + public event EventHandler OnStart; + + /// + /// Triggered when the process output something + /// + public event EventHandler OnOutput; + + #endregion + + #endregion + + /// + /// Initializes a new intance of the class + /// + public HiddenProcess(string workingDirectory, string fileName, string arguments, PriorityClass priority, bool endAsParent) { + + // Save + WorkingDirectory = workingDirectory; + FileName = fileName; + Arguments = arguments; + EndAsParent = endAsParent; + + Priority = priority; + + } + + /// + /// Starts the process, returns true if the process is started successfully + /// + public bool Start() { + + #region Create desktop + + // Create process virtual desktop + CreateDesktop("HiddenDesktop", IntPtr.Zero, IntPtr.Zero, 0, (uint)ACCESS_MASK.GENERIC_ALL, IntPtr.Zero); + + #endregion + + #region Create process + + // Process informations (this is set after the process is started) + PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION(); + // Process startup informations + STARTUPINFO sInfo = new STARTUPINFO { + + dwFlags = 0x100, // STARTF_USESTDHANDLES + lpDesktop = "HiddenDesktop" + + }; + + #region Pipe + + // Pipes security attributes + SECURITY_ATTRIBUTES pipeSecurityAttributes = new SECURITY_ATTRIBUTES() { + nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)), + lpSecurityDescriptor = IntPtr.Zero, + bInheritHandle = 1 + }; + + // Pipe handles + IntPtr hRead = IntPtr.Zero; + IntPtr hWrite = IntPtr.Zero; + + // We want to read the process output + if (OnOutput != null) { + + // The pipe has been created successfully + if (CreatePipe(out hRead, out hWrite, ref pipeSecurityAttributes, 0)) { + + // Set process error output, and start reading + sInfo.hStdOutput = hWrite; + Task.Run(() => ReadOutput(hRead)); + + } else { return false; } + + } + + #endregion + + // Create the process + if (!CreateProcess(null, string.Format("{0} {1}", FileName, Arguments), IntPtr.Zero, IntPtr.Zero, true, (uint)Priority, IntPtr.Zero, WorkingDirectory, ref sInfo, out pInfo)) { + return false; + } + + // Get process and save it + Process = Process.GetProcessById(pInfo.dwProcessId); + + // Trigger event + OnStart?.Invoke(this, Process); + + // Close process handles + CloseHandle(pInfo.hProcess); + CloseHandle(pInfo.hThread); + + // Close write pipe + CloseHandle(hWrite); + + #endregion + + #region Process check + + // Check if the process isn't already closed + // (Can occur when it's for example a console app) + if (Process != null && !Process.HasExited) { + + // If the process should end when the parent process end + if (EndAsParent) { + + // Track the process to end when the parent end + ChildProcessTracker.AddProcess(Process); + + } + + } + + #endregion + + return true; + + } + + /// + /// Reads the process output from an handle + /// + private void ReadOutput(IntPtr readHandle) { + + // Buffer & read length + byte[] buffer = new byte[ReadBufferSize]; + uint dwRead = 0; + + // Read as long as the process is available (not closed..) + while (ReadFile(readHandle, buffer, ReadBufferSize, ref dwRead, IntPtr.Zero)) { + + // For every buffer lines + foreach (string line in Encoding.UTF8.GetString(buffer, 0, Convert.ToInt32(dwRead)).Split(Environment.NewLine)) { + + // If the line isn't empty + if (!string.IsNullOrEmpty(line)) { + + // Trigger event + OnOutput?.Invoke(this, line.Trim()); + + } + + } + + // Reset buffer + buffer = new byte[ReadBufferSize]; + + } + + // Close read handle + CloseHandle(readHandle); + + } + + /// + /// Kill and dispose the process + /// + public void Dispose() { + + // Dispose process + Process?.Kill(); + Process?.Dispose(); + + } + + #region Enums + + #region Process priorities + + /// + /// Process priority enumeration + /// + public enum PriorityClass { + + ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000, + BELOW_NORMAL_PRIORITY_CLASS = 0x00004000, + HIGH_PRIORITY_CLASS = 0x00000080, + IDLE_PRIORITY_CLASS = 0x00000040, + NORMAL_PRIORITY_CLASS = 0x00000020, + REALTIME_PRIORITY_CLASS = 0x00000100 + + } + + + #endregion + #region Security Attributes + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_ATTRIBUTES { + public int nLength; + public IntPtr lpSecurityDescriptor; + public int bInheritHandle; + } + + #endregion + + #endregion + #region Dll-Imports + + #region Desktops + + [DllImport("user32.dll")] + public static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa); + + [Flags] + internal enum ACCESS_MASK : uint { + DESKTOP_NONE = 0, + DESKTOP_READOBJECTS = 0x0001, + DESKTOP_CREATEWINDOW = 0x0002, + DESKTOP_CREATEMENU = 0x0004, + DESKTOP_HOOKCONTROL = 0x0008, + DESKTOP_JOURNALRECORD = 0x0010, + DESKTOP_JOURNALPLAYBACK = 0x0020, + DESKTOP_ENUMERATE = 0x0040, + DESKTOP_WRITEOBJECTS = 0x0080, + DESKTOP_SWITCHDESKTOP = 0x0100, + + GENERIC_ALL = (DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU | + DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD | DESKTOP_JOURNALPLAYBACK | + DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP), + } + + #endregion + + #region Pipes + + [DllImport("kernel32.dll")] + static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool ReadFile(IntPtr handle, byte[] buffer, uint toRead, ref uint read, IntPtr lpOverLapped); + + #endregion + #region Create process + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + static extern bool CreateProcess( + string lpApplicationName, + string lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + bool bInheritHandles, + uint dwCreationFlags, + IntPtr lpEnvironment, + string lpCurrentDirectory, + [In] ref STARTUPINFO lpStartupInfo, + out PROCESS_INFORMATION lpProcessInformation); + + [StructLayout(LayoutKind.Sequential)] + internal struct PROCESS_INFORMATION { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + struct STARTUPINFO { + public Int32 cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public Int32 dwX; + public Int32 dwY; + public Int32 dwXSize; + public Int32 dwYSize; + public Int32 dwXCountChars; + public Int32 dwYCountChars; + public Int32 dwFillAttribute; + public Int32 dwFlags; + public Int16 wShowWindow; + public Int16 cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + #endregion + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CloseHandle(IntPtr handle); + + #endregion + + } + +} diff --git a/Trou/Trou.csproj b/Trou/Trou.csproj new file mode 100644 index 0000000..5d4709c --- /dev/null +++ b/Trou/Trou.csproj @@ -0,0 +1,35 @@ + + + + netcoreapp3.1 + NaolShow + ToWolf + https://github.com/NaolShow/Trou + README.md + © 2020 NaolShow, ToWolf + https://github.com/NaolShow/Trou/blob/master/logo.png?raw=true + logo.png + https://github.com/NaolShow/Trou + 1.0.0 + 1.0.0 + Tor,Privoxy,HTTP,Socks,Socks5 + Tor + Privoxy for the best anonymous HTTP proxy implementation on C# + true + true + false + + + + C:\Users\Loan\Dropbox\Developpement\CSharp\--Network\Trou\Trou\Trou.xml + + + + + + + True + + + + + diff --git a/Trou/Trou.xml b/Trou/Trou.xml new file mode 100644 index 0000000..0e696ed --- /dev/null +++ b/Trou/Trou.xml @@ -0,0 +1,582 @@ + + + + Trou + + + + + Allows to configure a + + + + + Path to the extracted Privoxy Bundle folder (folder that contains the privoxy.exe file). This is the fastest way of configuring paths.
+ For a more advanced paths configuration, you can set every paths manually (if those paths are set, they will not be recovered from path):
+ +
+
+ + + Path to the privoxy.exe file
+ If this value isn't set, will try to recover it using
+ (Default: null) +
+
+ + + Arguments that are used when starting the process
+ Useful when you want to add arguments that are not natively supported by Trou
+ -> List of the supported Privoxy arguments here +
+
+ + + Configuration pairs that are used when starting the process
+ Useful when you want to add configuration pairs that are not natively supported by Trou
+ -> List of the supported Tor arguments here +
+
+ + + Address that the is listening at (HTTP)
+ -> Documentation here
+ (Default: 127.0.0.1) +
+
+ + + Port that the is listening at (HTTP)
+ -> Documentation here
+ (Default: 8118) +
+
+ + + Address that the will forward requests to (SOCKS 5)
+ -> Documentation here
+ (Default: 127.0.0.1) +
+
+ + + Port that the will forward requests to (SOCKS 5)
+ -> Documentation here
+ (Default: 9050) +
+
+ + + Allows to configure a + + + + + Address that the will connect to
+ (Default: 127.0.0.1) +
+
+ + + Port that the will connect to
+ (Default: 9051) +
+
+ + + Password that the will use to authenticate to the
+ (Default: null) +
+
+ + + Allows to configure a + + + + + Path to the extracted Tor Bundle folder (folder that contains the Tor and Data folders). This is the fastest way of configuring paths
+ For a more advanced paths configuration, you can set every paths manually (if those paths are set, they will not be recovered from path):
+ , , , +
+
+ + + Path to the tor.exe file
+ If this value isn't set, will try to recover it using
+ (Default: null) +
+
+ + + Path where the cache will be stored
+ If this value isn't set, will use your ApplicationData folder
+ -> Documentation here
+ (Default: null) +
+
+ + + Path to the geoip file (this file is used when using nodes filtering) + If this value isn't set, will try to recover it using
+ -> Documentation here
+ (Default: null) +
+
+ + + Path to the geoip6 file (this file is used when using nodes filtering) + If this value isn't set, will try to recover it using
+ -> Documentation here
+ (Default: null) +
+
+ + + Arguments that are used when starting the process
+ Useful when you want to add arguments that are not natively supported by Trou
+ -> List of the supported Tor arguments here +
+
+ + + Address that the is listening at (SOCKS 5)
+ -> Documentation here
+ (Default: 127.0.0.1) +
+
+ + + Port that the is listening at (SOCKS 5)
+ -> Documentation here
+ (Default: 9050) +
+
+ + + Determines if should listen to
+ (Default: true) +
+
+ + + Address that the is listening at (for the )
+ -> Documentation here
+ (Default: 127.0.0.1) +
+
+ + + Port that the is listening at (for the )
+ -> Documentation here
+ (Default: 9051) +
+
+ + + Password that is required for the when he wants to control the
+ If the is set, will ignore
+ -> Documentation here
+ (Default: null) +
+
+ + + Password that is required for the when he wants to control the
+ If the is set, will ignore
+ -> Documentation here
+ (Default: null) +
+
+ + + Determines if should strictly respect the nodes filtering
+ (Default: true) +
+
+ + + List of accepted exit nodes
+ -> Documentation here
+ (Default: empty) +
+
+ + + List of accepted middle nodes
+ -> Documentation here
+ (Default: empty) +
+
+ + + List of accepted entry nodes
+ -> Documentation here
+ (Default: empty) +
+
+ + + List of non-accepted nodes
+ -> Documentation here
+ (Default: empty) +
+
+ + + List of non-accepted exit nodes
+ -> Documentation here
+ (Default: empty) +
+
+ + + Determines if the shouldn't accept connections to .onion websites
+ -> Documentation here
+ (Default: false) +
+
+ + + Determines if the should only accept connections to .onion websites
+ -> Documentation here
+ (Default: false) +
+
+ + + Allows to start a which will listen to an IP and a Port for HTTP requests + + + + + hidden process + + + + + process executable path + + + + + settings + + + + + Initializes a new intance of the service + + + + + Refreshes every Arguments and Configuration keys from the before starting the process + + + + + Starts the service + + + + + Dispose the service + + + + + Allows to start a which can interact with a + + + + + settings + + + + + tcp client + + + + + writer + + + + + reader + + + + + Initializes a new intance of the service + + + + + Refresh the circuit of (changes every nodes so get new IP Address)
+ Returns true if the refresh was successful +
+
+ + + Authenticates to
+ Returns true if the authentication was successful +
+
+ + + Quit/Log-Off from
+ Returns true if the log-off was successful +
+
+ + + Stops completely the
+ Returns true if the shutdown was successful +
+
+ + + Sets to a "sleep mode"
+ Returns true if the sleep mode set was successful +
+
+ + + Removes the "sleep mode" (Dormant) from + Returns true if the sleep mode remove was successful + + + + + Sends a text message to
+ Returns true if the sending was successful +
+
+ + + Starts the service + + + + + Dispose the service + + + + + Allows to start a which will listen to an IP and a Port for SOCKS 5 requests + + + + + hidden process + + + + + process executable path + + + + + settings + + + + + Triggered when process output informations + + + + + Triggered when process output warns or errors + + + + + Initializes a new intance of the service + + + + + Refreshes every Arguments from the before starting the process + + + + + Triggered when we receive process output that needs to be forwarded to events + + + + + Starts the service + + + + + Dispose the service + + + + + Sets an argument that can be active or not
+ (If the argument isn't active it will be removed) +
+
+ + + Sets a toggle argument and remove it if the value is false + + + + + Sets an argument to a list and remove it if the list is empty + + + + + Sets a path argument, throw an exception if the path isn't valid + + + + + Hash a password using the process + + + + + Allows processes to be automatically killed if this parent process unexpectedly quits. + This feature requires Windows 8 or greater. On Windows 7, nothing is done. + References: + http://stackoverflow.com/a/4657392/386091 + http://stackoverflow.com/a/9164742/386091 + + + + Add the process to be tracked. If our current process is killed, the child processes + that we are tracking will be automatically killed, too. If the child process terminates + first, that's fine, too. + + + + + Allows to start processes, they are placed in a virtual desktop in order to be hidden + + + + + working directory + + + + + executable file path + + + + + process instance + + + + + Determines if the should close when his parent process close + + + + + arguments + + + + + priority + + + + + read buffer size + + + + + Triggered when the process has completely started + + + + + Triggered when the process output something + + + + + Initializes a new intance of the class + + + + + Starts the process, returns true if the process is started successfully + + + + + Reads the process output from an handle + + + + + Kill and dispose the process + + + + + Process priority enumeration + + + + + Trou Proxy - .NET Core library made by NaolShow + -> https://github.com/NaolShow/Trou + + If you like the project, please star it :) (It's helping me a lot :D) + + + + Allows to start , and simultaneously + + + + + instance + + + + + instance + + + + + instance + + + + + Initializes a new intance of the service + + + + + Starts the service + + + + + Dispose the service + + +
+
diff --git a/Trou/TrouProxy.cs b/Trou/TrouProxy.cs new file mode 100644 index 0000000..65aa455 --- /dev/null +++ b/Trou/TrouProxy.cs @@ -0,0 +1,78 @@ +using System; +using Trou.Services; +using Trou.ServicesSettings; + +namespace Trou { + + /** + * + * Trou Proxy - .NET Core library made by NaolShow + * -> https://github.com/NaolShow/Trou + * + * If you like the project, please star it :) (It's helping me a lot :D) + * + **/ + + /// + /// Allows to start , and simultaneously + /// + public class TrouProxy : IDisposable { + + /// + /// instance + /// + public TorProxy Tor; + + /// + /// instance + /// + public TorController Controller; + + /// + /// instance + /// + public PrivoxyProxy Privoxy; + + #region Constructors + + /// + /// Initializes a new intance of the service + /// + public TrouProxy(TorProxySettings torSettings, TorControllerSettings controllerSettings, PrivoxyProxySettings privoxySettings) { + + // -- Instance + + Tor = new TorProxy(torSettings); + Controller = new TorController(controllerSettings); + + Privoxy = new PrivoxyProxy(privoxySettings); + + } + + #endregion + + #region On/Off/Terminate + + /// + /// Starts the service + /// + public void Start() { + Tor.Start(); + Controller.Start(); + Privoxy.Start(); + } + + /// + /// Dispose the service + /// + public void Dispose() { + Tor.Dispose(); + Controller.Dispose(); + Privoxy.Dispose(); + } + + #endregion + + } + +} diff --git a/TrouExample/Program.cs b/TrouExample/Program.cs new file mode 100644 index 0000000..a47d095 --- /dev/null +++ b/TrouExample/Program.cs @@ -0,0 +1,51 @@ +using System; +using System.Net; +using Trou; +using Trou.ServicesSettings; + +namespace TrouExample { + + /// + /// Program class + /// + public class Program { + + /// + /// Entry point + /// + private static void Main() { + + // - Instantiate Trou proxy + + TrouProxy proxy = new TrouProxy(new TorProxySettings() { + TorBundlePath = @"C:\Users\Loan\Dropbox\Developpement\CSharp\--Network\Trou\TrouExample\bin\Debug\netcoreapp3.0\Proxies\TorProxy" + }, new TorControllerSettings() { + + }, new PrivoxyProxySettings() { + PrivoxyBundlePath = @"C:\Users\Loan\Dropbox\Developpement\CSharp\--Network\Trou\TrouExample\bin\Debug\netcoreapp3.0\Proxies\PrivoxyProxy" + }); + + // - Start + + proxy.Start(); + + // - Check ip + + // Create client connected to Tor using Trou + WebClient client = new WebClient() { + Proxy = new WebProxy("127.0.0.1:8118") + }; + + // Write TOR Ip address + Console.WriteLine(client.DownloadString("http://api.ipify.org")); + + // - Stop + Console.ReadLine(); + client.Dispose(); + proxy.Dispose(); + + } + + } + +} diff --git a/TrouExample/TrouExample.csproj b/TrouExample/TrouExample.csproj new file mode 100644 index 0000000..cb3a743 --- /dev/null +++ b/TrouExample/TrouExample.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp3.1 + + + + + + + diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cb55a3284e54719a7202769b641772bf6535afb7 GIT binary patch literal 35204 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 literal 0 HcmV?d00001