initial commit
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using PureCrack.Util;
|
||||
|
||||
namespace PureCrack.Setup;
|
||||
|
||||
public static class CertManager
|
||||
{
|
||||
private static readonly TimeSpan ValidityWindow = TimeSpan.FromDays(3650.0);
|
||||
|
||||
private static readonly TimeSpan RegenIfWithin = TimeSpan.FromDays(30.0);
|
||||
|
||||
public static string RelayPfxPath => Path.Combine(Workspace.DataDir, "relay.pfx");
|
||||
|
||||
public static string AgentPfxPath => Path.Combine(Workspace.DataDir, "agent.pfx");
|
||||
|
||||
public static X509Certificate2 EnsureRelayCert()
|
||||
{
|
||||
X509Certificate2 x509Certificate = LoadIfFresh(RelayPfxPath, "relay cert");
|
||||
if (x509Certificate != null)
|
||||
{
|
||||
return x509Certificate;
|
||||
}
|
||||
Log.Info("relay cert: generating self-signed SAN cert");
|
||||
using RSA key = RSA.Create(2048);
|
||||
CertificateRequest certificateRequest = new CertificateRequest("CN=api.purecoder.io, O=PureCrack, OU=Relay", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
SubjectAlternativeNameBuilder subjectAlternativeNameBuilder = new SubjectAlternativeNameBuilder();
|
||||
subjectAlternativeNameBuilder.AddDnsName("api.purecoder.io");
|
||||
subjectAlternativeNameBuilder.AddDnsName("api1.purecoder.io");
|
||||
subjectAlternativeNameBuilder.AddDnsName("api2.purecoder.io");
|
||||
subjectAlternativeNameBuilder.AddDnsName("*.purecoder.io");
|
||||
certificateRequest.CertificateExtensions.Add(subjectAlternativeNameBuilder.Build());
|
||||
certificateRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(certificateAuthority: true, hasPathLengthConstraint: false, 0, critical: true));
|
||||
certificateRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, critical: true));
|
||||
byte[] array = certificateRequest.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1.0), DateTimeOffset.UtcNow.Add(ValidityWindow)).Export(X509ContentType.Pfx, "");
|
||||
File.WriteAllBytes(RelayPfxPath, array);
|
||||
Log.Ok($"relay cert: written to {RelayPfxPath} ({array.Length:N0}b)");
|
||||
X509Certificate2 x509Certificate2 = new X509Certificate2(array, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
|
||||
InstallToRoot(x509Certificate2);
|
||||
return x509Certificate2;
|
||||
}
|
||||
|
||||
public static byte[] EnsureAgentCertPfxBytes()
|
||||
{
|
||||
if (File.Exists(AgentPfxPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
X509Certificate2 x509Certificate = new X509Certificate2(AgentPfxPath, "");
|
||||
if (x509Certificate.NotAfter > DateTime.UtcNow.Add(RegenIfWithin))
|
||||
{
|
||||
Log.Info($"agent cert: reusing existing (expires {x509Certificate.NotAfter:yyyy-MM-dd})");
|
||||
return File.ReadAllBytes(AgentPfxPath);
|
||||
}
|
||||
TimeSpan regenIfWithin = RegenIfWithin;
|
||||
Log.Warn($"agent cert: expires within {regenIfWithin.TotalDays:0} days, regenerating");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn("agent cert: existing PFX unreadable, regenerating (" + ex.Message + ")");
|
||||
}
|
||||
}
|
||||
Log.Info("agent cert: generating self-signed PureRAT Agent cert");
|
||||
using RSA key = RSA.Create(2048);
|
||||
using X509Certificate2 x509Certificate2 = new CertificateRequest("CN=PureRAT Agent", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1).CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1.0), DateTimeOffset.UtcNow.Add(ValidityWindow));
|
||||
byte[] array = x509Certificate2.Export(X509ContentType.Pfx, "");
|
||||
File.WriteAllBytes(AgentPfxPath, array);
|
||||
Log.Ok($"agent cert: written to {AgentPfxPath} ({array.Length:N0}b)");
|
||||
return array;
|
||||
}
|
||||
|
||||
public static void Wipe()
|
||||
{
|
||||
string[] array = new string[2] { RelayPfxPath, AgentPfxPath };
|
||||
foreach (string text in array)
|
||||
{
|
||||
if (File.Exists(text))
|
||||
{
|
||||
File.Delete(text);
|
||||
Log.Bullet("cert: removed " + text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static X509Certificate2? LoadIfFresh(string pfxPath, string label)
|
||||
{
|
||||
if (!File.Exists(pfxPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
X509Certificate2 x509Certificate = new X509Certificate2(pfxPath, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
|
||||
if (x509Certificate.NotAfter > DateTime.UtcNow.Add(RegenIfWithin))
|
||||
{
|
||||
Log.Info($"{label}: reusing existing (expires {x509Certificate.NotAfter:yyyy-MM-dd}, " + "thumbprint " + x509Certificate.Thumbprint.Substring(0, 12) + "…)");
|
||||
return x509Certificate;
|
||||
}
|
||||
TimeSpan regenIfWithin = RegenIfWithin;
|
||||
Log.Warn($"{label}: expires within {regenIfWithin.TotalDays:0} days, regenerating");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn(label + ": existing PFX unreadable, regenerating (" + ex.Message + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void InstallToRoot(X509Certificate2 cert)
|
||||
{
|
||||
try
|
||||
{
|
||||
using X509Store x509Store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
|
||||
x509Store.Open(OpenFlags.ReadWrite);
|
||||
X509Certificate2Enumerator enumerator = x509Store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, cert.SubjectName.Name, validOnly: false).GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
X509Certificate2 current = enumerator.Current;
|
||||
if (!(current.Thumbprint == cert.Thumbprint) && current.NotAfter < DateTime.UtcNow.AddYears(1))
|
||||
{
|
||||
x509Store.Remove(current);
|
||||
Log.Bullet("relay cert: pruned stale Root entry (thumbprint " + current.Thumbprint.Substring(0, 12) + "…)");
|
||||
}
|
||||
}
|
||||
x509Store.Add(cert);
|
||||
x509Store.Close();
|
||||
Log.Ok("relay cert: installed in LocalMachine\\Root (thumbprint " + cert.Thumbprint.Substring(0, 12) + "…)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Err("relay cert: failed to install to Root store: " + ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using PureCrack.Util;
|
||||
|
||||
namespace PureCrack.Setup;
|
||||
|
||||
public static class HostsManager
|
||||
{
|
||||
private const string HostsPath = "C:\\Windows\\System32\\drivers\\etc\\hosts";
|
||||
|
||||
private const string BackupSuffix = ".purecrack-backup";
|
||||
|
||||
public static readonly string[] Domains = new string[3] { "api.purecoder.io", "api1.purecoder.io", "api2.purecoder.io" };
|
||||
|
||||
public static string Path => "C:\\Windows\\System32\\drivers\\etc\\hosts";
|
||||
|
||||
public static string BackupPath => "C:\\Windows\\System32\\drivers\\etc\\hosts.purecrack-backup";
|
||||
|
||||
public static bool Ensure()
|
||||
{
|
||||
if (!File.Exists("C:\\Windows\\System32\\drivers\\etc\\hosts"))
|
||||
{
|
||||
throw new FileNotFoundException("C:\\Windows\\System32\\drivers\\etc\\hosts missing — Windows install looks broken");
|
||||
}
|
||||
string content = File.ReadAllText("C:\\Windows\\System32\\drivers\\etc\\hosts");
|
||||
HashSet<string> present = ScanPresent(content);
|
||||
List<string> list = Domains.Where((string d) => !present.Contains(d)).ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
Log.Info($"hosts: all {Domains.Length} entries already present");
|
||||
return false;
|
||||
}
|
||||
if (!File.Exists(BackupPath))
|
||||
{
|
||||
File.Copy("C:\\Windows\\System32\\drivers\\etc\\hosts", BackupPath);
|
||||
Log.Bullet("hosts: backup saved to " + BackupPath);
|
||||
}
|
||||
string text = EnsureTrailingNewline(content);
|
||||
foreach (string item in list)
|
||||
{
|
||||
text = text + "127.0.0.1 " + item + "\n";
|
||||
Log.Bullet("hosts: add 127.0.0.1 " + item);
|
||||
}
|
||||
File.WriteAllText("C:\\Windows\\System32\\drivers\\etc\\hosts", text);
|
||||
FlushDns();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Remove()
|
||||
{
|
||||
if (!File.Exists("C:\\Windows\\System32\\drivers\\etc\\hosts"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string[] array = File.ReadAllLines("C:\\Windows\\System32\\drivers\\etc\\hosts");
|
||||
List<string> list = new List<string>(array.Length);
|
||||
int num = 0;
|
||||
string[] array2 = array;
|
||||
foreach (string text in array2)
|
||||
{
|
||||
string trimmed = text.Trim();
|
||||
if (trimmed.StartsWith("#") || trimmed.Length == 0)
|
||||
{
|
||||
list.Add(text);
|
||||
}
|
||||
else if (Domains.Any((string d) => LineMapsDomainToLoopback(trimmed, d)))
|
||||
{
|
||||
num++;
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(text);
|
||||
}
|
||||
}
|
||||
if (num > 0)
|
||||
{
|
||||
File.WriteAllText("C:\\Windows\\System32\\drivers\\etc\\hosts", string.Join("\n", list));
|
||||
Log.Ok($"hosts: removed {num} entries");
|
||||
FlushDns();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsWritable()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (File.Open("C:\\Windows\\System32\\drivers\\etc\\hosts", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<string> ScanPresent(string content)
|
||||
{
|
||||
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
string[] array = content.Split(new char[1] { '\n' });
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
string text = array[i].Trim();
|
||||
if (text.Length == 0 || text.StartsWith("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string[] domains = Domains;
|
||||
foreach (string text2 in domains)
|
||||
{
|
||||
if (LineMapsDomainToLoopback(text, text2))
|
||||
{
|
||||
hashSet.Add(text2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return hashSet;
|
||||
}
|
||||
|
||||
private static bool LineMapsDomainToLoopback(string line, string domain)
|
||||
{
|
||||
string[] array = line.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (array.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!array[0].StartsWith("127."))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = 1; i < array.Length; i++)
|
||||
{
|
||||
string text = array[i];
|
||||
if (text.StartsWith("#"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (string.Equals(text, domain, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string EnsureTrailingNewline(string content)
|
||||
{
|
||||
if (content.Length != 0 && content[content.Length - 1] != '\n')
|
||||
{
|
||||
return content + "\n";
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private static void FlushDns()
|
||||
{
|
||||
try
|
||||
{
|
||||
using Process process = Process.Start(new ProcessStartInfo("ipconfig", "/flushdns")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
});
|
||||
process?.WaitForExit(5000);
|
||||
Log.Bullet("hosts: dns cache flushed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn("ipconfig /flushdns failed (non-fatal): " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user