VMProtect/runtime/VMProtect.Runtime/LicensingManager.cs

664 lines
18 KiB
C#

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;
*/
}
}