diff options
Diffstat (limited to 'runtime/VMProtect.Runtime/LicensingManager.cs')
-rw-r--r-- | runtime/VMProtect.Runtime/LicensingManager.cs | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/runtime/VMProtect.Runtime/LicensingManager.cs b/runtime/VMProtect.Runtime/LicensingManager.cs new file mode 100644 index 0000000..d6d30c2 --- /dev/null +++ b/runtime/VMProtect.Runtime/LicensingManager.cs @@ -0,0 +1,664 @@ +using System; +using System.Globalization; +using System.Net; +using System.Net.Cache; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using Numerics; + +// ReSharper disable once CheckNamespace +namespace VMProtect +{ + public class LicensingManager + { + public class BaseRequest + { + protected bool BuildUrl(byte[] licenseData) + { + var urlSize = BitConverter.ToInt32(licenseData, (int)Fields.ActivationUrlSize * sizeof(uint)); + if (urlSize == 0) + return false; + + var urlOffset = BitConverter.ToInt32(licenseData, (int)Fields.ActivationUrlOffset * sizeof(uint)); + Url = Encoding.UTF8.GetString(licenseData, urlOffset, urlSize); + if (Url[Url.Length - 1] != '/') + Url += '/'; + return true; + } + protected void EncodeUrl() + { + Url = Convert.ToBase64String(Encoding.UTF8.GetBytes(Url)); + } + protected void AppendUrlParam(string param, string value) + { + AppendUrl(param, false); + AppendUrl(value, true); + } + private void AppendUrl(string str, bool escape) + { + if (escape) + { + var sb = new StringBuilder(Url); + foreach (var c in str) + { + switch (c) + { + case '+': + sb.Append("%2B"); + break; + case '/': + sb.Append("%2F"); + break; + case '=': + sb.Append("%3D"); + break; + default: + sb.Append(c); + break; + } + } + Url = sb.ToString(); + } else + { + Url += str; + } + } + + public bool Send() + { + try + { + using (var wc = new WebClient()) + { + ServicePointManager.ServerCertificateValidationCallback += + (sender, certificate, chain, sslPolicyErrors) => true; + wc.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); + wc.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"); + Response = wc.DownloadString(Url); + try + { + var strDt = wc.ResponseHeaders.Get("Date"); + var dt = DateTime.ParseExact(strDt, "ddd, dd MMM yyyy HH:mm:ss 'GMT'", + CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.AssumeUniversal); + GlobalData.SetServerDate((uint)((dt.Year << 16) + (dt.Month << 8) + dt.Day)); + } + catch (Exception) + { + //не смогли вытащить дату из заголовков - прощаем? + } + return true; + } + } + catch (Exception) + { + return false; + } + } + + public string Url { get; private set; } + public string Response { get; private set; } + } + + public class ActivationRequest : BaseRequest + { + public ActivationStatus Process(byte[] licenseData, string code, bool offline) + { + if (!VerifyCode(code)) + return ActivationStatus.BadCode; + + if (!BuildUrl(licenseData, code, offline)) + return ActivationStatus.NotAvailable; + + if (offline) + return ActivationStatus.Ok; + + if (!Send()) + return ActivationStatus.NoConnection; + + var res = Response; + if (string.IsNullOrEmpty(res)) + return ActivationStatus.BadReply; + + // possible answers: OK, BAD, BANNED, USED, EXPIRED + // if OK - see the Serial number below + + if (res == "BAD") + return ActivationStatus.BadCode; + + if (res == "BANNED") + return ActivationStatus.Banned; + + if (res == "USED") + return ActivationStatus.AlreadyUsed; + + if (res == "EXPIRED") + return ActivationStatus.Expired; + + var crPos = res.IndexOf('\n'); + if (crPos != 2) + return ActivationStatus.BadReply; + + if (res[0] != 'O' || res[1] != 'K') + return ActivationStatus.BadReply; + + if (res.Length - 3 < 64) + return ActivationStatus.BadReply; + + Serial = res.Substring(3); + return ActivationStatus.Ok; + } + public string Serial + { + get; private set; + } + private bool VerifyCode(string code) + { + if (string.IsNullOrEmpty(code) || code.Length > 32) + return false; + return code.ToLower().TrimEnd("0123456789abcdefghijklmnopqrstuvwxyz-".ToCharArray()).Length == 0; + } + private bool BuildUrl(byte[] licenseData, string code, bool offline) + { + if (!offline) { + if (!base.BuildUrl(licenseData)) + return false; + } + + // hwid -> base64 + var hwid = Convert.ToBase64String(Core.Instance.HWID.GetBytes()); + + // hash -> base64 + var modSize = BitConverter.ToInt32(licenseData, (int)Fields.ModulusSize * sizeof(uint)); + var modOffset = BitConverter.ToInt32(licenseData, (int)Fields.ModulusOffset * sizeof(uint)); + using (var sha = new SHA1Managed()) + { + var modulus = sha.ComputeHash(licenseData, modOffset, modSize); + var hash = Convert.ToBase64String(modulus, 0, 20); + + // build Url + AppendUrlParam(offline ? "type=activation&code=" : "activation.php?code=", code); + AppendUrlParam("&hwid=", hwid); + AppendUrlParam("&hash=", hash); + } + + if (offline) + EncodeUrl(); + + return true; + } + } + + public class DeactivationRequest : BaseRequest + { + public ActivationStatus Process(byte[] licenseData, string serial, bool offline) + { + if (!VerifySerial(serial)) + return ActivationStatus.BadCode; + + if (!BuildUrl(licenseData, serial, offline)) + return ActivationStatus.NotAvailable; + + if (offline) + return ActivationStatus.Ok; + + if (!Send()) + return ActivationStatus.NoConnection; + + var res = Response; + if (string.IsNullOrEmpty(res)) + return ActivationStatus.BadReply; + + if (res == "OK") + return ActivationStatus.Ok; + + if (res == "ERROR") + return ActivationStatus.Corrupted; + + if (res == "UNKNOWN") + return ActivationStatus.SerialUnknown; + + return ActivationStatus.BadReply; + } + + private bool VerifySerial(string serial) + { + return !string.IsNullOrEmpty(serial); + } + + private bool BuildUrl(byte[] licenseData, string serial, bool offline) + { + if (!offline) { + if (!base.BuildUrl(licenseData)) + return false; + } + + try + { + var serialBytes = Convert.FromBase64String(serial); + using (var sha = new SHA1Managed()) + { + var serialHash = sha.ComputeHash(serialBytes); + var hash = Convert.ToBase64String(serialHash, 0, 20); + + AppendUrlParam(offline ? "type=deactivation&hash=" : "deactivation.php?hash=", hash); + } + } + catch (FormatException) + { + return false; + } + + if (offline) + EncodeUrl(); + + return true; + } + } + public LicensingManager(long instance) + { + _sessionKey = 0 - GlobalData.SessionKey(); + _licenseData = new byte[(uint)Faces.LICENSE_INFO_SIZE]; + Marshal.Copy(new IntPtr(instance + (uint)Faces.LICENSE_INFO), _licenseData, 0, _licenseData.Length); + _startTickCount = Environment.TickCount; + } + public LicensingManager(byte [] licenseData) + { + _licenseData = licenseData; + } + public SerialState GetSerialNumberState() + { + lock (_lock) + { + return (_state & (SerialState.Corrupted | SerialState.Invalid | SerialState.BadHwid | SerialState.Blacklisted)) != 0 ? _state : ParseSerial(null); + } + } + public ActivationStatus ActivateLicense(string code, out string serial) + { + serial = string.Empty; + if (!CheckLicenseDataCRC()) + return ActivationStatus.Corrupted; + + var request = new ActivationRequest(); + var res = request.Process(_licenseData, code, false); + if (res == ActivationStatus.Ok) + { + serial = request.Serial; + } + return res; + } + + public ActivationStatus DeactivateLicense(string serial) + { + return CheckLicenseDataCRC() ? + new DeactivationRequest().Process(_licenseData, serial, false) : + ActivationStatus.Corrupted; + } + + public ActivationStatus GetOfflineActivationString(string code, out string buf) + { + buf = string.Empty; + if (!CheckLicenseDataCRC()) + return ActivationStatus.Corrupted; + + var request = new ActivationRequest(); + var res = request.Process(_licenseData, code, true); + if (res == ActivationStatus.Ok) + { + buf = request.Url; + } + return res; + } + + public ActivationStatus GetOfflineDeactivationString(string serial, out string buf) + { + buf = string.Empty; + if (!CheckLicenseDataCRC()) + return ActivationStatus.Corrupted; + + var request = new DeactivationRequest(); + var res = request.Process(_licenseData, serial, true); + if (res == ActivationStatus.Ok) + { + buf = request.Url; + } + return res; + } + + private static BigInteger B2Bi(byte[] b) //reverse & make positive + { + Array.Reverse(b); + var b2 = new byte[b.Length + 1]; + Array.Copy(b, b2, b.Length); + return new BigInteger(b2); + } + + public SerialState SetSerialNumber(string serial) + { + lock (_lock) + { + SaveState(SerialState.Invalid); + + if (string.IsNullOrEmpty(serial)) + return SerialState.Invalid; // the key is empty + + // decode serial number from base64 + byte[] binarySerial; + try + { + binarySerial = Convert.FromBase64String(serial); + if (binarySerial.Length < 16) + return SerialState.Invalid; + } + catch (Exception) + { + return SerialState.Invalid; + } + + // check license data integrity + if (!CheckLicenseDataCRC()) { + return SaveState(SerialState.Corrupted); + } + + // check serial by black list + var blackListSize = BitConverter.ToInt32(_licenseData, (int)Fields.BlacklistSize * sizeof(uint)); + if (blackListSize != 0) { + var blackListOffset = BitConverter.ToInt32(_licenseData, (int)Fields.BlacklistOffset * sizeof(uint)); + + using (var hash = new SHA1Managed()) + { + var p = hash.ComputeHash(binarySerial); + int min = 0; + int max = blackListSize / 20 - 1; + while (min <= max) + { + int i = (min + max) / 2; + var blocked = true; + for (var j = 0; j < 20 / sizeof(uint); j++) { + var dw = BitConverter.ToUInt32(_licenseData, blackListOffset + i * 20 + j * sizeof(uint)); + var v = BitConverter.ToUInt32(p, j * sizeof(uint)); + if (dw == v) + continue; + + if (BitRotate.Swap(dw) > BitRotate.Swap(v)) + { + max = i - 1; + } + else + { + min = i + 1; + } + blocked = false; + break; + } + if (blocked) { + return SaveState(SerialState.Blacklisted); + } + } + } + } + + // decode serial number + var ebytes = new byte[BitConverter.ToInt32(_licenseData, (int)Fields.PublicExpSize * sizeof(uint))]; + Array.Copy(_licenseData, BitConverter.ToInt32(_licenseData, (int)Fields.PublicExpOffset * sizeof(uint)), ebytes, 0, ebytes.Length); + var e = B2Bi(ebytes); + var nbytes = new byte[BitConverter.ToInt32(_licenseData, (int)Fields.ModulusSize * sizeof(uint))]; + Array.Copy(_licenseData, BitConverter.ToInt32(_licenseData, (int)Fields.ModulusOffset * sizeof(uint)), nbytes, 0, nbytes.Length); + var n = B2Bi(nbytes); + var x = B2Bi(binarySerial); + + if (n < x) { + // data is too long to crypt + return SerialState.Invalid; + } + + _serial = BigInteger.ModPow(x, e, n).ToByteArray(); + Array.Reverse(_serial); + + if (_serial[0] != 0 || _serial[1] != 2) + return SerialState.Invalid; + + int pos; + for (pos = 2; pos < _serial.Length; pos++) { + if (_serial[pos] == 0) { + pos++; + break; + } + } + if (pos == _serial.Length) + return SerialState.Invalid; + + _start = pos; + return ParseSerial(null); + } + } + + public bool GetSerialNumberData(SerialNumberData data) + { + lock (_lock) + { + if (_state == SerialState.Corrupted) + return false; + + data.State = (_state & (SerialState.Invalid | SerialState.Blacklisted | SerialState.BadHwid)) != 0 ? _state : ParseSerial(data); + return true; + } + } + + public uint DecryptBuffer(uint p3, uint p2, uint p1, uint p0) + { + uint key0 = (uint)_productCode; + uint key1 = (uint)(_productCode >> 32) + _sessionKey; + + p0 = BitRotate.Left((p0 + _sessionKey) ^ key0, 7) + key1; + p1 = BitRotate.Left((p1 + _sessionKey) ^ key0, 11) + key1; + p2 = BitRotate.Left((p2 + _sessionKey) ^ key0, 17) + key1; + p3 = BitRotate.Left((p3 + _sessionKey) ^ key0, 23) + key1; + + if (p0 + p1 + p2 + p3 != _sessionKey * 4) + { + Core.ShowMessage("This code requires valid serial number to run.\nProgram will be terminated."); + Environment.Exit(1); + } + + return p3; + } + + [VMProtect.DeleteOnCompilation] + private enum ChunkType + { + Version = 0x01, // 1 byte of data - version + UserName = 0x02, // 1 + N bytes - length + N bytes of customer's name (without enging \0). + Email = 0x03, // 1 + N bytes - length + N bytes of customer's email (without ending \0). + HWID = 0x04, // 1 + N bytes - length + N bytes of hardware id (N % 4 == 0) + ExpDate = 0x05, // 4 bytes - (year << 16) + (month << 8) + (day) + RunningTimeLimit = 0x06, // 1 byte - number of minutes + ProductCode = 0x07, // 8 bytes - used for decrypting some parts of exe-file + UserData = 0x08, // 1 + N bytes - length + N bytes of user data + MaxBuild = 0x09, // 4 bytes - (year << 16) + (month << 8) + (day) + End = 0xFF // 4 bytes - checksum: the first four bytes of sha-1 hash from the data before that chunk + } + + private SerialState SaveState(SerialState state) + { + _state = state; + if ((_state & (SerialState.Invalid | SerialState.BadHwid)) != 0) + { + _serial = null; + _productCode = 0; + } + return _state; + } + private SerialState ParseSerial(SerialNumberData data) + { + if (_serial == null) + return SerialState.Invalid; + + var newState = _state & (SerialState.MaxBuildExpired | SerialState.DateExpired | SerialState.RunningTimeOver); + var pos = _start; + while (pos < _serial.Length) { + var b = _serial[pos++]; + byte s; + switch (b) { + case (byte)ChunkType.Version: + if (_serial[pos] != 1) + return SaveState(SerialState.Invalid); + pos += 1; + break; + case (byte)ChunkType.ExpDate: + var expDate = BitConverter.ToUInt32(_serial, pos); + if ((newState & SerialState.DateExpired) == 0) { + if (BitConverter.ToUInt32(_licenseData, (int)Fields.BuildDate * sizeof(uint)) > expDate || GetCurrentDate() > expDate) + newState |= SerialState.DateExpired; + } + if (data != null) { + data.Expires = new DateTime((int)(expDate >> 16), (byte)(expDate >> 8), (byte)expDate); + } + pos += 4; + break; + case (byte)ChunkType.RunningTimeLimit: + s = _serial[pos]; + if ((newState & SerialState.RunningTimeOver) == 0) { + var curTime = (Environment.TickCount - _startTickCount) / 1000 / 60; + if (curTime > s) + newState |= SerialState.RunningTimeOver; + } + if (data != null) + data.RunningTime = s; + pos += 1; + break; + case (byte)ChunkType.ProductCode: + if ((_state & SerialState.Invalid) != 0) + _productCode = BitConverter.ToInt64(_serial, pos); + pos += 8; + break; + case (byte)ChunkType.MaxBuild: + var maxBuildDate = BitConverter.ToUInt32(_serial, pos); + if ((newState & SerialState.MaxBuildExpired) == 0) { + if (BitConverter.ToUInt32(_licenseData, (int)Fields.BuildDate * sizeof(uint)) > maxBuildDate) + newState |= SerialState.MaxBuildExpired; + } + if (data != null) + { + data.MaxBuild = new DateTime((int)(maxBuildDate >> 16), (byte)(maxBuildDate >> 8), (byte)maxBuildDate); + } + pos += 4; + break; + case (byte)ChunkType.UserName: + s = _serial[pos++]; + if (data != null) + data.UserName = Encoding.UTF8.GetString(_serial, pos, s); + pos += s; + break; + case (byte)ChunkType.Email: + s = _serial[pos++]; + if (data != null) + data.EMail = Encoding.UTF8.GetString(_serial, pos, s); + pos += s; + break; + case (byte)ChunkType.HWID: + s = _serial[pos++]; + if ((_state & SerialState.Invalid) != 0) + { + var shwid = new byte[s]; + Array.Copy(_serial, pos, shwid, 0, s); + if (!Core.Instance.HWID.IsCorrect(shwid)) + return SaveState(SerialState.BadHwid); + } + pos += s; + break; + case (byte)ChunkType.UserData: + s = _serial[pos++]; + if (data != null) { + data.UserData = new byte[s]; + for (var i = 0; i < s; i++) { + data.UserData[i] = _serial[pos + i]; + } + } + pos += s; + break; + case (byte)ChunkType.End: + if (pos + 4 > _serial.Length) + return SaveState(SerialState.Invalid); + + if ((_state & SerialState.Invalid) != 0) { + // calc hash without last chunk + using (var hash = new SHA1Managed()) + { + var p = hash.ComputeHash(_serial, _start, pos - _start - 1); + + // check CRC + for (var i = 0; i < 4; i++) { + if (_serial[pos + i] != p[3 - i]) + return SaveState(SerialState.Invalid); + } + } + } + + return SaveState(newState); + } + } + + // SERIAL_CHUNK_END not found + return SaveState(SerialState.Invalid); + } + + [VMProtect.DeleteOnCompilation] + internal enum Fields + { + BuildDate, + PublicExpOffset, + PublicExpSize, + ModulusOffset, + ModulusSize, + BlacklistOffset, + BlacklistSize, + ActivationUrlOffset, + ActivationUrlSize, + CRCOffset, + Count + } + private bool CheckLicenseDataCRC() + { + var crcPos = BitConverter.ToInt32(_licenseData, (int)Fields.CRCOffset * sizeof(uint)); + var size = crcPos + 16; + if (size != _licenseData.Length) + return false; // bad key size + + // CRC check + using (var hash = new SHA1Managed()) + { + var h = hash.ComputeHash(_licenseData, 0, crcPos); + for (var i = crcPos; i < size; i++) + { + if (_licenseData[i] != h[i - crcPos]) + return false; + } + } + + return true; + } + + internal static uint GetCurrentDate() + { + var dt = DateTime.Now; + var curDate = (uint)((dt.Year << 16) + (dt.Month << 8) + dt.Day); + var serverDate = GlobalData.ServerDate(); + return serverDate > curDate ? serverDate : curDate; + } + + private readonly byte[] _licenseData; + private byte[] _serial; + private readonly object _lock = new object(); + private SerialState _state = SerialState.Invalid; + //TODO + //ReSharper disable once NotAccessedField.Local + private long _productCode; + private readonly int _startTickCount; + private int _start; + private uint _sessionKey; + /* + CryptoContainer *_serial; + */ + } +}
\ No newline at end of file |