664 lines
18 KiB
C#
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;
|
|
*/
|
|
}
|
|
} |