Я раньше пользовался The Enigma Protector, сейчас по идее надо его обновить, вопрос насколько всё это сейчас актуально? Как сейчас на Западе с хакерами — их прижали, или наоборот они распоясались? Есть ли среди хакеров такие, которые продают наши программы за крипту?
Я наверно не смогу шифровать код Mac версии своей программы, поэтому такая мысль – может быть, довольно важно сделать кейген для Mac другой, с другим алгоритмом, чтобы хакеры не смогли, разобрав код Mac-программы, написать кейген для Windows-версии?
Какой шифратор лучше выбрать — Execryptor или есть что-то новое?
Не решена ли как-то проблема ложных срабатываний антивирусов на шифрованный код?
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, Khimik, Вы писали:
K>Я раньше пользовался The Enigma Protector, сейчас по идее надо его обновить, вопрос насколько всё это сейчас актуально? Как сейчас на Западе с хакерами — их прижали, или наоборот они распоясались? Есть ли среди хакеров такие, которые продают наши программы за крипту? K>Я наверно не смогу шифровать код Mac версии своей программы, поэтому такая мысль – может быть, довольно важно сделать кейген для Mac другой, с другим алгоритмом, чтобы хакеры не смогли, разобрав код Mac-программы, написать кейген для Windows-версии? K>Какой шифратор лучше выбрать — Execryptor или есть что-то новое? K>Не решена ли как-то проблема ложных срабатываний антивирусов на шифрованный код?
Шифрование кода, в подавляющем числе случаем — это абсолютный overkill. Нормальный рабочий вариант — это внутренний проверки на модификацию кода, как статическую (патчи и кряки) так и динамическую (API hooking, DLL side loading, dynamic patching, etc.). Если таких проверок в коде, скажем, несколько десятков и они разнесены по времени, то вычистить их все крайне сложно и, главное, трудо- и времяемко. Это принципиальный момент, потому что хакерская сцена состоит на 99.9% из детей-любителей с соотвествующим уровнем скилзов, который хакают, чтобы выложить "релиз" и поразить камрадов своими l33t скилзами. Они могут снять подпись с exe и пропатчить `if (valid_license)`. Какая-то часть может посмотреть чуть дальше, но в общем и целом они ломают только то, что быстро ломается. Оставшиеся 0.1% — супер-квалифицированые товарищи, которые ломают в основном динамическими патчами. Сломать они сломают, но чистый 100% хак займет приличное количество времени. Им по-большому счету связываться с этим не имеет смысла, только если это не платный заказ или не какой-то high-profile софт.
Здравствуйте, Khimik, Вы писали: K>Я раньше пользовался The Enigma Protector, сейчас по идее надо его обновить, вопрос насколько всё это сейчас актуально? Как сейчас на Западе с хакерами — их прижали, или наоборот они распоясались? Есть ли среди хакеров такие, которые продают наши программы за крипту? K>Я наверно не смогу шифровать код Mac версии своей программы, поэтому такая мысль – может быть, довольно важно сделать кейген для Mac другой, с другим алгоритмом, чтобы хакеры не смогли, разобрав код Mac-программы, написать кейген для Windows-версии? K>Какой шифратор лучше выбрать — Execryptor или есть что-то новое? K>Не решена ли как-то проблема ложных срабатываний антивирусов на шифрованный код?
C кейгенами бороться очень просто: используй RSA c двумя ключами. Приватный ключ у девелопера, публичный в программе. Ключ шифруется приватным ключом при создании, а программа расшифровывает публичным. Не имея приватного ключа, создать валидный лицензионный ключ невозможно. Остаются патчи, но они скажем так менее привлекательны — пользователи боятся тк хз что там еще они поменяют и сколько троянов довесят.
По моему личному опыту, использовал VMProtect. Насколько я знаю один из немногих протекторов поддерживащих мак и линух. Протектор отличный, но у меня было три основные проблемы:
1) Размер исполняемых файлов удваивается как минимум за счет внедрения виртуальной машины. Собственно исполнение части кода на виртуальной машине и осложняет жизнь хацкерам, тк становится очень сложно понять что там происходит.
2) В связи с этим вместо кряков появляются покупки по краденным картам и чержбеки владельцев.
3) Антивирусы тоже не особо понимают код виртуальной машины, а следовательно на всякий случай задетектим его как вирус. Короче говоря ложных срабатываний полно, особенно у говно-антивирусов второго эшелона.
В итоге я решил от VMProtect отказаться, оставив однако схему лицензирования и генерации ключей. То есть используются точно такие же ключи, только проверку выполняет мой собственный код, а для пользователей ничего не изменилось.
Естественно появились патчи, но в целом продажи не изменились, и теперь фроды и борьба с антивирусниками занимают куда меньше времени. В итоге я пришел к выводу, что те кто не купят ваш софт в любом случае воспользутся или левой кредиткой или патчем. Да ну и пусть, c них все равно денег не получить. А те кто могут купить прогу, покупают ее как и раньше.
Поделюсь модулем для FPC/DELPHI который декодирует ключи VMProtect:
unit Licensing.VMProtect;
interface{$IFDEF FPC}
{$MODE DELPHI}
{$RANGECHECKS OFF}
{$OVERFLOWCHECKS OFF}
{$ELSE}
{$WEAKLINKRTTI ON}
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
{$ENDIF}uses{$IFDEF FPC}
Base64, Classes, SysUtils, Sha1, Md5;
{$ELSE}
System.NetEncoding, System.Classes, System.SysUtils, System.WideStrUtils,
System.Hash;
{$ENDIF}const
BuildDate = {$I BuildDate.inc};
type
TVMProtectDate = packed record
wYear: Word;
bMonth: Byte;
bDay: Byte;
end;
PVMProtectSerialNumberData = ^TVMProtectSerialNumberData;
TVMProtectSerialNumberData = packed record
nState: Longword;
wUserName: array [0..255] of WideChar;
wEMail: array [0..255] of WideChar;
dtExpire: TVMProtectDate;
dtMaxBuild: TVMProtectDate;
bRunningTime: Longword;
nUserDataLength: Byte;
bUserData: array [0..254] of Byte;
end;
const
SERIAL_STATE_SUCCESS = 0;
SERIAL_STATE_FLAG_CORRUPTED = $00000001;
SERIAL_STATE_FLAG_INVALID = $00000002;
SERIAL_STATE_FLAG_BLACKLISTED = $00000004;
SERIAL_STATE_FLAG_DATE_EXPIRED = $00000008;
SERIAL_STATE_FLAG_RUNNING_TIME_OVER = $00000010;
SERIAL_STATE_FLAG_BAD_HWID = $00000020;
SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED = $00000040;
type
LbIntBuf = packed record
dwLen: Integer;
pBuf: PByte;
end;
LbInteger = packed record
bSign: Boolean;
dwUsed: Integer;
IntBuf: LbIntBuf;
end;
{ TSerialDecoder }
TSerialDecoder = class
private
FSerialNum: TBytes;
FPublicExp: TBytes;
FPublicMod: TBytes;
FProductCode: UInt64;
FSerialNumberData: TVMProtectSerialNumberData;
procedure CheckSerialBlackList;
function GetDecodedBytes: TBytes;
function GetBytesFromBase64(const Value: string): TBytes;
function GetLargeIntFromBytes(const Bytes: TBytes): LbInteger;
function GetBytesFromLargeInt(const Value: LbInteger): TBytes;
protected
function GetBuildDate: TDate; virtual;
public
procedure SetPublicExp(const Value: string);
procedure SetPublicMod(const Value: string);
procedure SetSerialNum(const Value: string);
procedure SetProductCode(const Value: UInt64);
function VMProtectSetSerialNumber(const SerialNumber: string): LongWord;
function VMProtectGetSerialNumberData(Data: PVMProtectSerialNumberData;
DataSize: Integer): Boolean;
end;
implementation
const
cLESS_THAN = ShortInt(-1);
cEQUAL_TO = ShortInt(0);
cGREATER_THAN = ShortInt(1);
cPOSITIVE = True;
cNEGATIVE = False;
const
cBYTE_POSSIBLE_VALUES = 256;
cDEFAULT_PRECISION = 64;
cUSE_DEFAULT_PRECISION = 0;
cDEFAULT_SIGN = cPOSITIVE;
cDEFAULT_USED = 0;
cAPPEND_ARRAY = 0;
cPREPEND_ARRAY = 1;
const
sBIBufferNotAssigned = 'Buffer not assigned';
sBINoNumber = 'No number';
sBISubtractErr = 'Subtraction error';
sBIZeroDivide = 'Division by zero';
sBIQuotientErr = 'Quotient process error';
sBIZeroFactor = 'Factor is zero';
type
PBiByteArray = ^TBiByteArray;
TBiByteArray = array [0..Pred(MaxInt)] of Byte;
{$IFNDEF FPC}type
TBytesStream = class(System.Classes.TBytesStream)
public
function ReadByte: Byte;
function ReadWord: Word;
function ReadDWord: UInt32;
function ReadQWord: UInt64;
end;
{$ENDIF}procedure LbBiInit(out N1: LbInteger; const Precision: Integer);
begin
N1 := Default(LbInteger);
if Precision > 0 then
N1.IntBuf.dwLen := Precision
else
N1.IntBuf.dwLen := cDEFAULT_PRECISION;
N1.bSign := cDEFAULT_SIGN;
N1.dwUsed := cDEFAULT_USED;
N1.IntBuf.pBuf := PByte(AllocMem(N1.IntBuf.dwLen));
end;
procedure LbBiTrimSigZeros(var N1: LbInteger);
begin
if (not Assigned(N1.IntBuf.pBuf)) then
raise Exception.Create(sBIBufferNotAssigned);
while (PBiByteArray(N1.IntBuf.pBuf)[Pred(N1.dwUsed)] = 0) do
begin
Dec(N1.dwUsed);
if (N1.dwUsed <= 0) then
begin
N1.dwUsed := 1;
Exit;
end;
end;
end;
procedure LbBiRealloc(var N1: LbInteger; const Len: Integer);
var
TmpPtr: PByte;
begin
if (N1.dwUsed > Len) then
Exit;
TmpPtr := AllocMem(Len);
move(N1.IntBuf.pBuf^, TmpPtr^, N1.dwUsed);
FreeMem(N1.IntBuf.pBuf);
N1.IntBuf.dwLen := Len;
N1.IntBuf.pBuf := TmpPtr;
end;
procedure LbBiClear(var N1: LbInteger);
begin
N1.bSign := cDEFAULT_SIGN;
N1.dwUsed := cDEFAULT_USED;
FillChar(N1.IntBuf.pBuf^, N1.IntBuf.dwLen, $00);
end;
function LbBiIsZero(N1: LbInteger): Boolean;
begin
LbBiTrimSigZeros(N1);
Result := False;
if (N1.dwUsed = 1) and (PBiByteArray(N1.IntBuf.pBuf)[0] = 0) then
Result := True
end;
procedure LbBiAddByte(var N1: LbInteger; const Place: Integer;
const ByteValue: Byte);
begin
if (Place = cAPPEND_ARRAY) then
begin
if (Succ(N1.dwUsed) > N1.IntBuf.dwLen) then
LbBiRealloc(N1, Succ(N1.dwUsed));
PBiByteArray(N1.IntBuf.pBuf)[N1.dwUsed] := ByteValue;
Inc(N1.dwUsed);
end
else
begin
if (Place > N1.IntBuf.dwLen) then
LbBiRealloc(N1, Place);
PBiByteArray(N1.IntBuf.pBuf)[Pred(Place)] := ByteValue;
if (N1.dwUsed < Place) then
N1.dwUsed := Place;
end;
end;
function LbBiGetByteValue(const N1: LbInteger; const Place: Integer): Byte;
begin
if (N1.dwUsed < Place) then
begin
Result := 0;
Exit;
end;
Result := PBiByteArray(N1.IntBuf.pBuf)[Pred(Place)];
end;
function LbBiReverseBits(ByteValue: Byte): Byte;
var
I: Byte;
RBit: Byte;
begin
Result := 0;
RBit := $80;
for I := 1 to 8 do
begin
if ((ByteValue and $01) <> 0) then
Result := Result or RBit;
RBit := RBit shr 1;
ByteValue := ByteValue shr 1;
end;
end;
function LbBiIsOne(N1: LbInteger): Boolean;
begin
LbBiTrimSigZeros(N1);
Result := False;
if (N1.dwUsed = 1) and (PBiByteArray(N1.IntBuf.pBuf)[0] = 1) then
Result := True
end;
procedure LbBiCopy(var Dest: LbInteger; const Src: LbInteger;
const Len: Integer);
var
Ptr: PByte;
Size: Integer;
begin
FillChar(Dest.IntBuf.pBuf^, Dest.IntBuf.dwLen, $00);
Size := Integer(Len);
if Size > Dest.IntBuf.dwLen then
LbBiRealloc(Dest, Size);
Ptr := Dest.IntBuf.pBuf;
move(Src.IntBuf.pBuf^, Ptr^, Len);
if (Dest.dwUsed < Size) then
Dest.dwUsed := Size;
end;
function MultSpecialCase(const N1: LbInteger; const N2: LbInteger;
var Product: LbInteger): Boolean;
begin
Result := False;
if (LbBiIsZero(N1) or LbBiIsZero(N2)) then
begin
LbBiAddByte(Product, cPREPEND_ARRAY, $00);
Result := True;
Exit;
end;
if (LbBiIsOne(N1)) then
begin
Product.dwUsed := N2.dwUsed;
if (Product.IntBuf.dwLen < N2.IntBuf.dwLen) then
LbBiRealloc(Product, N2.IntBuf.dwLen);
LbBiCopy(Product, N2, N2.dwUsed);
Result := True;
Exit;
end;
if (LbBiIsOne(N2)) then
begin
Product.dwUsed := N1.dwUsed;
if (Product.IntBuf.dwLen < N1.IntBuf.dwLen) then
LbBiRealloc(Product, N1.IntBuf.dwLen);
LbBiCopy(Product, N1, N1.dwUsed);
Result := True;
end;
end;
procedure LbBiMultBase(N1: LbInteger; const N2: LbInteger;
var Product: LbInteger);
var
InxX: Integer;
InxY: Integer;
MaxX: Integer;
Carry: Integer;
Prd: Integer;
Plc: Integer;
TmpByte: Byte;
TmpInt: Integer;
begin
if (MultSpecialCase(N1, N2, Product)) then
Exit;
MaxX := Pred(N1.dwUsed);
TmpInt := Pred(N2.dwUsed);
for InxY := 0 to TmpInt do
begin
if PBiByteArray(N2.IntBuf.pBuf)[InxY] <> 0 then
begin
Carry := 0;
for InxX := 0 to MaxX do
begin
Plc := InxX + InxY;
Prd := PBiByteArray(N1.IntBuf.pBuf)[InxX];
Prd := Prd * PBiByteArray(N2.IntBuf.pBuf)[InxY];
if (Product.dwUsed < Plc) then
Prd := Prd + Carry
else
Prd := Prd + PBiByteArray(Product.IntBuf.pBuf)[Plc] + Carry;
TmpByte := Prd and $00FF;
Carry := Prd shr 8;
if (Succ(Plc) > Product.IntBuf.dwLen) then
LbBiRealloc(Product, Plc + 100);
PBiByteArray(Product.IntBuf.pBuf)[Plc] := TmpByte;
if (Product.dwUsed < Succ(Plc)) then
N1.dwUsed := Succ(Plc);
end;
LbBiAddByte(Product, (MaxX + InxY + 2), Carry);
end;
end;
LbBiTrimSigZeros(Product);
end;
procedure LbBiMult(const N1, N2: LbInteger; var Product: LbInteger);
begin
LbBiMultBase(N1, N2, Product);
if (N1.bSign = N2.bSign) then
Product.bSign := cPOSITIVE
else
Product.bSign := cNEGATIVE;
end;
procedure LbBiFree(var N1: LbInteger);
begin
if (Assigned(N1.IntBuf.pBuf)) then
FreeMem(N1.IntBuf.pBuf);
FillChar(N1, SizeOf(LbInteger), $00);
end;
procedure LbBiMultInPlace(var N1: LbInteger; const N2: LbInteger);
var
Product: LbInteger;
Precis: Integer;
begin
Precis := (N1.dwUsed + N2.dwUsed) * 2;
LbBiInit(Product, Precis);
LbBiMult(N1, N2, Product);
LbBiClear(N1);
N1.dwUsed := Product.dwUsed;
N1.bSign := Product.bSign;
if (N1.IntBuf.dwLen < Product.IntBuf.dwLen) then
LbBiRealloc(N1, Product.IntBuf.dwLen);
LbBiCopy(N1, Product, Product.dwUsed);
LbBiFree(Product);
end;
function LbBiFindFactor(B1: Byte): Byte;
begin
Result := 1;
while (B1 < $80) do
begin
B1 := (B1 shl 1);
Result := Result * 2;
end;
end;
procedure LbBiMulByDigitBase(N1: LbInteger; const N2: Byte;
var Product: LbInteger);
var
Count: Integer;
Carry: Byte;
Prd: WORD;
TmpByte: Byte;
TmpInt: Integer;
begin
if (N2 = 1) then
begin
if (Product.IntBuf.dwLen < N1.IntBuf.dwLen) then
LbBiRealloc(Product, N1.IntBuf.dwLen);
Product.dwUsed := N1.dwUsed;
Product.bSign := N1.bSign;
LbBiCopy(Product, N1, N1.dwUsed);
end;
if (N2 = 0) then
begin
Product.dwUsed := 1;
LbBiAddByte(Product, cPREPEND_ARRAY, 0);
end;
if LbBiIsOne(N1) then
begin
Product.dwUsed := 1;
LbBiAddByte(Product, cPREPEND_ARRAY, N2);
end;
if (N1.dwUsed = 1) and (PBiByteArray(N1.IntBuf.pBuf)[0] = 0) then
begin
Product.dwUsed := 1;
LbBiAddByte(Product, cPREPEND_ARRAY, 0);
end;
Carry := 0;
TmpInt := Pred(N1.dwUsed);
for Count := 0 to TmpInt do
begin
Prd := (PBiByteArray(N1.IntBuf.pBuf)[Count] * N2) + Carry;
TmpByte := Prd and $00FF;
Carry := Prd shr 8;
PBiByteArray(Product.IntBuf.pBuf)[Count] := TmpByte;
if (Product.dwUsed < Succ(Count)) then
N1.dwUsed := Succ(Count);
end;
LbBiAddByte(Product, Succ(N1.dwUsed), Carry);
LbBiTrimSigZeros(Product);
end;
procedure LbBiMulByDigit(const N1: LbInteger; const N2: Byte;
var Product: LbInteger);
begin
LbBiMulByDigitBase(N1, N2, Product);
Product.bSign := N1.bSign;
end;
procedure LbBiMulByDigitInPlace(var N1: LbInteger; const N2: Byte);
var
Product: LbInteger;
Precis: Integer;
begin
Precis := (N1.dwUsed + 1) * 2;
LbBiInit(Product, Precis);
try
LbBiMulByDigit(N1, N2, Product);
if (N1.IntBuf.dwLen < Product.IntBuf.dwLen) then
LbBiRealloc(N1, Product.IntBuf.dwLen);
N1.bSign := Product.bSign;
N1.dwUsed := Product.dwUsed;
LbBiCopy(N1, Product, Product.dwUsed);
finally
LbBiFree(Product);
end;
end;
procedure LbBiMove(var Fest: LbInteger; const Src: LbInteger;
const Place, Len: Integer);
var
Ptr: PByte;
Size: Integer;
begin
if (not Assigned(Fest.IntBuf.pBuf)) then
raise Exception.Create(sBIBufferNotAssigned);
if (Place = cAPPEND_ARRAY) then
begin
if ((Integer(Len) + Fest.dwUsed) > Fest.IntBuf.dwLen) then
LbBiRealloc(Fest, (Integer(Len) + Fest.dwUsed));
Ptr := Fest.IntBuf.pBuf;
Inc(Ptr, Fest.dwUsed);
move(Src.IntBuf.pBuf^, Ptr^, Len);
Inc(Fest.dwUsed, Len);
end
else
begin
Size := Pred(Place) + Integer(Len);
if Size > Fest.IntBuf.dwLen then
LbBiRealloc(Fest, Size);
Ptr := Fest.IntBuf.pBuf;
Inc(Ptr, Pred(Place));
move(Src.IntBuf.pBuf^, Ptr^, Len);
if (Fest.dwUsed < Size) then
Fest.dwUsed := Size;
end;
end;
function LbBiCompare(N1, N2: LbInteger): ShortInt;
var
Count: Integer;
begin
LbBiTrimSigZeros(N1);
LbBiTrimSigZeros(N2);
if (N1.bSign <> N2.bSign) then
begin
if (N1.bSign = cPOSITIVE) then
Result := cGREATER_THAN
else
Result := cLESS_THAN;
Exit;
end;
if (N1.dwUsed <> N2.dwUsed) then
begin
if (N1.dwUsed > N2.dwUsed) then
begin
Result := cGREATER_THAN;
Exit;
end
else
begin
Result := cLESS_THAN;
Exit;
end;
end;
Count := N1.dwUsed;
while PBiByteArray(N1.IntBuf.pBuf)[Pred(Count)] = PBiByteArray(N2.IntBuf.pBuf)
[Pred(Count)] do
begin
Dec(Count);
if (Count = 0) then
begin
Result := cEQUAL_TO;
Exit;
end;
end;
if PBiByteArray(N1.IntBuf.pBuf)[Pred(Count)] > PBiByteArray(N2.IntBuf.pBuf)
[Pred(Count)] then
Result := cGREATER_THAN
else
Result := cLESS_THAN;
end;
procedure LbBiFindLargestUsed(const N1, N2: LbInteger; out Count: Integer);
begin
if (N1.dwUsed >= N2.dwUsed) then
Count := N1.dwUsed
else
Count := N2.dwUsed;
end;
procedure LbBiAddBase(const N1, N2: LbInteger; var Sum: LbInteger);
var
Carry: Byte;
I: Integer;
Count: Integer;
TempWord: WORD;
TempByte: Byte;
begin
LbBiFindLargestUsed(N1, N2, Count);
if (LbBiIsZero(N1)) then
begin
LbBiCopy(Sum, N2, N2.dwUsed);
Exit;
end;
if (LbBiIsZero(N2)) then
begin
LbBiCopy(Sum, N1, N1.dwUsed);
Exit;
end;
Carry := 0;
if (Succ(Count) > Sum.dwUsed) then
LbBiRealloc(Sum, Succ(Count));
for I := 1 to Count do
begin
TempWord := LbBiGetByteValue(N1, I) + LbBiGetByteValue(N2, I) + Carry;
TempByte := TempWord and $00FF;
Carry := TempWord shr 8;
PBiByteArray(Sum.IntBuf.pBuf)[Sum.dwUsed] := TempByte;
Inc(Sum.dwUsed);
end;
LbBiAddByte(Sum, cAPPEND_ARRAY, Carry);
LbBiTrimSigZeros(Sum);
end;
function LbBiAbs(N1, N2: LbInteger): ShortInt;
var
Count: Integer;
begin
LbBiTrimSigZeros(N1);
LbBiTrimSigZeros(N2);
if (N1.dwUsed <> N2.dwUsed) then
begin
if (N1.dwUsed > N2.dwUsed) then
begin
Result := cGREATER_THAN;
Exit;
end
else
begin
Result := cLESS_THAN;
Exit;
end;
end;
Count := N1.dwUsed;
while PBiByteArray(N1.IntBuf.pBuf)[Pred(Count)] = PBiByteArray(N2.IntBuf.pBuf)
[Pred(Count)] do
begin
Dec(Count);
if (Count = 0) then
begin
Result := cEQUAL_TO;
Exit;
end;
end;
if PBiByteArray(N1.IntBuf.pBuf)[Pred(Count)] > PBiByteArray(N2.IntBuf.pBuf)
[Pred(Count)] then
Result := cGREATER_THAN
else
Result := cLESS_THAN;
end;
procedure LbBiSubBase(const N1, N2: LbInteger; var Diff: LbInteger);
var
TempInt: Integer;
Borrow: WORD;
Count: Integer;
I: Integer;
begin
if (LbBiIsZero(N1)) then
begin
LbBiCopy(Diff, N2, N2.dwUsed);
Exit;
end;
if (LbBiIsZero(N2)) then
begin
LbBiCopy(Diff, N1, N1.dwUsed);
Exit;
end;
Borrow := 0;
I := Pred(N1.dwUsed);
for Count := 0 to I do
begin
TempInt := PBiByteArray(N1.IntBuf.pBuf)[Count];
if (N2.dwUsed < Succ(Count)) then
TempInt := TempInt - Borrow
else
TempInt := TempInt - (PBiByteArray(N2.IntBuf.pBuf)[Count] + Borrow);
if (TempInt < 0) then
begin
Inc(TempInt, cBYTE_POSSIBLE_VALUES);
Borrow := 1;
end
else
Borrow := 0;
if (Succ(Diff.dwUsed) > Diff.IntBuf.dwLen) then
LbBiRealloc(Diff, Succ(Diff.dwUsed));
PBiByteArray(Diff.IntBuf.pBuf)[Diff.dwUsed] := TempInt;
Inc(Diff.dwUsed);
end;
if (Borrow <> 0) then
raise Exception.Create(sBISubtractErr);
LbBiTrimSigZeros(Diff);
end;
procedure LbBiAdd(const N1, N2: LbInteger; var Sum: LbInteger);
var
Value: ShortInt;
begin
if (N1.bSign = N2.bSign) then
begin
Sum.bSign := N1.bSign;
LbBiAddBase(N1, N2, Sum);
end
else
begin
Value := LbBiAbs(N1, N2);
if (Value = cEQUAL_TO) then
begin
LbBiAddByte(Sum, cPREPEND_ARRAY, $00);
Exit;
end
else if (Value = cGREATER_THAN) then
begin
Sum.bSign := N1.bSign;
LbBiSubBase(N1, N2, Sum);
end
else
begin
Sum.bSign := N2.bSign;
LbBiSubBase(N2, N1, Sum);
end;
end;
end;
procedure LbBiSub(const N1: LbInteger; N2: LbInteger; var Diff: LbInteger);
begin
N2.bSign := not N2.bSign;
LbBiAdd(N1, N2, Diff);
end;
procedure LbBiSubInPlace(var N1: LbInteger; const N2: LbInteger);
var
Difference: LbInteger;
Precision: Integer;
begin
if (N1.dwUsed > N2.dwUsed) then
Precision := Succ(N1.dwUsed)
else
Precision := Succ(N2.dwUsed);
LbBiInit(Difference, Precision);
try
LbBiSub(N1, N2, Difference);
LbBiClear(N1);
N1.dwUsed := Difference.dwUsed;
N1.bSign := Difference.bSign;
if (N1.IntBuf.dwLen < Difference.IntBuf.dwLen) then
LbBiRealloc(N1, Difference.IntBuf.dwLen);
LbBiCopy(N1, Difference, Difference.dwUsed);
finally
LbBiFree(Difference);
end;
end;
procedure LbBiDivByDigitBase(const N1: LbInteger; const N2: Byte;
var Quotient: LbInteger; var Remainder: Byte);
var
Factor: Byte;
I: Integer;
Temp: Integer;
SigDivd: LongInt;
LclQT: LongInt;
Carry: WORD;
Plc: Integer;
LclDVD: LbInteger;
Divisor: Byte;
begin
LbBiInit(LclDVD, N1.dwUsed);
Carry := 0;
try
if (LbBiIsZero(N1)) then
begin
LbBiAddByte(Quotient, cPREPEND_ARRAY, $00);
Exit;
end;
if (N2 = 1) then
begin
LbBiCopy(Quotient, N1, N1.dwUsed);
Exit;
end;
if (N2 = 0) then
raise Exception.Create(sBIZeroDivide);
LbBiCopy(LclDVD, N1, N1.dwUsed);
Divisor := N2;
Factor := LbBiFindFactor(N2);
if (Factor <> 1) then
begin
LbBiMulByDigitInPlace(LclDVD, Factor);
Divisor := Divisor * Factor;
end;
if PBiByteArray(LclDVD.IntBuf.pBuf)[Pred(LclDVD.dwUsed)] >= Divisor then
begin
LbBiAddByte(LclDVD, cAPPEND_ARRAY, $00);
end;
LbBiClear(Quotient);
Remainder := 0;
Plc := Pred(LclDVD.dwUsed);
if (LclDVD.dwUsed > Quotient.dwUsed) then
LbBiRealloc(Quotient, LclDVD.dwUsed);
Carry := 0;
Temp := Pred(LclDVD.dwUsed);
for I := Temp downto 0 do
begin
SigDivd := (Carry shl 8) or
(Integer(PBiByteArray(LclDVD.IntBuf.pBuf)[I]));
if (SigDivd < Divisor) then
begin
Carry := SigDivd;
Dec(Plc);
continue;
end;
LclQT := SigDivd div Divisor;
if (LclQT <> 0) then
begin
if (LclQT >= cBYTE_POSSIBLE_VALUES) then
LclQT := Pred(cBYTE_POSSIBLE_VALUES);
while SigDivd < (Divisor * LclQT) do
begin
Dec(LclQT);
if (LclQT = 0) then
raise Exception.Create(sBIQuotientErr);
end;
end;
if (LclQT <> 0) then
begin
PBiByteArray(Quotient.IntBuf.pBuf)[Plc] := LclQT;
if (Quotient.dwUsed < Succ(Plc)) then
Quotient.dwUsed := Succ(Plc);
Carry := SigDivd - (Divisor * LclQT);
end;
Dec(Plc);
end;
finally
Remainder := Carry;
if (Quotient.dwUsed = 0) then
LbBiAddByte(Quotient, cPREPEND_ARRAY, $00);
LbBiFree(LclDVD);
LbBiTrimSigZeros(Quotient);
end;
end;
procedure LbBiDivByDigit(const N1: LbInteger; const N2: Byte;
var Quotient: LbInteger; var Remainder: Byte);
begin
LbBiDivByDigitBase(N1, N2, Quotient, Remainder);
Quotient.bSign := N1.bSign;
end;
procedure LbBiDivByDigitInPlace(var N1: LbInteger; const N2: Byte;
var Remainder: Byte);
var
Temp: LbInteger;
Precision: Integer;
begin
Precision := (N1.dwUsed + 1) * 2;
LbBiInit(Temp, Precision);
try
LbBiDivByDigit(N1, N2, Temp, Remainder);
N1.dwUsed := Temp.dwUsed;
N1.bSign := Temp.bSign;
if (N1.IntBuf.dwLen < Temp.IntBuf.dwLen) then
LbBiRealloc(N1, Temp.IntBuf.dwLen);
LbBiCopy(N1, Temp, Temp.dwUsed);
finally
LbBiFree(Temp);
end;
end;
procedure LbBiDivBase(const N1, N2: LbInteger;
var Quotient, Remainder: LbInteger);
var
Factor: Byte;
InxQ: Integer;
InxX: Integer;
TmpByte: Byte;
TempInt: Integer;
SigDigit: Byte;
LclQT: LongInt;
LclDVD: LbInteger;
LclDSR: LbInteger;
TempDR: LbInteger;
TempBN: LbInteger;
SigDivd: LongInt;
begin
LbBiInit(LclDVD, N1.dwUsed);
LbBiInit(LclDSR, N1.dwUsed);
LbBiInit(TempDR, N1.dwUsed);
LbBiInit(TempBN, N1.dwUsed);
try
if (N1.dwUsed < 1) or (N2.dwUsed < 1) then
raise Exception.Create(sBINoNumber);
if LbBiIsZero(N1) then
begin
LbBiAddByte(Quotient, cPREPEND_ARRAY, $00);
LbBiAddByte(Remainder, cPREPEND_ARRAY, $00);
Exit;
end;
if LbBiIsOne(N2) then
begin
LbBiCopy(Quotient, N1, N1.dwUsed);
LbBiAddByte(Remainder, cPREPEND_ARRAY, $00);
Exit;
end;
if LbBiIsZero(N2) then
raise Exception.Create(sBIZeroDivide);
LbBiCopy(LclDVD, N1, N1.dwUsed);
LbBiCopy(LclDSR, N2, N2.dwUsed);
LbBiTrimSigZeros(LclDSR);
TmpByte := PBiByteArray(LclDSR.IntBuf.pBuf)[Pred(LclDSR.dwUsed)];
if (TmpByte = 0) then
raise Exception.Create(sBIZeroFactor);
Factor := LbBiFindFactor(TmpByte);
if (Factor <> 1) then
begin
LbBiMulByDigitInPlace(LclDVD, Factor);
LbBiMulByDigitInPlace(LclDSR, Factor);
end;
if PBiByteArray(LclDVD.IntBuf.pBuf)[Pred(LclDVD.dwUsed)] >=
PBiByteArray(LclDSR.IntBuf.pBuf)[Pred(LclDSR.dwUsed)] then
begin
LbBiAddByte(LclDVD, cAPPEND_ARRAY, $00);
end;
while (LclDVD.dwUsed < LclDSR.dwUsed) do
LbBiAddByte(LclDVD, cAPPEND_ARRAY, $00);
InxQ := LclDVD.dwUsed - LclDSR.dwUsed + 1;
InxX := LclDVD.dwUsed;
LbBiClear(Quotient);
LbBiClear(Remainder);
SigDigit := PBiByteArray(LclDSR.IntBuf.pBuf)[Pred(LclDSR.dwUsed)];
if (SigDigit = 0) then
begin
TempInt := Pred(LclDSR.dwUsed);
while SigDigit = 0 do
begin
SigDigit := PBiByteArray(LclDSR.IntBuf.pBuf)[TempInt];
Dec(TempInt);
if TempInt < 0 then
raise Exception.Create(sBIQuotientErr);
end;
end;
while InxQ >= 1 do
begin
if (LclDVD.dwUsed = 1) then
SigDivd := PBiByteArray(LclDVD.IntBuf.pBuf)[0]
else
SigDivd := Integer(PBiByteArray(LclDVD.IntBuf.pBuf)[InxX])
shl 8 + PBiByteArray(LclDVD.IntBuf.pBuf)[Pred(InxX)];
LclQT := SigDivd div SigDigit;
if (LclQT <> 0) then
begin
if (LclQT >= cBYTE_POSSIBLE_VALUES) then
LclQT := Pred(cBYTE_POSSIBLE_VALUES);
LbBiClear(TempDR);
LbBiMove(TempDR, LclDSR, InxQ, LclDSR.dwUsed);
LbBiMulByDigitInPlace(TempDR, LclQT);
while (LbBiCompare(LclDVD, TempDR) = cLESS_THAN) do
begin
Dec(LclQT);
if (LclQT = 0) then
break;
LbBiClear(TempDR);
LbBiMove(TempDR, LclDSR, InxQ, LclDSR.dwUsed);
LbBiMulByDigitInPlace(TempDR, LclQT);
end;
end;
if (LclQT <> 0) then
begin
LbBiAddByte(Quotient, InxQ, LclQT);
LbBiSubInPlace(LclDVD, TempDR);
end;
Dec(InxX);
Dec(InxQ);
end;
LbBiCopy(Remainder, LclDVD, LclDVD.dwUsed);
if (Factor <> 0) then
begin
if (Remainder.dwUsed > 1) then
begin
LbBiDivByDigitInPlace(Remainder, Factor, TmpByte);
end
else if (Remainder.dwUsed = 1) then
begin
TmpByte := PBiByteArray(Remainder.IntBuf.pBuf)[0];
TmpByte := TmpByte div Factor;
LbBiAddByte(Remainder, cPREPEND_ARRAY, TmpByte);
end;
end;
finally
LbBiFree(LclDVD);
LbBiFree(LclDSR);
LbBiFree(TempDR);
LbBiFree(TempBN);
if (Quotient.dwUsed = 0) then
LbBiAddByte(Quotient, cPREPEND_ARRAY, $00);
if (Remainder.dwUsed = 0) then
begin
LbBiAddByte(Remainder, cPREPEND_ARRAY, $00);
end;
LbBiTrimSigZeros(Quotient);
LbBiTrimSigZeros(Remainder);
end;
end;
procedure LbBiDiv(const N1, N2: LbInteger; var Quotient, Remainder: LbInteger);
begin
LbBiDivBase(N1, N2, Quotient, Remainder);
if (N1.bSign = N2.bSign) then
Quotient.bSign := cPOSITIVE
else
Quotient.bSign := cNEGATIVE;
end;
procedure LbBiMod(const N1, N2: LbInteger; var Remainder: LbInteger);
var
Quotient: LbInteger;
begin
LbBiInit(Quotient, N2.dwUsed);
LbBiDiv(N1, N2, Quotient, Remainder);
LbBiFree(Quotient);
end;
procedure LbBiModInPlace(var N1: LbInteger; const Modulas: LbInteger);
var
Remainder: LbInteger;
begin
LbBiInit(Remainder, Modulas.dwUsed);
LbBiMod(N1, Modulas, Remainder);
LbBiClear(N1);
N1.dwUsed := Remainder.dwUsed;
N1.bSign := Remainder.bSign;
if (N1.IntBuf.dwLen < Remainder.IntBuf.dwLen) then
LbBiRealloc(N1, Remainder.IntBuf.dwLen);
LbBiCopy(N1, Remainder, Remainder.dwUsed);
LbBiFree(Remainder);
end;
procedure LbBiPowerAndMod(const I1, Exponent, Modulus: LbInteger;
var Result: LbInteger);
var
BitCount: Integer;
I: Integer;
TempByte: Byte;
Hold: LbInteger;
begin
LbBiClear(Result);
if (LbBiIsZero(Exponent)) then
begin
LbBiAddByte(Result, cPREPEND_ARRAY, $01);
Exit;
end;
LbBiInit(Hold, cDEFAULT_PRECISION);
try
I := Exponent.dwUsed;
LbBiAddByte(Result, cPREPEND_ARRAY, $01);
while I > 0 do
begin
TempByte := LbBiGetByteValue(Exponent, I);
Dec(I);
BitCount := 8;
TempByte := LbBiReverseBits(TempByte);
while BitCount > 0 do
begin
LbBiMultInPlace(Result, Result);
LbBiModInPlace(Result, Modulus);
if Odd(TempByte) then
begin
LbBiMultInPlace(Result, I1);
LbBiModInPlace(Result, Modulus);
end;
TempByte := TempByte shr 1;
Dec(BitCount);
end;
end;
finally
LbBiFree(Hold);
end;
end;
procedure LbBiPowerAndModInPLace(var I1: LbInteger;
const Exponent, Modulus: LbInteger);
var
Result: LbInteger;
begin
LbBiInit(Result, cUSE_DEFAULT_PRECISION);
try
LbBiPowerAndMod(I1, Exponent, Modulus, Result);
LbBiClear(I1);
I1.dwUsed := Result.dwUsed;
I1.bSign := Result.bSign;
if (I1.IntBuf.dwLen < Result.IntBuf.dwLen) then
LbBiRealloc(I1, Result.IntBuf.dwLen);
LbBiCopy(I1, Result, Result.dwUsed);
finally
LbBiFree(Result);
end;
end;
{TSerialDecoder}function TSerialDecoder.GetDecodedBytes: TBytes;
var
Serial, Exponent, Modulas, Output: LbInteger;
begin
Serial := GetLargeIntFromBytes(FSerialNum);
try
Modulas := GetLargeIntFromBytes(FPublicMod);
try
Exponent := GetLargeIntFromBytes(FPublicExp);
try
LbBiInit(Output, cUSE_DEFAULT_PRECISION);
try
LbBiPowerAndMod(Serial, Exponent, Modulas, Output);
Result := GetBytesFromLargeInt(Output);
finally
LbBiFree(Output);
end;
finally
LbBiFree(Exponent);
end;
finally
LbBiFree(Modulas);
end;
finally
LbBiFree(Serial);
end;
end;
function TSerialDecoder.GetBytesFromBase64(const Value: string): TBytes;
{$IFDEF FPC}var
Input: TStringStream;
Output: TMemoryStream;
Decoder: TBase64DecodingStream;
{$ENDIF}begin{$IFDEF FPC}
Input := TStringStream.Create(Value);
try
Decoder := TBase64DecodingStream.Create(Input);
try
Output := TMemoryStream.Create;
try
Result := nil;
Output.CopyFrom(Decoder, Decoder.size);
SetLength(Result, Output.size);
Move(Output.Memory^, Result[0], Output.size);
finally
Output.Free;
end;
finally
Decoder.Free;
end;
finally
Input.Free;
end;
{$ELSE}
Result := TNetEncoding.Base64.DecodeStringToBytes(Value);
{$ENDIF}end;
function TSerialDecoder.GetLargeIntFromBytes(const Bytes: TBytes): LbInteger;
var
I: Integer;
begin
Result.bSign := cDEFAULT_SIGN;
Result.IntBuf.pBuf := AllocMem(Length(Bytes));
Result.IntBuf.dwLen := Length(Bytes);
Result.dwUsed := Length(Bytes);
for I := High(Bytes) downto Low(Bytes) do
Result.IntBuf.pBuf[High(Bytes) - I] := Bytes[I];
end;
function TSerialDecoder.GetBytesFromLargeInt(const Value: LbInteger): TBytes;
var
I: Integer;
begin
Result := nil;
SetLength(Result, Value.dwUsed);
for I := Low(Result) to High(Result) do
Result[High(Result) - I] := Value.IntBuf.pBuf[I];
end;
function TSerialDecoder.GetBuildDate: TDate;
begin{Constant is in format YYYY-MM-DD}
Result := EncodeDate(StrToInt(Copy(BuildDate, 1, 4)),
StrToInt(Copy(BuildDate, 6, 2)), StrToInt(Copy(BuildDate, 9, 2)));
end;
procedure TSerialDecoder.SetPublicExp(const Value: string);
begin
FPublicExp := GetBytesFromBase64(Value);
if Length(FPublicExp) >= 8 then
Assert(Length(FPublicExp) mod 8 = 0);
end;
procedure TSerialDecoder.SetPublicMod(const Value: string);
begin
FPublicMod := GetBytesFromBase64(Value);
if Length(FPublicMod) >= 8 then
Assert(Length(FPublicMod) mod 8 = 0);
end;
procedure TSerialDecoder.SetSerialNum(const Value: string);
begin
FSerialNum := GetBytesFromBase64(Value);
end;
procedure TSerialDecoder.SetProductCode(const Value: UInt64);
begin
FProductCode := Value;
end;
function TSerialDecoder.VMProtectSetSerialNumber(
const SerialNumber: string): LongWord;
var
HashData: PByte;
HashSize: Integer;
DataStart: Integer;
Digest: {$IFDEF FPC}TSHA1Digest{$ELSE}THashSHA1{$ENDIF};
BytesStream: TBytesStream;
Buffer: TBytes;
Len: Byte;
begin
try{Assign serial number}
SetSerialNum(SerialNumber);
{Check the black list}
CheckSerialBlackList;
{Decode license key}
BytesStream := TBytesStream.Create(GetDecodedBytes);
try{Find data section}while BytesStream.ReadByte <> 0 do
begin{Skip random data}end;
DataStart := BytesStream.Position;
while BytesStream.Position < BytesStream.Size do
begin
case BytesStream.ReadByte of
$01: {Version}begin
if BytesStream.ReadByte <> 1 then
Abort;
end;
$02: {User name}begin
Len := BytesStream.ReadByte;
SetLength(Buffer, Len);
BytesStream.ReadBuffer(Buffer[0], Length(Buffer));
{$IFDEF FPC}
FSerialNumberData.wUserName := TEncoding.UTF8.GetString(Buffer);
{$ELSE}
WStrPLCopy(@FSerialNumberData.wUserName, TEncoding.UTF8.GetString(
Buffer), Length(FSerialNumberData.wUserName) - 1);
{$ENDIF}end;
$03: {E-mail}begin
Len := BytesStream.ReadByte;
SetLength(Buffer, Len);
BytesStream.ReadBuffer(Buffer[0], Length(Buffer));
{$IFDEF FPC}
FSerialNumberData.wEMail := TEncoding.UTF8.GetString(Buffer);
{$ELSE}
WStrPLCopy(@FSerialNumberData.wEMail, TEncoding.UTF8.GetString(
Buffer), Length(FSerialNumberData.wEMail) - 1);
{$ENDIF}end;
$04: {Hardware ID}begin
BytesStream.Seek(BytesStream.ReadByte, soFromCurrent);
end;
$05: {License expiration date}begin
FSerialNumberData.dtExpire.bDay := BytesStream.ReadByte;
FSerialNumberData.dtExpire.bMonth := BytesStream.ReadByte;
FSerialNumberData.dtExpire.wYear := BytesStream.ReadWord;
end;
$06: {Maximum operation time}begin
FSerialNumberData.bRunningTime := BytesStream.ReadByte;
end;
$07: {Product code}begin
if BytesStream.ReadQWord <> FProductCode then
Abort;
end;
$08: {User data}begin
FSerialNumberData.nUserDataLength := BytesStream.ReadByte;
BytesStream.ReadBuffer(FSerialNumberData.bUserData,
FSerialNumberData.nUserDataLength);
end;
$09: {Maximum build date}begin
FSerialNumberData.dtMaxBuild.bDay := BytesStream.ReadByte;
FSerialNumberData.dtMaxBuild.bMonth := BytesStream.ReadByte;
FSerialNumberData.dtMaxBuild.wYear := BytesStream.ReadWord;
end;
$FF: {Checksum}begin
HashData := PByte(BytesStream.Memory) + DataStart;
HashSize := BytesStream.Position - DataStart - 1;
{$IFDEF FPC}
Digest := SHA1Buffer(HashData^, HashSize);
if NtoBE(BytesStream.ReadDWord) <> PLongWord(@Digest)^ then
Abort;
{$ELSE}
Digest := THashSHA1.Create;
Digest.Update(HashData^, HashSize);
if THash.ToBigEndian(BytesStream.ReadDWord) <> PLongWord(
@Digest.HashAsBytes[0])^ then
Abort;
{$ENDIF}
Break;
end
else
Abort;
end;
end;
finally
BytesStream.Free;
end;
{Loop finished and we check data}if (LongWord(FSerialNumberData.dtExpire) <> 0) and
(Date > EncodeDate(
FSerialNumberData.dtExpire.wYear,
FSerialNumberData.dtExpire.bMonth,
FSerialNumberData.dtExpire.bDay)) then
begin
FSerialNumberData.nState := SERIAL_STATE_FLAG_DATE_EXPIRED;
Exit(FSerialNumberData.nState);
end;
if (LongWord(FSerialNumberData.dtMaxBuild) <> 0) and
(GetBuildDate > EncodeDate(
FSerialNumberData.dtMaxBuild.wYear,
FSerialNumberData.dtMaxBuild.bMonth,
FSerialNumberData.dtMaxBuild.bDay)) then
begin
FSerialNumberData.nState := SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED;
Exit(FSerialNumberData.nState);
end;
{Everything appears to be fine}
FSerialNumberData.nState := SERIAL_STATE_SUCCESS;
Result := FSerialNumberData.nState;
except
FSerialNumberData.nState := SERIAL_STATE_FLAG_INVALID;
Result := FSerialNumberData.nState;
end;
end;
function TSerialDecoder.VMProtectGetSerialNumberData(
Data: PVMProtectSerialNumberData; DataSize: Integer): Boolean;
begin
Result := SizeOf(FSerialNumberData) = DataSize;
if Result then
Move(FSerialNumberData, Data^, DataSize);
end;
procedure TSerialDecoder.CheckSerialBlackList;
const
BlackList: TArray<string> = [{$I BlackList.inc}];
var
Value, Hash: string;
Stream: TMemoryStream;
begin{$IFDEF FPC}
Hash := MD5Print(MD5Buffer(FSerialNum[0], Length(FSerialNum)));
{$ELSE}
Stream := TMemoryStream.Create;
try
Stream.WriteBuffer(FSerialNum[0], Length(FSerialNum));
Stream.Position := 0;
Hash := THashMD5.GetHashString(Stream);
{$ENDIF}for Value in BlackList do
if Value = Hash then
Abort;
{$IFNDEF FPC}finally
Stream.Free;
end;
{$ENDIF}end;
{$IFNDEF FPC}function TBytesStream.ReadByte: Byte;
begin
ReadBuffer(Result, SizeOf(Result));
end;
function TBytesStream.ReadWord: Word;
begin
ReadBuffer(Result, SizeOf(Result));
end;
function TBytesStream.ReadDWord: UInt32;
begin
ReadBuffer(Result, SizeOf(Result));
end;
function TBytesStream.ReadQWord: UInt64;
begin
ReadBuffer(Result, SizeOf(Result));
end;
{$ENDIF}end.
В файл BuildDate.inc идет дата сборки в YYYY-MM-DD, например '2023-01-05'. В BlackList.inc идут MD5 хеши ключей по чьим ордерам был рефанд или чаржбек. Система сборки запускает скрипт на моем сервере, который получает список таких ордеров от пайпро и вычисляет хеши таких ключей. Таким образом украденные и отмененные ключи блокируются автоматически при пересборке софта.
Использовать класс примерно так:
SerialDecoder := TSerialDecoder.Create;
SerialDecoder.SetProductCode(UInt64($XXXXXXXXXXXXXXX));//<- в VMP файле это Base64, его надо сконвертировать в число UInt64.
SerialDecoder.SetPublicExp('AAEAAQ==');
SerialDecoder.SetPublicMod('сюда публичный ключ в Base64');
Flags := SerialDecoder.VMProtectSetSerialNumber('текст ключа что ввел пользователь');
if Flags <> 0 then
begin//Что-то не так, проверяйте SERIAL_STATE_FLAG_***end
else
begin
Info := Default(TVMProtectSerialNumberData);
if SerialDecoder.VMProtectGetSerialNumberData(@Info, SizeOf(Info)) then//Все хорошо, достаем из ключа данные end;
ЧВ>3) Антивирусы тоже не особо понимают код виртуальной машины, а следовательно на всякий случай задетектим его как вирус. Короче говоря ложных срабатываний полно, особенно у говно-антивирусов второго эшелона.
Это, как я понимаю, главная проблема. Может быть, стоит купить протектор, но использовать его без функции шифрования кода? Что он ещё умеет?
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, wantus, Вы писали:
W>Шифрование кода, в подавляющем числе случаем — это абсолютный overkill. Нормальный рабочий вариант — это внутренний проверки на модификацию кода, как статическую (патчи и кряки) так и динамическую (API hooking, DLL side loading, dynamic patching, etc.).
Я не очень понял, что это за проверки, поясните их принцип.
Полагаю, эти проверки устроены так, что в случае срабатывания отрубаются какие-то отдельные фичи программы? Так что программой в целом пользоваться по-прежнему можно, но будет мотивация всё-таки купить официальную версию. По идее, хакеры не разбираются во взламываемой программе и не будут проверять функциональность этих случайных фич.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, Черный 😈 Властелин, Вы писали:
ЧВ>В итоге я решил от VMProtect отказаться, оставив однако схему лицензирования и генерации ключей. То есть используются точно такие же ключи, только проверку выполняет мой собственный код, а для пользователей ничего не изменилось.
Интересно, схема генерации ключей точно ничем не защищена, не должна ли лицензироваться и т.д.
ЧВ>Поделюсь модулем для FPC/DELPHI который декодирует ключи VMProtect:
Коллеги, нет у кого-нибудь аналогичного кода для C#?
Здравствуйте, Khimik, Вы писали:
K>Здравствуйте, Черный 😈 Властелин, Вы писали:
ЧВ>>3) Антивирусы тоже не особо понимают код виртуальной машины, а следовательно на всякий случай задетектим его как вирус. Короче говоря ложных срабатываний полно, особенно у говно-антивирусов второго эшелона.
K>Это, как я понимаю, главная проблема. Может быть, стоит купить протектор, но использовать его без функции шифрования кода? Что он ещё умеет?
Причем тут шифрование? Проблема в другом: важное выделено жирным.
Антивирусы тоже не особо понимают код виртуальной машины
Хоть шифруй, хоть не шифруй — внутри все равно код виртуальной машины, которую анвири вкурить со смыслом не могут.
Но зло в другом!
Виртуальная машина или нет, шифрован код или нет — анвири все равно тупят, тупили и тупить будут.
История леденящая душу!
В одном из моих продуктов понадобилось мне, чтоб одна работающая копия могла завершить предыдущую, уже работающую.
Сказано-сделано!
Схема простая:
1) заводится именованный эвент, который ожидается в фоновом потоке в первом уже работающем экземпляре.
2) Второй экземпляр софтины, если запущен с определенным ключом командной строки (MySoft.exe /флаг_с_вещами_на_выход) открывает так называемый тьфу-млин, открывает вышеупомянутый эвент, сигналит, и тут же завершается (второй экземпляр)
3) первый (уже работающий) экземпляр, ловит в фоновом потоке событие, и как-то уже разруливается по делу, что мол пора завершаться: закрыть окна, сказать пользователю до свиданья, все дела.
Короче, проще пареной репы.
То бишь всю суть, делает этот именнованный эвент, ожидание и все такое. Фигня-война — написалось-причесалось за вечер со всеми делами (секурность, как закрываться в уже работающем экземпляре, что делать если закрываться пользователь все таки НЕ хочет — в общем все краевые ситуации).
Ну и прикрутилась к этой схеме простенькая отдельная exe-тулза для завершения работающего экземпляра приложения.
Совсем простенькая!
3 строчки кода(три, Карл! три): OpenEvent + SetEvent + CloseHandle.
И что тут блин началось!
Ага, млин да это мелварь, да всё такое, да она лезет в непонятные эвенты Виндовс.....
А я ж прихирел! Несколько раз перепроверял, пересобирал, еще раз перепроверял. И еще раз прихеривал, но уже сильнее...
Ан фиг! Мелварь и всё точка!
И ничего так, что основная софтина с десятком подобных эвентов дело имеет — с ней всё в порядке.
И опять же ничего так, что оная основная софтина шарится по чужим адресным пространствам, и какими только способами туда не пролезает. Моя софтина — в чужое адресное пространство "в дверь", а там заперто. Гуй с ним. Тогда через окно? Законопачено? Тады через чердак! И там не пущатЪ? Ну да фиг с Вами, тогда и по кул-хацки можно...
Ну и мало того, что лазиет по чужим адресным пространствам — дык это основная софтина, еще и данные оттуда, из-за забора к себе отгружает.
Казалось бы, вот на чтобы ругаться бы... Ан фиг! К основной софтине претензий нет.
А мелко-поделке в 3 строки кода (те самые OpenEvent+SetEvent...) — ПРЕТЕНЗИИ ТАКИ ЕСТЬ!!!
И это не говноантивирусы, это все таки был Avast — не самый поганый антивирус!
PS: зато родился лайф-хак как общаться в тех поддержкой Аваста на предмет false positive detection.
1) Начинать сходу по русски! Причем на русском-матерном! Пускай шекспирят на конференциях, да журналюгам очки втирают на аглицком. Здесь разговор по делу.
2) Начинать сие послание султанам-нах, нужно с эпической фразы: "Здорово щеглы, плятЪ!!!" (C).
3) Далее перечисление трех упомянутых функций, описание что и зачем, ссылка на официальную доку на официальном же сайте.
4-ое по списку но не менее важное: предложение выгнать нафиг студентов — рукожопых лоботрясов (C) аналитигоф первой линии!. Ну или хотя бы отправить в неоплачиваемый отпуск. А сэкомноленные средствА — всенепременно и заобязательно совместно — пропить. В хорошей компании, отличным чешским пивом.
Проверено!
Результат 100-процентный.
Забегали, засуеитились. Через секунду тикет висел уже в поддержке, через пару часов уже отписались в стиле "извини, колллега, хирню сморозили". Через два часа уже был именно что официальный ответ: ля-ля, ля-ля — наш косяк — в течении суток выложим исправления, и публичные базы обновим при ближайшем же ежедневном апдейте сигнатур.
Здравствуйте, sfsoft, Вы писали:
S>Здравствуйте, Черный 😈 Властелин, Вы писали:
S>Т.е. привязку к оборудованию ты не используешь? А как тогда отслеживается количество копий? Ключ можно всем друзьям раздать, например.
Есть такая штука — Valla — может быть чем-то поможет.
Здравствуйте, Khimik, Вы писали:
K>Здравствуйте, wantus, Вы писали:
W>>Шифрование кода, в подавляющем числе случаем — это абсолютный overkill. Нормальный рабочий вариант — это внутренний проверки на модификацию кода, как статическую (патчи и кряки) так и динамическую (API hooking, DLL side loading, dynamic patching, etc.).
K>Я не очень понял, что это за проверки, поясните их принцип. K>Полагаю, эти проверки устроены так, что в случае срабатывания отрубаются какие-то отдельные фичи программы? Так что программой в целом пользоваться по-прежнему можно, но будет мотивация всё-таки купить официальную версию. По идее, хакеры не разбираются во взламываемой программе и не будут проверять функциональность этих случайных фич.
Из простых вариантов
1. Добавить проверку подписи собственного exe (через WinVerifyTrust). По случайному таймеру, не сразу. Проверили, запомнили результат, запустили еще один таймер на несколько минут. Когда таймер сработал, покрасили background окна в красный цвет, или перевернули его кверх ногами, или еще чего-нибудь очевидно намеренное и кривое. Это уже достаточно противно патчить.
2. Добавить еще несколько копий этой проверки — полных копий всего кода, а не просто вызовов check_exe_signature() — и навешать их на разные события, 56-ой клик мышки или типа того. Опять же, все проверки и реакции на их результаты — отложенные, через таймеры, простые counters или чего там еще.
3. Могут запатчить сам WinVerifyTrust. На это добавляем вызовов WinVerifyTrust с кривыми параметрами и смотрим, что он таки возвращает ошибку.
Как бонус
4. Можно все эти проверки отключать, если похоже что программа бежит под дебагером — IsDebuggerPresent, PEB.BeingDebugged, NtQueryInformationProcess(..., 7, ...), etc. В принципе все хорошие дебагеры умеют притворяться, что их нет, но это простая в изготовлении какашка и подложить её не мешает.
5. Из той же серии в релиз билды можно нашпиговать NtSetInformationThread((HANDLE)-2, 0x11), что выключает дебаг текущего треда. Тоже обходится соответствующим add-on'ом к дебагеру, но далеко не все об этом знают.
Типа окопная партизанская война с целью измождения противника.
И как уже сказали — строго online licensing с public keys, никаких прошитых или алгоритмических ключей. Как побочный эффект, это помогает свести credit card fraud и chargebacks практически в ноль.
Здравствуйте, sfsoft, Вы писали:
S>Здравствуйте, Черный 😈 Властелин, Вы писали:
S>Т.е. привязку к оборудованию ты не используешь? А как тогда отслеживается количество копий? Ключ можно всем друзьям раздать, например.
Главное выделено жирным — привязка к оборудованию.
Нужно понимать его шире.
Привязка к оборудованию — это все таки просто некая сравнительно уникальная метка для конкретного пользователя\машины. Что это может быть на самом деле — да всё что угодно. А то упрутся в motherboard\hdd\что_еще серийный номер, и всё. Как будто ничего другого не бывает. И это не только, и главное, не обязательно именно железо.
Фантазируйте, коллеги, фантазируйте.
Всё равно всё сведется к банальному отношению (в математическом смысле). Сложность\надежность этого «замка» (hardware print) и ценность\важность\необходимость того функционала, который этот «замочек» защищает.
Ну, а «далее везде, со всеми остановками» (C)
Кто клиент (пользователь, компания)? Что делает код? «Где» это что-то делает ваш код (отдельный ПК или сервер)? Причем сервер в широком понимании: хошь веб-сервер, а хошь и нет — может это сервер печати бухгалтерского отдела конторы "Рога и Копыта"!?! Нужен ли по делу вашему приложению эти ваши интернеты?
Вот ответы на такие вопросы и дадут понимание, что нужно и что нет.
Примеры на вскидку: если веб для софтины нужен по делу, то тут не грех и онлайн-активацию приделать.
Примеру нумер Два: если ваш супер-пупер-сервер печати для бухгалтерского отдела конторы "Рога и копыта" начнётЪ печатитЪ вместо счетов фактур "Ойопта, нахЫр, наZ взломали!!!!" да еще и в псевдослучайном порядке, да еще и за день до сдачи балансового отчета...
Ну вы поняли... МарьВанна — она же главбух так называемого отдела, в момент подпишет платежку на ваш софт, и купит. Местный же кул-хацкер Пятя из IT-отела будет отметелен ногами в курилке если и не будет уволен, то увидит ближайшую премию только в случае скоропостижной кончины вышеозначенной МарьВанны, и то не факт.
А вот какую-нить онлайн активацию в эдаком (пусть и выдуманным) примере прикручивать может и не стОит. Ибо толковые и мнительные бухгалтерА (а толковые они заувсегда мнительные, и они в этом правы) могут подстремануться: а с какого перепуга это наш кул-супер-принтер в эти наши интернеты лезет? Чего он там забыл?
В общем размышлять следует в контексте и начиная со сценариев использования, а не с технической стороны: чего б такого бы шифрануть да захешить...
Здравствуйте, sfsoft, Вы писали: S>Здравствуйте, Черный 😈 Властелин, Вы писали: S>Т.е. привязку к оборудованию ты не используешь? А как тогда отслеживается количество копий? Ключ можно всем друзьям раздать, например.
Никак, все на доверии. Если кто-то будет использовать бесплатно, это не страшно, другие заплатят.
У меня несколько десятков ордеров в день, если переписываться с каждым у кого не работает ключ, поменялись железки или там закончились активации, когда тогда писать код?
Здравствуйте, Unhandled_Exception, Вы писали:
K>>Какой шифратор лучше выбрать — Execryptor или есть что-то новое?
U_E>Эх, это был первый проектор, который я использовал. Он уже очень много лет как не существует.
Сорри, я опечатался, хотел сказать The Enigma Protector.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, wantus, Вы писали:
W>1. Добавить проверку подписи собственного exe (через WinVerifyTrust). По случайному таймеру, не сразу. Проверили, запомнили результат, запустили еще один таймер на несколько минут. Когда таймер сработал, покрасили background окна в красный цвет, или перевернули его кверх ногами, или еще чего-нибудь очевидно намеренное и кривое. Это уже достаточно противно патчить.
Я сходу не нагуглил, что делает WinVerifyTrust, просьба пояснить. "запомнили результат" — какой результат, число возвращаемое WinVerifyTrust?
Я привык делать всё по-своему, мне легче изобрести велосипед, чем разбираться в чужих решениях. Можно подсчитать контрольную сумму exe файла собственной программы. Если хакер что-то в нём изменит, контрольная сумма поменяется, значит надо чтобы программа с временем это проверила, как вы написали.
Это не защитит от кейгена; по крайней мере, лично я рассылки ключей не боюсь (боюсь именно кейгена), поскольку моя программа требует регулярно обновлять ключи, чтобы ими можно было активировать (юзер может получить новый ключ на сайте).
Вот ещё пример простой уязвимости: если человек проинсталлирует программу, введёт ключ, а потом скопирует весь каталог с программой и перенесёт на другой компьютер. Значит надо использовать hardwareid, или хотя бы при активации запоминать, в каком каталоге хранится программа.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, sfsoft, Вы писали:
S>Т.е. привязку к оборудованию ты не используешь? А как тогда отслеживается количество копий? Ключ можно всем друзьям раздать, например.
Полагаю, любая лицензия подразумевает, что человек может хотя бы на минуту дать попользоваться друзьям, и тут в любом случае слишком сложно бороться. У меня ключ кодируется по данным покупателя (имя, фамилия и т.д.), и эти данные отображаются вверху главного окна программы.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Защита не нужна. Он-лайн активация, отзыв ключей (чтобы спокойно делать рефанды) и простая проверка ключа. Все.
Я в последнем большом обновлении всю защиту выкинул. Те два с половиной бедолаги, что в 2023 году все еще шарятся по помойкам в поисках кряка, все равно ничего не купят. Даже наличие кейгена не влияет на продажи по моему опыту.
Здравствуйте, Khimik, Вы писали:
K>Здравствуйте, wantus, Вы писали:
W>>1. Добавить проверку подписи собственного exe (через WinVerifyTrust). По случайному таймеру, не сразу. Проверили, запомнили результат, запустили еще один таймер на несколько минут. Когда таймер сработал, покрасили background окна в красный цвет, или перевернули его кверх ногами, или еще чего-нибудь очевидно намеренное и кривое. Это уже достаточно противно патчить.
K>Я сходу не нагуглил, что делает WinVerifyTrust, просьба пояснить.
2023 на дворе. Какие нафиг кейгены?
K>... юзер может получить новый ключ на сайте
То есть серверная часть уже есть? Это замечательно.
1. Напишите тривиальный HTTP/GET клиент на WinHTTP (пример есть на msdn), который из программы посылает на сайт MachineGUID и получает назад подписанную RSA ключом "лицензию" — [MachineGUID + срок годности + подпись]. Причем это можно даже по http делать, без https. На серверной стороне всё что требуется это вызвать "openssl rsautl -sign ..." из командной строки.
2. Зашейте public часть ключа в программу и периодически проверяйте наличие (а) лицензии (б) её подпись (в) соответствие MachineGUID.
3. Плюс добавьте отложенные по времени проверки целостности exe.
Этот комплект убирает риск 99% хаков.
И таки подписывайте ваши exe. Как минимум это делает очевидным, когда exe был модифицирован.
Здравствуйте, wantus, Вы писали:
K>>Это не защитит от кейгена
W>2023 на дворе. Какие нафиг кейгены?
Можно подробнее, чем 2023 год в этом плане отличается от 2012?
K>>... юзер может получить новый ключ на сайте
W>То есть серверная часть уже есть? Это замечательно.
W>1. Напишите тривиальный HTTP/GET клиент на WinHTTP (пример есть на msdn), который из программы посылает на сайт MachineGUID и получает назад подписанную RSA ключом "лицензию" — [MachineGUID + срок годности + подпись]. Причем это можно даже по http делать, без https. На серверной стороне всё что требуется это вызвать "openssl rsautl -sign ..." из командной строки. W>2. Зашейте public часть ключа в программу и периодически проверяйте наличие (а) лицензии (б) её подпись (в) соответствие MachineGUID. W>3. Плюс добавьте отложенные по времени проверки целостности exe.
Это всё надо искать web-программиста, я сам знаю только Delphi. Думал научиться писать cgi скрипты в Lazarus, а он какой-то нелепый, пока даже не могу без посторонней помощи скомпилировать Linux-программу. Но, может быть, скоро ChatGPT сможет нам писать web-скрипты, или учить нас это делать?
Мой нынешний код web-скрипта написал другой человек 15 лет назад, я давно потерял с ним контакты.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, wantus, Вы писали:
W>Из простых вариантов
W>1. Добавить проверку подписи собственного exe (через WinVerifyTrust). По случайному таймеру, не сразу. Проверили, запомнили результат, запустили еще один таймер на несколько минут. Когда таймер сработал, покрасили background окна в красный цвет, или перевернули его кверх ногами, или еще чего-нибудь очевидно намеренное и кривое. Это уже достаточно противно патчить.
Пропатчат место вызова WinVerifyTrust.
W>2. Добавить еще несколько копий этой проверки — полных копий всего кода, а не просто вызовов check_exe_signature() — и навешать их на разные события, 56-ой клик мышки или типа того. Опять же, все проверки и реакции на их результаты — отложенные, через таймеры, простые counters или чего там еще.
Положат рядом прокси DLL с "правильным" WinVerifyTrust
W>3. Могут запатчить сам WinVerifyTrust. На это добавляем вызовов WinVerifyTrust с кривыми параметрами и смотрим, что он таки возвращает ошибку.
В прокси DLL сначала вызовут оригинальную WinVerifyTrust, если ничего страшного не произошло — вернут "правильный" результат.
W>Как бонус
W>4. Можно все эти проверки отключать, если похоже что программа бежит под дебагером — IsDebuggerPresent, PEB.BeingDebugged, NtQueryInformationProcess(..., 7, ...), etc. В принципе все хорошие дебагеры умеют притворяться, что их нет, но это простая в изготовлении какашка и подложить её не мешает.
На момент написания прокси DLL поставят ScyllaHide и обойдут все эти проверки на отладчик.
W>5. Из той же серии в релиз билды можно нашпиговать NtSetInformationThread((HANDLE)-2, 0x11), что выключает дебаг текущего треда. Тоже обходится соответствующим add-on'ом к дебагеру, но далеко не все об этом знают.
Из той же серии про ScyllaHide.
W>Типа окопная партизанская война с целью измождения противника.
Я вижу тут измождение только со стороны разработчика. Даже написать вызов WinVerifyTrust у него займет гораздо больше времени чем у крякера занопить его в готовом бинарнике.
W>И как уже сказали — строго online licensing с public keys, никаких прошитых или алгоритмических ключей. Как побочный эффект, это помогает свести credit card fraud и chargebacks практически в ноль.
Public Keys для этого нужно так нихренова защитить, а то получится все таже шляпа, что и с WinVerifyTrust.
Здравствуйте, drVanо, Вы писали:
V>Здравствуйте, wantus, Вы писали:
W>>Из простых вариантов
W>>1. Добавить проверку подписи собственного exe (через WinVerifyTrust). По случайному таймеру, не сразу. Проверили, запомнили результат, запустили еще один таймер на несколько минут. Когда таймер сработал, покрасили background окна в красный цвет, или перевернули его кверх ногами, или еще чего-нибудь очевидно намеренное и кривое. Это уже достаточно противно патчить.
V>Пропатчат место вызова WinVerifyTrust.
Могут.
W>>2. Добавить еще несколько копий этой проверки — полных копий всего кода, а не просто вызовов check_exe_signature() — и навешать их на разные события, 56-ой клик мышки или типа того. Опять же, все проверки и реакции на их результаты — отложенные, через таймеры, простые counters или чего там еще.
V>Положат рядом прокси DLL с "правильным" WinVerifyTrust
Это да. Но это лечится аудитом загруженных DLL, который делается без единого api вызова, либо через LoadLibraryEx + LOAD_LIBRARY_SEARCH_SYSTEM32.
W>>3. Могут запатчить сам WinVerifyTrust. На это добавляем вызовов WinVerifyTrust с кривыми параметрами и смотрим, что он таки возвращает ошибку.
V>В прокси DLL сначала вызовут оригинальную WinVerifyTrust, если ничего страшного не произошло — вернут "правильный" результат.
По уму — да, в реальности — нет, им лень.
W>>Как бонус
V>На момент написания прокси DLL поставят ScyllaHide и обойдут все эти проверки на отладчик.
V>Из той же серии про ScyllaHide.
Ну да, я же написал, что обходится. Но не все школьники про это знают.
W>>Типа окопная партизанская война с целью измождения противника.
V>Я вижу тут измождение только со стороны разработчика. Даже написать вызов WinVerifyTrust у него займет гораздо больше времени чем у крякера занопить его в готовом бинарнике.
Каждый видит чего ему хочется, но в целом это зависит от разработчика. Далеко не все болваны.
W>>И как уже сказали — строго online licensing с public keys, никаких прошитых или алгоритмических ключей. Как побочный эффект, это помогает свести credit card fraud и chargebacks практически в ноль.
V>Public Keys для этого нужно так нихренова защитить, а то получится все таже шляпа, что и с WinVerifyTrust.
Проверять надо целостность exe и плясять от этого.
Но это все оффтоп. Поинт в том, что прокси dll не входят в арсенал подавляющего большинства l33t haxxors, от которых то и стоит по сути защищаться. От более квалифицированных товарищей тоже можно, причем в том же стиле, но это уже next level.
Здравствуйте, Khimik, Вы писали:
K>Это всё надо искать web-программиста, я сам знаю только Delphi. Думал научиться писать cgi скрипты в Lazarus, а он какой-то нелепый, пока даже не могу без посторонней помощи скомпилировать Linux-программу.
Здравствуйте, Черный 😈 Властелин, Вы писали:
ЧВ>У меня несколько десятков ордеров в день, если переписываться с каждым у кого не работает ключ, поменялись железки или там закончились активации, когда тогда писать код?
С другой стороны, если у тебя несколько десятков ордеров в день, зачем писать код?
Здравствуйте, wantus, Вы писали:
V>>В прокси DLL сначала вызовут оригинальную WinVerifyTrust, если ничего страшного не произошло — вернут "правильный" результат. W>По уму — да, в реальности — нет, им лень.
А откуда вы знаете что им лень, а что не лень? Вы предлагаете откровенно слабые варианты, которые может обойти даже начинающий реверсер.
V>>Из той же серии про ScyllaHide.
W>Ну да, я же написал, что обходится. Но не все школьники про это знают.
Школьники уже знают много чего, просто поверьте.
W>Проверять надо целостность exe и плясять от этого.
Вы где предлагаете проверять целостность на диске или в памяти? Если на диске, то в самом простом случае вам подсунут девственно чистый файл, а в более "сложном" сам бинарник будут патчить лоадером прямо в памяти. Если вы предлагаете проверять целостность имейджа в памяти, то разработчику как минимум нужно будет самому разбираться во внутренностях РЕ формата (понимать как файл проецируется на память, исключать из подсчета CRC релоки, IAT, сегменты данных и прочие прелести).
W>Но это все оффтоп. Поинт в том, что прокси dll не входят в арсенал подавляющего большинства l33t haxxors, от которых то и стоит по сути защищаться. От более квалифицированных товарищей тоже можно, причем в том же стиле, но это уже next level.
Прокси DLL я привел в качестве примера, в данном случае большинство из ваших вариантов обходятся банальным лоадером.
Здравствуйте, Sharowarsheg, Вы писали:
S> ЧВ>У меня несколько десятков ордеров в день, если переписываться с каждым у кого не работает ключ, поменялись железки или там закончились активации, когда тогда писать код?
S> С другой стороны, если у тебя несколько десятков ордеров в день, зачем писать код?
Здорово же, когда работа совпадает с любимым делом
Здравствуйте, drVanо, Вы писали:
V> Если вы предлагаете проверять целостность имейджа в памяти, то разработчику как минимум нужно будет самому разбираться во внутренностях РЕ формата (понимать как файл проецируется на память, исключать из подсчета CRC релоки, IAT, сегменты данных и прочие прелести).
Ты все усложняешь. Целостность всего образа проверять смысла нет. Достаточно проверить целостность критически важных данных и кода. Это делается проще, чем ты описываешь.
Здравствуйте, drVanо, Вы писали:
V>А откуда вы знаете что им лень, а что не лень? Вы предлагаете откровенно слабые варианты, которые может обойти даже начинающий реверсер.
Я понимаю, что вы стекольщик и вам надо нагнетать, но преувеличивать уж настолько сильно тоже не стоит.
V>Школьники уже знают много чего, просто поверьте.
Школьники как были балбесами в погоне за street cred, так и остались. Ничего принципиально за последнее время не поменялось.
V>Прокси DLL я привел в качестве примера, в данном случае большинство из ваших вариантов обходятся банальным лоадером.
Ну так себе пример то был, хотя это и уже не l33t уровень. "Банальным лоадером" можно обойти, но их делать умеет еще меньший процент.
Для не high-profile софта даже небольшая защита снижает риск кряков практически до нуля, потому что никто из тех, кто может его сломать, не будет тратить на него время.
Здравствуйте, rudzuk, Вы писали:
R>Ты все усложняешь. Целостность всего образа проверять смысла нет. Достаточно проверить целостность критически важных данных и кода. Это делается проще, чем ты описываешь.
Дык в области критичных данных и кода как раз могут встретиться релоки, особенно на х86.
ЧВ>>У меня несколько десятков ордеров в день, если переписываться с каждым у кого не работает ключ, поменялись железки или там закончились активации, когда тогда писать код? S>С другой стороны, если у тебя несколько десятков ордеров в день, зачем писать код?
В смысле, зачем? Я так к ним и пришел, постоянно совершенствуя и разрабатывая свой софт.
Конечно можно кого-то нанять, но я не люблю людей, а вот код и компы да — потому по сути занимаюсь любимым делом с достойной оплатой.
Здравствуйте, Черный 😈 Властелин, Вы писали:
ЧВ>>>У меня несколько десятков ордеров в день, если переписываться с каждым у кого не работает ключ, поменялись железки или там закончились активации, когда тогда писать код? S>>С другой стороны, если у тебя несколько десятков ордеров в день, зачем писать код?
ЧВ>В смысле, зачем? Я так к ним и пришел, постоянно совершенствуя и разрабатывая свой софт.
ЧВ>Конечно можно кого-то нанять, но я не люблю людей, а вот код и компы да — потому по сути занимаюсь любимым делом с достойной оплатой.
Разумно, да.
С третьей стороны, найми кого-нибудь на техподдержку тогда? Научить человека понимать что там с ключами и проча и перевыдывать их — не слишком сложно, и ты будешь иметь дело с одним человеком, а не со всеми сразу?
С четвертой стороны, забей — можно сказать, что я неудачно пошутил, и это будет не слишком далеко от истины.
Здравствуйте, wantus, Вы писали:
V>>А откуда вы знаете что им лень, а что не лень? Вы предлагаете откровенно слабые варианты, которые может обойти даже начинающий реверсер. W>Я понимаю, что вы стекольщик и вам надо нагнетать,
Вы меня с кем-то путаете.
W>но преувеличивать уж настолько сильно тоже не стоит.
Вы реверсингом сколько лет занимаетесь, если не секрет?
W>Школьники как были балбесами в погоне за street cred, так и остались. Ничего принципиально за последнее время не поменялось.
Здравствуйте, drVanо, Вы писали:
V> R>Ты все усложняешь. Целостность всего образа проверять смысла нет. Достаточно проверить целостность критически важных данных и кода. Это делается проще, чем ты описываешь.
V> Дык в области критичных данных и кода как раз могут встретиться релоки, особенно на х86.
В области кода могут, но нужно написать его так, чтобы их небыло, это не сложно. В области данных... Откуда в области хранения открытого ключа взяться релокам?
Здравствуйте, rudzuk, Вы писали:
R>Здравствуйте, drVanо, Вы писали:
V>> R>Ты все усложняешь. Целостность всего образа проверять смысла нет. Достаточно проверить целостность критически важных данных и кода. Это делается проще, чем ты описываешь.
V>> Дык в области критичных данных и кода как раз могут встретиться релоки, особенно на х86.
R>В области кода могут, но нужно написать его так, чтобы их небыло, это не сложно. В области данных... Откуда в области хранения открытого ключа взяться релокам?
Любые ссылки из кода на данные (в том числе и на ваш открытый ключ), данные на данные, данные на код гарантировано имеют релоки на x86. Учите матчасть.
Здравствуйте, drVanо, Вы писали:
V> V>> Дык в области критичных данных и кода как раз могут встретиться релоки, особенно на х86.
V> R>В области кода могут, но нужно написать его так, чтобы их небыло, это не сложно. В области данных... Откуда в области хранения открытого ключа взяться релокам?
V> Любые ссылки из кода на данные (в том числе и на ваш открытый ключ), данные на данные, данные на код гарантировано имеют релоки на x86. Учите матчасть.
Я же написал, что проверяемый код нужно написать так, чтобы этих ссылок небыло. И это не сложно.
Здравствуйте, rudzuk, Вы писали:
V>> Любые ссылки из кода на данные (в том числе и на ваш открытый ключ), данные на данные, данные на код гарантировано имеют релоки на x86. Учите матчасть.
R>Я же написал, что проверяемый код нужно написать так, чтобы этих ссылок небыло. И это не сложно.
Ну хорошо, давайте вы напишете такой код + все что сюда понаписали ТС-у, а я его попробую взломать. Как вам такое?
Здравствуйте, rudzuk, Вы писали:
R>Мне сантиметрами меряться не очень интересно. Давай я признаю, что у тебя длиннее, а ты признаешь, что написать код без релоков это не бином Ньютона?
Давай ты лучше признаешь, что все что ты предлагал ТС-у вместе с вантусом — это полный шлак.
Здравствуйте, drVanо, Вы писали:
V> R>Мне сантиметрами меряться не очень интересно. Давай я признаю, что у тебя длиннее, а ты признаешь, что написать код без релоков это не бином Ньютона?
V> Давай ты лучше признаешь, что все что ты предлагал ТС-у вместе с вантусом — это полный шлак.
Ты невнимательно читаешь. Я ТС'у ничего не предлагал. Я сказал, что контролировать целостность критически важного кода и данных не так сложно, как ты пытаешься преподнести. Поэтому — нет.
Здравствуйте, rudzuk, Вы писали:
R>Ты невнимательно читаешь. Я ТС'у ничего не предлагал. Я сказал, что контролировать целостность критически важного кода и данных не так сложно, как ты пытаешься преподнести. Поэтому — нет.
Вот же ты сам предлагал:
Ты все усложняешь. Целостность всего образа проверять смысла нет. Достаточно проверить целостность критически важных данных и кода. Это делается проще, чем ты описываешь.
В итоге ты предлагаешь человеку самостоятельно писать PIC, чтобы потом проверять целостность участков кода. Я даже больше чем уверен, что он даже их (эти самые участки) не найдет.
К слову сказать твой открытый ключ можно подменить прямо в памяти в момент "десериализации" его в BigInteger даже не трогая код, который с ним работает, и все твои проверки на целостность будут просто бесполезны.
Здравствуйте, drVanо, Вы писали:
V> R>Ты невнимательно читаешь. Я ТС'у ничего не предлагал. Я сказал, что контролировать целостность критически важного кода и данных не так сложно, как ты пытаешься преподнести. Поэтому — нет.
V> Вот же ты сам предлагал:
V> Ты все усложняешь. Целостность всего образа проверять смысла нет. Достаточно проверить целостность критически важных данных и кода. Это делается проще, чем ты описываешь.
V> В итоге ты предлагаешь человеку самостоятельно писать PIC, чтобы потом проверять целостность участков кода. Я даже больше чем уверен, что он даже их (эти самые участки) не найдет.
Нет, ТС'у я ничего не предлагаю. Я сказал, отвечая совсем не ТС'у, что имеет место быть очевидное усложнение достаточно простой задачи.
V> К слову сказать твой открытый ключ можно подменить прямо в памяти в момент "десериализации" его в BigInteger даже не трогая код, который с ним работает, и все твои проверки на целостность будут просто бесполезны.
Здравствуйте, Sharowarsheg, Вы писали:
S>Вечные лицензии, мне помнится. Деньги получаешь один раз, а поддержка навсегда. Через какое-то время проект приходится закрывать.
Представляю себе этот ужас. Наверное есть сегменты продуктов, где практика вечных лицензий может быть оправдана, но если это что-то техническое и тем более связанное с компиляцией и кодом — шансов выжить с такой моделью нет.
Здравствуйте, Aquilaware, Вы писали:
A>Представляю себе этот ужас. Наверное есть сегменты продуктов, где практика вечных лицензий может быть оправдана, но если это что-то техническое и тем более связанное с компиляцией и кодом — шансов выжить с такой моделью нет.
Вечные лицензии обычно практикуются в самом начале пути, чтобы привлечь первых пользователей. Использовать такое для устоявшегося продукта смысла никакого, конечно же. Нельзя предоставлять неограниченный ресурс за ограниченные деньги.
Здравствуйте, wantus, Вы писали:
W>1. Добавить проверку подписи собственного exe (через WinVerifyTrust).
я бы еще добавил: помимо основного метода проверки ключа (RSA) иметь несколько простых дополнительных (а-ля третий байт ключа всегда делится на 7). После загрузки ключа в память он тиражируется в десяток-другой мест в памяти и точно так же, по таймеру или событию, проверяется. Тиражирование нужно, чтобы было сложнее ставить брейкпоинты на память. Разные проверки нужны, чтобы их нельзя было обезвредить анализом кода или поведения.
Здравствуйте, drVanо, Вы писали:
V>Здравствуйте, wantus, Вы писали:
V>>>А откуда вы знаете что им лень, а что не лень? Вы предлагаете откровенно слабые варианты, которые может обойти даже начинающий реверсер. W>>Я понимаю, что вы стекольщик и вам надо нагнетать,
V>Вы меня с кем-то путаете.
Вы продаете продукт для защиты от хакеров. Чем страшнее хакеры, тем лучше бизнес.
W>>но преувеличивать уж настолько сильно тоже не стоит.
V>Вы реверсингом сколько лет занимаетесь, если не секрет?
С конца 90х, но это видимо вопрос с подвохом?
V>Ну ну, вам с дивана виднее
Здравствуйте, JustPassingBy, Вы писали:
JPB>Вечные лицензии обычно практикуются в самом начале пути, чтобы привлечь первых пользователей. Использовать такое для устоявшегося продукта смысла никакого, конечно же. Нельзя предоставлять неограниченный ресурс за ограниченные деньги.
Здравствуйте, drVanо, Вы писали:
V>К слову сказать твой открытый ключ можно подменить прямо в памяти в момент "десериализации" его в BigInteger даже не трогая код, который с ним работает, и все твои проверки на целостность будут просто бесполезны.
Просто публичный ключ должен вычисляться, а не лежать константой в области данных.
Здравствуйте, K13, Вы писали:
K13>Просто публичный ключ должен вычисляться, а не лежать константой в области данных.
Для того чтобы понимать как защитить открытый ключ от подмены, нужно как минимум представлять как работают стандартные библиотеки, которые манипулируют большими числами. Открытый ключ это просто большое число, которое загоняется в класс BigInteger, и дальше над ним производят математические действия (внутри modpow). Дак вот перехватив любой из методов, которые используются при проверке правильности серийного номера, можно подменить открытый ключ прямо в памяти BigInteger-а (особо "умные" делают это через перехват стандартной функции выделения памяти из CRT).
Здравствуйте, drVanо, Вы писали:
V> K13>Просто публичный ключ должен вычисляться, а не лежать константой в области данных.
V> Для того чтобы понимать как защитить открытый ключ от подмены, нужно как минимум представлять как работают стандартные библиотеки, которые манипулируют большими числами. Открытый ключ это просто большое число, которое загоняется в класс BigInteger, и дальше над ним производят математические действия (внутри modpow). Дак вот перехватив любой из методов, которые используются при проверке правильности серийного номера, можно подменить открытый ключ прямо в памяти BigInteger-а (особо "умные" делают это через перехват стандартной функции выделения памяти из CRT).
Вот я и говорю — фантазии это, с прикольными допущениями, что будет использоваться стандартное API.
Да нет же, я сказал, что мне сантиметрами меряться не интересно, и что готов признать твое превосходство. У тебя удивительная способность не понимать, что тебе говорят
V> Зачем заново начинать этот разговор?
Безотносительно используемого протектора могу порекомендовать несколько способов, которые могут усложнить жизнь крякеру:
1. Строковые константы. Многие программисты, которые не знакомы с основами реверсинга даже не пытаются шифровать критичные строки. Многие например хранят ключи в виде base64, выдают сообщения типа "Неверный регистрационный код", по ссылкам на который можно достаточно быстро найти код функции, которая выдает это сообщение, даже не запуская дебаггер.
Для некритичных строк достаточно будет динамического создания их прямо на стеке. Самый простой способ:
А также варианты для более продвинутых пользователей.
2. Проверка CRC участков кода, используемых для проверки регистрации ключей. В большинстве случаев совершенно бесполезно, т.к. начинающие программисты даже не догадываются как и где их будут ломать. Если вы собираетесь реализовать данный функционал собственными силами, то нужно учитывать, что в отладчике крякеру достаточно будет найдет место, с помощью которого вы считаете CRC по региону кода, после этого код будет пропатчен и будет выдавать всегда "правильный" результат. Как можно этому противостоять? Для начала можно познакомиться с возможностями NtReadVirtualMemory, с помощью которого можно обойти все бряки, устанавливаемые крякером в отладчике. Следующим этапом можно порекомендовать использовать NtReadVirtualMemory напрямую через SYSCALL/SYSENTER, но для этого придется познакомиться с простейшими способами получения номеров сервисов, в том числе как их можно вызывать из WOW64.
3. Детект отладчиков. Если вы прокачали п. 2 до уровня "бог", то можете с легкостью детектить современные юзермодные отладчики, даже при использовании специальных плагинов, которые работают через хук функций из NTDLL и физически не могут перехватывать ваши собственные вызовы SYSCALL/SYSENTER.
Здравствуйте, rudzuk, Вы писали:
R>Да нет же, я сказал, что мне сантиметрами меряться не интересно, и что готов признать твое превосходство. У тебя удивительная способность не понимать, что тебе говорят
, а потом удивляться, почему тебя никто не понимает.
Да нет, откровенная хрень, это твои допущения. Допущения о том, что разработчик не в курсе, как работает асимметричка. О наличии одного единственного места, где делаются проверки. О том, что возможности защиты можно продемонстрировать простеньким крэкми. Вот это — хрень.
Здравствуйте, rudzuk, Вы писали:
R>Да нет, откровенная хрень, это твои допущения. Допущения о том, что разработчик не в курсе, как работает асимметричка. О наличии одного единственного места, где делаются проверки. О том, что возможности защиты можно продемонстрировать простеньким крэкми. Вот это — хрень.
Воу, воу! Я смотрю у тебя немного сантиметров наросло в нужном месте. Я так понимаю ты уже готов предоставить свой бинарник для теста чья хрень круче?
Здравствуйте, drVanо, Вы писали:
V> R>Да нет, откровенная хрень, это твои допущения. Допущения о том, что разработчик не в курсе, как работает асимметричка. О наличии одного единственного места, где делаются проверки. О том, что возможности защиты можно продемонстрировать простеньким крэкми. Вот это — хрень.
V> Воу, воу! Я смотрю у тебя немного сантиметров наросло в нужном месте. Я так понимаю ты уже готов предоставить свой бинарник для теста чья хрень круче?
Вот, ты либо не читаешь, либо смысл написанного от тебя ускользает.
Здравствуйте, rudzuk, Вы писали:
R>Вот, ты либо не читаешь, либо смысл написанного от тебя ускользает.
Чтобы смысл ускользал или не ускользал он (смысл) как минимум должен быть. А его нет, есть только твоя теоретическая хрень и ты почему-то боишься проверить свою хрень теорию на практике.
P.S. Предлагаю прекратить этот бессмысленный разговор, тем более что кроме отсутствия недостающих сантиметров тебе больше нечего предложить в качестве аргументов.
Здравствуйте, drVanо, Вы писали:
V> P.S. Предлагаю прекратить этот бессмысленный разговор, тем более что кроме отсутствия недостающих сантиметров тебе больше нечего предложить в качестве аргументов.
Когда смысл ускользнул, продолжать, действительно, нет смысла.
Здравствуйте, wantus, Вы писали:
W>Да, елки-палки. W>Разговор не о том, что несложные вещи не ломаются. W>Разговор о том, что их зачастую достаточно для того, чтобы по сети не плавали взломанные версии.
Знаешь, я вот иногда прихожу сюда (на форум программистов) и реально @хуеваю от того факта, что у многих здешних программистов напрочь отсутствует способность делать логические выводы даже по тем тезисам, которыми они же (эти самые тезисы) сюда (на форум программистов) и притащили. Давай я попробую разжевать весь тот бред, а также твои неправильные логические выводы, которые ты и твой коллега по цеху здесь напостили.
Добавить проверку подписи собственного exe (через WinVerifyTrust)
У начинающего крякера (этот такой 15-ти летний пацан, который прочитал пару статей Криса Касперски, пол статьи из журнала "Хакера", умеет запускать дебаггер и знает чем отличается условный от безусловного перехода) на отлом этой защиты у него уйдет минут 10. Почему этот способ по моему мнению полный шлак? Да потому, что даже минимальных знаний в реверсинге будет достаточно чтобы его обойти. А если учесть, что программист, который решил закодить этот способ убил на него час-два-три (чтение документации по WinVerifyTrust, сам кодинг, отладка и все остальные прелести), а крякер на эту "защиту" потратил всего 10 минут, то нужна ли эта защита? Думаю что нахрен не нужна, потому что программист за эти самые полчаса может написать какой-то полезный функционал, который в конечном итоге принесет ему реальные деньги, а не мнимую защиту от кряка.
Добавить еще несколько копий этой проверки — полных копий всего кода
Опустим некоторые технические моменты написания такой полноценной проверки целостности образа в памяти, на которые я намекал
. Просто скажем, что по сравнению c п.1 квалификация программиста, который самостоятельно будет реализовывать подобный функционал, должна быть гораздо выше, а вот квалификация крякера будет тойже самой (ну может быть он найдет еще какую-нить статейку про то как в отладчике работают бряки на доступ к данным) и ему будет достаточно пропатчить всего одну функцию, которая считает эту самую CRC. Итого — на имлементацию, отладку и прочие прелести у программиста ушел день/неделя/месяц, а у 15-ти летнего пацана, который ждаст фор фан запустил вашу программу в отладчике, в сумме с п.1 ушел максимум час. Почему этот способ по моему мнению полный шлак? Читай выводы п.1.
Могут запатчить сам WinVerifyTrust. На это добавляем вызовов WinVerifyTrust с кривыми параметрами и смотрим, что он таки возвращает ошибку.
Ну если 15-ти летний пацан не дурак, а он не дурак и тоже умеет читать документацию по WinAPI, то он пропатчит проверку результата так, чтобы в случае критической ошибки код этой самой ошибки вернулся в вызывающий код.
4. Можно все эти проверки отключать, если похоже что программа бежит под дебагером — IsDebuggerPresent, PEB.BeingDebugged, NtQueryInformationProcess(..., 7, ...), etc. В принципе все хорошие дебагеры умеют притворяться, что их нет, но это простая в изготовлении какашка и подложить её не мешает.
15-ти летний пацан уже решил пару крякмиксов и тоже знает про IsDebuggerPresent, PEB.BeingDebugged, NtQueryInformationProcess. Почему данный способ полный шлак? Потому что, программист еще даже не закодил все это дело в свою программу, а 15-ти летний пацан это все уже сломал, скачав ScyllaHide.
Типа окопная партизанская война с целью измождения противника.
Подведем итоги окопной войны? С одной стороны в "окопе" сидит программист, который уже потратил кучу времени на имплементацию твоего бреда, при этом не написал еще ниодной строчки полезного для его программы (своих пользователей) кода, а с другой стороны в "окопе" сидит 15-ти летний пацан, который только вчера вживую увидел программу, а сегодня ради прикола, потратив на порядок меньше времени, уже все это взломал.
Нужна ли ТС-у такая защита? Думаю что нахрен не нужна. Любой думающий своей головой шароварщик прекрасно понимает, что его время гораздо ценнее, чем время 15-ти летнего пацана, и что лучше это время потратить на написание полезный функционала, чем на "мега крутую защиту от Сифона и Бороды вантуса и рудзика".
Ну а теперь на десерт самый твой последний тезис:
Разговор о том, что их зачастую достаточно для того, чтобы по сети не плавали взломанные версии.
Кто-то мне может показать логическую связь между тем, что программист на имплементацию всего это бреда потратил неделю, а 15-ти летний пацан потратил на взлом программы 3 часа, с тем фактом, что по сети вдруг перестали плавать взломанные версии? Я вот например вообще не вижу никакой связи. Если даже 15-ти летний пацан с нулевым опытом смог справиться с "задачей", то взломанные версии будут плавать везде, да еще как! Хотя это скорее всего к тому вопросу, почему у некоторых здешних обитателей иногда напрочь отсутствует какая-либо логика (некоторые ее отсутствие прикрывают недостатком сантиметров
, что при проверке целостности нет смысла проверять весь образ, достаточно ограничиться критически важными областями, и что позиционно-независимый код написать не сложно. Весна, что ты делаешь...
Здравствуйте, TailWind, Вы писали:
TW> Он, конечно, не совсем культурно себя ведёт TW> Но он прав
Да не, он расказывает сказочки о 15-летних ксакепах умножающих труд программиста на ноль за десять минут
Сейчас даже просто использование нативной компиляции уже является достаточным препятствием для "юных дарований". Все потому, что они привыкли "ломать" софт, для которого есть декомпилятор, и даже просто использование примитивной обфускации ставит их в тупик. Я, если что, говорю о 95% интеллектуального большинства. Более того, не так давно читал интервью с антивирусным аналитиком из одной очень известной конторы, так он сокрушался, что очень хреново анализировать малвару написанную на Go т.к. она компилируется в натив, а вот малвару под дотнет ему анализировать нравится
TW> Делал такую защиту: TW> — проверка CRC exe файла (не в памяти, а на диске) TW> — отложенная по таймеру
TW> Ну и опытные хакеры надо мной поржали, взломав за день
Перед опытным и упорным реверсером не устоит ни одна защита (его вмпротект — тоже), это нужно понимать. Но и цели делать непробиваемую защиту нет, и это тоже нужно понимать. Мой софт с 2016 года в сети. Нет, ни кряков, ни кейгенов. Несколько слитых ключей и все. Софт дороже $300. Но это все потому, что я Неуловимый Джо (предвосхищаю, на всякий случай).
Здравствуйте, rudzuk, Вы писали:
R>Сейчас даже просто использование нативной компиляции уже является достаточным препятствием для "юных дарований".
Вот оно че, Михалыч! Нативная компиляция — это все что нужно знать об уровне знаний нашего иксперта! Я ему про релоки рассказываю, а он мне про C#. Теперь все встало на свои места. Вопросов больше не имею.
Здравствуйте, drVanо, Вы писали:
V> Вот оно че, Михалыч! Нативная компиляция — это все что нужно знать об уровне знаний нашего иксперта! Я ему про релоки рассказываю, а он мне про C#. Теперь все встало на свои места. Вопросов больше не имею.
Мде. Качай понималку написаннго, оно в жизни помогает.
Здравствуйте, rudzuk, Вы писали:
V>> Вот оно че, Михалыч! Нативная компиляция — это все что нужно знать об уровне знаний нашего иксперта! Я ему про релоки рассказываю, а он мне про C#. Теперь все встало на свои места. Вопросов больше не имею.
R>Мде. Качай понималку написаннго, оно в жизни помогает.
Я тебе просто дам совет на будущее — перед теми как залезать в спор со своим икспертным мнением, соизволь хоть немного изучить матчасть. В приличном обществе аргументы типа "качай понималку" не канают.
Здравствуйте, drVanо, Вы писали:
V> Я тебе просто дам совет на будущее — перед теми как залезать в спор со своим икспертным мнением, соизволь хоть немного изучить матчасть. В приличном обществе аргументы типа "качай понималку" не канают.
Так это и не было аргументом. Это было пожелание человеку, который, очевидно, не понимает смысла написанного.
з.ы. Кстати, ты еще не все мои сообщения заминусовал. Не сдерживай себя!
Здравствуйте, Черный 😈 Властелин, Вы писали:
ЧВ>пришел к выводу, что те кто не купят ваш софт в любом случае воспользутся или левой кредиткой или патчем. Да ну и пусть, c них все равно денег не получить. А те кто могут купить прогу, покупают ее как и раньше.
У меня сложилось еще более радикальное мнение: до некоторого порога цены (для рядового пользователя это где-то $30-50) даже наличие ключей практически не влияет на продажи, а уж наличие шифрования — и подавно.
Сужу по своему основному продукту, в котором нет вообще никакой защиты (купившим лицензию дается ссылка на скачивание полной версии). Найти в сети пиратку — раз плюнуть, они валяются повсеместно, но количество продаж всегда четко коррелирует с количеством показов в гугле. То есть, народ или опасается пираток (и обоснованно, как известно), либо ему банально в лом заниматься криминалом ради экономии суммы, примерно равной продуктовой корзине на один-два дня.
Отсюда вывод: основная ценность ключей — не в борьбе с пиратством, а в возможности эффективно блокировать лицензии после возвратов, а также блокировать кардеров.
Уже много лет собираюсь приделать к этому продукту какие-нибудь примитивные ключи (о шифровании кода даже не задумываюсь — нет смысла), но все руки не доходят.
Здравствуйте, Khimik, Вы писали:
K>Какой шифратор лучше выбрать — Execryptor или есть что-то новое?
Для нативного кода наверное еще можно как-то обходится без протектора, но если это что-то типа Java или .NET и вы собираетесь продавать коммерческий продукт — то разорвут на части, какую бы вы нишу не выбрали. С одной стороны грызут кул-хацкеры, с другой стороны — конкуренты. Как только у вас рождается что-то более-менее востребованное и популярное — вся эта братия быстро налетает как мухи на мёд.
Кто-то скажет — да ну его, пиратство — это бесплатная реклама. Отчасти это так, но только до тех пор пока не начинается срез дохода. Вот тогда-то и приходится суетиться, но лучше решение этой ситуации закладывать сразу пока вас не сдоили за бесплатно.