├── LibFreeVPN ├── VPNServerConfigParserFullBase.cs ├── IVPNGenericMultiProviderParser.cs ├── LibFreeVPN.csproj ├── IVPNServer.cs ├── Providers │ ├── ShIo.cs │ ├── ShEs.cs │ ├── Vlist.cs │ ├── ShAzsm.cs │ ├── ShJhk.cs │ ├── ShNo.cs │ ├── Cdcsv.cs │ ├── ShV2x.cs │ ├── WhiteLabelVpnAppApi1.cs │ ├── BeautyBird.cs │ ├── Aob.cs │ ├── ShWrd.cs │ ├── OvpnZip.cs │ ├── Ldcl.cs │ └── ShMpn.cs ├── VPNProviderBase.cs ├── VPNProviders.cs ├── VPNServerConfigParserBase.cs ├── Memecrypto │ ├── AesCtr.cs │ ├── XXTEA.cs │ └── Pbkdf2.cs ├── VPNProviderHelpers.cs ├── IVPNServerConfigParser.cs ├── IVPNProvider.cs ├── Servers │ ├── SSHServer.cs │ ├── V2RayServerSurge.cs │ ├── OpenVpnServer.cs │ ├── V2Ray │ │ └── V2RayConfigJson.cs │ └── SocksHttpParser.cs ├── VPNServerBase.cs ├── VPNGenericMultiProviderParser.cs └── Utilities.cs ├── LibreFreeVPN.sln ├── FreeVPNC ├── FreeVPNC.csproj └── Program.cs ├── README.md └── .gitignore /LibFreeVPN/VPNServerConfigParserFullBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LibFreeVPN 7 | { 8 | public abstract class VPNServerConfigParserFullBase : VPNServerConfigParserBase 9 | where TServer : IVPNServer 10 | { 11 | public sealed override IEnumerable<(string hostname, string port)> ParseConfig(string config) 12 | { 13 | return ParseConfigFull(config).Select((tuple) => (tuple.hostname, tuple.port)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LibFreeVPN/IVPNGenericMultiProviderParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LibFreeVPN 6 | { 7 | public interface IVPNGenericMultiProviderParser 8 | { 9 | /// 10 | /// Parse a config of a generic multi-provider client, and return a list of servers. 11 | /// 12 | /// Config to parse 13 | /// Registry data 14 | /// An iterator over a collection of VPN servers 15 | IEnumerable Parse(string config, IReadOnlyDictionary extraRegistry); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LibFreeVPN/LibFreeVPN.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | $([System.String]::Format('{0}.{1:00}{2:00}.{3:00}{4:00}.0', $([System.DateTime]::UtcNow.Year),$([System.DateTime]::UtcNow.Month),$([System.DateTime]::UtcNow.Day),$([System.DateTime]::UtcNow.Hour),$([System.DateTime]::UtcNow.Minute))) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LibFreeVPN/IVPNServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LibFreeVPN 6 | { 7 | /// 8 | /// Describes a VPN server. 9 | /// 10 | public interface IVPNServer : IEquatable 11 | { 12 | /// 13 | /// Protocol of this VPN server 14 | /// 15 | ServerProtocol Protocol { get; } 16 | 17 | /// 18 | /// Configuration of this VPN server, format is dependent on the protocol type. 19 | /// 20 | string Config { get; } 21 | 22 | /// 23 | /// Key-value store describing information that may or may not be in the configuration. Dependent on the protocol type, but generally would include keys like host, port, username, password. 24 | /// 25 | IReadOnlyDictionary Registry { get; } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/ShIo.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Memecrypto; 2 | using LibFreeVPN.ProviderHelpers; 3 | using LibFreeVPN.Servers; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace LibFreeVPN.Providers 12 | { 13 | // Android app. SocksHttp using SSH + v2ray. 14 | 15 | public sealed class ShIo : VPNProviderHttpGetBase 16 | { 17 | public sealed class Parser : SocksHttpWithOvpnParserTea 18 | { 19 | protected override string OvpnKey => "setOpenVPN"; 20 | 21 | protected override string OuterKey => Encoding.ASCII.FromBase64String("YVolUlUlaXRSTUJeOCZVZQ=="); 22 | } 23 | 24 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5tb2JpbGVhcHAucm92cG4="; 25 | 26 | public override string SampleVersion => "4.0"; 27 | 28 | public override bool HasProtocol(ServerProtocol protocol) => 29 | protocol == ServerProtocol.SSH || protocol == ServerProtocol.V2Ray; 30 | 31 | protected override string RequestUri => Encoding.ASCII.FromBase64String("aHR0cHM6Ly9pb3Zwbi5tZS9hcHAvY29uZmlnL2hkaGRoZGRkZC5waHA="); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/ShEs.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Memecrypto; 2 | using LibFreeVPN.ProviderHelpers; 3 | using LibFreeVPN.Servers; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Text; 10 | using System.Text.Json; 11 | using System.Threading.Tasks; 12 | 13 | namespace LibFreeVPN.Providers 14 | { 15 | // Android app. SocksHttp using SSH + OpenVPN. 16 | 17 | public sealed class ShEs : VPNProviderHttpGetBase 18 | { 19 | public sealed class Parser : SocksHttpWithOvpnParserTea 20 | { 21 | protected override string OvpnKey => "setOpenVPN"; 22 | 23 | protected override string OuterKey => Encoding.ASCII.FromBase64String("YU5MY0cyRlQ2OXZBQk5CcQ=="); 24 | } 25 | 26 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5taXNha2kuZXNhbnZwbg=="; 27 | 28 | public override string SampleVersion => "1.3.6"; 29 | 30 | public override bool HasProtocol(ServerProtocol protocol) => 31 | protocol == ServerProtocol.SSH || protocol == ServerProtocol.OpenVPN; 32 | 33 | protected override string RequestUri => Encoding.ASCII.FromBase64String("aHR0cHM6Ly9lLXNhbi12cG4uaW4ubmV0L1VwZGF0ZS9lLXNhbi12cG4uanNvbg=="); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LibFreeVPN/VPNProviderBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace LibFreeVPN 8 | { 9 | public abstract class VPNProviderBase : IVPNProvider 10 | { 11 | public virtual string Name => GetType().Name; 12 | public abstract string SampleSource { get; } 13 | public abstract string SampleVersion { get; } 14 | public abstract bool RiskyRequests { get; } 15 | 16 | public abstract bool HasProtocol(ServerProtocol protocol); 17 | 18 | protected abstract Task> GetServersAsyncImpl(); 19 | 20 | public async Task> GetServersAsync() 21 | { 22 | try 23 | { 24 | return await GetServersAsyncImpl(); 25 | } 26 | catch 27 | { 28 | return Enumerable.Empty(); 29 | } 30 | } 31 | 32 | protected Dictionary CreateExtraRegistry() 33 | { 34 | return new Dictionary() 35 | { 36 | { ServerRegistryKeys.ProviderName, Name } 37 | }; 38 | } 39 | 40 | protected static Dictionary CreateExtraRegistry(string name) 41 | { 42 | return new Dictionary() 43 | { 44 | { ServerRegistryKeys.ProviderName, name } 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LibreFreeVPN.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33530.505 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibFreeVPN", "LibFreeVPN\LibFreeVPN.csproj", "{3889974B-B65A-4DF4-801F-9EF0FBBE872D}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FreeVPNC", "FreeVPNC\FreeVPNC.csproj", "{CE097991-0F74-48F0-AD5A-C2ABC050E58E}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {3889974B-B65A-4DF4-801F-9EF0FBBE872D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {3889974B-B65A-4DF4-801F-9EF0FBBE872D}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {3889974B-B65A-4DF4-801F-9EF0FBBE872D}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {3889974B-B65A-4DF4-801F-9EF0FBBE872D}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {CE097991-0F74-48F0-AD5A-C2ABC050E58E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {CE097991-0F74-48F0-AD5A-C2ABC050E58E}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {CE097991-0F74-48F0-AD5A-C2ABC050E58E}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {CE097991-0F74-48F0-AD5A-C2ABC050E58E}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {8DF347E2-9379-45A9-B2BE-04F3D820282E} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /LibFreeVPN/VPNProviders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace LibFreeVPN 8 | { 9 | /// 10 | /// Provide operations over available VPN providers. 11 | /// 12 | public static class VPNProviders 13 | { 14 | private class Enumerable : IEnumerable 15 | { 16 | public IEnumerator GetEnumerator() 17 | { 18 | return s_VpnProviders.GetEnumerator(); 19 | } 20 | 21 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 22 | } 23 | private readonly static List s_VpnProviders = new List(); 24 | 25 | static VPNProviders() 26 | { 27 | // Get all IVPNProviders by reflection 28 | var typeInterface = typeof(IVPNProvider); 29 | foreach (var type in AppDomain.CurrentDomain.GetAssemblies().SelectMany(asm => asm.GetTypes()).Where(type => 30 | typeInterface.IsAssignableFrom(type) && type.IsPublic && !type.IsInterface && !type.IsAbstract 31 | )) 32 | { 33 | // Try to create instance (ignore it on failure), and add created object to the list 34 | try 35 | { 36 | s_VpnProviders.Add((IVPNProvider)Activator.CreateInstance(type)); 37 | } 38 | catch { } 39 | } 40 | } 41 | 42 | /// 43 | /// Gets an iterator of providers. 44 | /// 45 | public static IEnumerable Providers => new Enumerable(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FreeVPNC/FreeVPNC.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1;net6.0;net8.0;net472 6 | $([System.String]::Format('{0}.{1:00}{2:00}.{3:00}{4:00}.0', $([System.DateTime]::UtcNow.Year),$([System.DateTime]::UtcNow.Month),$([System.DateTime]::UtcNow.Day),$([System.DateTime]::UtcNow.Hour),$([System.DateTime]::UtcNow.Minute))) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/Vlist.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.ProviderHelpers; 2 | using LibFreeVPN.Servers; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Text.Json; 10 | using System.Threading.Tasks; 11 | 12 | namespace LibFreeVPN.Providers.Vlist 13 | { 14 | // Android apps. Base64 encoded list of v2ray urls. 15 | public abstract class VlistBase : VPNProviderHttpGetBase 16 | { 17 | // non-risky request by default: google drive 18 | public override bool RiskyRequests => false; 19 | 20 | public override bool HasProtocol(ServerProtocol protocol) 21 | { 22 | return protocol == ServerProtocol.V2Ray; 23 | } 24 | 25 | protected override Task> GetServersAsyncImpl(string config) 26 | { 27 | // Base64 decode the config 28 | config = Encoding.UTF8.GetString(Convert.FromBase64String(config)); 29 | 30 | // And try to parse it 31 | return Task.FromResult>( 32 | config.Split('\n') 33 | .Where((line) => !string.IsNullOrEmpty(line)) 34 | .SelectMany((line) => V2RayServer.ParseConfigFull(line, CreateExtraRegistry())) 35 | .Where((server) => server.Registry[ServerRegistryKeys.Hostname] != "0.0.0.0") 36 | .Distinct() 37 | .ToList()); 38 | } 39 | } 40 | 41 | public sealed class VlistBla : VlistBase 42 | { 43 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5ibGFibGFfc29mdHdhcmUudnBu"; 44 | 45 | public override string SampleVersion => "1.1.5"; 46 | 47 | protected override string RequestUri => Encoding.ASCII.FromBase64String("aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL3VjP2lkPTFoLUI3bF9YcHAzdVozSlJYNDBienFNSmVCUkpSc2FxTCZleHBvcnQ9ZG93bmxvYWQ="); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LibFreeVPN/VPNServerConfigParserBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LibFreeVPN 6 | { 7 | public abstract class VPNServerConfigParserBase : IVPNServerConfigParser 8 | where TServer : IVPNServer 9 | { 10 | public abstract IEnumerable<(string hostname, string port)> ParseConfig(string config); 11 | public abstract IEnumerable<(string config, string hostname, string port)> ParseConfigFull(string config); 12 | public virtual string GetUserVisibleConfig(string config, IReadOnlyDictionary registry) 13 | => config; 14 | 15 | public abstract TServer CreateInstance(string config); 16 | public abstract TServer CreateInstance(string config, string hostname, string port); 17 | public abstract TServer CreateInstance(string config, IReadOnlyDictionary extraRegistry); 18 | public abstract TServer CreateInstance(string config, string hostname, string port, IReadOnlyDictionary extraRegistry); 19 | public abstract TServer CreateInstance(string hostname, string port); 20 | public abstract TServer CreateInstance(string hostname, string port, IReadOnlyDictionary extraRegistry); 21 | 22 | 23 | public TServer CreateInstance(string config, string hostname, int port) => CreateInstance(config, hostname, port.ToString()); 24 | 25 | public TServer CreateInstance(string config, string hostname, int port, IReadOnlyDictionary extraRegistry) 26 | => CreateInstance(config, hostname, port.ToString(), extraRegistry); 27 | 28 | public TServer CreateInstance(string hostname, int port, IReadOnlyDictionary extraRegistry) 29 | => CreateInstance(hostname, port.ToString(), extraRegistry); 30 | 31 | public abstract bool CanCreateInstance(bool withConfig); 32 | 33 | public virtual void AddExtraProperties(IDictionary registry, string config) { } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LibFreeVPN/Memecrypto/AesCtr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | 7 | namespace LibFreeVPN.Memecrypto 8 | { 9 | public static class AesCtr 10 | { 11 | public static void Transform(byte[] key, byte[] salt, Stream inputStream, Stream outputStream) 12 | { 13 | SymmetricAlgorithm aes = Aes.Create(); 14 | aes.Mode = CipherMode.ECB; 15 | aes.Padding = PaddingMode.None; 16 | 17 | int blockSize = aes.BlockSize / 8; 18 | 19 | if (salt.Length != blockSize) 20 | { 21 | throw new ArgumentException( 22 | "Salt size must be same as block size " + 23 | $"(actual: {salt.Length}, expected: {blockSize})"); 24 | } 25 | 26 | var counter = (byte[])salt.Clone(); 27 | 28 | var xorMask = new Queue(); 29 | 30 | var zeroIv = new byte[blockSize]; 31 | ICryptoTransform counterEncryptor = aes.CreateEncryptor(key, zeroIv); 32 | 33 | int b; 34 | while ((b = inputStream.ReadByte()) != -1) 35 | { 36 | if (xorMask.Count == 0) 37 | { 38 | var counterModeBlock = new byte[blockSize]; 39 | 40 | counterEncryptor.TransformBlock( 41 | counter, 0, counter.Length, counterModeBlock, 0); 42 | 43 | for (var i2 = counter.Length - 1; i2 >= 0; i2--) 44 | { 45 | if (++counter[i2] != 0) 46 | { 47 | break; 48 | } 49 | } 50 | 51 | foreach (var b2 in counterModeBlock) 52 | { 53 | xorMask.Enqueue(b2); 54 | } 55 | } 56 | 57 | var mask = xorMask.Dequeue(); 58 | outputStream.WriteByte((byte)(((byte)b) ^ mask)); 59 | } 60 | } 61 | 62 | public static byte[] Transform(byte[] key, byte[] salt, byte[] data) 63 | { 64 | using (var msIn = new MemoryStream(data)) 65 | using (var msOut = new MemoryStream()) 66 | { 67 | Transform(key, salt, msIn, msOut); 68 | return msOut.ToArray(); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /LibFreeVPN/VPNProviderHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace LibFreeVPN.ProviderHelpers 7 | { 8 | public abstract class VPNProviderHttpGetBase : VPNProviderBase 9 | { 10 | public override bool RiskyRequests => true; 11 | protected abstract string RequestUri { get; } 12 | protected abstract Task> GetServersAsyncImpl(string config); 13 | 14 | protected Task> GetServersAsyncImpl(string config) 15 | where TParser : VPNGenericMultiProviderParser, new() 16 | { 17 | var extraRegistry = CreateExtraRegistry(); 18 | return Task.FromResult(VPNGenericMultiProviderParser.ParseConfig(config, extraRegistry)); 19 | } 20 | 21 | protected override async Task> GetServersAsyncImpl() 22 | { 23 | var config = await ServerUtilities.HttpClient.GetStringAsync(RequestUri); 24 | return await GetServersAsyncImpl(config); 25 | } 26 | } 27 | 28 | public abstract class VPNProviderHttpGetBase : VPNProviderHttpGetBase 29 | where TParser : VPNGenericMultiProviderParser, new() 30 | { 31 | protected override Task> GetServersAsyncImpl(string config) 32 | => GetServersAsyncImpl(config); 33 | } 34 | 35 | public abstract class VPNProviderGithubRepoFileBase : VPNProviderHttpGetBase 36 | { 37 | protected abstract string RepoName { get; } 38 | protected virtual string BranchName => "main"; 39 | protected abstract string ConfigName { get; } 40 | 41 | private static string RequestUriGetter(VPNProviderGithubRepoFileBase self) 42 | => string.Format("https://raw.githubusercontent.com/{0}/{1}/{2}", self.RepoName, self.BranchName, self.ConfigName); 43 | protected override string RequestUri => this.SingleInstanceByType(RequestUriGetter); 44 | 45 | public override bool RiskyRequests => false; 46 | } 47 | 48 | public abstract class VPNProviderGithubRepoFileBase : VPNProviderGithubRepoFileBase 49 | where TParser : VPNGenericMultiProviderParser, new() 50 | { 51 | protected override Task> GetServersAsyncImpl(string config) 52 | => GetServersAsyncImpl(config); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /LibFreeVPN/IVPNServerConfigParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LibFreeVPN 6 | { 7 | /// 8 | /// Defines a parser for a VPN server configuration. 9 | /// 10 | public interface IVPNServerConfigParser 11 | { 12 | /// 13 | /// Parses configuration data for a iterable collection of hostname/port tuples. 14 | /// 15 | /// Configuration string 16 | /// Iterable collection of hostname/port tuples 17 | IEnumerable<(string hostname, string port)> ParseConfig(string config); 18 | 19 | /// 20 | /// Parses configuration data for a iterable collection of config/hostname/port tuples. 21 | /// 22 | /// Configuration string 23 | /// Iterable collection of config/hostname/port tuples 24 | IEnumerable<(string config, string hostname, string port)> ParseConfigFull(string config); 25 | 26 | /// 27 | /// Given a parsed config, and server registry data, get the final user visible config. 28 | /// 29 | /// Parsed config 30 | /// Registry data 31 | /// User visible config 32 | string GetUserVisibleConfig(string config, IReadOnlyDictionary registry); 33 | 34 | /// 35 | /// Given an unparsed config, add any extra registry data that is common to this server type. 36 | /// 37 | /// Registry data 38 | /// Unparsed config 39 | void AddExtraProperties(IDictionary registry, string config); 40 | } 41 | 42 | public interface IVPNServerConfigParser : IVPNServerConfigParser 43 | where TServer : IVPNServer 44 | { 45 | TServer CreateInstance(string config); 46 | 47 | TServer CreateInstance(string config, string hostname, string port); 48 | 49 | TServer CreateInstance(string config, IReadOnlyDictionary extraRegistry); 50 | TServer CreateInstance(string config, string hostname, string port, IReadOnlyDictionary extraRegistry); 51 | 52 | TServer CreateInstance(string hostname, string port); 53 | 54 | TServer CreateInstance(string hostname, string port, IReadOnlyDictionary extraRegistry); 55 | 56 | bool CanCreateInstance(bool withConfig); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /LibFreeVPN/IVPNProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace LibFreeVPN 7 | { 8 | /// 9 | /// Describes a VPN provider, that is, some remote service that provides VPN servers. 10 | /// 11 | public interface IVPNProvider 12 | { 13 | /// 14 | /// User-visible name 15 | /// 16 | string Name { get; } 17 | 18 | /// 19 | /// Source of the sample reversed to initially understand this implemention. 20 | /// Should be link to app store (for phone app), link to sample (for PC app), etc. 21 | /// Slight obfuscation (rot13, base64 etc) of implementor's choice can be performed on sample source if wanted :) 22 | /// 23 | string SampleSource { get; } 24 | 25 | /// 26 | /// Version number of the sample reversed to initially understand this implemention. 27 | /// For phone app, should be version number reported by app store. Otherwise, version number reported by application, or modified date / build date of executable. 28 | /// 29 | string SampleVersion { get; } 30 | 31 | /// 32 | /// When true, calling GetServersAsync will involve network requests to servers ran by the developers of the sample, or entities involved with them.
33 | /// When false, network requests made by GetServersAsync are only to servers not directly ran by the sample developers, for example large public git forges (github/gitlab), blog hosting (wordpress.com/blogger/blogspot), social networks, etc. 34 | ///
35 | bool RiskyRequests { get; } 36 | 37 | /// 38 | /// Returns false if the list of servers is known ahead of time (that is, without any network request) to not contain at least one server with the specified protocol. 39 | /// Otherwise, returns true. 40 | /// 41 | /// Protocol type to check 42 | /// True if the list of servers is known to contain at least one server of , false otherwise. 43 | bool HasProtocol(ServerProtocol protocol); 44 | 45 | /// 46 | /// Asynchronous method for obtaining the list of servers from this provider.. 47 | /// 48 | /// Task object representing the asynchronous operation. On success, returns an iterator over a collection of VPN servers. 49 | Task> GetServersAsync(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/ShAzsm.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.ProviderHelpers; 2 | using LibFreeVPN.Servers; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Security.Cryptography; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace LibFreeVPN.Providers 13 | { 14 | // Android app, distributed outside of Play Store. SocksHttp fork. SSH + v2ray (v2ray can domain front through zoom) 15 | public sealed class ShAzsm : VPNProviderGithubRepoFileBase 16 | { 17 | public sealed class Parser : SocksHttpParser 18 | { 19 | protected override string CountryNameKey => "FLAG"; 20 | protected override string V2RayKey => "v2rayJson"; 21 | protected override string ServerTypeKey => "sPais"; 22 | 23 | // Instead of reimplementing the key derivation code, use the already derived key 24 | private static readonly byte[] s_AesKey = 25 | { 26 | 0xf0, 0x00, 0x8d, 0xc9, 0x5b, 0x34, 0x67, 0xf7, 27 | 0xa3, 0x55, 0x4c, 0x23, 0x69, 0xe8, 0x54, 0xd1, 28 | 0x5c, 0x04, 0xd9, 0xd4, 0xb6, 0x5d, 0x8c, 0xbd, 29 | 0xed, 0xee, 0xa6, 0x2f, 0x77, 0xfb, 0x22, 0xef 30 | }; 31 | 32 | private static readonly byte[] s_AesIv = new byte[0x10]; // all-zero IV 33 | 34 | protected override string DecryptOuter(string ciphertext) 35 | { 36 | var cipherTextBytes = Convert.FromBase64String(ciphertext.Replace('-', '+').Replace('_', '/')); 37 | using (var aes = new AesManaged()) 38 | { 39 | aes.BlockSize = 128; 40 | aes.KeySize = 256; 41 | aes.Padding = PaddingMode.PKCS7; 42 | using (var dec = aes.CreateDecryptor(s_AesKey, s_AesIv)) 43 | { 44 | return Encoding.UTF8.GetString(dec.TransformFinalBlock(cipherTextBytes, 0, cipherTextBytes.Length)); 45 | } 46 | } 47 | } 48 | } 49 | 50 | public override string SampleSource => "aHR0cHM6Ly93d3cubWVkaWFmaXJlLmNvbS9maWxlL2l2aGJsajhwdnNlbGtxcS9PcGVuVlBOKzIwNDguYXBrL2ZpbGU="; 51 | 52 | public override string SampleVersion => "1.1"; 53 | 54 | public override bool HasProtocol(ServerProtocol protocol) => 55 | protocol == ServerProtocol.SSH || protocol == ServerProtocol.V2Ray; 56 | 57 | protected override string RepoName => Encoding.ASCII.FromBase64String("U1VTSUUtMjAyMy9KU09O"); 58 | protected override string ConfigName => Encoding.ASCII.FromBase64String("ZmlsZXMvY29uZmlnLmpzb24="); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # librefreevpn 2 | 3 | Get the VPN configs that "free VPN" mobile apps use without trusting or running their official clients. 4 | 5 | Use those VPNs on any device which has a client for those protocols. 6 | 7 | Any custom code for DPI/etc workarounds implemented in these clients (ie, websocket + domain fronting CDNs, for example) is not implemented here. 8 | 9 | Library targets .NET Standard 2.0, command line application targets .NET Core 3.1, .NET 6, .NET 8 and .NET Framework 4.7.2. 10 | 11 | ## Why? 12 | 13 | I reverse engineer things. Sometimes I get bored, and "free VPN" mobile apps are somewhat interesting targets to look at. 14 | 15 | They mainly tend to be wrappers around typical VPN/obfuscating proxy protocol clients (or in some cases SSH tunnel), getting configs from some remote C2 server. 16 | 17 | Therefore, I decided to write a small library around reimplementing the core of these clients (that is, getting the configs). And implemented a few examples (from both Android and iOS targets). 18 | 19 | ## Library documentation 20 | 21 | Get `VPNProviders.Providers`, run whatever LINQ queries you want, then call `GetServersAsync()` to actually make the network requests to get the configs from that provider. 22 | 23 | Note that various strings are lightly obfuscated as a way to discourage the use of search engines/etc to find "interesting" constant strings here. 24 | 25 | `RiskyRequests` is set to true if the C2 is a server run by/for the developers of the sample in question. If it's not true, then the C2 is a third-party server, like a big git forge, blog, social network or so on. 26 | 27 | ## Command line documentation 28 | 29 | Command line help is available, see `freevpnc help` etc. 30 | 31 | `freevpnc list` will only show providers with risky requests, and `freevpnc getall` will only make risky requests, if `-r` option is used. 32 | 33 | However, `freevpn get` will always make risky requests, if that provider makes them, as the provider to get is specified on the command line in that case. 34 | 35 | ### Examples 36 | 37 | Get all OpenVPN configs without risky requests: `freevpnc getall -i openvpn` 38 | 39 | Get all OpenVPN and v2ray configs without risky requests: `freevpnc getall -i openvpn v2ray` 40 | 41 | Get all configs except OpenVPN, without risky requests: `freevpnc getall -e openvpn` 42 | 43 | List all providers that provide OpenVPN servers or SSH tunnels, including those that would make risky requests: `freevpnc list -i openvpn ssh -r` 44 | 45 | Get all configs from provider `BeautyBird`: `freevpnc get BeautyBird` 46 | 47 | ## License 48 | 49 | All code in this repository is under the AGPLv3 license, albeit the XXTEA implementation is derived from https://github.com/xxtea/xxtea-dotnet (MIT licensed). 50 | 51 | AGPLv3 license was chosen to discourage certain types of people from using this codebase. 52 | If you want to use this codebase on any kind of "money site" or adware-filled mobile app - this means you! 53 | 54 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/ShJhk.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Memecrypto; 2 | using LibFreeVPN.ProviderHelpers; 3 | using LibFreeVPN.Servers; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Text; 10 | using System.Text.Json; 11 | using System.Threading.Tasks; 12 | 13 | // Android apps. SocksHttp using SSH + OpenVPN. 14 | // All of these by same developer, same github account used, xxtea + custom unicode rot. 15 | namespace LibFreeVPN.Providers.ShJhk 16 | { 17 | public abstract class ParserBase : SocksHttpWithOvpnNumericParserTea 18 | where TType : ParserBase, new() 19 | { 20 | protected override string DecryptOuter(string ciphertext) 21 | { 22 | return DecryptInner(ciphertext); 23 | } 24 | } 25 | public sealed class Parser4669 : ParserBase 26 | { 27 | protected override int InnerKey => 4669; 28 | } 29 | 30 | public abstract class ShJhkBase : VPNProviderGithubRepoFileBase 31 | where TParser : ParserBase, new() 32 | { 33 | 34 | protected override string ConfigName => Encoding.ASCII.FromBase64String("dXBkYXRlcw=="); 35 | 36 | public override bool HasProtocol(ServerProtocol protocol) => 37 | protocol == ServerProtocol.SSH || protocol == ServerProtocol.OpenVPN; 38 | } 39 | 40 | public sealed class ShJk : ShJhkBase 41 | { 42 | 43 | protected override string RepoName => Encoding.ASCII.FromBase64String("SkhLVlBOL0pL"); 44 | 45 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5mYXN0dnBuLmpr"; 46 | 47 | public override string SampleVersion => "2.2"; 48 | } 49 | 50 | public sealed class ShJkV : ShJhkBase 51 | { 52 | protected override string RepoName => Encoding.ASCII.FromBase64String("SkhLVlBOL0pWUE5WSVA="); 53 | 54 | public override string SampleSource => "aHR0cHM6Ly9naXRodWIuY29tL1RIQU5EQVJMSU4yMDE1L1Rlc3QvcmVsZWFzZXMvZG93bmxvYWQvdjIuMC4wL0pLLlZJUC5WUE5fMi4wLjAuYXBr"; 55 | 56 | public override string SampleVersion => "2.0.0"; 57 | } 58 | 59 | public sealed class ShMmt : ShJhkBase 60 | { 61 | protected override string RepoName => Encoding.ASCII.FromBase64String("SkhLVlBOL01NVFZQTg=="); 62 | 63 | // No sample found - saw in repo list and observed to use the same memecrypto as the others here 64 | public override string SampleSource => "aHR0cHM6Ly9naXRodWIuY29tL0pIS1ZQTi9NTVRWUE4="; 65 | 66 | public override string SampleVersion => "N/A"; 67 | } 68 | 69 | public sealed class ShKo : ShJhkBase 70 | { 71 | protected override string RepoName => Encoding.ASCII.FromBase64String("SkhLVlBOL0tPS08="); 72 | 73 | // No sample found - saw in repo list and observed to use the same memecrypto as the others here 74 | public override string SampleSource => "aHR0cHM6Ly9naXRodWIuY29tL0pIS1ZQTi9LT0tP"; 75 | 76 | public override string SampleVersion => "N/A"; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /LibFreeVPN/Servers/SSHServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LibFreeVPN.Servers 7 | { 8 | public class SSHServer : VPNServerBase 9 | { 10 | public override ServerProtocol Protocol => ServerProtocol.SSH; 11 | 12 | private readonly string m_Config; 13 | 14 | public override string Config => m_Config; 15 | 16 | private static readonly string s_InvalidChars = "'`\";&<>|(){}"; 17 | 18 | private static bool ValidUsername(string username) 19 | { 20 | // disallow '-' at start 21 | if (username[0] == '-') return false; 22 | // disallow '\\' at end 23 | if (username[username.Length - 1] == '\\') return false; 24 | for (int i = 0; i < username.Length; i++) 25 | { 26 | var chr = new string(username[i], 1); 27 | if (s_InvalidChars.Contains(chr)) return false; 28 | // Disallow '-' after whitespace 29 | if (string.IsNullOrWhiteSpace(chr)) 30 | { 31 | if (i < (username.Length - 1) && username[i + 1] == '-') return false; 32 | } 33 | } 34 | return true; 35 | } 36 | 37 | public SSHServer(string hostname, string port, string username, string password) : base(hostname, port, username, password) 38 | { 39 | var sb = new StringBuilder(); 40 | var registryPass = Registry[ServerRegistryKeys.Password]; 41 | if (registryPass.StartsWith("-----BEGIN ") && registryPass.Contains("-----END ") && registryPass.Contains(" PRIVATE KEY-----") && ServerUtilities.NewLines.Any((nl) => registryPass.Contains(nl))) 42 | { 43 | sb.AppendLine("# Private key:"); 44 | sb.AppendLine(registryPass); 45 | } 46 | else 47 | { 48 | sb.AppendFormat("# Password: {0}", Registry[ServerRegistryKeys.Password]); 49 | } 50 | sb.AppendLine(); 51 | // Some providers use shell metacharacters in usernames. 52 | // OpenSSH 9.6 (specifically, since this commit: https://github.com/openbsd/src/commit/ba05a7aae989020b8d05cc93cc6200109bba5a7b) disallows providing them, as part of a defense in depth approach. 53 | // The official workaround here is to use an SSH config file. 54 | // Reimplement the username checks done by OpenSSH and provide a note here. 55 | if (!ValidUsername(Registry[ServerRegistryKeys.Username])) 56 | { 57 | sb.AppendLine("# Username is considered invalid by OpenSSH 9.6 and later when passed on the command line."); 58 | } 59 | sb.AppendFormat("ssh -p {2} -D 1080 -q -N -f \"{0}@{1}\"", 60 | Registry[ServerRegistryKeys.Username], 61 | Registry[ServerRegistryKeys.Hostname], 62 | Registry[ServerRegistryKeys.Port]); 63 | sb.AppendLine(); 64 | m_Config = sb.ToString(); 65 | } 66 | 67 | public SSHServer(string hostname, string port, string username, string password, IReadOnlyDictionary extraRegistry) : this(hostname, port, username, password) 68 | { 69 | foreach (var pair in extraRegistry) m_Registry.Add(pair.Key, pair.Value); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/ShNo.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Memecrypto; 2 | using LibFreeVPN.ProviderHelpers; 3 | using LibFreeVPN.Servers; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Text; 10 | using System.Text.Json; 11 | using System.Threading.Tasks; 12 | 13 | // Android apps. SocksHttp using SSH + OpenVPN + V2Ray. 14 | // All of these by same developer, same github repo used, same xxtea constant, differs only in xxtea keys. 15 | namespace LibFreeVPN.Providers.ShNo 16 | { 17 | public abstract class ShNoBase : VPNProviderGithubRepoFileBase 18 | where TParser : SocksHttpWithOvpnParserTea, new() 19 | { 20 | public override bool HasProtocol(ServerProtocol protocol) => 21 | protocol == ServerProtocol.SSH || protocol == ServerProtocol.OpenVPN || protocol == ServerProtocol.V2Ray; 22 | 23 | protected override string RepoName => Encoding.ASCII.FromBase64String("QW51cmFrMjUzNC9vaG12cG4="); 24 | } 25 | 26 | public sealed class ShNoo : ShNoBase 27 | { 28 | public sealed class Parser : SocksHttpWithOvpnParserTea 29 | { 30 | protected override string OuterKey => Encoding.ASCII.FromBase64String("b2htMDkwNTI5"); 31 | } 32 | 33 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS53aWxsYmVhbG9uZS5vaG12cG4="; 34 | 35 | public override string SampleVersion => "4.5"; 36 | 37 | protected override string ConfigName => Encoding.ASCII.FromBase64String("b2htLmpzb24="); 38 | } 39 | 40 | public sealed class ShNosa : ShNoBase 41 | { 42 | public sealed class Parser : SocksHttpWithOvpnParserTea 43 | { 44 | protected override string OuterKey => Encoding.ASCII.FromBase64String("c2F0aHUyMDA1MzA="); 45 | } 46 | 47 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5wdWJsaWNzZXJ2aWNlc3NoLnNhdGh1dnBu"; 48 | 49 | public override string SampleVersion => "2.5"; 50 | 51 | protected override string ConfigName => Encoding.ASCII.FromBase64String("c2F0aHUuanNvbg=="); 52 | } 53 | 54 | public sealed class ShNona : ShNoBase 55 | { 56 | public sealed class Parser : SocksHttpWithOvpnParserTea 57 | { 58 | protected override string OuterKey => Encoding.ASCII.FromBase64String("bmFtbzA5MDUyOQ=="); 59 | } 60 | 61 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5hcHB4cmF5c2VydmljZS5uYW1vdnBu"; 62 | 63 | public override string SampleVersion => "2.5"; 64 | 65 | protected override string ConfigName => Encoding.ASCII.FromBase64String("bmFtby5qc29u"); 66 | } 67 | 68 | public sealed class ShNogo : ShNoBase 69 | { 70 | public sealed class Parser : SocksHttpWithOvpnParserTea 71 | { 72 | protected override string OuterKey => Encoding.ASCII.FromBase64String("Z29vZHZwbjA5MDUyOQ=="); 73 | } 74 | 75 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5vdnBuc2V2aWNlLmdvb2R2cG4="; 76 | 77 | public override string SampleVersion => "2.5"; 78 | 79 | protected override string ConfigName => Encoding.ASCII.FromBase64String("Z29vZHZwbi5qc29u"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/Cdcsv.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.ProviderHelpers; 2 | using LibFreeVPN.Servers; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Text.Json; 10 | using System.Threading.Tasks; 11 | using System.Xml.Linq; 12 | 13 | namespace LibFreeVPN.Providers 14 | { 15 | public class Cscdv : VPNProviderHttpGetBase 16 | { 17 | // Android app, openvpn + ikev2 - we only care about openvpn due to unreliability of ikev2 clients. 18 | 19 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5kb3JhY29uZS5zdGFndnBu"; 20 | 21 | public override string SampleVersion => "17.3.15"; 22 | 23 | public override bool HasProtocol(ServerProtocol protocol) 24 | { 25 | return protocol == ServerProtocol.OpenVPN; 26 | } 27 | 28 | 29 | private static readonly string s_ConfigKey = Encoding.ASCII.GetString(Convert.FromBase64String("b3BuY29uZmln")); 30 | private static readonly string s_UsernameKey = Encoding.ASCII.GetString(Convert.FromBase64String("aWtldjJfdXNlcl9uYW1l")); 31 | private static readonly string s_PasswordKey = Encoding.ASCII.GetString(Convert.FromBase64String("aWtldjJfdXNlcl9wYXNzd29yZA==")); 32 | private static readonly string s_CountryKey = Encoding.ASCII.GetString(Convert.FromBase64String("c2VydmVyX25hbWU=")); 33 | private static readonly string s_ArrayKey = Encoding.ASCII.GetString(Convert.FromBase64String("aWtldjJzZXJ2ZXJz")); 34 | private static IEnumerable ParseJson(JsonElement json, string selfName) 35 | { 36 | var empty = Enumerable.Empty(); 37 | return json.EnumerateArray().SelectMany((obj) => 38 | { 39 | if (!obj.TryGetProperty(s_ConfigKey, out var config)) return empty; 40 | var configStr = config.GetString(); 41 | if (string.IsNullOrEmpty(configStr)) return empty; 42 | if (!obj.TryGetProperty(s_UsernameKey, out var username)) return empty; 43 | if (!obj.TryGetProperty(s_PasswordKey, out var password)) return empty; 44 | 45 | var extraRegistry = CreateExtraRegistry(selfName); 46 | extraRegistry.Add(ServerRegistryKeys.Username, username.GetString()); 47 | extraRegistry.Add(ServerRegistryKeys.Password, password.GetString()); 48 | 49 | if (obj.TryGetProperty(s_CountryKey, out var country)) 50 | { 51 | extraRegistry.Add(ServerRegistryKeys.Country, country.GetString()); 52 | extraRegistry.Add(ServerRegistryKeys.DisplayName, country.GetString()); 53 | } 54 | 55 | return OpenVpnServer.ParseConfigFull(config.GetString(), extraRegistry); 56 | }); 57 | } 58 | 59 | protected override string RequestUri => Encoding.ASCII.GetString(Convert.FromBase64String("aHR0cHM6Ly9zZXJ2ZXIuc3RhZ3Zwbi5jb20vYXBpL3YxL2FwcC1kYXRhLW5ldw==")); 60 | protected override Task> GetServersAsyncImpl(string config) 61 | { 62 | var json = JsonDocument.Parse(config); 63 | 64 | if (json.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 65 | if (!json.RootElement.TryGetProperty(s_ArrayKey, out var array)) throw new InvalidDataException(); 66 | if (array.ValueKind != JsonValueKind.Array) throw new InvalidDataException(); 67 | 68 | return Task.FromResult(ParseJson(array, Name).Distinct()); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/ShV2x.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.ProviderHelpers; 2 | using LibFreeVPN.Servers; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Text.Json; 9 | using System.Threading.Tasks; 10 | 11 | // Android apps, sockshttp fork with v2ray only. 12 | // Risky requests made - C2 attempts to track users for being able to only send "premium" servers to paid users. 13 | namespace LibFreeVPN.Providers.ShV2x 14 | { 15 | public sealed class Parser : SocksHttpParserAesPbkdf2 16 | { 17 | protected override string ServersArrayKey => "servers"; 18 | protected override string ServerNameKey => "name"; 19 | protected override string CountryNameKey => "flag"; 20 | protected override string V2RayKey => "Connection_Config"; 21 | protected override string ServerTypeKey => "OTHER"; 22 | 23 | protected override string OuterKeyId => Encoding.ASCII.FromBase64String("cFhQV1VqRm0waFc2MTJ0YXY1RXo="); 24 | protected override IEnumerable ParseServer(JsonDocument root, JsonElement server, IReadOnlyDictionary passedExtraRegistry) 25 | { 26 | string name, country, v2ray; 27 | 28 | if (!server.TryGetPropertyString(ServerNameKey, out name)) throw new InvalidDataException(); 29 | if (!server.TryGetPropertyString(CountryNameKey, out country)) throw new InvalidDataException(); 30 | if (!server.TryGetProperty(V2RayKey, out var v2rayObj)) throw new InvalidDataException(); 31 | if (v2rayObj.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 32 | if (!v2rayObj.TryGetPropertyString(ServerTypeKey, out v2ray)) throw new InvalidDataException(); 33 | // no trusting the client here, "premium" servers give a dummy config for unregistered user: 34 | if (v2ray.StartsWith("vmess://eyJhZGQiOiI5OCIsImFpZCI6IjAiLC")) throw new InvalidDataException(); 35 | 36 | var extraRegistry = new Dictionary(); 37 | foreach (var kv in passedExtraRegistry) extraRegistry.Add(kv.Key, kv.Value); 38 | extraRegistry.Add(ServerRegistryKeys.DisplayName, name); 39 | extraRegistry.Add(ServerRegistryKeys.Country, country); 40 | return V2RayServer.ParseConfigFull(v2ray, extraRegistry); 41 | } 42 | } 43 | 44 | public abstract class ProviderBase : VPNProviderHttpGetBase 45 | { 46 | protected override Task> GetServersAsyncImpl(string config) 47 | { 48 | var index = config.LastIndexOf("
\n"); 49 | if (index >= 0) 50 | { 51 | config = config.Substring(index + "
\n".Length); 52 | } 53 | 54 | return GetServersAsyncImpl(config); 55 | } 56 | } 57 | 58 | public sealed class ShV2xSup : ProviderBase 59 | { 60 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS52MnJheXguc3VwcmF2cG4="; 61 | 62 | public override string SampleVersion => "1.9.20"; 63 | 64 | protected override string RequestUri => Encoding.ASCII.FromBase64String( 65 | "aHR0cHM6Ly9meGdvbGRlbnNpZ25hbHMuY29tL1RvcFZQTi92Mi9hcGkvbWFpbi5waHA/YWNjZXNzX3Rva2VuPSZHZXRTZXJ2ZXJMaXN0PSZkZWZ1YWx0U2VydmVySUQ9JmRldmljZV9pZD0maXNwPU1UTiZ2ZXJzaW9uPTU1NSZpc1BhcnRuZXJzaGlwPUZBTFNF" 66 | ); 67 | 68 | public override bool HasProtocol(ServerProtocol protocol) 69 | => protocol == ServerProtocol.V2Ray; 70 | 71 | 72 | } 73 | 74 | public sealed class ShV2xNet : ProviderBase 75 | { 76 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS52Mm5ldC52MnJheS52cG4="; 77 | 78 | public override string SampleVersion => "1.9.16.6"; 79 | 80 | protected override string RequestUri => Encoding.ASCII.FromBase64String( 81 | "aHR0cHM6Ly9hcGkudjJuZXQubGl2ZS92Mm5ldC92Mi9hcGkvbWFpbi5waHA/YWNjZXNzX3Rva2VuPSZHZXRTZXJ2ZXJMaXN0PSZkZWZ1YWx0U2VydmVySUQ9JmRldmljZV9pZD0maXNwPU1UTiZ2ZXJzaW9uPTU1NSZpc1BhcnRuZXJzaGlwPUZBTFNF" 82 | ); 83 | 84 | public override bool HasProtocol(ServerProtocol protocol) 85 | => protocol == ServerProtocol.V2Ray; 86 | 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/WhiteLabelVpnAppApi1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Text; 5 | using System.Text.Json; 6 | using System.Threading.Tasks; 7 | using System.Linq; 8 | using System.IO; 9 | using LibFreeVPN.Servers; 10 | 11 | namespace LibFreeVPN.Providers 12 | { 13 | public class WhiteLabelVpnAppApi1 : VPNProviderBase 14 | { 15 | // example of sample that uses "b25lY29ubmVjdA==" API to provide openvpn configs. 16 | // POST data, URL, and JSON object keys lightly obfuscated given the nature of the white-labeled API used here. 17 | // sample may be changed in future if one is found with a higher-plan API key :) 18 | public override string Name => s_Name; 19 | private static readonly string s_Name = nameof(WhiteLabelVpnAppApi1); 20 | 21 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS54bngudnBuYmx1ZXByb3h5"; 22 | 23 | public override string SampleVersion => "1.61"; 24 | 25 | public override bool RiskyRequests => true; 26 | 27 | public override bool HasProtocol(ServerProtocol protocol) 28 | { 29 | return protocol == ServerProtocol.OpenVPN; 30 | } 31 | 32 | 33 | private static readonly string s_ConfigKey = Encoding.ASCII.GetString(Convert.FromBase64String("b3ZwbkNvbmZpZ3VyYXRpb24=")); 34 | private static readonly string s_UsernameKey = Encoding.ASCII.GetString(Convert.FromBase64String("dnBuVXNlck5hbWU=")); 35 | private static readonly string s_PasswordKey = Encoding.ASCII.GetString(Convert.FromBase64String("dnBuUGFzc3dvcmQ=")); 36 | private static readonly string s_DisplayNameKey = Encoding.ASCII.GetString(Convert.FromBase64String("c2VydmVyTmFtZQ==")); 37 | private static readonly string s_CountryKey = Encoding.ASCII.GetString(Convert.FromBase64String("Y291bnRyeQ==")); 38 | private static IEnumerable ParseJson(JsonDocument json) 39 | { 40 | var empty = Enumerable.Empty(); 41 | return json.RootElement.EnumerateArray().SelectMany((obj) => 42 | { 43 | if (!obj.TryGetProperty(s_ConfigKey, out var config)) return empty; 44 | if (!obj.TryGetProperty(s_UsernameKey, out var username)) return empty; 45 | if (!obj.TryGetProperty(s_PasswordKey, out var password)) return empty; 46 | 47 | var extraRegistry = CreateExtraRegistry(s_Name); 48 | extraRegistry.Add(ServerRegistryKeys.Username, username.GetString()); 49 | extraRegistry.Add(ServerRegistryKeys.Password, password.GetString()); 50 | 51 | if (obj.TryGetProperty(s_DisplayNameKey, out var displayName)) 52 | { 53 | extraRegistry.Add(ServerRegistryKeys.DisplayName, displayName.GetString()); 54 | } 55 | 56 | if (obj.TryGetProperty(s_CountryKey, out var country)) 57 | { 58 | extraRegistry.Add(ServerRegistryKeys.Country, country.GetString()); 59 | } 60 | 61 | return OpenVpnServer.ParseConfigFull(config.GetString(), extraRegistry); 62 | }); 63 | } 64 | 65 | private static readonly string s_RequestUri = Encoding.ASCII.GetString(Convert.FromBase64String("aHR0cHM6Ly9kZXZlbG9wZXIub25lY29ubmVjdC50b3Avdmlldy9mcm9udC9jb250cm9sbGVyLnBocA==")); 66 | private static readonly byte[] s_PostData = Convert.FromBase64String("YWN0aW9uPWZldGNoVXNlclNlcnZlcnMmcGFja2FnZV9uYW1lPWNvbS54bngudnBuYmx1ZXByb3h5JmFwaV9rZXk9QUVJRU1tLlVqSWRQLld0Sy5RdUtjYThZWlFxaVd2blVUR0FRZ2tZcndURXBWYUZpaU4mdHlwZT1wcm8="); 67 | protected override async Task> GetServersAsyncImpl() 68 | { 69 | // single POST request gives out some JSON. 70 | var httpClient = ServerUtilities.HttpClient; 71 | var reqContent = new ByteArrayContent(s_PostData); 72 | reqContent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"); 73 | var post = await httpClient.PostAsync( 74 | s_RequestUri, 75 | reqContent 76 | ); 77 | 78 | var content = await post.Content.ReadAsStringAsync(); 79 | var json = JsonDocument.Parse(content); 80 | // On failure, an object is returned. 81 | // On success, an array of objects is returned. 82 | // So if we didn't get an array, throw exception. 83 | if (json.RootElement.ValueKind != JsonValueKind.Array) 84 | { 85 | throw new InvalidDataException(); 86 | } 87 | 88 | return ParseJson(json); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /LibFreeVPN/VPNServerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LibFreeVPN 7 | { 8 | public abstract class VPNServerBase : IVPNServer 9 | { 10 | protected readonly Dictionary m_Registry = new Dictionary(); 11 | public abstract ServerProtocol Protocol { get; } 12 | public virtual string Config => string.Empty; 13 | public IReadOnlyDictionary Registry => m_Registry; 14 | public bool Equals(IVPNServer other) 15 | { 16 | if (Protocol != other.Protocol) return false; 17 | // empty hostname means the actual hostname is unknown due to potential use of domain fronting (etc) 18 | if (Registry.TryGetValue(ServerRegistryKeys.Hostname, out var xHost) && other.Registry.TryGetValue(ServerRegistryKeys.Hostname, out var yHost) && !string.IsNullOrEmpty(xHost) && !string.IsNullOrEmpty(yHost)) 19 | return xHost == yHost; 20 | return Config == other.Config; 21 | } 22 | 23 | public override int GetHashCode() 24 | { 25 | var hashProtocol = Protocol.GetHashCode(); 26 | if (!Registry.TryGetValue(ServerRegistryKeys.Hostname, out var strToHash) && !string.IsNullOrEmpty(strToHash)) 27 | strToHash = Config; 28 | return hashProtocol ^ strToHash.GetHashCode(); 29 | } 30 | 31 | protected VPNServerBase() { } 32 | 33 | public VPNServerBase(string hostname, string port) 34 | { 35 | m_Registry.Add(ServerRegistryKeys.Hostname, hostname); 36 | m_Registry.Add(ServerRegistryKeys.Port, port); 37 | } 38 | 39 | public VPNServerBase(string hostname, string port, string username) : this(hostname, port) 40 | { 41 | m_Registry.Add(ServerRegistryKeys.Username, username); 42 | } 43 | 44 | public VPNServerBase(string hostname, string port, string username, string password) : this(hostname, port, username) 45 | { 46 | m_Registry.Add(ServerRegistryKeys.Password, password); 47 | } 48 | } 49 | 50 | /// 51 | /// Base class for VPN servers where configs are parsed. 52 | /// 53 | /// Type of self 54 | /// Config parser 55 | public abstract class VPNServerBase : VPNServerBase 56 | where TSelf : VPNServerBase 57 | where TParser : IVPNServerConfigParser, new() 58 | { 59 | private static readonly TParser s_Parser = new TParser(); 60 | 61 | private readonly string m_Config; 62 | 63 | public override string Config => s_Parser.GetUserVisibleConfig(m_Config, m_Registry); 64 | 65 | public VPNServerBase(string config) 66 | { 67 | m_Config = config; 68 | } 69 | 70 | public VPNServerBase(string config, string hostname, string port) : this(config) 71 | { 72 | m_Registry.Add(ServerRegistryKeys.Hostname, hostname); 73 | m_Registry.Add(ServerRegistryKeys.Port, port); 74 | } 75 | 76 | public VPNServerBase(string config, IReadOnlyDictionary extraRegistry) : this(config) 77 | { 78 | foreach (var pair in extraRegistry) m_Registry.Add(pair.Key, pair.Value); 79 | } 80 | 81 | public VPNServerBase(string config, string hostname, string port, IReadOnlyDictionary extraRegistry) : this(config, hostname, port) 82 | { 83 | foreach (var pair in extraRegistry) m_Registry.Add(pair.Key, pair.Value); 84 | } 85 | 86 | public static IEnumerable ParseConfig(string config) 87 | { 88 | if (!s_Parser.CanCreateInstance(false)) return Enumerable.Empty(); 89 | var extraRegistry = new Dictionary(); 90 | s_Parser.AddExtraProperties(extraRegistry, config); 91 | return s_Parser.ParseConfig(config).Select((tuple) => s_Parser.CreateInstance(tuple.hostname, tuple.port, extraRegistry)); 92 | } 93 | 94 | public static IEnumerable ParseConfig(string config, Dictionary extraRegistry) 95 | { 96 | if (!s_Parser.CanCreateInstance(false)) return Enumerable.Empty(); 97 | s_Parser.AddExtraProperties(extraRegistry, config); 98 | return s_Parser.ParseConfig(config).Select((tuple) => s_Parser.CreateInstance(tuple.hostname, tuple.port, extraRegistry)); 99 | } 100 | 101 | public static IEnumerable ParseConfigFull(string config) 102 | { 103 | if (!s_Parser.CanCreateInstance(true)) return Enumerable.Empty(); 104 | var extraRegistry = new Dictionary(); 105 | s_Parser.AddExtraProperties(extraRegistry, config); 106 | return s_Parser.ParseConfigFull(config).Select((tuple) => s_Parser.CreateInstance(tuple.config, tuple.hostname, tuple.port, extraRegistry)); 107 | } 108 | 109 | public static IEnumerable ParseConfigFull(string config, Dictionary extraRegistry) 110 | { 111 | if (!s_Parser.CanCreateInstance(true)) return Enumerable.Empty(); 112 | s_Parser.AddExtraProperties(extraRegistry, config); 113 | return s_Parser.ParseConfigFull(config).Select((tuple) => s_Parser.CreateInstance(tuple.config, tuple.hostname, tuple.port, extraRegistry)); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/BeautyBird.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Servers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | using System.Text.Json; 10 | using System.Threading.Tasks; 11 | using System.Xml.Linq; 12 | 13 | namespace LibFreeVPN.Providers 14 | { 15 | // iOS app. v2ray using "Surge" client. C2 is a github repo. 16 | // Configs are split by "country" but this seems meaningless, one check of "US" config gave two endpoints, one in .nl and one in .dk 17 | // Configs are updated multiple times per hour. 18 | // Each config includes at least one endpoint entunneling through websockets on cloudflare with randomly generated registered domains. 19 | public sealed class BeautyBird : VPNProviderBase 20 | { 21 | private static readonly string s_Name = nameof(BeautyBird); 22 | public override string Name => s_Name; 23 | 24 | public override string SampleSource => "aHR0cHM6Ly9hcHBzLmFwcGxlLmNvbS91cy9hcHAvdnBuLWZyZWUtdnBuLWZhc3QvaWQ2NTA0NjM1ODcz"; 25 | 26 | public override string SampleVersion => "1.1.8"; 27 | 28 | public override bool RiskyRequests => false; 29 | 30 | public override bool HasProtocol(ServerProtocol protocol) => 31 | protocol == ServerProtocol.V2Ray; 32 | 33 | private static readonly byte[] s_AesKey = 34 | { 35 | 0x42, 0x34, 0x18, 0x35, 0x29, 0xe5, 0xd9, 0x3e, 0xc1, 0x69, 0x76, 0xa0, 0x40, 0xad, 0x7d, 0xbc, 36 | 0x7c, 0x92, 0x70, 0x8a, 0x0f, 0xf8, 0x5a, 0xb7, 0x0b, 0x83, 0x35, 0xca, 0x7f, 0x4d, 0x37, 0x3d 37 | }; 38 | 39 | private static readonly byte[] s_AesIv = 40 | { 41 | 0x6a, 0x30, 0x6b, 0x70, 0x7f, 0xa9, 0x22, 0x40, 0x0c, 0x9d, 0x66, 0xa2, 0xbc, 0x6d, 0x12, 0x64 42 | }; 43 | 44 | private static readonly AesManaged s_AesAlgo = new AesManaged() 45 | { 46 | Padding = PaddingMode.PKCS7, 47 | Mode = CipherMode.CBC, 48 | KeySize = 256, 49 | BlockSize = 128 50 | }; 51 | 52 | private static readonly string s_RepoName = Encoding.ASCII.GetString(Convert.FromBase64String("R3lyZmFsY29uVlBOL25vZGVz")); 53 | 54 | private static IEnumerable ParseJson(JsonElement json) 55 | { 56 | var empty = Enumerable.Empty(); 57 | return json.EnumerateArray().SelectMany((obj) => 58 | { 59 | if (!obj.TryGetProperty("proxy", out var proxyElem)) return empty; 60 | var extraRegistry = CreateExtraRegistry(s_Name); 61 | if (obj.TryGetProperty("name", out var displayNameElem)) 62 | { 63 | extraRegistry.Add(ServerRegistryKeys.DisplayName, displayNameElem.GetString()); 64 | } 65 | 66 | var proxy = proxyElem.GetString(); 67 | if (proxy[0] == '{') 68 | return V2RayServer.ParseConfigFull(proxy, extraRegistry); 69 | return V2RayServerSurge.ParseConfigFull(proxy, extraRegistry); 70 | }); 71 | } 72 | 73 | private static async Task> GetServersAsync(string url) 74 | { 75 | var httpClient = ServerUtilities.HttpClient; 76 | var cipherText = Convert.FromBase64String(await httpClient.GetStringAsync(url)); 77 | var crypto = s_AesAlgo.CreateDecryptor(s_AesKey, s_AesIv); 78 | var plainText = Encoding.UTF8.GetString(crypto.TransformFinalBlock(cipherText, 0, cipherText.Length)); 79 | var configJson = JsonDocument.Parse(plainText); 80 | if (configJson.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 81 | if (!configJson.RootElement.TryGetProperty("code", out var codeElem)) throw new InvalidDataException(); 82 | if (codeElem.GetInt32() != 0) throw new InvalidDataException(); 83 | if (!configJson.RootElement.TryGetProperty("data", out var dataElem)) throw new InvalidDataException(); 84 | if (dataElem.ValueKind != JsonValueKind.Array) throw new InvalidDataException(); 85 | return ParseJson(dataElem); 86 | } 87 | 88 | private static async Task> TryGetServersAsync(string url) 89 | { 90 | try 91 | { 92 | return await GetServersAsync(url); 93 | } catch 94 | { 95 | return Enumerable.Empty(); 96 | } 97 | } 98 | 99 | protected override async Task> GetServersAsyncImpl() 100 | { 101 | var httpClient = ServerUtilities.HttpClient; 102 | // Get the list of files in the root of the repository 103 | HttpResponseMessage listResponse = null; 104 | using (var listRequest = new HttpRequestMessage(HttpMethod.Get, string.Format("https://github.com/{0}/tree-commit-info/main", s_RepoName))) 105 | { 106 | listRequest.Headers.Accept.ParseAdd("application/json"); 107 | listResponse = await httpClient.SendAsync(listRequest); 108 | } 109 | 110 | var listJsonStr = await listResponse.Content.ReadAsStringAsync(); 111 | var listJson = JsonDocument.Parse(listJsonStr); 112 | if (listJson.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 113 | // take the filenames we want, convert them to their URLs 114 | var configUrls = listJson.RootElement.EnumerateObject() 115 | .Where((prop) => prop.Name.StartsWith("node") && prop.Name.EndsWith(".txt")) 116 | .Select((prop) => string.Format("https://raw.githubusercontent.com/{0}/main/{1}", s_RepoName, prop.Name)); 117 | 118 | // for each of them, download and parse them all 119 | var configTasks = configUrls.Select((url) => TryGetServersAsync(url)).ToList(); 120 | // await all the tasks 121 | await Task.WhenAll(configTasks); 122 | 123 | // and squash them down to one list 124 | return configTasks.SelectMany((task) => task.Result).Distinct(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/Aob.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Servers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Text.Json; 9 | using System.Text.Json.Nodes; 10 | using System.Threading.Tasks; 11 | 12 | namespace LibFreeVPN.Providers 13 | { 14 | // Android apps. v2ray(vless) with regular v2ray-client json config. C2 is custom servers, pointed to by a github repo. 15 | // Some iterations point to dummy configs on github. 16 | public sealed class Aob : VPNProviderBase 17 | { 18 | private static readonly string s_Name = nameof(Aob); 19 | public override string Name => s_Name; 20 | 21 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5mdG9vbHMuYnJhdmV2cG4="; 22 | 23 | public override string SampleVersion => "13.6"; 24 | 25 | public override bool RiskyRequests => true; 26 | 27 | public override bool HasProtocol(ServerProtocol protocol) => 28 | protocol == ServerProtocol.V2Ray; 29 | 30 | private static readonly string s_RepoName = Encoding.ASCII.GetString(Convert.FromBase64String("YXBwb25ib2FyZDIwMTkvYXBp")); 31 | 32 | private static readonly string s_DisplayNameKey = Encoding.ASCII.GetString(Convert.FromBase64String("c2VydmVyX2lk")); 33 | private static readonly string s_CountryKey = Encoding.ASCII.GetString(Convert.FromBase64String("aG9zdG5hbWU=")); 34 | 35 | private static readonly string s_FirstToSecondKey = Encoding.ASCII.GetString(Convert.FromBase64String("YmFja2VuZFVybHM=")); 36 | 37 | private static IEnumerable ParseJson(JsonElement json) 38 | { 39 | var empty = Enumerable.Empty(); 40 | return json.EnumerateArray().SelectMany((obj) => 41 | { 42 | if (!obj.TryGetProperty("config", out var config)) return empty; 43 | 44 | var extraRegistry = CreateExtraRegistry(s_Name); 45 | 46 | if (obj.TryGetProperty(s_DisplayNameKey, out var displayName)) 47 | { 48 | extraRegistry.Add(ServerRegistryKeys.DisplayName, displayName.GetString()); 49 | } 50 | 51 | if (obj.TryGetProperty(s_CountryKey, out var country)) 52 | { 53 | extraRegistry.Add(ServerRegistryKeys.Country, country.GetString()); 54 | } 55 | 56 | return V2RayServer.ParseConfigFull(config.ToString(), extraRegistry); 57 | }); 58 | } 59 | 60 | private static async Task> GetServersAsync(string url) 61 | { 62 | var httpClient = ServerUtilities.HttpClient; 63 | var firstConf = await httpClient.GetStringAsync(url); 64 | var firstJson = JsonDocument.Parse(firstConf); 65 | if (firstJson.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 66 | if (!firstJson.RootElement.TryGetProperty(s_FirstToSecondKey, out var backendElem)) throw new InvalidDataException(); 67 | if (backendElem.ValueKind != JsonValueKind.Array) throw new InvalidDataException(); 68 | string secondConf = null; 69 | foreach (var valueJson in backendElem.EnumerateArray()) 70 | { 71 | try 72 | { 73 | var secondUrl = valueJson.GetString(); 74 | if (secondUrl.StartsWith("https://raw.githubusercontent.com/")) continue; 75 | secondConf = await httpClient.GetStringAsync(secondUrl); 76 | break; 77 | } catch { } 78 | } 79 | 80 | if (string.IsNullOrEmpty(secondConf)) throw new InvalidDataException(); 81 | 82 | var secondJson = JsonDocument.Parse(secondConf); 83 | 84 | if (secondJson.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 85 | if (!secondJson.RootElement.TryGetProperty("servers", out var serversElem)) throw new InvalidDataException(); 86 | if (serversElem.ValueKind != JsonValueKind.Array) throw new InvalidDataException(); 87 | 88 | return ParseJson(serversElem); 89 | } 90 | 91 | private static async Task> TryGetServersAsync(string url) 92 | { 93 | try 94 | { 95 | return await GetServersAsync(url); 96 | } 97 | catch 98 | { 99 | return Enumerable.Empty(); 100 | } 101 | } 102 | 103 | protected override async Task> GetServersAsyncImpl() 104 | { 105 | var httpClient = ServerUtilities.HttpClient; 106 | // Get the list of files in the root of the repository 107 | HttpResponseMessage listResponse = null; 108 | using (var listRequest = new HttpRequestMessage(HttpMethod.Get, string.Format("https://github.com/{0}/tree-commit-info/master", s_RepoName))) 109 | { 110 | listRequest.Headers.Accept.ParseAdd("application/json"); 111 | listResponse = await httpClient.SendAsync(listRequest); 112 | } 113 | 114 | var listJsonStr = await listResponse.Content.ReadAsStringAsync(); 115 | var listJson = JsonDocument.Parse(listJsonStr); 116 | if (listJson.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 117 | // take the filenames we want, convert them to their URLs 118 | var configUrls = listJson.RootElement.EnumerateObject() 119 | .Where((prop) => prop.Name.EndsWith(".txt")) 120 | .Select((prop) => string.Format("https://raw.githubusercontent.com/{0}/master/{1}", s_RepoName, prop.Name)); 121 | 122 | // for each of them, download and parse them all 123 | var configTasks = configUrls.Select((url) => TryGetServersAsync(url)).ToList(); 124 | // await all the tasks 125 | await Task.WhenAll(configTasks); 126 | 127 | // and squash them down to one list 128 | return configTasks.SelectMany((task) => task.Result).Distinct(); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /LibFreeVPN/VPNGenericMultiProviderParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Text.Json; 8 | using System.Text.Json.Nodes; 9 | 10 | namespace LibFreeVPN 11 | { 12 | /// 13 | /// Base class for a generic multi-provider parser 14 | /// 15 | /// Object implementing this class 16 | public abstract class VPNGenericMultiProviderParser : IVPNGenericMultiProviderParser 17 | where TParser : VPNGenericMultiProviderParser, new() 18 | { 19 | private static readonly TParser s_Parser = new TParser(); 20 | 21 | public abstract IEnumerable Parse(string config, IReadOnlyDictionary extraRegistry); 22 | public static IEnumerable ParseConfig(string config) 23 | { 24 | return ParseConfig(config, new Dictionary()); 25 | } 26 | 27 | public static IEnumerable ParseConfig(string config, IReadOnlyDictionary extraRegistry) 28 | { 29 | try 30 | { 31 | return s_Parser.Parse(config, extraRegistry).Distinct(); 32 | } 33 | catch { return Enumerable.Empty(); } 34 | } 35 | } 36 | /// 37 | /// Base class for a multi-provider parser for a JSON object with an array of servers where 38 | /// 39 | /// Object implementing this class 40 | public abstract class VPNJsonArrInObjMultiProviderParser : VPNGenericMultiProviderParser 41 | where TParser : VPNJsonArrInObjMultiProviderParser, new() 42 | { 43 | // Base object 44 | /// 45 | /// JSON key of the servers array. 46 | /// 47 | protected virtual string ServersArrayKey => "Servers"; 48 | 49 | /// 50 | /// Enumerator over any additional (optional) servers arrays. 51 | /// 52 | protected virtual IEnumerable OptionalServersArrayKeys => Enumerable.Empty(); 53 | 54 | /// 55 | /// Decrypt outer ciphertext (entire json object) if required 56 | /// 57 | /// Ciphertext 58 | /// Plaintext 59 | protected virtual string DecryptOuter(string ciphertext) => ciphertext; 60 | 61 | /// 62 | /// Decrypt inner ciphertext (json string value) if required 63 | /// 64 | /// JSON object key 65 | /// Ciphertext 66 | /// Plaintext 67 | protected virtual string DecryptInner(string jsonKey, string ciphertext) => ciphertext; 68 | 69 | protected abstract IEnumerable ParseServer(JsonDocument root, JsonElement server, IReadOnlyDictionary extraRegistry); 70 | 71 | private static bool MethodIsDerived(MethodInfo baseMethod, MethodInfo derivedMethod) 72 | { 73 | return baseMethod.DeclaringType != derivedMethod.DeclaringType && baseMethod.GetBaseDefinition() == derivedMethod.GetBaseDefinition(); 74 | } 75 | 76 | private static readonly Type[] s_DecryptInnerArgs = 77 | { 78 | typeof(string), typeof(string) 79 | }; 80 | 81 | private static readonly bool s_DecryptInnerIsDerived = MethodIsDerived( 82 | typeof(VPNJsonArrInObjMultiProviderParser).GetMethod("DecryptInner", BindingFlags.Instance | BindingFlags.NonPublic, null, s_DecryptInnerArgs, null), 83 | typeof(TParser).GetMethod("DecryptInner", BindingFlags.Instance | BindingFlags.NonPublic, null, s_DecryptInnerArgs, null) 84 | ); 85 | 86 | private JsonElement DecryptServer(JsonElement server) 87 | { 88 | if (server.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 89 | // If DecryptInner wasn't overridden then this is a no-op anyway. 90 | if (!s_DecryptInnerIsDerived) return server; 91 | var ret = new JsonObject(); 92 | foreach (var elem in server.EnumerateObject()) 93 | { 94 | if (elem.Value.ValueKind != JsonValueKind.String) 95 | { 96 | // BUGBUG: any objects/arrays with crypted strings are still untouched here 97 | ret.Add(elem.Name, JsonValue.Create(elem.Value)); 98 | continue; 99 | } 100 | 101 | ret.Add(elem.Name, DecryptInner(elem.Name, elem.Value.GetString())); 102 | } 103 | return JsonDocument.Parse(ret.ToJsonString()).RootElement; 104 | } 105 | 106 | private IEnumerable TryParseServer(JsonDocument root, JsonElement server, IReadOnlyDictionary extraRegistry) 107 | { 108 | try 109 | { 110 | return ParseServer(root, DecryptServer(server), extraRegistry); 111 | } 112 | catch { return Enumerable.Empty(); } 113 | } 114 | 115 | public override sealed IEnumerable Parse(string config, IReadOnlyDictionary extraRegistry) 116 | { 117 | // Decrypt outer ciphertext 118 | config = DecryptOuter(config); 119 | 120 | var json = JsonDocument.Parse(config); 121 | if (json.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 122 | 123 | if (!json.RootElement.TryGetProperty(ServersArrayKey, out var servers)) throw new InvalidDataException(); 124 | if (servers.ValueKind != JsonValueKind.Array) throw new InvalidDataException(); 125 | 126 | IEnumerable serversEnum = servers.EnumerateArray(); 127 | foreach (var key in OptionalServersArrayKeys) 128 | { 129 | if (!json.RootElement.TryGetProperty(key, out var optional)) continue; 130 | if (optional.ValueKind != JsonValueKind.Array) continue; 131 | serversEnum = serversEnum.Concat(optional.EnumerateArray()); 132 | } 133 | 134 | return serversEnum.SelectMany((server) => TryParseServer(json, server, extraRegistry)).Distinct(); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /LibFreeVPN/Servers/V2RayServerSurge.cs: -------------------------------------------------------------------------------- 1 | using IniParser.Parser; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.Json; 8 | using System.Text.Json.Nodes; 9 | 10 | namespace LibFreeVPN.Servers 11 | { 12 | public sealed class V2RayServerSurge : VPNServerBase 13 | { 14 | 15 | public sealed class Parser : VPNServerConfigParserFullBase 16 | { 17 | public override V2RayServerSurge CreateInstance(string config) 18 | => new V2RayServerSurge(config); 19 | 20 | public override V2RayServerSurge CreateInstance(string config, string hostname, string port) 21 | => new V2RayServerSurge(config, hostname, port); 22 | 23 | public override V2RayServerSurge CreateInstance(string config, IReadOnlyDictionary extraRegistry) 24 | => new V2RayServerSurge(config, extraRegistry); 25 | 26 | public override V2RayServerSurge CreateInstance(string config, string hostname, string port, IReadOnlyDictionary extraRegistry) 27 | => new V2RayServerSurge(config, hostname, port, extraRegistry); 28 | 29 | public override V2RayServerSurge CreateInstance(string hostname, string port) 30 | { 31 | throw new NotSupportedException(); 32 | } 33 | 34 | public override V2RayServerSurge CreateInstance(string hostname, string port, IReadOnlyDictionary extraRegistry) 35 | { 36 | throw new NotSupportedException(); 37 | } 38 | 39 | public override bool CanCreateInstance(bool withConfig) 40 | => withConfig; 41 | 42 | private static IniDataParser s_IniParser = new IniDataParser(); 43 | 44 | private static char[] s_SplitKv = { '=' }; 45 | 46 | public override IEnumerable<(string config, string hostname, string port)> ParseConfigFull(string config) 47 | { 48 | s_IniParser.Configuration.SkipInvalidLines = true; 49 | var iniData = s_IniParser.Parse(config); 50 | 51 | var splitData = iniData["Proxy"] 52 | .Where((data) => data.Value.Contains(',')) 53 | .Select((data) => 54 | { 55 | var value = data.Value.Split(','); 56 | // protocol,hostname,port,kv1,kv2,kv3... 57 | // protocol://base64-json-config 58 | for (int i = 0; i < value.Length; i++) value[i] = value[i].Trim(); 59 | return value; 60 | }); 61 | 62 | 63 | // TODO: handle other protocols here where relevant 64 | // known other protocols to exist: ss (shadowsocks?), trojan 65 | 66 | var vmessData = splitData.Where((value) => value[0].ToLower() == "vmess") 67 | .Select((value) => 68 | { 69 | var dict = new Dictionary() 70 | { 71 | { "hostname", value[1] }, 72 | { "port", value[2] } 73 | }; 74 | for (int i = 3; i < value.Length; i++) 75 | { 76 | var kv = value[i].Split(s_SplitKv, 2); 77 | kv[0] = kv[0].Trim(); 78 | kv[1] = kv[1].Trim(); 79 | 80 | dict.Add(kv[0], kv[1]); 81 | } 82 | 83 | return dict; 84 | }) 85 | .Where((dict) => dict.ContainsKey("username")) 86 | .Select((dict) => 87 | { 88 | // BUGBUG: probably needs more work when more surge configs are found 89 | var hostname = dict["hostname"]; 90 | var port = dict["port"]; 91 | 92 | var jsonConfig = new JsonObject() 93 | { 94 | ["v"] = "2", 95 | ["ps"] = string.Format("{0}:{1}", hostname, port), 96 | ["add"] = hostname, 97 | ["port"] = port, 98 | ["id"] = dict["username"], 99 | ["aid"] = "0", 100 | ["net"] = dict.GetValue("ws").ToLower() == "true" ? "ws" : "tcp", 101 | ["scy"] = "auto", 102 | ["type"] = "none", 103 | ["host"] = dict.GetValue("ws-host"), 104 | ["path"] = dict.GetValue("ws-path"), 105 | ["tls"] = dict.GetValue("tls").ToLower() == "true" ? "tls" : "", 106 | ["sni"] = dict.GetValue("sni"), 107 | ["alpn"] = "" 108 | }; 109 | 110 | var thisConfig = string.Format("vmess://{0}", Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonConfig.ToJsonString()))); 111 | var ws_host = dict.GetValue("ws-host"); 112 | if (!string.IsNullOrEmpty(ws_host) && ws_host != hostname) hostname = string.Empty; 113 | return (thisConfig, hostname, port); 114 | }); 115 | 116 | return vmessData; 117 | } 118 | 119 | public override void AddExtraProperties(IDictionary registry, string config) 120 | { 121 | registry.Add(ServerRegistryKeys.OriginalConfigType, "surge"); 122 | registry.Add(ServerRegistryKeys.OriginalConfig, config); 123 | } 124 | } 125 | 126 | public override ServerProtocol Protocol => ServerProtocol.V2Ray; 127 | 128 | 129 | public V2RayServerSurge(string config) : base(config) 130 | { 131 | } 132 | 133 | public V2RayServerSurge(string config, string hostname, string port) : base(config, hostname, port) 134 | { 135 | } 136 | 137 | public V2RayServerSurge(string config, IReadOnlyDictionary extraRegistry) : base(config, extraRegistry) 138 | { 139 | } 140 | 141 | public V2RayServerSurge(string config, string hostname, string port, IReadOnlyDictionary extraRegistry) : base(config, hostname, port, extraRegistry) 142 | { 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/ShWrd.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Memecrypto; 2 | using LibFreeVPN.ProviderHelpers; 3 | using LibFreeVPN.Servers; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Security.Cryptography; 10 | using System.Text; 11 | using System.Text.Json; 12 | using System.Threading.Tasks; 13 | 14 | // Android apps. SocksHttp using SSH + OpenVPN 15 | // All of these by same developer. 16 | // Original apps use cname to github pages, this implementation hits the repo directly. 17 | namespace LibFreeVPN.Providers.ShWrd 18 | { 19 | public abstract class ShWrdBase : VPNProviderGithubRepoFileBase 20 | where TParser : SocksHttpParser, new() 21 | { 22 | 23 | protected override string ConfigName => Encoding.ASCII.FromBase64String("RmlsZS5qc29u"); 24 | 25 | public override bool HasProtocol(ServerProtocol protocol) => 26 | protocol == ServerProtocol.SSH || protocol == ServerProtocol.OpenVPN || protocol == ServerProtocol.V2Ray; 27 | 28 | 29 | 30 | protected override string RepoName => Encoding.ASCII.FromBase64String("aHVubWFpL3dhcnJpbmdkYQ=="); 31 | } 32 | 33 | public sealed class ShWrdPk : ShWrdBase 34 | { 35 | public sealed class Parser : SocksHttpWithOvpnParserTea 36 | { 37 | protected override string OuterKey => Encoding.ASCII.FromBase64String("cHVrYW5ndnBuMjcwQA==" ); 38 | } 39 | 40 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5wdWthbmd2cG4udGg="; 41 | 42 | public override string SampleVersion => "7.4"; 43 | 44 | protected override string ConfigName => Encoding.ASCII.FromBase64String("cHVrYW5ndnBuL3VwLWZpbGUuanNvbg=="); 45 | } 46 | 47 | public sealed class ShWrdKh : ShWrdBase 48 | { 49 | public sealed class Parser : SocksHttpParserTeaAes 50 | { 51 | protected override string OuterKey => Encoding.ASCII.FromBase64String("a2hhbXZwbjI3MEA="); 52 | } 53 | 54 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5oa2FtdnBuLm5ldA=="; 55 | 56 | public override string SampleVersion => "14.0"; 57 | 58 | protected override string ConfigName => Encoding.ASCII.FromBase64String("a2hhbXZwbi91cGRhdGUtZmlsZS5qc29u"); 59 | } 60 | 61 | public sealed class ShWrdMt : ShWrdBase 62 | { 63 | public sealed class Parser : SocksHttpWithOvpnParserTea 64 | { 65 | protected override string OuterKey => Encoding.ASCII.FromBase64String("bXR2cG4yNUA="); 66 | } 67 | 68 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5tdHZwbi5tdHZwbg=="; 69 | 70 | public override string SampleVersion => "2.2"; 71 | 72 | protected override string RepoName => Encoding.ASCII.FromBase64String("aHVubWFpL01ULVZQTg=="); 73 | } 74 | 75 | public sealed class ShWrdErr : ShWrdBase 76 | { 77 | public sealed class Parser : SocksHttpParserAes 78 | { 79 | protected override byte[] OuterKey => new byte[] { 80 | 0xd2, 0x25, 0x9f, 0xcd, 0x59, 0x94, 0xa1, 0xb4, 81 | 0x8c, 0x90, 0x2c, 0xf0, 0x55, 0x3c, 0x85, 0x7c, 82 | 0xb8, 0xd8, 0x35, 0x4b, 0x40, 0x07, 0xbc, 0x4f, 83 | 0xbf, 0xdc, 0x80, 0x6b, 0x08, 0xa9, 0x1e, 0xc9 84 | }; 85 | } 86 | 87 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5tdHZwbi5tdHZwbg=="; 88 | 89 | public override string SampleVersion => "2.2"; 90 | 91 | protected override string RepoName => Encoding.ASCII.FromBase64String("RVJST1ItVlBOL2Vycm9yLXZwbi5naXRodWIuaW8="); 92 | 93 | protected override string ConfigName => Encoding.ASCII.FromBase64String("Y29uZmlnLmpzb24="); 94 | } 95 | 96 | public sealed class ShWrdPnt : ShWrdBase 97 | { 98 | public sealed class Parser : SocksHttpWithOvpnNumericParserTea 99 | { 100 | protected override string ServerTypeKey => "Category"; 101 | protected override uint TeaDeltaOuter => 0xD1FBFA0B; 102 | protected override string OuterKey => Encoding.ASCII.FromBase64String("Y29tLnBudHZwbi5uZXQubQ=="); 103 | protected override int InnerKey => 7376; 104 | protected override bool OvpnPortIsBogus => true; 105 | 106 | protected override string DecryptInner(string jsonKey, string ciphertext) 107 | { 108 | if (jsonKey == OvpnPortKey) return ciphertext.Split(':')[0]; 109 | if (jsonKey != HostnameKey && jsonKey != UsernameKey && jsonKey != PasswordKey && jsonKey != V2RayKey) return ciphertext; 110 | 111 | return DecryptInner(ciphertext); 112 | } 113 | } 114 | 115 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5wbnR2cG4ubmV0"; 116 | 117 | public override string SampleVersion => "1.0.5"; 118 | 119 | protected override string RepoName => Encoding.ASCII.FromBase64String("c2Fuc29lMjAyMi9QTlRfVlBO"); 120 | 121 | protected override string ConfigName => Encoding.ASCII.FromBase64String("Y29uZmlnLmZpbGU="); 122 | 123 | public override bool HasProtocol(ServerProtocol protocol) => 124 | protocol == ServerProtocol.OpenVPN; 125 | } 126 | 127 | public sealed class ShWrdIda : ShWrdBase 128 | { 129 | public sealed class Parser : SocksHttpParserTeaAes 130 | { 131 | protected override string CountryNameKey => "FLAG"; 132 | 133 | protected override string V2RayKey => "V2json"; 134 | 135 | protected override string ServerTypeKey => "tunnelT"; 136 | 137 | protected override string OuterKey => Encoding.ASCII.FromBase64String("MTAyMzI1MzU="); 138 | 139 | protected override uint TeaDelta => 0xD1FBFA0B; 140 | 141 | // No inner crypto 142 | protected override string DecryptInner(string jsonKey, string ciphertext) => ciphertext; 143 | } 144 | 145 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWlkYS5wcm8ubmV0"; 146 | 147 | public override string SampleVersion => "10.10"; 148 | 149 | protected override string RepoName => Encoding.ASCII.FromBase64String("RWtyb21TU0gvTnVydWxmYWhtaQ=="); 150 | 151 | protected override string ConfigName => Encoding.ASCII.FromBase64String("ZmlsZWlkYXZwbi5qc29u"); 152 | 153 | public override bool HasProtocol(ServerProtocol protocol) => 154 | protocol == ServerProtocol.SSH; 155 | 156 | 157 | protected override async Task> GetServersAsyncImpl() 158 | { 159 | // v2ray servers in this config do not work, neither do any other servers with hostnames matching any v2ray servers. 160 | var servers = await base.GetServersAsyncImpl(); 161 | var list = servers.Where((server) => server.Protocol == ServerProtocol.V2Ray).Select((server) => server.Registry[ServerRegistryKeys.Hostname]).ToList(); 162 | return servers.Where((server) => server.Protocol != ServerProtocol.V2Ray && !list.Contains(server.Registry[ServerRegistryKeys.Hostname])); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/OvpnZip.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Servers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Security.Cryptography; 9 | using System.Text; 10 | using System.Text.Json; 11 | using System.Threading.Tasks; 12 | using ICSharpCode.SharpZipLib.Zip; 13 | 14 | // Android apps. OpenVPN, with several layers of memecrypto (AES + zip password (any crypto variant) + AES) 15 | namespace LibFreeVPN.Providers.OvpnZip 16 | { 17 | public abstract class OvpnZipBase : VPNProviderBase 18 | { 19 | public override bool RiskyRequests => true; 20 | 21 | protected abstract string RequestUri { get; } 22 | 23 | protected abstract byte[] MainKey { get; } 24 | protected abstract byte[] MainIv { get; } 25 | protected abstract byte[] ZipKey { get; } 26 | protected abstract byte[] ZipIv { get; } 27 | 28 | 29 | public override bool HasProtocol(ServerProtocol protocol) 30 | => protocol == ServerProtocol.OpenVPN; 31 | 32 | private static byte[] AesDecrypt(byte[] cipherTextBytes, byte[] key, byte[] iv) 33 | { 34 | using (var aes = new AesManaged()) 35 | { 36 | aes.BlockSize = 128; 37 | aes.KeySize = 128; 38 | aes.Padding = PaddingMode.PKCS7; 39 | using (var dec = aes.CreateDecryptor(key, iv)) 40 | { 41 | return dec.TransformFinalBlock(cipherTextBytes, 0, cipherTextBytes.Length); 42 | } 43 | } 44 | } 45 | 46 | private byte[] DecryptZip(byte[] bytes) => AesDecrypt(bytes, ZipKey, ZipIv); 47 | private string DecryptConf(byte[] bytes) => Encoding.UTF8.GetString(AesDecrypt(bytes, MainKey, MainIv)); 48 | private string DecryptElem(string str) => Encoding.UTF8.GetString(AesDecrypt(Convert.FromBase64String(str), MainKey, MainIv)); 49 | 50 | private static readonly byte[] s_PostData = 51 | Convert.FromBase64String("TWVzc2FnZUZpbGVNeT1NZXNzYWdlRmlsZU15JlBhc3NGaWxlPVBhc3NGaWxlJkZpbGVzPUZpbGVzJlZlcnNpb25GaWxlPVZlcnNpb25GaWxlJk1lc3NhZ2VGaWxlVGg9TWVzc2FnZUZpbGVUaCZNZXNzYWdlRmlsZUVuPU1lc3NhZ2VGaWxlRW4m"); 52 | 53 | private static readonly string s_PasswordElement = Encoding.ASCII.GetString(Convert.FromBase64String("UGFzc0ZpbGU=")); 54 | private static readonly string s_ConfsElement = Encoding.ASCII.GetString(Convert.FromBase64String("RmlsZXM=")); 55 | 56 | private async Task> GetServersAsync(string url, string password) 57 | { 58 | // download and decrypt the zip 59 | var httpClient = ServerUtilities.HttpClient; 60 | var zipBytes = DecryptZip(await httpClient.GetByteArrayAsync(url)); 61 | 62 | var list = new List>(); 63 | 64 | // extract and decrypt each config from the zip 65 | using (var zipMs = new MemoryStream(zipBytes)) 66 | using (var zip = new ZipFile(zipMs)) 67 | { 68 | zip.Password = password; 69 | 70 | foreach (var entry in zip.OfType().Where((entry) => !entry.IsDirectory)) 71 | { 72 | try 73 | { 74 | var bytes = new byte[(int)entry.Size]; 75 | using (var stream = zip.GetInputStream(entry)) 76 | { 77 | await stream.ReadAsync(bytes, 0, bytes.Length); 78 | } 79 | var conf = DecryptConf(bytes); 80 | list.Add(new KeyValuePair(entry.Name, conf)); 81 | } 82 | catch { } 83 | } 84 | } 85 | 86 | // parse each config 87 | return list.SelectMany((kv) => 88 | { 89 | var conf = kv.Value; 90 | var registry = CreateExtraRegistry(); 91 | registry.Add(ServerRegistryKeys.DisplayName, Path.GetFileName(kv.Key)); 92 | // Remove some part of the config that vanilla OpenVPN doesn't like. 93 | var offStart = conf.IndexOf(""); 94 | var offEnd = conf.IndexOf(""); 95 | if (offStart != -1 && offEnd != -1) 96 | { 97 | offEnd += "".Length; 98 | conf = conf.Substring(0, offStart) + conf.Substring(offEnd); 99 | } 100 | return OpenVpnServer.ParseConfigFull(conf, registry); 101 | }); 102 | } 103 | 104 | private async Task> TryGetServersAsync(string url, string password) 105 | { 106 | try 107 | { 108 | return await GetServersAsync(url, password); 109 | } 110 | catch 111 | { 112 | return Enumerable.Empty(); 113 | } 114 | } 115 | 116 | protected override async Task> GetServersAsyncImpl() 117 | { 118 | var httpClient = ServerUtilities.HttpClient; 119 | var reqContent = new ByteArrayContent(s_PostData); 120 | reqContent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"); 121 | var post = await httpClient.PostAsync( 122 | RequestUri, 123 | reqContent 124 | ); 125 | 126 | var content = await post.Content.ReadAsStringAsync(); 127 | var json = JsonDocument.Parse(content); 128 | if (json.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 129 | 130 | // get the zip password 131 | if (!json.RootElement.TryGetPropertyString(s_PasswordElement, out var password)) throw new InvalidDataException(); 132 | password = DecryptElem(password); 133 | 134 | // get the files object as string 135 | if (!json.RootElement.TryGetPropertyString(s_ConfsElement, out var confsStr)) throw new InvalidDataException(); 136 | var confsJson = JsonDocument.Parse(confsStr.Replace("\n", "")); 137 | if (confsJson.RootElement.ValueKind != JsonValueKind.Array) throw new InvalidDataException(); 138 | // get the URLs 139 | var configUrls = confsJson.RootElement.EnumerateArray().SelectMany((elem) => 140 | { 141 | if (elem.ValueKind != JsonValueKind.Object) return Enumerable.Empty(); 142 | if (!elem.TryGetPropertyString("url", out var url)) return Enumerable.Empty(); 143 | return DecryptElem(url).EnumerableSingle(); 144 | }); 145 | 146 | 147 | // for each of them, download and parse them all 148 | var configTasks = configUrls.Select((url) => TryGetServersAsync(url, password)).ToList(); 149 | // await all the tasks 150 | await Task.WhenAll(configTasks); 151 | 152 | // and squash them down to one list 153 | return configTasks.SelectMany((task) => task.Result).Distinct(); 154 | } 155 | } 156 | 157 | public sealed class OvpnZipPdv : OvpnZipBase 158 | { 159 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPXBsYWR1ay5wbGFkdWsuYXBwdnBu"; 160 | 161 | public override string SampleVersion => "28"; 162 | 163 | protected override string RequestUri => 164 | Encoding.ASCII.FromBase64String("aHR0cDovL3dpbjk5LnNtdGhhaTRnLndpbi9wbGFkdWtfd3MvZmlsZS8="); 165 | 166 | protected override byte[] MainKey => ("UGxhZHVrVlBOX0tleTAyNA==").FromBase64String(); 167 | 168 | protected override byte[] MainIv => ("UGxhZHVrVlBOX1ZlYzAyNA==").FromBase64String(); 169 | 170 | protected override byte[] ZipKey => ("UGxhZHVrWmlwS2V5MjAyNA==").FromBase64String(); 171 | 172 | protected override byte[] ZipIv => ("UGxhZHVrWmlwVmVjMjAyNA==").FromBase64String(); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /LibFreeVPN/Memecrypto/XXTEA.cs: -------------------------------------------------------------------------------- 1 | /**********************************************************\ 2 | | | 3 | | XXTEA.cs | 4 | | | 5 | | XXTEA encryption algorithm library for .NET. | 6 | | | 7 | | Encryption Algorithm Authors: | 8 | | David J. Wheeler | 9 | | Roger M. Needham | 10 | | | 11 | | Code Author: Ma Bingyao | 12 | | LastModified: Mar 10, 2015 | 13 | | | 14 | \**********************************************************/ 15 | 16 | using System; 17 | using System.Collections.Concurrent; 18 | using System.Text; 19 | 20 | namespace LibFreeVPN.Memecrypto 21 | { 22 | // XXTEA implementation with user-specified delta. 23 | // Used for many forks of sockshttp memecrypto. 24 | public sealed class XXTEA 25 | { 26 | private static readonly UTF8Encoding s_utf8 = new UTF8Encoding(); 27 | private static readonly ConcurrentDictionary s_Instances = new ConcurrentDictionary(); 28 | 29 | private readonly uint m_delta; 30 | 31 | private static uint MX(uint sum, uint y, uint z, int p, uint e, uint[] k) 32 | { 33 | return (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z); 34 | } 35 | 36 | private XXTEA(uint delta) 37 | { 38 | m_delta = delta; 39 | } 40 | 41 | public static XXTEA Create(uint delta = 0x9e3779b9u) 42 | { 43 | return s_Instances.GetOrAdd(delta, new XXTEA(delta)); 44 | } 45 | 46 | public byte[] Encrypt(byte[] data, byte[] key) 47 | { 48 | if (data.Length == 0) 49 | { 50 | return data; 51 | } 52 | return ToByteArray(Encrypt(ToUIntArray(data, true), ToUIntArray(FixKey(key), false)), false); 53 | } 54 | 55 | public byte[] Encrypt(string data, byte[] key) 56 | { 57 | return Encrypt(s_utf8.GetBytes(data), key); 58 | } 59 | 60 | public byte[] Encrypt(byte[] data, string key) 61 | { 62 | return Encrypt(data, s_utf8.GetBytes(key)); 63 | } 64 | 65 | public byte[] Encrypt(string data, string key) 66 | { 67 | return Encrypt(s_utf8.GetBytes(data), s_utf8.GetBytes(key)); 68 | } 69 | 70 | public string EncryptToBase64String(byte[] data, byte[] key) 71 | { 72 | return Convert.ToBase64String(Encrypt(data, key)); 73 | } 74 | 75 | public string EncryptToBase64String(string data, byte[] key) 76 | { 77 | return Convert.ToBase64String(Encrypt(data, key)); 78 | } 79 | 80 | public string EncryptToBase64String(byte[] data, string key) 81 | { 82 | return Convert.ToBase64String(Encrypt(data, key)); 83 | } 84 | 85 | public string EncryptToBase64String(string data, string key) 86 | { 87 | return Convert.ToBase64String(Encrypt(data, key)); 88 | } 89 | 90 | public byte[] Decrypt(byte[] data, byte[] key) 91 | { 92 | if (data.Length == 0) 93 | { 94 | return data; 95 | } 96 | return ToByteArray(Decrypt(ToUIntArray(data, false), ToUIntArray(FixKey(key), false)), true); 97 | } 98 | 99 | public byte[] Decrypt(byte[] data, string key) 100 | { 101 | return Decrypt(data, s_utf8.GetBytes(key)); 102 | } 103 | 104 | public byte[] DecryptBase64String(string data, byte[] key) 105 | { 106 | return Decrypt(Convert.FromBase64String(data), key); 107 | } 108 | 109 | public byte[] DecryptBase64String(string data, string key) 110 | { 111 | return Decrypt(Convert.FromBase64String(data), key); 112 | } 113 | 114 | public string DecryptToString(byte[] data, byte[] key) 115 | { 116 | return s_utf8.GetString(Decrypt(data, key)); 117 | } 118 | 119 | public string DecryptToString(byte[] data, string key) 120 | { 121 | return s_utf8.GetString(Decrypt(data, key)); 122 | } 123 | 124 | public string DecryptBase64StringToString(string data, byte[] key) 125 | { 126 | return s_utf8.GetString(DecryptBase64String(data, key)); 127 | } 128 | 129 | public string DecryptBase64StringToString(string data, string key) 130 | { 131 | return s_utf8.GetString(DecryptBase64String(data, key)); 132 | } 133 | 134 | private uint[] Encrypt(uint[] v, uint[] k) 135 | { 136 | int n = v.Length - 1; 137 | if (n < 1) 138 | { 139 | return v; 140 | } 141 | uint z = v[n], y, sum = 0, e; 142 | int p, q = 6 + 52 / (n + 1); 143 | unchecked 144 | { 145 | while (0 < q--) 146 | { 147 | sum += m_delta; 148 | e = sum >> 2 & 3; 149 | for (p = 0; p < n; p++) 150 | { 151 | y = v[p + 1]; 152 | z = v[p] += MX(sum, y, z, p, e, k); 153 | } 154 | y = v[0]; 155 | z = v[n] += MX(sum, y, z, p, e, k); 156 | } 157 | } 158 | return v; 159 | } 160 | 161 | private uint[] Decrypt(uint[] v, uint[] k) 162 | { 163 | int n = v.Length - 1; 164 | if (n < 1) 165 | { 166 | return v; 167 | } 168 | uint z, y = v[0], sum, e; 169 | int p, q = 6 + 52 / (n + 1); 170 | unchecked 171 | { 172 | sum = (uint)(q * m_delta); 173 | while (sum != 0) 174 | { 175 | e = sum >> 2 & 3; 176 | for (p = n; p > 0; p--) 177 | { 178 | z = v[p - 1]; 179 | y = v[p] -= MX(sum, y, z, p, e, k); 180 | } 181 | z = v[n]; 182 | y = v[0] -= MX(sum, y, z, p, e, k); 183 | sum -= m_delta; 184 | } 185 | } 186 | return v; 187 | } 188 | 189 | private static byte[] FixKey(byte[] key) 190 | { 191 | if (key.Length == 16) return key; 192 | byte[] fixedkey = new byte[16]; 193 | if (key.Length < 16) 194 | { 195 | Buffer.BlockCopy(key, 0, fixedkey, 0, key.Length); 196 | } 197 | else 198 | { 199 | Buffer.BlockCopy(key, 0, fixedkey, 0, 16); 200 | } 201 | return fixedkey; 202 | } 203 | 204 | private static uint[] ToUIntArray(byte[] data, bool includeLength) 205 | { 206 | int length = data.Length; 207 | int n = (((length & 3) == 0) ? (length >> 2) : ((length >> 2) + 1)); 208 | uint[] result; 209 | if (includeLength) 210 | { 211 | result = new uint[n + 1]; 212 | result[n] = (uint)length; 213 | } 214 | else 215 | { 216 | result = new uint[n]; 217 | } 218 | for (int i = 0; i < length; i++) 219 | { 220 | result[i >> 2] |= (uint)data[i] << ((i & 3) << 3); 221 | } 222 | return result; 223 | } 224 | 225 | private static byte[] ToByteArray(uint[] data, bool includeLength) 226 | { 227 | int n = data.Length << 2; 228 | if (includeLength) 229 | { 230 | int m = (int)data[data.Length - 1]; 231 | n -= 4; 232 | if ((m < n - 3) || (m > n)) 233 | { 234 | return null; 235 | } 236 | n = m; 237 | } 238 | byte[] result = new byte[n]; 239 | for (int i = 0; i < n; i++) 240 | { 241 | result[i] = (byte)(data[i >> 2] >> ((i & 3) << 3)); 242 | } 243 | return result; 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /FreeVPNC/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLine.Text; 3 | using LibFreeVPN; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Runtime.InteropServices; 9 | using System.Threading.Tasks; 10 | 11 | namespace FreeVPNC 12 | { 13 | public class Program 14 | { 15 | interface IBaseOptionsInclude 16 | { 17 | IEnumerable IncludeProtocols { get; set; } 18 | } 19 | 20 | class BaseOptionsSearch : IBaseOptionsInclude 21 | { 22 | [Option('i', "include", HelpText = "Include only providers supporting these protocols (OpenVPN, WireGuard, V2Ray, SSH)")] 23 | public IEnumerable IncludeProtocols { get; set; } 24 | 25 | [Option('e', "exclude", HelpText = "Exclude providers supporting these protocols (OpenVPN, WireGuard, V2Ray, SSH)")] 26 | public IEnumerable ExcludeProtocols { get; set; } 27 | 28 | [Option('r', "risky", Default = false, HelpText = "Include providers that make risky requests (to servers ran by the developers of the sample, or entities involved with them)")] 29 | public bool AllowRiskyRequests { get; set; } 30 | 31 | public bool HasIncludeProtocols => IncludeProtocols.Any(); 32 | public bool HasExcludeProtocols => ExcludeProtocols.Any(); 33 | 34 | public IEnumerable IncludeProtocolsFiltered => HasExcludeProtocols ? IncludeProtocols.Except(ExcludeProtocols) : IncludeProtocols; 35 | 36 | public IEnumerable FilterProviders(IEnumerable providers) 37 | { 38 | return providers.Where((prov) => 39 | { 40 | if (!AllowRiskyRequests && prov.RiskyRequests) return false; 41 | if (HasIncludeProtocols) 42 | { 43 | return IncludeProtocolsFiltered.Any(prot => prov.HasProtocol(prot)); 44 | } 45 | else if (HasExcludeProtocols) 46 | { 47 | return !ExcludeProtocols.Any(prot => prov.HasProtocol(prot)); 48 | } 49 | return true; 50 | }); 51 | } 52 | 53 | public IEnumerable FilterProviders() => FilterProviders(VPNProviders.Providers); 54 | } 55 | 56 | interface IBaseOptionsGet 57 | { 58 | [Option('o', "output", HelpText = "Path to output configurations to (defaults to standard output)")] 59 | string OutputPath { get; set; } 60 | 61 | [Option('l', "long", HelpText = "Outputs long original configuration where relevant")] 62 | bool RequestOriginalConfig { get; set; } 63 | } 64 | 65 | class BaseOptionsGet : IBaseOptionsGet, IBaseOptionsInclude 66 | { 67 | public string OutputPath { get; set; } 68 | public bool RequestOriginalConfig { get; set; } 69 | 70 | [Option('i', "include", HelpText = "Include only servers of these protocols (OpenVPN, WireGuard, V2Ray, SSH)")] 71 | public IEnumerable IncludeProtocols { get; set; } 72 | } 73 | 74 | class BaseOptionsGetSearch : BaseOptionsSearch, IBaseOptionsGet 75 | { 76 | public string OutputPath { get; set; } 77 | public bool RequestOriginalConfig { get; set; } 78 | } 79 | 80 | [Verb("list", HelpText = "List providers")] 81 | class ListOptions : BaseOptionsSearch { } 82 | 83 | [Verb("get", HelpText = "Get configs from single provider")] 84 | class GetOptions : BaseOptionsGet 85 | { 86 | [Value(0, Required = true, HelpText = "Provider name")] 87 | public string ProviderName { get; set; } 88 | } 89 | 90 | [Verb("getall", HelpText = "Get configs from multiple providers")] 91 | class GetAllOptions : BaseOptionsGetSearch { } 92 | 93 | static Task ListProviders(ListOptions op) 94 | { 95 | foreach (var provider in op.FilterProviders()) 96 | { 97 | Console.WriteLine(provider.Name); 98 | var protocols = (Enum.GetValues(typeof(ServerProtocol)) as ServerProtocol[]).Where((prot) => provider.HasProtocol(prot)).ToArray(); 99 | Console.WriteLine("Protocols: {0}", string.Join(", ", protocols)); 100 | if (provider.RiskyRequests) Console.WriteLine("Makes risky requests"); 101 | Console.WriteLine(); 102 | } 103 | return Task.FromResult(0); 104 | } 105 | 106 | static async Task PerformGetOperation(TOperation op, IEnumerable providers) 107 | where TOperation : IBaseOptionsGet, IBaseOptionsInclude 108 | { 109 | var tasks = providers.Select((prov) => prov.GetServersAsync()).ToArray(); 110 | await Task.WhenAll(tasks); 111 | 112 | var servers = tasks.SelectMany((t) => t.Result); 113 | var dict = new Dictionary(); 114 | 115 | if (!string.IsNullOrEmpty(op.OutputPath) && !Directory.Exists(op.OutputPath)) Directory.CreateDirectory(op.OutputPath); 116 | 117 | foreach (var server in servers) 118 | { 119 | if (op.IncludeProtocols.Any() && !op.IncludeProtocols.Contains(server.Protocol)) continue; 120 | 121 | var dispNameWithProv = string.Format("{0}_{1}", 122 | server.Registry[ServerRegistryKeys.ProviderName], 123 | server.Registry[ServerRegistryKeys.DisplayName]); 124 | var dispName = string.Join("_", dispNameWithProv.Split(Path.GetInvalidFileNameChars())); 125 | var config = server.Config; 126 | var typeString = server.Protocol.ToString(); 127 | if (op.RequestOriginalConfig && server.Registry.ContainsKey(ServerRegistryKeys.OriginalConfig)) 128 | { 129 | typeString = string.Format("{0}_{1}", typeString, server.Registry[ServerRegistryKeys.OriginalConfigType]); 130 | config = server.Registry[ServerRegistryKeys.OriginalConfig]; 131 | } 132 | int dispIdx = 0; 133 | if (dict.ContainsKey(dispName)) dispIdx = (++dict[dispName]); 134 | else dict.Add(dispName, 0); 135 | 136 | if (string.IsNullOrEmpty(op.OutputPath)) 137 | { 138 | if (dispIdx != 0) Console.WriteLine("[{0}] {1}_{2}", typeString, dispName, dispIdx); 139 | else Console.WriteLine("[{0}] {1}", typeString, dispName); 140 | Console.WriteLine(); 141 | Console.WriteLine(config); 142 | Console.WriteLine(); 143 | } 144 | else 145 | { 146 | if (dispIdx != 0) dispName = string.Format("{0}_{1}", dispName, dispIdx); 147 | var pathName = Path.Combine(op.OutputPath, string.Format("{0}.{1}.txt", dispName, typeString)); 148 | Console.WriteLine("Writing {0}...", pathName); 149 | File.WriteAllText(pathName, config); 150 | } 151 | } 152 | return 0; 153 | } 154 | 155 | static Task GetProvider(GetOptions op) 156 | { 157 | return PerformGetOperation(op, VPNProviders.Providers.Where((prov) => prov.Name == op.ProviderName)); 158 | } 159 | 160 | static Task GetAllProviders(GetAllOptions op) 161 | { 162 | return PerformGetOperation(op, op.FilterProviders()); 163 | } 164 | 165 | public static async Task Main(string[] args) 166 | { 167 | var parser = new Parser((ps) => { 168 | ps.CaseInsensitiveEnumValues = true; 169 | }); 170 | var parsed = parser.ParseArguments(args); 171 | 172 | parsed.WithNotParsed(_ => 173 | { 174 | var helpText = HelpText.AutoBuild(parsed, h => 175 | { 176 | h.AutoVersion = false; 177 | h.Copyright = string.Empty; 178 | h.AddEnumValuesToHelpText = true; 179 | 180 | return h; 181 | }); 182 | Console.WriteLine(helpText); 183 | }); 184 | 185 | return await parsed.MapResult( 186 | (ListOptions op) => ListProviders(op), 187 | (GetOptions op) => GetProvider(op), 188 | (GetAllOptions op) => GetAllProviders(op), 189 | errors => Task.FromResult(1)); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | *.env 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Ww][Ii][Nn]32/ 28 | [Aa][Rr][Mm]/ 29 | [Aa][Rr][Mm]64/ 30 | [Aa][Rr][Mm]64[Ee][Cc]/ 31 | bld/ 32 | [Oo]bj/ 33 | [Oo]ut/ 34 | [Ll]og/ 35 | [Ll]ogs/ 36 | 37 | # Build results on 'Bin' directories 38 | **/[Bb]in/* 39 | # Uncomment if you have tasks that rely on *.refresh files to move binaries 40 | # (https://github.com/github/gitignore/pull/3736) 41 | #!**/[Bb]in/*.refresh 42 | 43 | # Visual Studio 2015/2017 cache/options directory 44 | .vs/ 45 | # Uncomment if you have tasks that create the project's static files in wwwroot 46 | #wwwroot/ 47 | 48 | # Visual Studio 2017 auto generated files 49 | Generated\ Files/ 50 | 51 | # MSTest test Results 52 | [Tt]est[Rr]esult*/ 53 | [Bb]uild[Ll]og.* 54 | *.trx 55 | 56 | # NUnit 57 | *.VisualState.xml 58 | TestResult.xml 59 | nunit-*.xml 60 | 61 | # Approval Tests result files 62 | *.received.* 63 | 64 | # Build Results of an ATL Project 65 | [Dd]ebugPS/ 66 | [Rr]eleasePS/ 67 | dlldata.c 68 | 69 | # Benchmark Results 70 | BenchmarkDotNet.Artifacts/ 71 | 72 | # .NET Core 73 | project.lock.json 74 | project.fragment.lock.json 75 | artifacts/ 76 | 77 | # ASP.NET Scaffolding 78 | ScaffoldingReadMe.txt 79 | 80 | # StyleCop 81 | StyleCopReport.xml 82 | 83 | # Files built by Visual Studio 84 | *_i.c 85 | *_p.c 86 | *_h.h 87 | *.ilk 88 | *.meta 89 | *.obj 90 | *.idb 91 | *.iobj 92 | *.pch 93 | *.pdb 94 | *.ipdb 95 | *.pgc 96 | *.pgd 97 | *.rsp 98 | # but not Directory.Build.rsp, as it configures directory-level build defaults 99 | !Directory.Build.rsp 100 | *.sbr 101 | *.tlb 102 | *.tli 103 | *.tlh 104 | *.tmp 105 | *.tmp_proj 106 | *_wpftmp.csproj 107 | *.log 108 | *.tlog 109 | *.vspscc 110 | *.vssscc 111 | .builds 112 | *.pidb 113 | *.svclog 114 | *.scc 115 | 116 | # Chutzpah Test files 117 | _Chutzpah* 118 | 119 | # Visual C++ cache files 120 | ipch/ 121 | *.aps 122 | *.ncb 123 | *.opendb 124 | *.opensdf 125 | *.sdf 126 | *.cachefile 127 | *.VC.db 128 | *.VC.VC.opendb 129 | 130 | # Visual Studio profiler 131 | *.psess 132 | *.vsp 133 | *.vspx 134 | *.sap 135 | 136 | # Visual Studio Trace Files 137 | *.e2e 138 | 139 | # TFS 2012 Local Workspace 140 | $tf/ 141 | 142 | # Guidance Automation Toolkit 143 | *.gpState 144 | 145 | # ReSharper is a .NET coding add-in 146 | _ReSharper*/ 147 | *.[Rr]e[Ss]harper 148 | *.DotSettings.user 149 | 150 | # TeamCity is a build add-in 151 | _TeamCity* 152 | 153 | # DotCover is a Code Coverage Tool 154 | *.dotCover 155 | 156 | # AxoCover is a Code Coverage Tool 157 | .axoCover/* 158 | !.axoCover/settings.json 159 | 160 | # Coverlet is a free, cross platform Code Coverage Tool 161 | coverage*.json 162 | coverage*.xml 163 | coverage*.info 164 | 165 | # Visual Studio code coverage results 166 | *.coverage 167 | *.coveragexml 168 | 169 | # NCrunch 170 | _NCrunch_* 171 | .NCrunch_* 172 | .*crunch*.local.xml 173 | nCrunchTemp_* 174 | 175 | # MightyMoose 176 | *.mm.* 177 | AutoTest.Net/ 178 | 179 | # Web workbench (sass) 180 | .sass-cache/ 181 | 182 | # Installshield output folder 183 | [Ee]xpress/ 184 | 185 | # DocProject is a documentation generator add-in 186 | DocProject/buildhelp/ 187 | DocProject/Help/*.HxT 188 | DocProject/Help/*.HxC 189 | DocProject/Help/*.hhc 190 | DocProject/Help/*.hhk 191 | DocProject/Help/*.hhp 192 | DocProject/Help/Html2 193 | DocProject/Help/html 194 | 195 | # Click-Once directory 196 | publish/ 197 | 198 | # Publish Web Output 199 | *.[Pp]ublish.xml 200 | *.azurePubxml 201 | # Note: Comment the next line if you want to checkin your web deploy settings, 202 | # but database connection strings (with potential passwords) will be unencrypted 203 | *.pubxml 204 | *.publishproj 205 | 206 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 207 | # checkin your Azure Web App publish settings, but sensitive information contained 208 | # in these scripts will be unencrypted 209 | PublishScripts/ 210 | 211 | # NuGet Packages 212 | *.nupkg 213 | # NuGet Symbol Packages 214 | *.snupkg 215 | # The packages folder can be ignored because of Package Restore 216 | **/[Pp]ackages/* 217 | # except build/, which is used as an MSBuild target. 218 | !**/[Pp]ackages/build/ 219 | # Uncomment if necessary however generally it will be regenerated when needed 220 | #!**/[Pp]ackages/repositories.config 221 | # NuGet v3's project.json files produces more ignorable files 222 | *.nuget.props 223 | *.nuget.targets 224 | 225 | # Microsoft Azure Build Output 226 | csx/ 227 | *.build.csdef 228 | 229 | # Microsoft Azure Emulator 230 | ecf/ 231 | rcf/ 232 | 233 | # Windows Store app package directories and files 234 | AppPackages/ 235 | BundleArtifacts/ 236 | Package.StoreAssociation.xml 237 | _pkginfo.txt 238 | *.appx 239 | *.appxbundle 240 | *.appxupload 241 | 242 | # Visual Studio cache files 243 | # files ending in .cache can be ignored 244 | *.[Cc]ache 245 | # but keep track of directories ending in .cache 246 | !?*.[Cc]ache/ 247 | 248 | # Others 249 | ClientBin/ 250 | ~$* 251 | *~ 252 | *.dbmdl 253 | *.dbproj.schemaview 254 | *.jfm 255 | *.pfx 256 | *.publishsettings 257 | orleans.codegen.cs 258 | 259 | # Including strong name files can present a security risk 260 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 261 | #*.snk 262 | 263 | # Since there are multiple workflows, uncomment next line to ignore bower_components 264 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 265 | #bower_components/ 266 | 267 | # RIA/Silverlight projects 268 | Generated_Code/ 269 | 270 | # Backup & report files from converting an old project file 271 | # to a newer Visual Studio version. Backup files are not needed, 272 | # because we have git ;-) 273 | _UpgradeReport_Files/ 274 | Backup*/ 275 | UpgradeLog*.XML 276 | UpgradeLog*.htm 277 | ServiceFabricBackup/ 278 | *.rptproj.bak 279 | 280 | # SQL Server files 281 | *.mdf 282 | *.ldf 283 | *.ndf 284 | 285 | # Business Intelligence projects 286 | *.rdl.data 287 | *.bim.layout 288 | *.bim_*.settings 289 | *.rptproj.rsuser 290 | *- [Bb]ackup.rdl 291 | *- [Bb]ackup ([0-9]).rdl 292 | *- [Bb]ackup ([0-9][0-9]).rdl 293 | 294 | # Microsoft Fakes 295 | FakesAssemblies/ 296 | 297 | # GhostDoc plugin setting file 298 | *.GhostDoc.xml 299 | 300 | # Node.js Tools for Visual Studio 301 | .ntvs_analysis.dat 302 | node_modules/ 303 | 304 | # Visual Studio 6 build log 305 | *.plg 306 | 307 | # Visual Studio 6 workspace options file 308 | *.opt 309 | 310 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 311 | *.vbw 312 | 313 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 314 | *.vbp 315 | 316 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 317 | *.dsw 318 | *.dsp 319 | 320 | # Visual Studio 6 technical files 321 | *.ncb 322 | *.aps 323 | 324 | # Visual Studio LightSwitch build output 325 | **/*.HTMLClient/GeneratedArtifacts 326 | **/*.DesktopClient/GeneratedArtifacts 327 | **/*.DesktopClient/ModelManifest.xml 328 | **/*.Server/GeneratedArtifacts 329 | **/*.Server/ModelManifest.xml 330 | _Pvt_Extensions 331 | 332 | # Paket dependency manager 333 | **/.paket/paket.exe 334 | paket-files/ 335 | 336 | # FAKE - F# Make 337 | **/.fake/ 338 | 339 | # CodeRush personal settings 340 | **/.cr/personal 341 | 342 | # Python Tools for Visual Studio (PTVS) 343 | **/__pycache__/ 344 | *.pyc 345 | 346 | # Cake - Uncomment if you are using it 347 | #tools/** 348 | #!tools/packages.config 349 | 350 | # Tabs Studio 351 | *.tss 352 | 353 | # Telerik's JustMock configuration file 354 | *.jmconfig 355 | 356 | # BizTalk build output 357 | *.btp.cs 358 | *.btm.cs 359 | *.odx.cs 360 | *.xsd.cs 361 | 362 | # OpenCover UI analysis results 363 | OpenCover/ 364 | 365 | # Azure Stream Analytics local run output 366 | ASALocalRun/ 367 | 368 | # MSBuild Binary and Structured Log 369 | *.binlog 370 | MSBuild_Logs/ 371 | 372 | # AWS SAM Build and Temporary Artifacts folder 373 | .aws-sam 374 | 375 | # NVidia Nsight GPU debugger configuration file 376 | *.nvuser 377 | 378 | # MFractors (Xamarin productivity tool) working folder 379 | **/.mfractor/ 380 | 381 | # Local History for Visual Studio 382 | **/.localhistory/ 383 | 384 | # Visual Studio History (VSHistory) files 385 | .vshistory/ 386 | 387 | # BeatPulse healthcheck temp database 388 | healthchecksdb 389 | 390 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 391 | MigrationBackup/ 392 | 393 | # Ionide (cross platform F# VS Code tools) working folder 394 | **/.ionide/ 395 | 396 | # Fody - auto-generated XML schema 397 | FodyWeavers.xsd 398 | 399 | # VS Code files for those working on multiple tools 400 | .vscode/* 401 | !.vscode/settings.json 402 | !.vscode/tasks.json 403 | !.vscode/launch.json 404 | !.vscode/extensions.json 405 | !.vscode/*.code-snippets 406 | 407 | # Local History for Visual Studio Code 408 | .history/ 409 | 410 | # Built Visual Studio Code Extensions 411 | *.vsix 412 | 413 | # Windows Installer files from build outputs 414 | *.cab 415 | *.msi 416 | *.msix 417 | *.msm 418 | *.msp 419 | /FreeVPNC/Properties/launchSettings.json 420 | -------------------------------------------------------------------------------- /LibFreeVPN/Servers/OpenVpnServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LibFreeVPN.Servers 7 | { 8 | public sealed class OpenVpnServer : VPNServerBase 9 | { 10 | public sealed class Parser : VPNServerConfigParserFullBase 11 | { 12 | public override OpenVpnServer CreateInstance(string config) 13 | => new OpenVpnServer(config); 14 | 15 | public override OpenVpnServer CreateInstance(string config, string hostname, string port) 16 | => new OpenVpnServer(config, hostname, port); 17 | 18 | public override OpenVpnServer CreateInstance(string config, IReadOnlyDictionary extraRegistry) 19 | => new OpenVpnServer(config, extraRegistry); 20 | 21 | public override OpenVpnServer CreateInstance(string config, string hostname, string port, IReadOnlyDictionary extraRegistry) 22 | => new OpenVpnServer(config, hostname, port, extraRegistry); 23 | 24 | public override OpenVpnServer CreateInstance(string hostname, string port) 25 | { 26 | throw new NotSupportedException(); 27 | } 28 | 29 | public override OpenVpnServer CreateInstance(string hostname, string port, IReadOnlyDictionary extraRegistry) 30 | { 31 | throw new NotSupportedException(); 32 | } 33 | 34 | public override bool CanCreateInstance(bool withConfig) 35 | => withConfig; 36 | 37 | public override string GetUserVisibleConfig(string config, IReadOnlyDictionary registry) 38 | { 39 | bool gotUsername = registry.TryGetValue(ServerRegistryKeys.Username, out var username); 40 | bool gotPassword = registry.TryGetValue(ServerRegistryKeys.Password, out var password); 41 | if (!gotUsername && !gotPassword) return config; 42 | var sb = new StringBuilder(); 43 | if (gotUsername) 44 | { 45 | sb.AppendFormat("# Username: {0}", username); 46 | sb.AppendLine(); 47 | } 48 | if (gotPassword) 49 | { 50 | sb.AppendFormat("# Password: {0}", password); 51 | sb.AppendLine(); 52 | } 53 | sb.Append(config); 54 | return sb.ToString(); 55 | } 56 | 57 | public override IEnumerable<(string config, string hostname, string port)> ParseConfigFull(string config) 58 | { 59 | // take the config and split it by newline 60 | var split = config.Split(ServerUtilities.NewLines, StringSplitOptions.None); 61 | 62 | // remove all lines starting with comment 63 | { 64 | var splitList = new List(); 65 | for (int i = 0; i < split.Length; i++) 66 | { 67 | var line = split[i]; 68 | if (line.Length > 0 && line[0] == '#') continue; 69 | splitList.Add(line); 70 | } 71 | split = splitList.ToArray(); 72 | } 73 | 74 | // walk through the split config, and create "clean" config (with no servers), "servers" (containing just the servers), "server index" (arr index of first server) 75 | var configClean = new List(); 76 | var servers = new List(); 77 | var serverIdx = -1; 78 | string port = null; 79 | 80 | for (int i = 0; i < split.Length; i++) 81 | { 82 | var tokenIdx = split[i].IndexOf(' '); 83 | if (tokenIdx != -1 && split[i].Substring(0, tokenIdx).ToLower() == "remote") 84 | { 85 | if (serverIdx == -1) serverIdx = i; 86 | servers.Add(split[i]); 87 | } 88 | else 89 | { 90 | if (port == null && tokenIdx != -1 && split[i].Substring(0, tokenIdx).ToLower() == "port") 91 | { 92 | var portCommentIdx = split[i].IndexOf('#'); 93 | var cleanPort = split[i]; 94 | if (portCommentIdx != -1) cleanPort = split[i].Substring(0, portCommentIdx); 95 | port = cleanPort.Split(' ')[1]; 96 | } 97 | configClean.Add(split[i]); 98 | } 99 | } 100 | 101 | // for each server, take a copy of the configClean, add the server in, yield return it 102 | return servers.SelectMany((server) => 103 | { 104 | var thisConfig = new List(configClean); 105 | thisConfig.Insert(serverIdx, server); 106 | 107 | var serverCommentIdx = server.IndexOf('#'); 108 | var cleanServer = server; 109 | if (serverCommentIdx != -1) cleanServer = server.Substring(0, serverCommentIdx); 110 | var splitServer = cleanServer.Split(' '); 111 | var thisPort = string.Empty; 112 | if (splitServer.Length < 2) return Enumerable.Empty<(string, string, string)>(); 113 | else if (splitServer.Length == 2) 114 | { 115 | if (port == null) return Enumerable.Empty<(string, string, string)>(); 116 | thisPort = port; 117 | } 118 | else 119 | { 120 | thisPort = splitServer[2]; 121 | } 122 | 123 | return (string.Join("\r\n", thisConfig.ToArray()), splitServer[1], thisPort).EnumerableSingle(); 124 | }).ToList(); 125 | } 126 | } 127 | public OpenVpnServer(string config) : base(config) 128 | { 129 | } 130 | 131 | public OpenVpnServer(string config, string hostname, string port) : base(config, hostname, port) 132 | { 133 | } 134 | 135 | public OpenVpnServer(string config, IReadOnlyDictionary extraRegistry) : base(config, extraRegistry) 136 | { 137 | } 138 | 139 | public OpenVpnServer(string config, string hostname, string port, IReadOnlyDictionary extraRegistry) : base(config, hostname, port, extraRegistry) 140 | { 141 | } 142 | 143 | public override ServerProtocol Protocol => ServerProtocol.OpenVPN; 144 | 145 | public static string InjectHostIntoConfig(string config, string hostname, string port) 146 | { 147 | // take the config and split it by newline 148 | var split = config.Split(ServerUtilities.NewLines, StringSplitOptions.None); 149 | 150 | // remove all lines starting with comment 151 | { 152 | var splitList = new List(); 153 | for (int i = 0; i < split.Length; i++) 154 | { 155 | var line = split[i]; 156 | if (line.Length > 0 && line[0] == '#') continue; 157 | splitList.Add(line); 158 | } 159 | split = splitList.ToArray(); 160 | } 161 | 162 | // walk through the split config, and create "clean" config (with no servers), "servers" (containing just the servers), "server index" (arr index of first server) 163 | var configClean = new List(); 164 | //var servers = new List(); 165 | var serverIdx = -1; 166 | var protoIdx = -1; 167 | var devIdx = -1; 168 | 169 | for (int i = 0; i < split.Length; i++) 170 | { 171 | var tokenIdx = split[i].IndexOf(' '); 172 | if (tokenIdx != -1 && split[i].Substring(0, tokenIdx).ToLower() == "remote") 173 | { 174 | if (serverIdx == -1) serverIdx = i; 175 | //servers.Add(split[i]); 176 | } 177 | else 178 | { 179 | if (protoIdx == -1 && tokenIdx != -1 && split[i].Substring(0, tokenIdx).ToLower() == "proto") 180 | protoIdx = i; 181 | else if (devIdx == -1 && tokenIdx != -1 && split[i].Substring(0, tokenIdx).ToLower() == "dev") 182 | devIdx = i; 183 | configClean.Add(split[i]); 184 | } 185 | } 186 | 187 | if (serverIdx == -1) 188 | { 189 | // No server was found, so use the protoIdx or devIdx 190 | if (protoIdx == -1) 191 | { 192 | protoIdx = devIdx + 1; 193 | // BUGBUG: add support for udp too if needed 194 | configClean.Insert(devIdx, "proto tcp"); 195 | } 196 | serverIdx = protoIdx + 1; 197 | } 198 | 199 | 200 | var thisConfig = new List(configClean); 201 | thisConfig.Insert(serverIdx, string.Format("remote {0} {1}", hostname, port)); 202 | return string.Join("\r\n", thisConfig.ToArray()); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /LibFreeVPN/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Collections.Specialized; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Text; 8 | using System.Text.Json; 9 | 10 | namespace LibFreeVPN 11 | { 12 | /// 13 | /// Protocol of a VPN server 14 | /// 15 | public enum ServerProtocol 16 | { 17 | /// 18 | /// Protocol is unknown before the server-side configuration has been retrieved. 19 | /// 20 | Unknown, 21 | /// 22 | /// OpenVPN server 23 | /// 24 | OpenVPN, 25 | /// 26 | /// WireGuard server 27 | /// 28 | WireGuard, 29 | /// 30 | /// V2Ray (no matter the underlying protocol - vmess/vless/socks/...) 31 | /// 32 | V2Ray, 33 | /// 34 | /// SSH tunnel 35 | /// 36 | SSH, 37 | } 38 | 39 | public static class ServerRegistryKeys 40 | { 41 | /// 42 | /// Hostname or IP address of the VPN server 43 | /// If the config potentially uses some form of domain fronting supported by the client then this will be empty. 44 | /// 45 | public const string Hostname = "hostname"; 46 | /// 47 | /// Port of the VPN server 48 | /// 49 | public const string Port = "port"; 50 | /// 51 | /// Username to log in to the VPN server 52 | /// 53 | public const string Username = "username"; 54 | /// 55 | /// Password to log in to the VPN server 56 | /// 57 | public const string Password = "password"; 58 | /// 59 | /// Country where the VPN server is hosted in 60 | /// 61 | public const string Country = "country"; 62 | /// 63 | /// Provider's display name of this VPN server. 64 | /// 65 | public const string DisplayName = "displayname"; 66 | /// 67 | /// Name of provider that this VPN server was obtained from 68 | /// 69 | public const string ProviderName = "providername"; 70 | /// 71 | /// Format of original config where provided (currently only for v2ray) 72 | /// 73 | public const string OriginalConfigType = "originalconfigtype"; 74 | /// 75 | /// Original configuration where provided 76 | /// 77 | public const string OriginalConfig = "originalconfig"; 78 | } 79 | 80 | internal static class ServerUtilities 81 | { 82 | public static IReadOnlyDictionary EmptyRegistry { get; } = new Dictionary(); 83 | public static readonly string[] NewLines = new string[] { "\r\n", "\n" }; 84 | public static readonly HttpClient HttpClient = new HttpClient(); 85 | 86 | /// 87 | /// Gets a value from a dictionary, returning a default value if the key doesn't exist.
88 | /// If is and is , returns an empty string when the key doesn't exist. 89 | ///
90 | /// Type of dictionary key 91 | /// Type of dictionary value 92 | /// Dictionary to read from 93 | /// Key to read the value of 94 | /// Default value to return if key does not exist 95 | /// Value of dictionary key, or if the key does not exist. 96 | public static TValue GetValue(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue = default) 97 | { 98 | if (dictionary.TryGetValue(key, out var value)) return value; 99 | 100 | // for TValue==string, return empty string instead of null string 101 | if (typeof(TValue) == typeof(string) && (string)(object)defaultValue == null) return (TValue)(object)string.Empty; 102 | return defaultValue; 103 | 104 | } 105 | 106 | public static void AddNonNullValue(this IDictionary dictionary, TKey key, TValue value) 107 | { 108 | if (typeof(TValue) == typeof(string) && string.IsNullOrEmpty((string)(object)value)) return; 109 | else if (!typeof(TValue).IsValueType && value == null) return; 110 | 111 | dictionary.Add(key, value); 112 | } 113 | 114 | public static void AddNonNullValue(this NameValueCollection dictionary, string key, string value) 115 | { 116 | if (string.IsNullOrEmpty(value)) return; 117 | 118 | dictionary.Add(key, value); 119 | } 120 | 121 | public static void AddNonNullValue(this IDictionary dictionary, TKey key, TValue? value) 122 | where TValue : struct 123 | { 124 | if (!value.HasValue) return; 125 | dictionary.Add(key, value.Value); 126 | } 127 | 128 | public static bool TryGetPropertyString(this JsonElement element, string key, out string value) 129 | { 130 | value = default; 131 | if (!element.TryGetProperty(key, out var property)) return false; 132 | if (property.ValueKind != JsonValueKind.String) return false; 133 | value = property.GetString(); 134 | return true; 135 | } 136 | 137 | public static IEnumerable EnumerableSingle(this TValue val) => new TValue[] { val }; 138 | } 139 | 140 | internal static class LazySingleton 141 | { 142 | private static class KeyedObjects 143 | { 144 | internal static ConcurrentDictionary<(TValue, Func), T> s_Objects = new ConcurrentDictionary<(TValue, Func), T>(); 145 | } 146 | 147 | private static class TypeKeyedObjects 148 | { 149 | internal static ConcurrentDictionary<(Type, Func), T> s_Objects = new ConcurrentDictionary<(Type, Func), T>(); 150 | } 151 | 152 | internal static ConcurrentDictionary, T> s_NonKeyedObjects = new ConcurrentDictionary, T>(); 153 | 154 | private static T Intern(T v) 155 | { 156 | if (typeof(T) == typeof(string)) v = (T)(object)string.Intern((string)(object)v); 157 | return v; 158 | } 159 | 160 | private static T GetImpl(Func key) => Intern(key()); 161 | 162 | private static T GetImpl((TData, Func) key) => Intern(key.Item2(key.Item1)); 163 | 164 | /// 165 | /// Gets a lazy singleton using the provided delegate, calling it to initialise if required. 166 | /// 167 | /// Should only be used when the delegate will only be called from one place, otherwise conflicts will occur. 168 | /// Delegate that initialises and returns a value. 169 | /// The initialised value. 170 | internal static T Get(Func func) 171 | { 172 | return s_NonKeyedObjects.GetOrAdd(func, GetImpl); 173 | } 174 | 175 | /// 176 | /// Gets a lazy singleton using the provided delegate and data argument, calling it to initialise if required. 177 | /// 178 | /// Delegate that takes a single parameter to initialise and returns a value. 179 | /// The parameter of the delegate, used to initialise the value. 180 | /// The initialised value. 181 | internal static T Get(Func func, TData data) 182 | { 183 | return KeyedObjects.s_Objects.GetOrAdd((data, func), GetImpl); 184 | } 185 | 186 | /// 187 | /// Gets a lazy singleton through the type-keyed dictionary using the provided delegate and data argument, calling it to initialise if required. 188 | /// 189 | /// Delegate that takes a single parameter to initialise and returns a value. 190 | /// The parameter of the delegate, used to initialise the value. 191 | /// The initialised value. 192 | internal static T GetByType(Func func, TData data) 193 | { 194 | return TypeKeyedObjects.s_Objects.GetOrAdd((data.GetType(), func), (kv) => Intern(kv.Item2(data))); 195 | } 196 | } 197 | 198 | internal static class LazySingleton 199 | { 200 | internal static T SingleInstance(this Func func) => LazySingleton.Get(func); 201 | 202 | internal static T SingleInstance(this TData data, Func func) => LazySingleton.Get(func, data); 203 | 204 | internal static T SingleInstanceByType(this TData data, Func func) => LazySingleton.GetByType(func, data); 205 | 206 | internal static byte[] FromBase64String(this string base64String) => LazySingleton.Get(Convert.FromBase64String, base64String); 207 | 208 | internal static string FromBase64String(this Encoding encoding, string base64string) => LazySingleton.Get((data) => encoding.GetString(Convert.FromBase64String(data)), base64string); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/Ldcl.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Servers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Text.Json; 9 | using System.Text.Json.Nodes; 10 | using System.Threading.Tasks; 11 | 12 | // Android app, openvpn. Own-hosted risky C2, split between free and paid configs. 13 | // No trusting client here - can only get paid config with signed ticket from Google Play or Amazon Appstore 14 | namespace LibFreeVPN.Providers 15 | { 16 | public sealed class Ldcl : VPNProviderBase 17 | { 18 | private static readonly string s_BaseUri = Encoding.ASCII.GetString(Convert.FromBase64String("aHR0cHM6Ly9hcGk0LmNhbmR5bGluay5jb20vcHJvZC9hcGkvdjIuMC4wLw==")); 19 | private static readonly string s_ApiKey = Encoding.ASCII.GetString(Convert.FromBase64String("ckRVWWJXWGFSZDFJY2ZnTUw0V1NQM0l5M2gxdnl2YkIza1BHZVY0UQ==")); 20 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWNvbS5jYW5keWxpbmsub3BlbnZwbg=="; 21 | 22 | public override string SampleVersion => "4.2.1"; 23 | 24 | public override bool RiskyRequests => true; 25 | 26 | private static readonly string s_RouteCountries = Encoding.ASCII.GetString(Convert.FromBase64String("Y29ubmVjdC9jb3VudHJ5")); 27 | private static readonly string s_RouteMainConfig = Encoding.ASCII.GetString(Convert.FromBase64String("cmVtb3RlX2NvbmZpZz90eXBlPW1vYmlsZV9jb25maWc=")); 28 | private static readonly string s_RouteCountryConfig = Encoding.ASCII.GetString(Convert.FromBase64String("Y29ubmVjdA==")); 29 | 30 | private static readonly string s_ObtainableCountryElement = Encoding.ASCII.GetString(Convert.FromBase64String("aXNfYXZhaWxhYmxlX2Zvcl9mcmVl")); 31 | private static readonly string s_MainConfigsElement = Encoding.ASCII.GetString(Convert.FromBase64String("c3RlYWx0aF9tb2RlX2NvbmZpZw==")); 32 | 33 | private static readonly string s_MobileOsElement = Encoding.ASCII.GetString(Convert.FromBase64String("RGV2aWNlT1M=")); 34 | private static readonly string s_RequestType = Encoding.ASCII.GetString(Convert.FromBase64String("Q29ubmVjdA==")); 35 | 36 | public override bool HasProtocol(ServerProtocol protocol) 37 | => protocol == ServerProtocol.OpenVPN; 38 | 39 | private static string MakeUri(string route) 40 | { 41 | var sb = new StringBuilder(s_BaseUri); 42 | sb.Append(route); 43 | return sb.ToString(); 44 | } 45 | 46 | private static async Task MakeRequestAsync(string route) 47 | { 48 | HttpResponseMessage listResponse = null; 49 | using (var listRequest = new HttpRequestMessage(HttpMethod.Get, MakeUri(route))) 50 | { 51 | listRequest.Headers.Add("x-api-key", s_ApiKey); 52 | listResponse = await ServerUtilities.HttpClient.SendAsync(listRequest); 53 | } 54 | 55 | return await listResponse.Content.ReadAsStringAsync(); 56 | } 57 | 58 | private static string MakePostRequestData(string country) 59 | { 60 | var attributes = new JsonObject() 61 | { 62 | ["Country"] = country, 63 | [s_MobileOsElement] = "Android" 64 | }; 65 | 66 | var data = new JsonObject() 67 | { 68 | ["type"] = s_RequestType, 69 | ["attributes"] = attributes 70 | }; 71 | 72 | var req = new JsonObject() 73 | { 74 | ["data"] = data 75 | }; 76 | 77 | return req.ToJsonString(); 78 | } 79 | 80 | private static async Task MakePostRequestAsync(string route, string country) 81 | { 82 | var reqContent = new ByteArrayContent(Encoding.UTF8.GetBytes(MakePostRequestData(country))); 83 | reqContent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); 84 | reqContent.Headers.Add("x-api-key", s_ApiKey); 85 | var response = await ServerUtilities.HttpClient.PostAsync( 86 | MakeUri(route), 87 | reqContent 88 | ); 89 | return await response.Content.ReadAsStringAsync(); 90 | } 91 | 92 | private IEnumerable ParseMainConfig(string mainConf) 93 | { 94 | try 95 | { 96 | var mainConfJson = JsonDocument.Parse(mainConf); 97 | if (mainConfJson.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 98 | if (!mainConfJson.RootElement.TryGetProperty("data", out var mainConfData)) throw new InvalidDataException(); 99 | if (mainConfData.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 100 | if (!mainConfData.TryGetProperty("attributes", out var elemAttributes)) throw new InvalidDataException(); 101 | if (elemAttributes.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 102 | if (!elemAttributes.TryGetProperty("config", out var elemAttrConfig)) throw new InvalidDataException(); 103 | if (elemAttrConfig.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 104 | if (!elemAttrConfig.TryGetProperty(s_MainConfigsElement, out var mainConfsObject)) throw new InvalidDataException(); 105 | if (mainConfsObject.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 106 | if (!mainConfsObject.TryGetProperty("configs", out var serversData)) throw new InvalidDataException(); 107 | if (serversData.ValueKind != JsonValueKind.Array) throw new InvalidDataException(); 108 | return serversData.EnumerateArray().SelectMany((elem) => 109 | { 110 | try 111 | { 112 | if (elem.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 113 | if (!elem.TryGetPropertyString("FileName", out var confName)) throw new InvalidDataException(); 114 | if (!elem.TryGetPropertyString("File", out var conf)) throw new InvalidDataException(); 115 | conf = Encoding.UTF8.GetString(Convert.FromBase64String(conf)); 116 | var registry = CreateExtraRegistry(); 117 | registry.Add(ServerRegistryKeys.DisplayName, confName); 118 | return OpenVpnServer.ParseConfigFull(conf, registry); 119 | } catch { return Enumerable.Empty(); } 120 | }).ToList(); 121 | } catch { return Enumerable.Empty(); } 122 | } 123 | 124 | private async Task> GetAndParseCountryConfigAsync(string country) 125 | { 126 | try 127 | { 128 | var config = await MakePostRequestAsync(s_RouteCountryConfig, country); 129 | var mainConfJson = JsonDocument.Parse(config); 130 | if (mainConfJson.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 131 | if (!mainConfJson.RootElement.TryGetProperty("data", out var mainConfData)) throw new InvalidDataException(); 132 | if (mainConfData.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 133 | if (!mainConfData.TryGetProperty("attributes", out var mainConfsObject)) throw new InvalidDataException(); 134 | if (mainConfsObject.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 135 | 136 | if (!mainConfsObject.TryGetPropertyString("FileName", out var confName)) throw new InvalidDataException(); 137 | if (!mainConfsObject.TryGetPropertyString("File", out var conf)) throw new InvalidDataException(); 138 | conf = Encoding.UTF8.GetString(Convert.FromBase64String(conf)); 139 | var registry = CreateExtraRegistry(); 140 | registry.Add(ServerRegistryKeys.DisplayName, confName); 141 | registry.Add(ServerRegistryKeys.Country, country); 142 | return OpenVpnServer.ParseConfigFull(conf, registry); 143 | } 144 | catch { return Enumerable.Empty(); } 145 | } 146 | 147 | protected override async Task> GetServersAsyncImpl() 148 | { 149 | // Get list of countries 150 | var countries = await MakeRequestAsync(s_RouteCountries); 151 | var countriesJson = JsonDocument.Parse(countries); 152 | if (countriesJson.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 153 | if (!countriesJson.RootElement.TryGetProperty("data", out var countriesData)) throw new InvalidDataException(); 154 | if (countriesData.ValueKind != JsonValueKind.Array) throw new InvalidDataException(); 155 | var countriesList = countriesData.EnumerateArray().SelectMany((elem) => 156 | { 157 | try 158 | { 159 | if (elem.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 160 | if (!elem.TryGetProperty("attributes", out var elemAttributes)) throw new InvalidDataException(); 161 | if (elemAttributes.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 162 | if (!elemAttributes.TryGetProperty(s_ObtainableCountryElement, out var isObtainable)) throw new InvalidDataException(); 163 | if (isObtainable.ValueKind != JsonValueKind.True) throw new InvalidDataException(); 164 | if (!elemAttributes.TryGetPropertyString("Code", out var country)) throw new InvalidDataException(); 165 | return country.EnumerableSingle(); 166 | } catch { return Enumerable.Empty(); } 167 | }).ToList(); 168 | 169 | // Get initial set of configs 170 | var mainConf = await MakeRequestAsync(s_RouteMainConfig); 171 | var ret = ParseMainConfig(mainConf); 172 | 173 | // Get remaining configs 174 | var tasks = countriesList.Select((country) => GetAndParseCountryConfigAsync(country)).ToList(); 175 | await Task.WhenAll(tasks); 176 | return tasks.SelectMany((task) => task.Result).Concat(ret); 177 | 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /LibFreeVPN/Memecrypto/Pbkdf2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | 7 | // Backport of later PBKDF2 (supporting hash algorithm that isn't SHA1) from later .NET to .NET Standard 2.0 8 | namespace LibFreeVPN.Memecrypto 9 | { 10 | public class Pbkdf2 : DeriveBytes 11 | { 12 | 13 | internal static class Helpers 14 | { 15 | public static byte[] CloneByteArray(byte[] src) 16 | { 17 | if (src == null) 18 | { 19 | return null; 20 | } 21 | 22 | return (byte[])(src.Clone()); 23 | } 24 | 25 | public static byte[] GenerateRandom(int count) 26 | { 27 | byte[] buffer = new byte[count]; 28 | using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) 29 | { 30 | rng.GetBytes(buffer); 31 | } 32 | return buffer; 33 | } 34 | 35 | // encodes the integer i into a 4-byte array, in big endian. 36 | public static void WriteInt(uint i, byte[] arr, int offset) 37 | { 38 | unchecked 39 | { 40 | Debug.Assert(arr != null); 41 | Debug.Assert(arr.Length >= offset + sizeof(uint)); 42 | 43 | arr[offset] = (byte)(i >> 24); 44 | arr[offset + 1] = (byte)(i >> 16); 45 | arr[offset + 2] = (byte)(i >> 8); 46 | arr[offset + 3] = (byte)i; 47 | } 48 | } 49 | } 50 | 51 | private const int MinimumSaltSize = 8; 52 | 53 | private readonly byte[] _password; 54 | private byte[] _salt; 55 | private uint _iterations; 56 | private HMAC _hmac; 57 | private int _blockSize; 58 | 59 | private byte[] _buffer; 60 | private uint _block; 61 | private int _startIndex; 62 | private int _endIndex; 63 | 64 | public HashAlgorithmName HashAlgorithm { get; } 65 | 66 | public Pbkdf2(byte[] password, byte[] salt, int iterations) 67 | : this(password, salt, iterations, HashAlgorithmName.SHA1) 68 | { 69 | } 70 | 71 | public Pbkdf2(byte[] password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm) 72 | { 73 | if (salt == null) 74 | throw new ArgumentNullException(nameof(salt)); 75 | if (salt.Length < MinimumSaltSize) 76 | throw new ArgumentException("SR.Cryptography_PasswordDerivedBytes_FewBytesSalt", nameof(salt)); 77 | if (iterations <= 0) 78 | throw new ArgumentOutOfRangeException(nameof(iterations), "SR.ArgumentOutOfRange_NeedPosNum"); 79 | if (password == null) 80 | throw new NullReferenceException(); // This "should" be ArgumentNullException but for compat, we throw NullReferenceException. 81 | 82 | _salt = Helpers.CloneByteArray(salt); 83 | _iterations = (uint)iterations; 84 | _password = Helpers.CloneByteArray(password); 85 | HashAlgorithm = hashAlgorithm; 86 | _hmac = OpenHmac(); 87 | // _blockSize is in bytes, HashSize is in bits. 88 | _blockSize = _hmac.HashSize >> 3; 89 | 90 | Initialize(); 91 | } 92 | 93 | public Pbkdf2(string password, byte[] salt) 94 | : this(password, salt, 1000) 95 | { 96 | } 97 | 98 | public Pbkdf2(string password, byte[] salt, int iterations) 99 | : this(password, salt, iterations, HashAlgorithmName.SHA1) 100 | { 101 | } 102 | 103 | public Pbkdf2(string password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm) 104 | : this(Encoding.UTF8.GetBytes(password), salt, iterations, hashAlgorithm) 105 | { 106 | } 107 | 108 | public Pbkdf2(string password, int saltSize) 109 | : this(password, saltSize, 1000) 110 | { 111 | } 112 | 113 | public Pbkdf2(string password, int saltSize, int iterations) 114 | : this(password, saltSize, iterations, HashAlgorithmName.SHA1) 115 | { 116 | } 117 | 118 | public Pbkdf2(string password, int saltSize, int iterations, HashAlgorithmName hashAlgorithm) 119 | { 120 | if (saltSize < 0) 121 | throw new ArgumentOutOfRangeException(nameof(saltSize), "SR.ArgumentOutOfRange_NeedNonNegNum"); 122 | if (saltSize < MinimumSaltSize) 123 | throw new ArgumentException("SR.Cryptography_PasswordDerivedBytes_FewBytesSalt", nameof(saltSize)); 124 | if (iterations <= 0) 125 | throw new ArgumentOutOfRangeException(nameof(iterations), "SR.ArgumentOutOfRange_NeedPosNum"); 126 | 127 | _salt = Helpers.GenerateRandom(saltSize); 128 | _iterations = (uint)iterations; 129 | _password = Encoding.UTF8.GetBytes(password); 130 | HashAlgorithm = hashAlgorithm; 131 | _hmac = OpenHmac(); 132 | // _blockSize is in bytes, HashSize is in bits. 133 | _blockSize = _hmac.HashSize >> 3; 134 | 135 | Initialize(); 136 | } 137 | 138 | public int IterationCount 139 | { 140 | get 141 | { 142 | return (int)_iterations; 143 | } 144 | 145 | set 146 | { 147 | if (value <= 0) 148 | throw new ArgumentOutOfRangeException(nameof(value), "SR.ArgumentOutOfRange_NeedPosNum"); 149 | _iterations = (uint)value; 150 | Initialize(); 151 | } 152 | } 153 | 154 | public byte[] Salt 155 | { 156 | get 157 | { 158 | return Helpers.CloneByteArray(_salt); 159 | } 160 | 161 | set 162 | { 163 | if (value == null) 164 | throw new ArgumentNullException(nameof(value)); 165 | if (value.Length < MinimumSaltSize) 166 | throw new ArgumentException("SR.Cryptography_PasswordDerivedBytes_FewBytesSalt"); 167 | _salt = Helpers.CloneByteArray(value); 168 | Initialize(); 169 | } 170 | } 171 | 172 | protected override void Dispose(bool disposing) 173 | { 174 | if (disposing) 175 | { 176 | if (_hmac != null) 177 | { 178 | _hmac.Dispose(); 179 | _hmac = null; 180 | } 181 | 182 | if (_buffer != null) 183 | Array.Clear(_buffer, 0, _buffer.Length); 184 | if (_password != null) 185 | Array.Clear(_password, 0, _password.Length); 186 | if (_salt != null) 187 | Array.Clear(_salt, 0, _salt.Length); 188 | } 189 | base.Dispose(disposing); 190 | } 191 | 192 | public override byte[] GetBytes(int cb) 193 | { 194 | Debug.Assert(_blockSize > 0); 195 | 196 | if (cb <= 0) 197 | throw new ArgumentOutOfRangeException(nameof(cb), "SR.ArgumentOutOfRange_NeedPosNum"); 198 | byte[] password = new byte[cb]; 199 | 200 | int offset = 0; 201 | int size = _endIndex - _startIndex; 202 | if (size > 0) 203 | { 204 | if (cb >= size) 205 | { 206 | Buffer.BlockCopy(_buffer, _startIndex, password, 0, size); 207 | _startIndex = _endIndex = 0; 208 | offset += size; 209 | } 210 | else 211 | { 212 | Buffer.BlockCopy(_buffer, _startIndex, password, 0, cb); 213 | _startIndex += cb; 214 | return password; 215 | } 216 | } 217 | 218 | Debug.Assert(_startIndex == 0 && _endIndex == 0, "Invalid start or end index in the internal buffer."); 219 | 220 | while (offset < cb) 221 | { 222 | byte[] T_block = Func(); 223 | int remainder = cb - offset; 224 | if (remainder > _blockSize) 225 | { 226 | Buffer.BlockCopy(T_block, 0, password, offset, _blockSize); 227 | offset += _blockSize; 228 | } 229 | else 230 | { 231 | Buffer.BlockCopy(T_block, 0, password, offset, remainder); 232 | offset += remainder; 233 | Buffer.BlockCopy(T_block, remainder, _buffer, _startIndex, _blockSize - remainder); 234 | _endIndex += (_blockSize - remainder); 235 | return password; 236 | } 237 | } 238 | return password; 239 | } 240 | 241 | public byte[] CryptDeriveKey(string algname, string alghashname, int keySize, byte[] rgbIV) 242 | { 243 | // If this were to be implemented here, CAPI would need to be used (not CNG) because of 244 | // unfortunate differences between the two. Using CNG would break compatibility. Since this 245 | // assembly currently doesn't use CAPI it would require non-trivial additions. 246 | // In addition, if implemented here, only Windows would be supported as it is intended as 247 | // a thin wrapper over the corresponding native API. 248 | // Note that this method is implemented in PasswordDeriveBytes (in the Csp assembly) using CAPI. 249 | throw new PlatformNotSupportedException(); 250 | } 251 | 252 | public override void Reset() 253 | { 254 | Initialize(); 255 | } 256 | 257 | [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "HMACSHA1 is needed for compat. (https://g...content-available-to-author-only...b.com/dotnet/corefx/issues/9438)")] 258 | private HMAC OpenHmac() 259 | { 260 | Debug.Assert(_password != null); 261 | 262 | HashAlgorithmName hashAlgorithm = HashAlgorithm; 263 | 264 | if (string.IsNullOrEmpty(hashAlgorithm.Name)) 265 | throw new CryptographicException("SR.Cryptography_HashAlgorithmNameNullOrEmpty"); 266 | 267 | if (hashAlgorithm == HashAlgorithmName.SHA1) 268 | return new HMACSHA1(_password); 269 | if (hashAlgorithm == HashAlgorithmName.SHA256) 270 | return new HMACSHA256(_password); 271 | if (hashAlgorithm == HashAlgorithmName.SHA384) 272 | return new HMACSHA384(_password); 273 | if (hashAlgorithm == HashAlgorithmName.SHA512) 274 | return new HMACSHA512(_password); 275 | 276 | throw new CryptographicException(string.Format("SR.Cryptography_UnknownHashAlgorithm", hashAlgorithm.Name)); 277 | } 278 | 279 | private void Initialize() 280 | { 281 | if (_buffer != null) 282 | Array.Clear(_buffer, 0, _buffer.Length); 283 | _buffer = new byte[_blockSize]; 284 | _block = 1; 285 | _startIndex = _endIndex = 0; 286 | } 287 | 288 | // This function is defined as follows: 289 | // Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i) 290 | // where i is the block number. 291 | private byte[] Func() 292 | { 293 | byte[] temp = new byte[_salt.Length + sizeof(uint)]; 294 | Buffer.BlockCopy(_salt, 0, temp, 0, _salt.Length); 295 | Helpers.WriteInt(_block, temp, _salt.Length); 296 | 297 | temp = _hmac.ComputeHash(temp); 298 | 299 | byte[] ret = temp; 300 | for (int i = 2; i <= _iterations; i++) 301 | { 302 | temp = _hmac.ComputeHash(temp); 303 | 304 | for (int j = 0; j < _blockSize; j++) 305 | { 306 | ret[j] ^= temp[j]; 307 | } 308 | } 309 | 310 | // increment the block count. 311 | _block++; 312 | return ret; 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /LibFreeVPN/Servers/V2Ray/V2RayConfigJson.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using static LibFreeVPN.Servers.V2RayServer.Parser; 7 | 8 | // JSON object defs for the v2ray conf part that we care about: 9 | 10 | namespace LibFreeVPN.Servers.V2Ray 11 | { 12 | public class Outbounds4Ray 13 | { 14 | public string tag { get; set; } 15 | 16 | public string protocol { get; set; } 17 | 18 | public Outboundsettings4Ray settings { get; set; } 19 | 20 | public StreamSettings4Ray streamSettings { get; set; } 21 | 22 | public Mux4Ray mux { get; set; } 23 | } 24 | 25 | public class UsersItem4Ray 26 | { 27 | public string id { get; set; } 28 | 29 | public int? alterId { get; set; } 30 | 31 | public string email { get; set; } 32 | 33 | public string security { get; set; } 34 | 35 | public string encryption { get; set; } 36 | 37 | public string flow { get; set; } 38 | } 39 | 40 | public class Outboundsettings4Ray 41 | { 42 | public List vnext { get; set; } 43 | 44 | public List servers { get; set; } 45 | 46 | public Response4Ray response { get; set; } 47 | 48 | public string domainStrategy { get; set; } 49 | 50 | public int? userLevel { get; set; } 51 | 52 | public FragmentItem4Ray fragment { get; set; } 53 | 54 | public string secretKey { get; set; } 55 | 56 | public List address { get; set; } 57 | 58 | public List peers { get; set; } 59 | 60 | public bool? noKernelTun { get; set; } 61 | 62 | public int? mtu { get; set; } 63 | 64 | public List reserved { get; set; } 65 | 66 | public int? workers { get; set; } 67 | } 68 | 69 | public class WireguardPeer4Ray 70 | { 71 | public string endpoint { get; set; } 72 | public string publicKey { get; set; } 73 | } 74 | 75 | public class VnextItem4Ray 76 | { 77 | public string address { get; set; } 78 | 79 | public int port { get; set; } 80 | 81 | public List users { get; set; } 82 | } 83 | 84 | public class ServersItem4Ray 85 | { 86 | public string email { get; set; } 87 | 88 | public string address { get; set; } 89 | 90 | public string method { get; set; } 91 | 92 | public bool? ota { get; set; } 93 | 94 | public string password { get; set; } 95 | 96 | public int port { get; set; } 97 | 98 | public int? level { get; set; } 99 | 100 | public string flow { get; set; } 101 | 102 | public List users { get; set; } 103 | } 104 | 105 | public class SocksUsersItem4Ray 106 | { 107 | public string user { get; set; } 108 | 109 | public string pass { get; set; } 110 | 111 | public int? level { get; set; } 112 | } 113 | 114 | public class Mux4Ray 115 | { 116 | public bool enabled { get; set; } 117 | public int? concurrency { get; set; } 118 | public int? xudpConcurrency { get; set; } 119 | public string xudpProxyUDP443 { get; set; } 120 | } 121 | 122 | public class Response4Ray 123 | { 124 | public string type { get; set; } 125 | } 126 | 127 | public class StreamSettings4Ray 128 | { 129 | public string network { get; set; } 130 | 131 | public string security { get; set; } 132 | 133 | public TlsSettings4Ray tlsSettings { get; set; } 134 | 135 | public TcpSettings4Ray tcpSettings { get; set; } 136 | 137 | public KcpSettings4Ray kcpSettings { get; set; } 138 | 139 | public WsSettings4Ray wsSettings { get; set; } 140 | 141 | public HttpupgradeSettings4Ray httpupgradeSettings { get; set; } 142 | 143 | public XhttpSettings4Ray xhttpSettings { get; set; } 144 | 145 | public HttpSettings4Ray httpSettings { get; set; } 146 | 147 | public QuicSettings4Ray quicSettings { get; set; } 148 | 149 | public TlsSettings4Ray realitySettings { get; set; } 150 | 151 | public GrpcSettings4Ray grpcSettings { get; set; } 152 | 153 | public Sockopt4Ray sockopt { get; set; } 154 | } 155 | 156 | public class TlsSettings4Ray 157 | { 158 | public bool? allowInsecure { get; set; } 159 | 160 | public string serverName { get; set; } 161 | 162 | public List alpn { get; set; } 163 | 164 | public string fingerprint { get; set; } 165 | 166 | public bool? show { get; set; } 167 | public string publicKey { get; set; } 168 | public string shortId { get; set; } 169 | public string spiderX { get; set; } 170 | } 171 | 172 | public class TcpSettings4Ray 173 | { 174 | public Header4Ray header { get; set; } 175 | } 176 | 177 | public class Header4Ray 178 | { 179 | public string type { get; set; } 180 | 181 | public HttpRequest4Ray request { get; set; } 182 | 183 | public HttpResponse4Ray response { get; set; } 184 | 185 | public string domain { get; set; } 186 | } 187 | 188 | public class KcpSettings4Ray 189 | { 190 | public int mtu { get; set; } 191 | 192 | public int tti { get; set; } 193 | 194 | public int uplinkCapacity { get; set; } 195 | 196 | public int downlinkCapacity { get; set; } 197 | 198 | public bool congestion { get; set; } 199 | 200 | public int readBufferSize { get; set; } 201 | 202 | public int writeBufferSize { get; set; } 203 | 204 | public Header4Ray header { get; set; } 205 | 206 | public string seed { get; set; } 207 | } 208 | 209 | public class WsSettings4Ray 210 | { 211 | public string path { get; set; } 212 | public string host { get; set; } 213 | 214 | public Headers4Ray headers { get; set; } 215 | } 216 | 217 | public class Headers4Ray 218 | { 219 | public string Host { get; set; } 220 | 221 | [JsonPropertyName("User-Agent")] 222 | public string UserAgent { get; set; } 223 | } 224 | 225 | public class HttpupgradeSettings4Ray 226 | { 227 | public string path { get; set; } 228 | 229 | public string host { get; set; } 230 | } 231 | 232 | public class XhttpSettings4Ray 233 | { 234 | public string path { get; set; } 235 | public string host { get; set; } 236 | public string mode { get; set; } 237 | public object extra { get; set; } 238 | } 239 | 240 | public class HttpSettings4Ray 241 | { 242 | public string path { get; set; } 243 | 244 | public List host { get; set; } 245 | } 246 | 247 | public class QuicSettings4Ray 248 | { 249 | public string security { get; set; } 250 | 251 | public string key { get; set; } 252 | 253 | public Header4Ray header { get; set; } 254 | } 255 | 256 | public class GrpcSettings4Ray 257 | { 258 | public string authority { get; set; } 259 | public string serviceName { get; set; } 260 | public bool multiMode { get; set; } 261 | public int? idle_timeout { get; set; } 262 | public int? health_check_timeout { get; set; } 263 | public bool? permit_without_stream { get; set; } 264 | public int? initial_windows_size { get; set; } 265 | } 266 | 267 | public class Sockopt4Ray 268 | { 269 | public string dialerProxy { get; set; } 270 | } 271 | 272 | public class FragmentItem4Ray 273 | { 274 | public string packets { get; set; } 275 | public string length { get; set; } 276 | public string interval { get; set; } 277 | } 278 | 279 | public class HttpRequest4Ray 280 | { 281 | public string version { get; set; } 282 | public string method { get; set; } 283 | public List path { get; set; } 284 | public Dictionary> headers { get; set; } 285 | } 286 | 287 | public class HttpResponse4Ray 288 | { 289 | public string version { get; set; } 290 | public string status { get; set; } 291 | public string reason { get; set; } 292 | public Dictionary> headers { get; set; } 293 | } 294 | 295 | /* 296 | "xhttpSettings": { 297 | "host": "example.com", 298 | "path": "/yourpath", // must be the same 299 | "mode": "auto", 300 | "extra": { 301 | "headers": { 302 | // "key": "value" 303 | }, 304 | "xPaddingBytes": "100-1000", 305 | "noGRPCHeader": false, // stream-up/one, client only 306 | "noSSEHeader": false, // server only 307 | "scMaxEachPostBytes": 1000000, // packet-up only 308 | "scMinPostsIntervalMs": 30, // packet-up, client only 309 | "scMaxBufferedPosts": 30, // packet-up, server only 310 | "scStreamUpServerSecs": "20-80", // stream-up, server only 311 | "xmux": { // h2/h3 mainly, client only 312 | "maxConcurrency": "16-32", 313 | "maxConnections": 0, 314 | "cMaxReuseTimes": 0, 315 | "hMaxRequestTimes": "600-900", 316 | "hMaxReusableSecs": "1800-3000", 317 | "hKeepAlivePeriod": 0 318 | }, 319 | "downloadSettings": { // client only 320 | "address": "", // another domain/IP 321 | "port": 443, 322 | "network": "xhttp", // must be "xhttp" 323 | "security": "tls", // or "reality" 324 | "tlsSettings": { 325 | // ... 326 | }, 327 | "xhttpSettings": { 328 | "path": "/yourpath", // must be the same 329 | // ... other XHTTP params specific to download if needed 330 | }, 331 | "sockopt": {} // will be replaced by upload's "sockopt" if the latter's "penetrate" is true 332 | } 333 | } 334 | } 335 | */ 336 | 337 | public class XhttpExtra4Ray 338 | { 339 | public Dictionary headers { get; set; } 340 | 341 | public string xPaddingBytes { get; set; } 342 | 343 | public bool? noGRPCHeader { get; set; } 344 | 345 | public bool? noSSEHeader { get; set; } 346 | 347 | public int? scMaxEachPostBytes { get; set; } 348 | 349 | public int? scMinPostsIntervalMs { get; set; } 350 | public int? scMaxBufferedPosts { get; set; } 351 | 352 | public string scStreamUpServerSecs { get; set; } 353 | 354 | public XhttpXmux4Ray xmux { get; set; } 355 | 356 | public XhttpDownloadSettings4Ray downloadSettings { get; set; } 357 | } 358 | 359 | public class XhttpXmux4Ray 360 | { 361 | public string maxConcurrency { get; set; } 362 | public int? maxConnections { get; set; } 363 | 364 | public int? cMaxReuseTimes { get; set; } 365 | 366 | public string hMaxRequestTimes { get; set; } 367 | 368 | public string hMaxReusableSecs { get; set; } 369 | 370 | public int? hKeepAlivePeriod { get; set; } 371 | } 372 | 373 | public class XhttpDownloadSettings4Ray 374 | { 375 | public string address { get; set; } 376 | public int? port { get; set; } 377 | public string network { get; set; } 378 | public string security { get; set; } 379 | public TlsSettings4Ray tlsSettings { get; set; } 380 | public XhttpSettings4Ray xhttpSettings { get; set; } 381 | public Sockopt4Ray sockopt { get; set; } 382 | } 383 | 384 | public class Outbound4Sbox 385 | { 386 | public string type { get; set; } 387 | public string tag { get; set; } 388 | public string server { get; set; } 389 | public int? server_port { get; set; } 390 | public List server_ports { get; set; } 391 | public string uuid { get; set; } 392 | public string security { get; set; } 393 | public int? alter_id { get; set; } 394 | public string flow { get; set; } 395 | public string hop_interval { get; set; } 396 | public int? up_mbps { get; set; } 397 | public int? down_mbps { get; set; } 398 | public string auth_str { get; set; } 399 | public int? recv_window_conn { get; set; } 400 | public int? recv_window { get; set; } 401 | public bool? disable_mtu_discovery { get; set; } 402 | public string detour { get; set; } 403 | public string method { get; set; } 404 | public string username { get; set; } 405 | public string password { get; set; } 406 | public string congestion_control { get; set; } 407 | public string version { get; set; } 408 | public string network { get; set; } 409 | public string packet_encoding { get; set; } 410 | public List local_address { get; set; } 411 | public string private_key { get; set; } 412 | public string peer_public_key { get; set; } 413 | public List reserved { get; set; } 414 | public int? mtu { get; set; } 415 | public string plugin { get; set; } 416 | public string plugin_opts { get; set; } 417 | public Tls4Sbox tls { get; set; } 418 | public Multiplex4Sbox multiplex { get; set; } 419 | public Transport4Sbox transport { get; set; } 420 | public HyObfs4Sbox obfs { get; set; } 421 | public List outbounds { get; set; } 422 | public bool? interrupt_exist_connections { get; set; } 423 | } 424 | 425 | public class Tls4Sbox 426 | { 427 | public bool enabled { get; set; } 428 | public string server_name { get; set; } 429 | public bool? insecure { get; set; } 430 | public List alpn { get; set; } 431 | public Utls4Sbox utls { get; set; } 432 | public Reality4Sbox reality { get; set; } 433 | } 434 | 435 | public class Multiplex4Sbox 436 | { 437 | public bool enabled { get; set; } 438 | public string protocol { get; set; } 439 | public int max_connections { get; set; } 440 | public bool? padding { get; set; } 441 | } 442 | 443 | public class Utls4Sbox 444 | { 445 | public bool enabled { get; set; } 446 | public string fingerprint { get; set; } 447 | } 448 | 449 | public class Reality4Sbox 450 | { 451 | public bool enabled { get; set; } 452 | public string public_key { get; set; } 453 | public string short_id { get; set; } 454 | } 455 | 456 | public class Transport4Sbox 457 | { 458 | public string type { get; set; } 459 | 460 | [JsonPropertyName("host")] 461 | public JsonElement? host { get; set; } 462 | public string path { get; set; } 463 | public Headers4Sbox headers { get; set; } 464 | 465 | public string service_name { get; set; } 466 | public string idle_timeout { get; set; } 467 | public string ping_timeout { get; set; } 468 | public bool? permit_without_stream { get; set; } 469 | 470 | [JsonIgnore] 471 | public string hostString 472 | { 473 | get 474 | { 475 | if (host?.ValueKind == JsonValueKind.String) 476 | { 477 | return host?.ToString(); 478 | } 479 | 480 | return null; 481 | } 482 | } 483 | 484 | [JsonIgnore] 485 | public List hostList 486 | { 487 | get 488 | { 489 | if (host?.ValueKind == JsonValueKind.Array) 490 | { 491 | try 492 | { 493 | return host?.Deserialize>(); 494 | } 495 | catch 496 | { 497 | return null; 498 | } 499 | } 500 | 501 | return null; 502 | } 503 | } 504 | } 505 | 506 | public class Headers4Sbox 507 | { 508 | public string Host { get; set; } 509 | } 510 | 511 | public class HyObfs4Sbox 512 | { 513 | public string type { get; set; } 514 | public string password { get; set; } 515 | } 516 | } 517 | -------------------------------------------------------------------------------- /LibFreeVPN/Providers/ShMpn.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Memecrypto; 2 | using LibFreeVPN.ProviderHelpers; 3 | using LibFreeVPN.Providers.ShWrd; 4 | using LibFreeVPN.Servers; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Net.Http; 10 | using System.Security.Cryptography; 11 | using System.Text; 12 | using System.Text.Json; 13 | using System.Threading; 14 | using System.Threading.Tasks; 15 | 16 | // Android apps. SocksHttp using SSH + V2ray. 17 | namespace LibFreeVPN.Providers.ShMpn 18 | { 19 | public abstract class ParserBase : SocksHttpParserAesPbkdf2 20 | where TType : ParserBase, new() 21 | { 22 | protected override IEnumerable OptionalServersArrayKeys => "Networks".EnumerableSingle(); 23 | protected override string CountryNameKey => "FLAG"; 24 | protected override string ServerTypeKey => "isV2ray"; 25 | protected override string V2RayKey => "v2rayJson"; 26 | protected override string UsernameKey => "XUser"; 27 | protected override string PasswordKey => "XPass"; 28 | 29 | protected virtual string SSHPrivkeyKey => "servermessage"; 30 | protected static string ReverseRot1(string text, int offset, int length) 31 | { 32 | var chars = text.ToCharArray(); 33 | for (int i = 0; i < length; i++) 34 | { 35 | var idx = i + offset; 36 | var chr = chars[idx]; 37 | if (chr >= '0' && chr <= '9') 38 | { 39 | chars[idx] = (char)((((chr - '0') + 9) % 10) + '0'); 40 | } 41 | if (chr < 'A' || (chr >= 0x5B && chr <= 0x60) || chr > 'z') continue; 42 | int baseVal = 'A' + (chr & 0x20); 43 | chars[idx] = (char)(((chr - baseVal + 25) % 26) + baseVal); 44 | } 45 | return new string(chars); 46 | } 47 | 48 | protected override IEnumerable ParseServer(JsonDocument root, JsonElement server, IReadOnlyDictionary passedExtraRegistry) 49 | { 50 | string serverType = null; 51 | if (!server.TryGetProperty(ServerTypeKey, out var serverTypeJson)) 52 | { 53 | // This is Network element, aka SSH tunnel 54 | serverType = "ssh"; 55 | } 56 | else 57 | { 58 | switch (serverTypeJson.ValueKind) 59 | { 60 | case JsonValueKind.String: 61 | serverType = serverTypeJson.GetString().ToLower() == "true" ? "v2ray" : "unknown"; 62 | break; 63 | case JsonValueKind.True: 64 | serverType = "v2ray"; 65 | break; 66 | case JsonValueKind.False: 67 | serverType = "unknown"; 68 | break; 69 | default: 70 | throw new InvalidDataException(); 71 | } 72 | } 73 | 74 | string hostname, port; 75 | string username = null, password = null, v2ray = null; 76 | string name, country; 77 | 78 | if (!server.TryGetPropertyString(ServerNameKey, out name)) throw new InvalidDataException(); 79 | 80 | var extraRegistry = new Dictionary(); 81 | foreach (var kv in passedExtraRegistry) extraRegistry.Add(kv.Key, kv.Value); 82 | extraRegistry.Add(ServerRegistryKeys.DisplayName, name); 83 | if (server.TryGetPropertyString(CountryNameKey, out country)) extraRegistry.Add(ServerRegistryKeys.Country, country); 84 | 85 | switch (serverType.ToLower()) 86 | { 87 | case "ssh": // ssh 88 | if (!server.TryGetPropertyString(HostnameKey, out hostname)) throw new InvalidDataException(); 89 | if (!server.TryGetPropertyString(PortKey, out port)) throw new InvalidDataException(); 90 | if (!server.TryGetPropertyString(UsernameKey, out username)) throw new InvalidDataException(); 91 | server.TryGetPropertyString(PasswordKey, out password); 92 | if (string.IsNullOrEmpty(password) && !server.TryGetPropertyString(SSHPrivkeyKey, out password)) throw new InvalidDataException(); 93 | return hostname.Split(';').Select((host) => new SSHServer(host, port, username, password, extraRegistry)).ToList(); 94 | case "v2ray": // v2ray 95 | if (!server.TryGetPropertyString(V2RayKey, out v2ray)) throw new InvalidDataException(); 96 | return V2RayServer.ParseConfigFull(v2ray, extraRegistry); 97 | default: 98 | throw new InvalidDataException(); 99 | } 100 | } 101 | } 102 | 103 | public abstract class ParserBaseXtea : ParserBase 104 | where TType : ParserBaseXtea, new() 105 | { 106 | protected abstract uint[] InnerKey { get; } 107 | 108 | protected virtual bool HasReverseRot => true; 109 | 110 | private static void XteaDecryptBlock(uint num_rounds, uint[] v, uint[] key) 111 | { 112 | uint i; 113 | uint v0 = v[0], v1 = v[1], delta = 0x9E3779B9, sum = delta * num_rounds; 114 | for (i = 0; i < num_rounds; i++) 115 | { 116 | v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); 117 | sum -= delta; 118 | v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); 119 | } 120 | v[0] = v0; v[1] = v1; 121 | } 122 | 123 | private static bool s_IsBigEndian = BitConverter.GetBytes(0x12345678)[0] == 0x12; 124 | 125 | private static uint SwapEndianness(uint x) 126 | { 127 | // swap adjacent 16-bit blocks 128 | x = (x >> 16) | (x << 16); 129 | // swap adjacent 8-bit blocks 130 | return ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8); 131 | } 132 | 133 | private static void SwapEndianness(ref uint x) => x = SwapEndianness(x); 134 | 135 | private string DecryptInner(string ciphertext) 136 | { 137 | if (ciphertext.Length == 0) return ciphertext; 138 | var blocks = ciphertext.Split(' '); 139 | var plaintext = new byte[blocks.Length * 8]; 140 | 141 | int offset = 0; 142 | 143 | foreach (var block in blocks) 144 | { 145 | var bytes = Convert.FromBase64String(block); 146 | uint[] ints = new uint[2] { 147 | BitConverter.ToUInt32(bytes, 0), 148 | BitConverter.ToUInt32(bytes, 4) 149 | }; 150 | if (!s_IsBigEndian) 151 | { 152 | SwapEndianness(ref ints[0]); 153 | SwapEndianness(ref ints[1]); 154 | } 155 | XteaDecryptBlock(32, ints, InnerKey); 156 | if (!s_IsBigEndian) 157 | { 158 | SwapEndianness(ref ints[0]); 159 | SwapEndianness(ref ints[1]); 160 | } 161 | Buffer.BlockCopy(ints, 0, plaintext, offset, 4); 162 | Buffer.BlockCopy(ints, 4, plaintext, offset + 4, 4); 163 | offset += 8; 164 | } 165 | 166 | return Encoding.UTF8.GetString(plaintext).TrimEnd('\x00'); 167 | } 168 | 169 | 170 | 171 | protected override string DecryptInner(string jsonKey, string ciphertext) 172 | { 173 | if (jsonKey != HostnameKey && jsonKey != UsernameKey && jsonKey != PasswordKey && jsonKey != SSHPrivkeyKey && jsonKey != V2RayKey) return ciphertext; 174 | 175 | ciphertext = DecryptInner(ciphertext); 176 | 177 | if (jsonKey != SSHPrivkeyKey) return ciphertext; 178 | if (!HasReverseRot) return ciphertext; 179 | 180 | var start = ciphertext.IndexOf("KEY-----"); 181 | if (start == -1) return ciphertext; 182 | ciphertext = ciphertext.Replace("\\n", "\n"); 183 | start = ciphertext.IndexOf("KEY-----"); 184 | start += "KEY-----".Length; 185 | var end = ciphertext.IndexOf("-----", start); 186 | if (end == -1) return ciphertext; 187 | return ReverseRot1(ciphertext, start, end - start); 188 | } 189 | } 190 | 191 | public abstract class ParserBaseRot : ParserBase 192 | where TType : ParserBaseRot, new() 193 | { 194 | protected override string ServersArrayKey => "serversList"; 195 | protected override IEnumerable OptionalServersArrayKeys => "networksList".EnumerableSingle(); 196 | protected override string ServerNameKey => "name"; 197 | protected override string CountryNameKey => "flag"; 198 | protected override string V2RayKey => "v2rayEntry"; 199 | protected override string HostnameKey => "host"; 200 | protected override string PortKey => "portDirect"; 201 | protected override string UsernameKey => "username"; 202 | protected override string PasswordKey => "password"; 203 | 204 | protected override string SSHPrivkeyKey => "pubkey"; 205 | 206 | 207 | internal static ThreadLocal s_CurrentOuterKey = new ThreadLocal(); 208 | protected override HashAlgorithmName HashAlgorithm => HashAlgorithmName.SHA1; 209 | 210 | protected override string OuterKeyId => s_CurrentOuterKey.Value; 211 | 212 | protected override string DecryptInner(string jsonKey, string ciphertext) 213 | { 214 | if (jsonKey == SSHPrivkeyKey) 215 | { 216 | var start = ciphertext.IndexOf("KEY-----"); 217 | if (start == -1) return ciphertext; 218 | start += "KEY-----".Length; 219 | var end = ciphertext.IndexOf("-----", start); 220 | if (end == -1) return ciphertext; 221 | return ReverseRot1(ciphertext, start, end - start); 222 | } else if (jsonKey == V2RayKey) 223 | { 224 | if (ciphertext.StartsWith("vmess://")) 225 | { 226 | var vmjsonText = Encoding.UTF8.GetString(Convert.FromBase64String(ciphertext.Substring("vmess://".Length))); 227 | var vmjson = JsonDocument.Parse(vmjsonText); 228 | if (!vmjson.RootElement.TryGetPropertyString("id", out var vmessid)) return ciphertext; 229 | vmjsonText = vmjsonText.Replace(vmessid, ReverseRot1(vmessid, 0, vmessid.Length)); 230 | return string.Format("vmess://{0}", Convert.ToBase64String(Encoding.UTF8.GetBytes(vmjsonText))); 231 | } 232 | if (ciphertext.StartsWith("vless://") || ciphertext.StartsWith("trojan://")) 233 | { 234 | var parsed = new UriBuilder(new Uri(ciphertext.Trim())); 235 | parsed.UserName = ReverseRot1(parsed.UserName, 0, parsed.UserName.Length); 236 | return parsed.ToString(); 237 | } 238 | } 239 | return ciphertext; 240 | } 241 | 242 | protected override string DecryptOuter(string ciphertext) 243 | { 244 | ciphertext = ciphertext.Substring(0, 5) + ciphertext.Substring(8, 15) + ciphertext.Substring(26); 245 | var chars = Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String(ciphertext)))).ToCharArray(); 246 | Array.Reverse(chars); 247 | for (int i = 0; i < chars.Length; i++) 248 | { 249 | var chr = chars[i]; 250 | if (chr < 'A' || chr == 0x60 || chr > 'z') continue; 251 | int baseVal = 'A' + (chr & 0x20); 252 | chars[i] = (char)(((chr - baseVal + 20) % 26) + baseVal); 253 | } 254 | 255 | return base.DecryptOuter(new string(chars)); 256 | } 257 | 258 | public string DecryptString(string ciphertext) => DecryptOuter(ciphertext); 259 | } 260 | 261 | public sealed class ParserConfigRot : ParserBaseRot 262 | { 263 | protected override string OuterKeyId => m_OuterKeyId; 264 | 265 | public string OverrideOuterKeyId { set => m_OuterKeyId = value; } 266 | 267 | private string m_OuterKeyId = "m"; 268 | } 269 | 270 | public sealed class ParserRot : ParserBaseRot { } 271 | 272 | public abstract class ParserXxteaBase : SocksHttpWithOvpnNumericParserTeaOuter 273 | where TType : ParserXxteaBase, new() 274 | { 275 | protected override int InnerKey => 4669; 276 | protected override string OvpnKey => "Ovpn_Cert"; 277 | protected override string V2RayKey => "ServerCloudFront"; 278 | } 279 | 280 | public sealed class ParserXxtea : ParserXxteaBase { } 281 | 282 | public abstract class ShMpnBase : VPNProviderGithubRepoFileBase 283 | where TParser : SocksHttpParser, new() 284 | { 285 | protected override string RepoName => Encoding.ASCII.FromBase64String("TWluYURpTmFiaWwvdXBkYXRlLmxpbms="); 286 | 287 | public override bool HasProtocol(ServerProtocol protocol) => 288 | protocol == ServerProtocol.SSH || protocol == ServerProtocol.V2Ray; 289 | } 290 | 291 | public abstract class ShMpnRotBase : ShMpnBase 292 | where TParser : ParserBaseRot, new() 293 | { 294 | protected virtual string SecondConfigUrlKey => "ServerLink"; 295 | protected virtual string InnerSeedKey => "ServerPassWD"; 296 | 297 | protected override async Task> GetServersAsyncImpl(string config) 298 | { 299 | // Decrypt the obtained config. 300 | var firstParser = new ParserConfigRot(); 301 | config = firstParser.DecryptString(config); 302 | 303 | // Parse it to get the real key-id and the actual config url. 304 | var json = JsonDocument.Parse(config); 305 | if (json.RootElement.ValueKind != JsonValueKind.Object) throw new InvalidDataException(); 306 | if (!json.RootElement.TryGetPropertyString(SecondConfigUrlKey, out var secondConfigUrl)) throw new InvalidDataException(); 307 | if (!json.RootElement.TryGetPropertyString(InnerSeedKey, out var innerSeedKey)) throw new InvalidDataException(); 308 | 309 | innerSeedKey = firstParser.DecryptString(innerSeedKey); 310 | 311 | firstParser.OverrideOuterKeyId = innerSeedKey; 312 | secondConfigUrl = firstParser.DecryptString(secondConfigUrl); 313 | 314 | // If this isn't supposed to make risky requests, ensure the domain matches. 315 | if (!RiskyRequests) 316 | { 317 | var parsedUrl = new Uri(secondConfigUrl); 318 | var firstUrl = new Uri(RequestUri); 319 | if (firstUrl.Host != parsedUrl.Host) throw new InvalidDataException(); 320 | } 321 | 322 | // Get the second config. 323 | config = await ServerUtilities.HttpClient.GetStringAsync(secondConfigUrl); 324 | 325 | // Ensure the parser is set to use the correct key-id for this thread. 326 | ParserBaseRot.s_CurrentOuterKey.Value = innerSeedKey; 327 | 328 | // Try to parse the config. 329 | return await GetServersAsyncImpl(config); 330 | } 331 | } 332 | 333 | 334 | 335 | public sealed class ShMpnBee : ShMpnBase 336 | { 337 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWRldi5kZXY3LmJlZQ=="; 338 | 339 | public override string SampleVersion => "38.4"; 340 | 341 | protected override string RepoName => Encoding.ASCII.FromBase64String("YWJkb2VsMTAzL3pvbmV0dW5uZWw="); 342 | 343 | protected override string ConfigName => Encoding.ASCII.FromBase64String("YmlnanNvbg=="); 344 | } 345 | 346 | public sealed class ShMpnBee2 : ShMpnBase 347 | { 348 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWRldi5kZXY3LmJlZQ=="; 349 | 350 | public override string SampleVersion => "83.0"; 351 | 352 | protected override string RepoName => Encoding.ASCII.FromBase64String("YWJkb2VsMTAzL3pvbmV0dW5uZWw="); 353 | 354 | protected override string ConfigName => Encoding.ASCII.FromBase64String("emNiYw=="); 355 | } 356 | 357 | public sealed class ShMpnBee3 : ShMpnBase 358 | { 359 | 360 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPWRldi5kZXY3LmJlZQ=="; 361 | 362 | public override string SampleVersion => "83.0"; 363 | 364 | protected override string RepoName => Encoding.ASCII.FromBase64String("YWJkb2VsMTAzL3pvbmV0dW5uZWw="); 365 | 366 | protected override string ConfigName => Encoding.ASCII.FromBase64String("YmlnanNvbg=="); 367 | } 368 | 369 | public sealed class ShMpnMp : ShMpnRotBase 370 | { 371 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPXZwbi5taW5hcHJvbmV0LmNvbS5lZw=="; 372 | 373 | public override string SampleVersion => "58.0"; 374 | 375 | protected override string ConfigName => Encoding.ASCII.FromBase64String("bWluYXByb25ldF91cGRhdGVy"); 376 | } 377 | public sealed class ShMpnOc : ShMpnRotBase 378 | { 379 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPW9wZW5jdXN0b20ubWluYXByb25ldC5jb20uZWc="; 380 | 381 | public override string SampleVersion => "7.0"; 382 | 383 | protected override string ConfigName => Encoding.ASCII.FromBase64String("b3BlbmN1c3RvbV91cGRhdGVy"); 384 | } 385 | public sealed class ShMpnSd : ShMpnRotBase 386 | { 387 | public override string SampleSource => "aHR0cHM6Ly9wbGF5Lmdvb2dsZS5jb20vc3RvcmUvYXBwcy9kZXRhaWxzP2lkPXNsb3dkbnMubWluYXByb25ldHZwbi5jb20uZWc="; 388 | 389 | public override string SampleVersion => "2.0"; 390 | 391 | protected override string ConfigName => Encoding.ASCII.FromBase64String("c2xvd2Ruc191cGRhdGVy"); 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /LibFreeVPN/Servers/SocksHttpParser.cs: -------------------------------------------------------------------------------- 1 | using LibFreeVPN.Memecrypto; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Text.Json; 9 | 10 | namespace LibFreeVPN.Servers 11 | { 12 | // Parser for SocksHtttp configs. (android client) 13 | // There are dozens of forks of this, each with their own obfuscation methods and memecrypto applied to configs etc 14 | // Therefore, implement some hierarchy to deal with it, implement the most common method and so on. 15 | public abstract class SocksHttpParser : VPNJsonArrInObjMultiProviderParser 16 | where TType : SocksHttpParser, new() 17 | { 18 | 19 | // Server object 20 | protected virtual string ServerNameKey => "Name"; 21 | protected virtual string CountryNameKey => "Flag"; 22 | protected virtual string HostnameKey => "ServerIP"; 23 | protected virtual string PortKey => "ServerPort"; 24 | 25 | protected virtual string UsernameKey => "ServerUser"; 26 | protected virtual string PasswordKey => "ServerPass"; 27 | 28 | protected virtual string V2RayKey => "V2RayJson"; 29 | 30 | protected virtual string ServerTypeKey => "Tunnel"; 31 | 32 | protected override IEnumerable ParseServer(JsonDocument root, JsonElement server, IReadOnlyDictionary passedExtraRegistry) 33 | { 34 | if (!server.TryGetPropertyString(ServerTypeKey, out var serverType)) 35 | { 36 | // some variants do not have ServerType and just support SSH 37 | serverType = "ssh"; 38 | } 39 | string hostname, port; 40 | string username = null, password = null, v2ray = null; 41 | string name, country; 42 | 43 | if (!server.TryGetPropertyString(ServerNameKey, out name)) throw new InvalidDataException(); 44 | if (!server.TryGetPropertyString(CountryNameKey, out country)) throw new InvalidDataException(); 45 | if (!server.TryGetPropertyString(HostnameKey, out hostname)) throw new InvalidDataException(); 46 | if (!server.TryGetPropertyString(PortKey, out port)) throw new InvalidDataException(); 47 | 48 | var extraRegistry = new Dictionary(); 49 | foreach (var kv in passedExtraRegistry) extraRegistry.Add(kv.Key, kv.Value); 50 | extraRegistry.Add(ServerRegistryKeys.DisplayName, name); 51 | extraRegistry.Add(ServerRegistryKeys.Country, country); 52 | 53 | if (serverType.ToLower().StartsWith("ssh ")) serverType = "ssh"; 54 | 55 | switch (serverType.ToLower()) 56 | { 57 | case "ssh": 58 | if (!server.TryGetPropertyString(UsernameKey, out username)) throw new InvalidDataException(); 59 | if (!server.TryGetPropertyString(PasswordKey, out password)) throw new InvalidDataException(); 60 | return new SSHServer(hostname, port, username, password, extraRegistry).EnumerableSingle(); 61 | case "v2ray": 62 | if (!server.TryGetPropertyString(V2RayKey, out v2ray)) throw new InvalidDataException(); 63 | return V2RayServer.ParseConfigFull(v2ray, extraRegistry); 64 | default: 65 | throw new InvalidDataException(); 66 | } 67 | } 68 | } 69 | 70 | // Parser for SocksHttp configs (with OpenVPN+SSH support - same server running both) 71 | public abstract class SocksHttpWithOvpnParser : SocksHttpParser 72 | where TType : SocksHttpWithOvpnParser, new() 73 | { 74 | protected virtual string OvpnKey => "OvpnCertificate"; 75 | protected virtual string OvpnPortKey => "TCPPort"; 76 | protected override string PortKey => "SSHPort"; 77 | protected override string UsernameKey => "Username"; 78 | protected override string PasswordKey => "Password"; 79 | protected override string V2RayKey => "V2Ray"; 80 | protected override string ServerTypeKey => "SelectType"; 81 | protected override IEnumerable ParseServer(JsonDocument root, JsonElement server, IReadOnlyDictionary passedExtraRegistry) 82 | { 83 | if (!server.TryGetPropertyString(ServerTypeKey, out var serverType)) throw new InvalidDataException(); 84 | 85 | string hostname, port; 86 | string username = null, password = null, v2ray = null, ovpnconf = null, ovpnport = null; 87 | string name, country; 88 | 89 | if (!server.TryGetPropertyString(ServerNameKey, out name)) throw new InvalidDataException(); 90 | if (!server.TryGetPropertyString(CountryNameKey, out country)) throw new InvalidDataException(); 91 | if (!server.TryGetPropertyString(HostnameKey, out hostname)) throw new InvalidDataException(); 92 | if (!server.TryGetPropertyString(PortKey, out port)) throw new InvalidDataException(); 93 | 94 | var extraRegistry = new Dictionary(); 95 | foreach (var kv in passedExtraRegistry) extraRegistry.Add(kv.Key, kv.Value); 96 | extraRegistry.Add(ServerRegistryKeys.DisplayName, name); 97 | extraRegistry.Add(ServerRegistryKeys.Country, country); 98 | 99 | switch (serverType.ToLower()) 100 | { 101 | case "default": 102 | case "ssh": 103 | if (!server.TryGetPropertyString(UsernameKey, out username)) throw new InvalidDataException(); 104 | if (!server.TryGetPropertyString(PasswordKey, out password)) throw new InvalidDataException(); 105 | if (!server.TryGetPropertyString(OvpnKey, out ovpnconf)) 106 | { 107 | if (root.RootElement.TryGetPropertyString(OvpnKey, out ovpnconf)) ovpnconf = DecryptInner(OvpnKey, ovpnconf); 108 | else ovpnconf = null; 109 | } 110 | if (!server.TryGetPropertyString(OvpnPortKey, out ovpnport)) ovpnport = null; 111 | 112 | var sshOnlyEnum = new SSHServer(hostname, port, username, password, extraRegistry).EnumerableSingle(); 113 | if (!string.IsNullOrEmpty(ovpnconf)) 114 | { 115 | var ovpnRegistry = new Dictionary(); 116 | foreach (var kv in extraRegistry) ovpnRegistry.Add(kv.Key, kv.Value); 117 | ovpnRegistry.Add(ServerRegistryKeys.Username, username); 118 | ovpnRegistry.Add(ServerRegistryKeys.Password, password); 119 | return OpenVpnServer.ParseConfigFull(OpenVpnServer.InjectHostIntoConfig(ovpnconf, hostname, ovpnport), ovpnRegistry).Concat(sshOnlyEnum); 120 | } 121 | return sshOnlyEnum; 122 | case "v2ray": 123 | if (!server.TryGetPropertyString(V2RayKey, out v2ray)) throw new InvalidDataException(); 124 | return V2RayServer.ParseConfigFull(v2ray, extraRegistry); 125 | default: 126 | throw new InvalidDataException(); 127 | } 128 | } 129 | } 130 | 131 | public abstract class SocksHttpWithOvpnNumericParser : SocksHttpWithOvpnParser 132 | where TType : SocksHttpWithOvpnNumericParser, new() 133 | { 134 | protected override string CountryNameKey => "FLAG"; 135 | protected override string ServerTypeKey => "serverType"; 136 | protected override string OvpnPortKey => "TcpPort"; 137 | protected override string OvpnKey => "ovpnCertificate"; 138 | 139 | protected virtual bool OvpnPortIsBogus => false; 140 | 141 | protected override IEnumerable ParseServer(JsonDocument root, JsonElement server, IReadOnlyDictionary passedExtraRegistry) 142 | { 143 | string serverType = null; 144 | if (!server.TryGetProperty(ServerTypeKey, out var serverTypeJson)) throw new InvalidDataException(); 145 | switch (serverTypeJson.ValueKind) 146 | { 147 | case JsonValueKind.String: 148 | serverType = serverTypeJson.GetString(); 149 | break; 150 | case JsonValueKind.Number: 151 | serverType = serverTypeJson.GetInt32().ToString(); 152 | break; 153 | default: 154 | throw new InvalidDataException(); 155 | } 156 | 157 | string port; 158 | string username = null, password = null, v2ray = null, ovpnconf = null; 159 | string name, country; 160 | 161 | if (!server.TryGetPropertyString(ServerNameKey, out name)) throw new InvalidDataException(); 162 | if (!server.TryGetPropertyString(CountryNameKey, out country)) throw new InvalidDataException(); 163 | if (!server.TryGetPropertyString(HostnameKey, out var hostnames)) throw new InvalidDataException(); 164 | if (!server.TryGetPropertyString(OvpnPortKey, out port)) throw new InvalidDataException(); 165 | 166 | // if hostname contains semicolon then it's a list of hostnames 167 | return hostnames.Split(';').SelectMany((hostname) => 168 | { 169 | var extraRegistry = new Dictionary(); 170 | foreach (var kv in passedExtraRegistry) extraRegistry.Add(kv.Key, kv.Value); 171 | extraRegistry.Add(ServerRegistryKeys.DisplayName, name); 172 | extraRegistry.Add(ServerRegistryKeys.Country, country); 173 | 174 | switch (serverType.ToLower()) 175 | { 176 | case "0": // ovpn 177 | if (!server.TryGetPropertyString(UsernameKey, out username)) throw new InvalidDataException(); 178 | if (!server.TryGetPropertyString(PasswordKey, out password)) throw new InvalidDataException(); 179 | if (!server.TryGetPropertyString(OvpnKey, out ovpnconf)) 180 | { 181 | if (root.RootElement.TryGetPropertyString(OvpnKey, out ovpnconf)) ovpnconf = DecryptInner(OvpnKey, ovpnconf); 182 | else ovpnconf = null; 183 | } 184 | if (string.IsNullOrEmpty(ovpnconf)) throw new InvalidDataException(); 185 | if (OvpnPortIsBogus) 186 | { 187 | // Port in the JSON object is bogus, the real port is in the openvpn config 188 | var real = OpenVpnServer.ParseConfigFull(ovpnconf).FirstOrDefault(); 189 | if (real == null || !real.Registry.TryGetValue(ServerRegistryKeys.Port, out port)) throw new InvalidDataException(); 190 | 191 | } 192 | var ovpnRegistry = new Dictionary(); 193 | foreach (var kv in extraRegistry) ovpnRegistry.Add(kv.Key, kv.Value); 194 | ovpnRegistry.Add(ServerRegistryKeys.Username, username); 195 | ovpnRegistry.Add(ServerRegistryKeys.Password, password); 196 | return OpenVpnServer.ParseConfigFull(OpenVpnServer.InjectHostIntoConfig(ovpnconf, hostname, port), ovpnRegistry); 197 | case "1": // ssh 198 | if (!server.TryGetPropertyString(UsernameKey, out username)) throw new InvalidDataException(); 199 | if (!server.TryGetPropertyString(PasswordKey, out password)) throw new InvalidDataException(); 200 | return new SSHServer(hostname, port, username, password, extraRegistry).EnumerableSingle(); 201 | // 2 => dns 202 | case "3": // v2ray 203 | if (!server.TryGetPropertyString(V2RayKey, out v2ray)) throw new InvalidDataException(); 204 | return V2RayServer.ParseConfigFull(v2ray, extraRegistry); 205 | // 4 => udp 206 | default: 207 | throw new InvalidDataException(); 208 | } 209 | }).ToArray(); 210 | } 211 | } 212 | 213 | // Subclass for the most common cryptoschemes used: 214 | public abstract class SocksHttpWithOvpnParserTea : SocksHttpWithOvpnParser 215 | where TType : SocksHttpWithOvpnParserTea, new() 216 | { 217 | protected virtual uint TeaDelta => 0x2E0BA747; 218 | 219 | private XXTEA XXTEA => XXTEA.Create(TeaDelta); 220 | 221 | protected abstract string OuterKey { get; } 222 | 223 | protected override string DecryptOuter(string ciphertext) 224 | { 225 | return XXTEA.DecryptBase64StringToString(ciphertext, OuterKey); 226 | } 227 | 228 | protected override string DecryptInner(string jsonKey, string ciphertext) 229 | { 230 | if (jsonKey != HostnameKey && jsonKey != UsernameKey && jsonKey != PasswordKey && jsonKey != OvpnKey && jsonKey != V2RayKey) return ciphertext; 231 | 232 | return Encoding.UTF8.GetString(Convert.FromBase64String(ciphertext)); 233 | } 234 | } 235 | 236 | public abstract class SocksHttpWithOvpnNumericParserTea : SocksHttpWithOvpnNumericParser 237 | where TType : SocksHttpWithOvpnNumericParserTea, new() 238 | { 239 | protected virtual uint TeaDeltaOuter => 0x2E0BA747; 240 | protected virtual uint TeaDeltaInner => 0x2E0BA747; 241 | 242 | private XXTEA XXTEAOuter => XXTEA.Create(TeaDeltaOuter); 243 | private XXTEA XXTEAInner => XXTEA.Create(TeaDeltaInner); 244 | protected virtual string OuterKey => InnerKey.ToString(); 245 | protected abstract int InnerKey { get; } 246 | 247 | protected override string DecryptOuter(string ciphertext) 248 | { 249 | return XXTEAOuter.DecryptBase64StringToString(ciphertext, OuterKey); 250 | } 251 | 252 | protected virtual string DecryptInner(string ciphertext) 253 | { 254 | var arr = XXTEAInner.DecryptBase64StringToString(ciphertext, InnerKey.ToString()).ToCharArray(); 255 | for (int i = 0; i < arr.Length; i++) arr[i] -= (char)(InnerKey * 2); 256 | return new string(arr); 257 | } 258 | 259 | protected override string DecryptInner(string jsonKey, string ciphertext) 260 | { 261 | if (jsonKey == OvpnPortKey) return ciphertext.Split(':')[0]; 262 | if (jsonKey != HostnameKey && jsonKey != UsernameKey && jsonKey != PasswordKey && jsonKey != OvpnKey && jsonKey != V2RayKey) return ciphertext; 263 | 264 | return DecryptInner(ciphertext); 265 | } 266 | } 267 | 268 | public abstract class SocksHttpWithOvpnNumericParserTeaOuter : SocksHttpWithOvpnNumericParserTea 269 | where TType : SocksHttpWithOvpnNumericParserTeaOuter, new() 270 | { 271 | protected override string DecryptOuter(string ciphertext) 272 | { 273 | return DecryptInner(ciphertext); 274 | } 275 | } 276 | 277 | public abstract class SocksHttpParserTeaAes : SocksHttpParser 278 | where TType : SocksHttpParserTeaAes, new() 279 | { 280 | protected virtual uint TeaDelta => 0x2E0BA747; 281 | 282 | private XXTEA XXTEA => XXTEA.Create(TeaDelta); 283 | 284 | protected abstract string OuterKey { get; } 285 | 286 | private static readonly byte[] s_InnerKey = 287 | { 288 | 0x4a, 0xf9, 0xa1, 0x4a, 0xb6, 0xda, 0x0e, 0xfc, 289 | 0xe7, 0x73, 0xf0, 0x1a, 0x02, 0x1c, 0xd5, 0x2e, 290 | 0x67, 0x5d, 0xbb, 0xa1, 0x52, 0x84, 0xe5, 0x6b, 291 | 0x57, 0x1d, 0xc1, 0xf5, 0x0e, 0xe2, 0x11, 0x76 292 | }; 293 | 294 | private static readonly byte[] s_InnerIv = new byte[0x10]; 295 | 296 | protected override string DecryptOuter(string ciphertext) 297 | { 298 | return XXTEA.DecryptBase64StringToString(ciphertext, OuterKey); 299 | } 300 | 301 | protected override string DecryptInner(string jsonKey, string ciphertext) 302 | { 303 | if (jsonKey != HostnameKey && jsonKey != UsernameKey && jsonKey != PasswordKey && jsonKey != V2RayKey) return ciphertext; 304 | 305 | var cipherTextBytes = Convert.FromBase64String(ciphertext); 306 | using (var aes = new AesManaged()) 307 | { 308 | aes.BlockSize = 128; 309 | aes.KeySize = 256; 310 | aes.Padding = PaddingMode.PKCS7; 311 | using (var dec = aes.CreateDecryptor(s_InnerKey, s_InnerIv)) 312 | { 313 | return Encoding.UTF8.GetString(dec.TransformFinalBlock(cipherTextBytes, 0, cipherTextBytes.Length)); 314 | } 315 | } 316 | } 317 | } 318 | 319 | public abstract class SocksHttpParserAes : SocksHttpParser 320 | where TType : SocksHttpParserAes, new() 321 | { 322 | protected abstract byte[] OuterKey { get; } 323 | 324 | private static readonly byte[] s_InnerKey = 325 | { 326 | 0x4a, 0xf9, 0xa1, 0x4a, 0xb6, 0xda, 0x0e, 0xfc, 327 | 0xe7, 0x73, 0xf0, 0x1a, 0x02, 0x1c, 0xd5, 0x2e, 328 | 0x67, 0x5d, 0xbb, 0xa1, 0x52, 0x84, 0xe5, 0x6b, 329 | 0x57, 0x1d, 0xc1, 0xf5, 0x0e, 0xe2, 0x11, 0x76 330 | }; 331 | 332 | private static readonly byte[] s_AesIv = new byte[0x10]; 333 | 334 | private static string DecryptAes(string ciphertext, byte[] key) 335 | { 336 | var cipherTextBytes = Convert.FromBase64String(ciphertext); 337 | using (var aes = new AesManaged()) 338 | { 339 | aes.BlockSize = 128; 340 | aes.KeySize = 256; 341 | aes.Padding = PaddingMode.PKCS7; 342 | using (var dec = aes.CreateDecryptor(key, s_AesIv)) 343 | { 344 | return Encoding.UTF8.GetString(dec.TransformFinalBlock(cipherTextBytes, 0, cipherTextBytes.Length)); 345 | } 346 | } 347 | } 348 | 349 | protected override string DecryptOuter(string ciphertext) 350 | { 351 | return DecryptAes(ciphertext, OuterKey); 352 | } 353 | 354 | protected override string DecryptInner(string jsonKey, string ciphertext) 355 | { 356 | if (jsonKey != HostnameKey && jsonKey != UsernameKey && jsonKey != PasswordKey && jsonKey != V2RayKey) return ciphertext; 357 | 358 | return DecryptAes(ciphertext, s_InnerKey); 359 | } 360 | } 361 | 362 | public abstract class SocksHttpParserAesPbkdf2 : SocksHttpParser 363 | where TType : SocksHttpParserAesPbkdf2, new() 364 | { 365 | protected abstract string OuterKeyId { get; } 366 | protected virtual HashAlgorithmName HashAlgorithm => HashAlgorithmName.SHA256; 367 | protected virtual int PbkdfRounds => 10000; 368 | 369 | protected override string DecryptOuter(string ciphertext) 370 | { 371 | var bytes = Convert.FromBase64String(ciphertext); 372 | var seed = new byte[0x10]; 373 | var iv = new byte[0x10]; 374 | var cipherTextBytes = new byte[bytes.Length - 0x20]; 375 | Buffer.BlockCopy(bytes, 0, seed, 0, 0x10); 376 | Buffer.BlockCopy(bytes, 0x10, iv, 0, 0x10); 377 | Buffer.BlockCopy(bytes, 0x20, cipherTextBytes, 0, cipherTextBytes.Length); 378 | 379 | using (var aes = new AesManaged()) 380 | { 381 | aes.BlockSize = 128; 382 | aes.KeySize = 256; 383 | aes.Padding = PaddingMode.PKCS7; 384 | byte[] key = null; 385 | using (var pbkdf2 = new Pbkdf2(OuterKeyId, seed, PbkdfRounds, HashAlgorithm)) 386 | key = pbkdf2.GetBytes(0x20); 387 | using (var dec = aes.CreateDecryptor(key, iv)) 388 | { 389 | return Encoding.UTF8.GetString(dec.TransformFinalBlock(cipherTextBytes, 0, cipherTextBytes.Length)); 390 | } 391 | } 392 | } 393 | } 394 | } 395 | --------------------------------------------------------------------------------