目录
??????授权文件保存在{commonappdata}目录下,名称为*.act,采用RSA2048私钥加密,通过公钥解密。公钥保存在*service.exe文件中,通过搜索“-----BEGIN PUBLIC KEY-----”可以获得。
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwknN5/81qIoM+k1WEc/5
A+u3YzWeDNE5i5rpayiNWv4TvxX9udMz3uKF4tmDJfjuSCVUHQWAjJefwSv4ZBQ7
egXHtGzh/HVY/Geei7A1TwOroHxxgUKVkXRoPGYIVNBhrqX0CsCgYCZv7Ij6CSy4
Iamw0Gb/BnZ4Uw1AkOs6DfbGaVspeQGg7oTjSj+TKOWdK1dJ7ZoGyoVQLqxpaq41
r893XqSK5mXlbE3pFSjS6Rn8unyt57EuIcFy+x2VqjOf3KiwiGCeIaQtAtF5Pqii
KoEMoGgcDedO4C6h/LKy1oXJR7QeHQijUAn7JXE/i0I57BXyKylR6/YIV6hV+Ovm
bQIDAQAB
-----END PUBLIC KEY-----
通过公钥解密授权文件的代码:
#pragma comment(lib,"crypt32.lib")
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")
#include <string>
#include <fstream>
#include "openssl/pem.h"
#include "openssl/rsa.h"
#include <openssl/bn.h>
using namespace std;
int main()
{
ifstream ifs("./vspdpro.act", ifstream::binary | ifstream::ate);
if (ifs) {
streampos size = ifs.tellg();
char* buffer = new char[size];
ifs.seekg(0, ifstream::beg);
ifs.read(buffer, size);
ifs.close();
string public_key = "-----BEGIN PUBLIC KEY-----\r\n" \
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwknN5/81qIoM+k1WEc/5\r\n" \
"A+u3YzWeDNE5i5rpayiNWv4TvxX9udMz3uKF4tmDJfjuSCVUHQWAjJefwSv4ZBQ7\r\n" \
"egXHtGzh/HVY/Geei7A1TwOroHxxgUKVkXRoPGYIVNBhrqX0CsCgYCZv7Ij6CSy4\r\n" \
"Iamw0Gb/BnZ4Uw1AkOs6DfbGaVspeQGg7oTjSj+TKOWdK1dJ7ZoGyoVQLqxpaq41\r\n" \
"r893XqSK5mXlbE3pFSjS6Rn8unyt57EuIcFy+x2VqjOf3KiwiGCeIaQtAtF5Pqii\r\n" \
"KoEMoGgcDedO4C6h/LKy1oXJR7QeHQijUAn7JXE/i0I57BXyKylR6/YIV6hV+Ovm\r\n" \
"bQIDAQAB\r\n" \
"-----END PUBLIC KEY-----";
BIO* bio = BIO_new_mem_buf(public_key.c_str(), public_key.size());
RSA* rsa = PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr);
BIO_free(bio);
printf("n='%s';\n", BN_bn2hex(RSA_get0_n(rsa)));
printf("e='%s';\n", BN_bn2hex(RSA_get0_e(rsa)));
unsigned char* from = (unsigned char*)buffer;
unsigned char to[256];
string txt = "";
while (true)
{
int len = RSA_public_decrypt(256, from, to, rsa, RSA_PKCS1_PADDING);
if (len < 0) break;
txt.append((char *)to, len);
from += 256;
}
printf("%s", txt.c_str());
delete[] buffer;
RSA_free(rsa);
}
}
解密后的授权文件为文本文件:
hid=xxxxxxxx_xxxxxxxx_xxxxxxxx_x_xxxxxxxx
license_key_code=XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX
licenseName=Single License SEC
product_id=114
product_name=Serial to Ethernet Connector
product_version=9
serverDate=2024-01-06
activationDate=2024-01-06
nextActivation=2024-01-07
firstActivation=2024-01-06
errorCode=0
key_type=2
registed_name=support
license_options=
key_options=14
serverTime=1704535404
activation_param=
subscription=0
key_not_check_hid=0
其中最关键的两个值key_type和errorCode:
0 = Single License 单机版许可授权
1 = Limited func (license_options)?表示根据功能授权,license_options的值表示功能的限制数量
2 = Limited time(key_options=days) 表示根据时间授权,key_options的值表示授权天数。
3 = OEM
0 = ALREADY_ACTIVATED?
DEMO_IS_OVER
NO_AVAILABLE_ACTIVATIONS
KEY_BANNED
+REACTIVATE
+STARTING
硬件编码由5部分组成,格式如下:
Cpu信息_硬盘几何信息_硬盘固件编号信息_0_系统BIOS信息
详细算法代码如下:
unit HardwareID;
interface
uses Windows,Registry,SysUtils;
function GetHid():string;
implementation
type
TRegisters = record
EAX: DWORD;
EBX: DWORD;
ECX: DWORD;
EDX: DWORD;
end;
procedure GetCPUID(Param: Cardinal; var Registers: TRegisters);
asm
PUSH EBX { save affected registers }
PUSH EDI
MOV EDI, Registers
XOR EBX, EBX { clear EBX register }
XOR ECX, ECX { clear ECX register }
XOR EDX, EDX { clear EDX register }
DB $0F, $A2 { CPUID opcode }
MOV TRegisters(EDI).&EAX, EAX { save EAX register }
MOV TRegisters(EDI).&EBX, EBX { save EBX register }
MOV TRegisters(EDI).&ECX, ECX { save ECX register }
MOV TRegisters(EDI).&EDX, EDX { save EDX register }
POP EDI { restore registers }
POP EBX
end;
function GetHid1():Integer;
var
regs1,regs2:TRegisters;
i:Cardinal;
begin
GetCPUID(0,regs1);
Result:= regs1.EBX xor regs1.ECX xor regs1.EDX;
if regs1.EAX>=1 then
begin
for i := 1 to 13 do
begin
GetCPUID(i,regs2);
case i of
2,3,4,5,8,9,12,13:
begin
Result:=Result xor regs2.EAX xor regs2.EBX xor regs2.ECX xor regs2.EDX;
end;
11:
begin
Result:=Result xor regs2.EAX xor regs2.EBX xor regs2.ECX xor(regs2.EDX and $FFFFFF00);
end;
end;
end;
end;
GetCPUID($80000000,regs1);
for i := $80000002 to regs1.EAX do
begin
GetCPUID(i,regs2);
case i of
$80000001,$80000002,$80000003,$80000004,$80000006,$80000008,$8000000A,$8000001A:
begin
Result:=Result xor regs2.EAX xor regs2.EBX xor regs2.ECX xor regs2.EDX;
end;
end;
end;
end;
function GetHid11():Integer;
var
SystemInfo:TSystemInfo;
begin
GetSystemInfo(SystemInfo);
if SystemInfo.wProcessorArchitecture>=0 then
Result:=2*SystemInfo.wProcessorArchitecture
else
Result:=2*SystemInfo.wProcessorArchitecture+1;
Result:=SystemInfo.dwNumberOfProcessors xor Result;
if Result>=0 then
Result := 2 * Result
else
Result := 2 * Result+1;
Result := SystemInfo.dwProcessorType xor Result;
if Result>=0 then
Result := 2 * Result
else
Result := 2 * Result+1;
Result := SystemInfo.wProcessorLevel xor Result;
if Result>=0 then
Result := 2 * Result
else
Result := 2 * Result+1;
Result := SystemInfo.wProcessorRevision xor Result;
if Result>=0 then
Result := 2 * Result
else
Result := 2 * Result+1;
end;
function GetHid2:Integer;
type
TDiskGeometry=record
Cylinders:LARGE_INTEGER;
MediaType:DWORD;
TracksPerCylinder:DWORD;
SectorsPerTrack:DWORD;
BytesPerSector:DWORD;
end;
var
hDevice : THandle;
DiskGeometry:TDiskGeometry;
cbBytesReturned:DWORD;
Value1,Value2:LARGE_INTEGER;
const
IOCTL_DISK_GET_DRIVE_GEOMETRY = $00070000;
begin
Result:=0;
hDevice := CreateFile( '\\.\PhysicalDrive0',GENERIC_READ,FILE_SHARE_READ or FILE_SHARE_WRITE,nil, OPEN_EXISTING, 0, 0 );
if hDevice<>INVALID_HANDLE_VALUE then
begin
if DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY,nil,0, @DiskGeometry,SizeOf(TDiskGeometry), cbBytesReturned, nil ) then
begin
Value1.QuadPart:=DiskGeometry.BytesPerSector * DiskGeometry.SectorsPerTrack*DiskGeometry.TracksPerCylinder*DiskGeometry.Cylinders.QuadPart;
if (Value1.QuadPart and $80000000)=0 then
Value2.QuadPart:=2*Value1.QuadPart
else
Value2.QuadPart:=(2*Value1.QuadPart) or 1;
Result:=Value2.LowPart xor Value1.HighPart;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
end;
CloseHandle(hDevice);
end;
end;
function GetHid3:Integer;
type
TStorgePropertyQuery=record
PropertyId:DWORD;
QueryType:DWORD;
AdditionalParameters:Byte;
end;
TStorgeDeviceDescriptor=record
Version:DWORD;
Size:DWORD;
DeviceType:Byte;
DeviceTypeModifier:Byte;
RemovableMedia:Boolean;
CommandQueueing:Boolean;
VendorIdOffset:DWORD;
ProductIdOffset:DWORD;
ProductRevisionOffset:DWORD;
SerialNumberOffset:DWORD;
BusType:DWORD;
RawPropertiesLength:DWORD;
RawDeviceProperties:Byte;
end;
PStorgeDeviceDescriptor=^TStorgeDeviceDescriptor;
TGetVersionInParams=record
bVersion:Byte;
bRevision:Byte;
bReserved:Byte;
bIDEDeviceMap:Byte;
fCapabilities:DWORD;
dwReserved:array[0..3] of DWORD;
end;
_IDEREGS=record
bFeaturesReg:Byte;
bSectorCountReg:Byte;
bSectorNumberReg:Byte;
bCylLowReg:Byte;
bCylHighReg:Byte;
bDriveHeadReg:Byte;
bCommandReg:Byte;
bReserved:Byte;
end;
TSendCmdInParams=record
cBufferSize:DWORD;
irDriveRegs:_IDEREGS;
bDriveNumber:Byte;
bReserved:array[0..2] of Byte;
dwReserved:array[0..3] of DWORD;
bBuffer:Byte;
end;
_DRIVERSTATUS=record
bDriverError:Byte;
bIDEError:Byte;
bReserved:array[0..1] of Byte;
dwReserved:array[0..1] of DWORD;
end;
TSendCmdOutParams=record
cBufferSize:DWORD;
DriverStatus:_DRIVERSTATUS;
bBuffer:Byte;
end;
PSendCmdOutParams=^TSendCmdOutParams;
var
hDevice : THandle;
StorgePropertyQuery:TStorgePropertyQuery;
StorgeDeviceDescriptor:PStorgeDeviceDescriptor;
GetVersionInParams:TGetVersionInParams;
SendCmdInParams:TSendCmdInParams;
SendCmdOutParams:PSendCmdOutParams;
cbBytesReturned,i:DWORD;
P:PChar;
W:PWORD;
Str:string;
const
IOCTL_STORAGE_QUERY_PROPERTY = $002D1400;
SMART_GET_VERSION = $00074080;
SMART_RCV_DRIVE_DATA = $0007C088;
IDENTIFY_BUFFER_SIZE = 512;
// Valid values for the bCommandReg member of IDEREGS.
ATAPI_ID_CMD = $A1; // Returns ID sector for ATAPI.
ID_CMD = $EC; // Returns ID sector for ATA.
SMART_CMD = $B0; // Performs SMART cmd.
begin
Result:=0;
hDevice := CreateFile( '\\.\PhysicalDrive0',GENERIC_READ,FILE_SHARE_READ or FILE_SHARE_WRITE,nil, OPEN_EXISTING, 0, 0 );
if hDevice<>INVALID_HANDLE_VALUE then
begin
ZeroMemory(@StorgePropertyQuery,SizeOf(TStorgePropertyQuery));
ZeroMemory(@GetVersionInParams,SizeOf(TGetVersionInParams));
StorgeDeviceDescriptor:=AllocMem(10000);
if DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,@StorgePropertyQuery,SizeOf(TStorgePropertyQuery),
StorgeDeviceDescriptor,10000, cbBytesReturned, nil ) then
begin
Str:='';
P:=PChar(@StorgeDeviceDescriptor^.Version);
if StorgeDeviceDescriptor^.SerialNumberOffset>0 then Str:=Str+StrPas(PChar(Integer(P)+StorgeDeviceDescriptor^.SerialNumberOffset));
if StorgeDeviceDescriptor^.VendorIdOffset>0 then Str:=Str+StrPas(PChar(Integer(P)+StorgeDeviceDescriptor^.VendorIdOffset));
if StorgeDeviceDescriptor^.ProductIdOffset>0 then Str:=Str+StrPas(PChar(Integer(P)+StorgeDeviceDescriptor^.ProductIdOffset));
for i := 1 to Length(Str) do
begin
Result:=Result xor Ord(Str[i]);
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
end;
end
else if DeviceIoControl(hDevice, SMART_GET_VERSION,nil,0,@GetVersionInParams,SizeOf(TGetVersionInParams), cbBytesReturned, nil) then
begin
if GetVersionInParams.bIDEDeviceMap>0 then
begin
SendCmdOutParams:=AllocMem(SizeOf(TSendCmdOutParams)+IDENTIFY_BUFFER_SIZE);
ZeroMemory(@SendCmdInParams,SizeOf(TSendCmdInParams));
SendCmdInParams.irDriveRegs.bSectorCountReg:=1;
SendCmdInParams.irDriveRegs.bSectorNumberReg:=1;
SendCmdInParams.irDriveRegs.bDriveHeadReg:=$A0;
if (GetVersionInParams.bIDEDeviceMap and $10) <>0 then
SendCmdInParams.irDriveRegs.bCommandReg:=ATAPI_ID_CMD
else
SendCmdInParams.irDriveRegs.bCommandReg:=ID_CMD;
SendCmdInParams.cBufferSize:=512;
if DeviceIoControl(hDevice, SMART_RCV_DRIVE_DATA,@SendCmdInParams,SizeOf(TSendCmdInParams),
SendCmdOutParams,SizeOf(TSendCmdOutParams)+IDENTIFY_BUFFER_SIZE, cbBytesReturned, nil ) then
begin
W:=@SendCmdOutParams^.bBuffer;
Inc(W,10);
Result:=W^;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
Inc(W,1);
Result:=Result xor W^;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
Inc(W,1);
Result:=Result xor W^;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
Inc(W,1);
Result:=Result xor W^;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
Inc(W,1);
Result:=Result xor W^;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
Inc(W,1);
Result:=Result xor W^;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
Inc(W,1);
Result:=Result xor W^;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
Inc(W,1);
Result:=Result xor W^;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
Inc(W,1);
Result:=Result xor W^;
if Result>=0 then
Result:=2*Result
else
Result:=(2*Result) or 1;
Inc(W,1);
Result:=Result xor W^;
end;
FreeMem(SendCmdOutParams);
end;
end;
FreeMemory(StorgeDeviceDescriptor);
CloseHandle(hDevice);
end;
end;
function GetHid5:Integer;
var
Reg:TRegistry;
Size,i:Integer;
B:TBytes;
begin
Result:=0;
Reg:=TRegistry.Create;
try
Reg.RootKey:=HKEY_LOCAL_MACHINE;
if Reg.OpenKeyReadOnly('HARDWARE\DESCRIPTION\System') then
begin
Size:=Reg.GetDataSize('SystemBiosVersion');
if Size>0 then
begin
SetLength(B,Size);
if Reg.ReadBinaryData('SystemBiosVersion',B[0],Size)<>0 then
begin
for i := 0 to Size - 1 do
begin
Result:=Result xor B[i];
if Result>=0 then
Result:=2*Result
else
Result:=2*Result or 1;
//UNICODE STRING
Result:=Result xor $00;
if Result>=0 then
Result:=2*Result
else
Result:=2*Result or 1;
end;
end;
end;
end;
finally
FreeAndNil(Reg);
end;
end;
function GetHid():string;
var
regs:TRegisters;
hid1,hid2,hid3,hid4,hid5:DWORD;
begin
GetCPUID(0,regs);
if regs.EAX>0 then
hid1:=GetHid1
else
hid1:=GetHid11;
hid2:=GetHid2;
hid3:=GetHid3;
hid4:=0;
hid5:=GetHid5;
Result:=Format('%x_%x_%x_%x_%x',[hid1,hid2,hid3,hid4,hid5]);
Result:=LowerCase(Result)
end;
end.
可以通过两种方式实现本地授权,一是重新生成RSA2048秘钥对,替换可执行文件内的公钥,用私钥加密授权文件保存为act文件,既可实现授权许可;二是通过dll劫持方式hook可执行文件内解密act文件的函数,修改返回的内容,既可实现本地授权许可。
//替换公钥
procedure PatchFile(FileName:string);
var
Stream:TMemoryStream;
SubStr,Str:string;
P:PByte;
Offset:Integer;
begin
SubStr:='-----BEGIN PUBLIC KEY-----';
Stream:=TMemoryStream.Create;
try
Stream.LoadFromFile(FileName);
Stream.Position:=0;
SetLength(Str,Stream.Size);
MoveMemory(@Str[1],Stream.Memory,Stream.Size);
Offset:=Pos(SubStr,Str);
if Offset<1 then raise Exception.Create('Can not find PublicKey,Patch failed!');
P:= PByte(DWORD(Stream.Memory)+Offset-1);
MoveMemory(P,@RSA_PUBLIC_KEY[1],Length(RSA_PUBLIC_KEY));
Stream.SaveToFile(FileName);
finally
Stream.Free;
end;
end;
通过FGInt实现RSA公钥解密私钥加密,也可以直接通过openssl实现,会增加2个dll文件。
unit FGIntRSA;
{
n=p*q
φ(n) = (p-1)(q-1)
d*e mod φ(n) = 1
public key => n e
private key => n d
public key enc dec => M= C^e mod n
private key enc dec => C= M^d mod n
}
interface
uses FGInt;
type
RSA_PADDING = (RSA_PKCS1_PADDING,RSA_X931_PADDING,RSA_NO_PADDING);
function RSA_Public_Decrypt(Src:string;StrE,StrN:string;Padding:RSA_PADDING = RSA_PKCS1_PADDING):string;
function RSA_Private_Encrypt(Src:string;StrD,StrN:string;Padding:RSA_PADDING = RSA_PKCS1_PADDING):string;
implementation
uses SysUtils,Windows;
const
RSA_PKCS1_PADDING_SIZE = 11;
function RSA_padding_check_PKCS1_type_1(Src:string):string;
var
p:PByte;
i,len:Integer;
begin
{
00 || 01 || PS || 00 || D
PS - padding string, at least 8 bytes of FF
D - data.
}
p:=PByte(@Src[1]);
if p^=$00 then inc(p);
if p^<>$01 then raise Exception.Create('RSA_R_BLOCK_TYPE_IS_NOT_01');
Inc(p);
for i := 0 to Length(Src) - 2 do
begin
if p^<>$FF then
begin
if p^=0 then
begin
Inc(p);
Break;
end
else
raise Exception.Create('RSA_R_BAD_FIXED_HEADER_DECRYPT');
end;
Inc(p);
end;
len:=Length(Src) - (Cardinal(p)-Cardinal(@Src[1]));
SetLength(Result,len);
MoveMemory(@Result[1],p,len);
end;
function RSA_padding_add_PKCS1_type_1(Src:string;Num:Integer):string;
var
p:PByte;
len:Integer;
begin
if Length(Src)>Num - RSA_PKCS1_PADDING_SIZE then raise Exception.Create('RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE');
SetLength(Result,Num);
p:=PByte(@Result[1]);
p^:=$00;
Inc(p);
p^:=$01;
Inc(p);
len:=Num - Length(Src) - 3;
FillMemory(p,len,$FF);
Inc(p,len);
p^:=$00;
Inc(p);
MoveMemory(p,@Src[1],Length(Src));
end;
function RSA_padding_check_X931(Src:string):string;
var
p:PByte;
i,len:Integer;
begin
p:=PByte(@Src[1]);
if p^ = $6B then
begin
Inc(p);
for i := 0 to Length(Src) - 3 do
begin
if p^ = $BA then
begin
inc(p);
Break;
end
else if p^<>$BB then raise Exception.Create('RSA_R_INVALID_PADDING');
Inc(p);
end;
len:=Length(Src) - (Cardinal(p)-Cardinal(@Src[1])) - 1;
end
else if p^ = $6A then
begin
Inc(p);
len:=Length(Src) - 2;
end
else
raise Exception.Create('RSA_R_INVALID_HEADER');
if PByte(Cardinal(p)+len)^ <> $CC then raise Exception.Create('RSA_R_INVALID_TRAILER');
SetLength(Result,len);
MoveMemory(@Result[1],p,len);
end;
function RSA_padding_add_X931(Src:string;Num:Integer):string;
var
p:PByte;
len:Integer;
begin
len:= Num -2 - Length(Src);
if len < 0 then raise Exception.Create('RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE');
SetLength(Result,Num);
p:=PByte(@Result[1]);
if len = 0 then
begin
p^:=$6A;
Inc(p);
end
else
begin
p^:=$6B;
Inc(p);
if len > 1 then
begin
FillMemory(p,len - 1,$BB);
Inc(p,len-1);
end;
p^:=$BA;
Inc(p);
end;
MoveMemory(p,@Src[1],Length(Src));
Inc(p,Length(Src));
p^:=$CC;
end;
function RSA_Public_Decrypt(Src:string;StrE,StrN:string;Padding:RSA_PADDING):string;
var
tempstr:string;
c,e,n,temp:TFGInt;
i,j,num:Integer;
begin
Base256StringToFGInt(StrE,E);
Base256StringToFGInt(StrN,N);
Result:='';
FGIntToBase2String(n,tempstr);
num:=Length(tempstr) div 8;
j :=Length(Src) div num;
if Length(Src) mod num <> 0 then inc(j);
for i := 1 To j do
begin
tempstr := copy(Src, 1, num);
delete(Src, 1, num);
Base256StringToFGInt(tempstr,c);
FGIntModExp(c, e, n, temp);
FGIntToBase256String(temp, tempstr);
case Padding of
RSA_PKCS1_PADDING:Result := Result + RSA_padding_check_PKCS1_type_1(tempstr);
RSA_X931_PADDING:Result:= Result + RSA_padding_check_X931(tempstr);
RSA_NO_PADDING:Result := Result + tempstr;
end;
FGIntDestroy(c);
FGIntDestroy(temp);
end;
FGIntDestroy(e);
FGIntDestroy(n);
end;
function RSA_Private_Encrypt(Src:string;StrD,StrN:string;Padding:RSA_PADDING):string;
var
i, j ,num,padding_size:Integer;
tempstr:string;
c,d,n,temp:TFGInt;
begin
Base256StringToFGInt(StrD,d);
Base256StringToFGInt(StrN,n);
Result:='';
FGIntToBase2String(n,tempstr);
num:=Length(tempstr) div 8;
padding_size:=0;
case Padding of
RSA_PKCS1_PADDING:padding_size:=RSA_PKCS1_PADDING_SIZE;
RSA_X931_PADDING:padding_size:=2;
RSA_NO_PADDING:padding_size:=0;
end;
j :=Length(Src) div (num - padding_size);
if Length(Src) mod (num - padding_size) <> 0 then inc(j);
for i := 1 To j do
begin
tempstr := copy(Src, 1, num - padding_size);
delete(Src, 1, num - padding_size);
case Padding of
RSA_PKCS1_PADDING:tempstr:=RSA_padding_add_PKCS1_type_1(tempstr,num);
RSA_X931_PADDING:tempstr:=RSA_padding_add_X931(tempstr,num);
RSA_NO_PADDING:tempstr:=tempstr;
end;
Base256StringToFGInt(tempstr, c);
FGIntModExp(c, d, n, temp);
FGIntToBase256String(temp,tempstr);
Result:=Result+tempstr;
FGIntDestroy(c);
FGIntDestroy(temp);
end;
FGIntDestroy(d);
FGIntDestroy(n);
end;
end.
通过python脚本创建winhttp.dll的劫持工程模板。
#CreatePatchDll.py
import os,sys,winreg,pefile
from string import Template
def IsKnownDLLs(dllname:str):
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,r"SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs")
try:
j = 0
while True:
_,value,_ = winreg.EnumValue(key, j)
if value.upper() == dllname.upper():
return True
j += 1
except WindowsError as e:
pass
return False
if __name__ == '__main__':
if (len(sys.argv)>1):
name = sys.argv[1]
else:
#name = 'version.dll'
print('Missing file name')
exit(-1)
if IsKnownDLLs(name):
print(f'{name} is system know dll!')
exit(-1)
dll_paths=[os.getcwd(),os.getenv('windir'),os.path.join(os.getenv('windir'),'system32')]
dll_path = None
for path in dll_paths:
if os.path.exists(os.path.join(path,name)):
dll_path = os.path.join(path,name)
break
if not dll_path:
print('Could not find %s' % name)
exit(-1)
name = os.path.basename(dll_path).split('.')[0]
pe = pefile.PE(dll_path)
dpr = f"""library {name};
uses
SysUtils,
Classes,
Windows,
u{name} in 'u{name}.pas';
{{$R *.res}}
procedure DLLHandler(Reason: Integer);
begin
case Reason of
DLL_PROCESS_ATTACH:
begin
OnLoad();
end;
DLL_PROCESS_DETACH:
begin
end;
DLL_THREAD_ATTACH:;
DLL_THREAD_DETACH:;
end;
end;
begin
DLLProc := @DLLHandler;
DLLProc(DLL_PROCESS_ATTACH);
end.
"""
code = Template(f"""unit u{name};
interface
uses Windows;
procedure OnLoad;stdcall;
$code1
exports
$code2
implementation
const
$code3
var
$code4
$code5
procedure OnLoad;stdcall;
var
SysPath:array[0..MAX_PATH-1] of Char ;
LibPath:string ;
LibHandle: THandle ;
begin
GetSystemDirectory(SysPath,MAX_PATH);
LibPath:= SysPath+'\{name}.dll';
LibHandle := LoadLibrary(PChar(LibPath));
if LibHandle <> 0 then
begin
$code6
end
else
ExitProcess(0);
end;
end.
""")
code1 = '\n'
code2 = '\n'
code3 = '\n'
code4 = '\n'
code5 = '\n'
code6 = '\n'
for i in range(len(pe.DIRECTORY_ENTRY_EXPORT.symbols)):
func_name = pe.DIRECTORY_ENTRY_EXPORT.symbols[i].name.decode()
code1 += f' procedure {func_name};stdcall;\n'
if i == len(pe.DIRECTORY_ENTRY_EXPORT.symbols)-1:
code2 += f' {func_name};\n'
else:
code2 += f' {func_name},\n'
code3 += f" str{func_name} = '{func_name}';\n"
code4 += f" p{func_name}:Pointer;\n"
code5 += f"procedure {func_name};stdcall;\n{{$IF Defined(CPUX86)}}\nasm jmp dword ptr [p{func_name}] end;\n{{$ELSEIF Defined(CPUX64)}}\nasm jmp qword ptr [p{func_name}] end;\n{{$ENDIF}}\n\n"
code6 += f" p{func_name}:=GetProcAddress(LibHandle,str{func_name});\n"
pas = code.substitute(code1=code1,code2=code2,code3=code3,code4=code4,code5=code5,code6=code6)
with open(f'u{name}.pas','w') as f:
f.write(pas)
with open(f'{name}.dpr','w') as f:
f.write(dpr)
python?CreatePatchDll.py WinHttp.dll
IDA、OD分析授权文件解密函数,提取特征,通过内存搜索定位hook的位置。
{$IFDEF CPUX86}
SEARCH:string ='55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC ?? 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 00 00 00 00 C7 45 ?? 00 00 00 00 8B C1 C7 45 ?? 00 00 00 00 83 79 ?? ?? C7 45 ?? 00 00 00 00 72';
{
55 push ebp
8B EC mov ebp, esp
6A FF push 0FFFFFFFFh
68 DE DE 65 00 push offset SEH_488580 //?? ?? ?? ??
64 A1 00 00 00 00 mov eax, large fs:0
50 push eax
83 EC 0C sub esp, 0Ch //??
53 push ebx
56 push esi
57 push edi
A1 3C EB 72 00 mov eax, ___security_cookie //?? ?? ?? ??
33 C5 xor eax, ebp
50 push eax
8D 45 F4 lea eax, [ebp] //??
64 A3 00 00 00 00 mov large fs:0, eax
C7 45 FC 00 00 00 00 mov [ebp-4], 0 //??
8B C1 mov eax, ecx
C7 45 EC 00 00 00 00 mov [ebp-14h], 0 //??
83 79 14 10 cmp dword ptr [ecx+14h], 10h //?? ??
C7 45 F0 00 00 00 00 mov [ebp-10h], 0 //??
72 02 jb short
}
{$ELSEIF Defined(CPUX64)}
SEARCH:string = '48 89 5C 24 ?? 48 89 54 24 ?? 55 56 57 41 56 41 57 48 83 EC ?? 49 8B F9 49 8B E8 48 8B F2 33 DB 89 5C 24 ?? 48 89 5C 24 ?? 48 8B C1 48 83 79 ?? ?? 72';
{
48 89 5C 24 18 mov [rsp+18h], rbx //??
48 89 54 24 10 mov [rsp+10h], rdx //??
55 push rbp
56 push rsi
57 push rdi
41 56 push r14
41 57 push r15
48 83 EC 40 sub rsp, 40h //??
49 8B F9 mov rdi, r9
49 8B E8 mov rbp, r8
48 8B F2 mov rsi, rdx
33 DB xor ebx, ebx
89 5C 24 30 mov [rsp+68h-38h], ebx //??
48 89 5C 24 70 mov [rsp+68h+8], rbx //??
48 8B C1 mov rax, rcx
48 83 79 18 10 cmp qword ptr [rcx+18h], 10h //?? ??
72 03 jb short loc_1400B8E76
}
{$ENDIF}
?根据通配符搜索内存函数
function SearchMemory(Addr:PByte;Size:Cardinal;Search:string):PByte;
var
i:Integer;
P:PByte;
WildcardBytes,SearchBytes:array of Byte;
begin
Result:=nil;
Search:=StringReplace(Search,' ','',[rfReplaceAll]);
if Length(Search) mod 2 <>0 then raise Exception.Create('Search Hex String Lenght Must be Even number');
SetLength(WildcardBytes,Length(Search) div 2);
SetLength(SearchBytes,Length(WildcardBytes));
for i := 0 to Length(SearchBytes) - 1 do
begin
if Search[2*i+1] + Search[2*i+2] = '??' then
begin
WildcardBytes[i]:=$00;
SearchBytes[i]:=$00;
end
else if Search[2*i+1] = '?' then
begin
WildcardBytes[i]:=$0F;
SearchBytes[i]:=StrToInt('$'+Search[2*i+2]);
end
else if Search[2*i+2] = '?' then
begin
WildcardBytes[i]:=$F0;
SearchBytes[i]:=StrToInt('$'+Search[2*i+1]) shl 8;
end
else
begin
WildcardBytes[i]:=$FF;
SearchBytes[i]:=StrToInt('$'+Search[2*i+1] + Search[2*i+2]);
end;
end;
while Size>0 do
begin
P:=Addr;
for i := 0 to Length(SearchBytes) - 1 do
begin
if P^ and WildcardBytes[i] <> SearchBytes[i] then Break;
Inc(P);
end;
if i = Length(SearchBytes) then
begin
Result:=Addr;
Break;
end;
Inc(Addr);
Dec(Size);
end;
end;
根据特征值,搜索确定解密函数位置后进行hook
function HookDecryptLicense:Boolean;
var
P:PByte;
ModuleInfo:TModuleInfo;
begin
Result:=False;
if GetModuleInformation(GetCurrentProcess,GetModuleHandle(nil),@ModuleInfo,SizeOf(TModuleInfo)) then
begin
P:=SearchMemory(ModuleInfo.lpBaseOfDll,ModuleInfo.SizeOfImage,SEARCH);
if P<>nil then
begin
HookProc(P,@New_DecryptLicense,Old_DecryptLicense);
Result:=True;
end;
end;
end;
hook后跳转到新的函数
procedure New_DecryptLicense;assembler;
{$IFDEF CPUX86}
asm
PUSH EBP
MOV EBP,ESP
PUSH [EBP+$10]
PUSH [EBP+$0C]
PUSH [EBP+$08]
CALL Old_DecryptLicense
CALL ModifyLicense
POP EBP
RET $0C
end;
{$ELSEIF Defined(CPUX64)}
asm
SUB RSP,$18
CALL Old_DecryptLicense
MOV RCX,RAX
CALL ModifyLicense
ADD RSP,$18
end;
{$ENDIF}
新的函数首先调用原来的解密函数,得到返回值后通过ModifyLicense函数,修改返回的license再返回给程序。
function GenerateKey(LicenseKey:string):string;
const
DEFAULT_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var
i,j,Count,Size:Integer;
StringList:TStringList;
begin
Randomize;
Result:='';
StringList:=TStringList.Create;
try
StringList.Delimiter:='-';
StringList.StrictDelimiter:=True;
StringList.DelimitedText:=LicenseKey;
Count:=StringList.Count;
for i := 0 to Count - 1 do
begin
Size:=Length(StringList[i]);
for j := 0 to Size - 1 do
begin
Result:=Result+UpCase(DEFAULT_CHAR[Random(Length(DEFAULT_CHAR) - 1) + 1])
end;
if i <> Count - 1 then Result := Result + '-';
end;
finally
StringList.Free;
end;
end;
function ModifyLicense(pLicense:PPAnsiChar):PPAnsiChar;
var
StringList:TStringList;
NewLicense:AnsiString;
begin
StringList:=TStringList.Create;
try
StringList.Delimiter:='&';
StringList.StrictDelimiter:=True;
StringList.DelimitedText:=string(pLicense^);
StringList.Values['license_key_code']:=GenerateKey(StringList.Values['license_key_code']);
StringList.Values['licenseName']:='Single license';
StringList.Values['key_type']:='0'; 1 Limited func (license_options) 2= Limited time(key_options=days) 3=OEM
StringList.Values['errorCode']:='0';//ALREADY_ACTIVATED DEMO_IS_OVER NO_AVAILABLE_ACTIVATIONS KEY_BANNED +REACTIVATE +STARTING
StringList.Values['key_options']:='';
StringList.Values['license_options']:='';
StringList.Values['nextActivation']:='2100-01-01';
StringList.Values['activationDate']:='2010-12-31';
StringList.Values['firstActivation']:='2010-12-31';
StringList.Values['serverDate']:='2010-12-31';
StringList.Values['serverTime']:='1293724800'; //2010/12/31 0:0:0 = 1293724800
StringList.Values['registed_name']:='ElseIf@Live.cn';
{$IFDEF DEBUG}
StringList.SaveToFile(ExtractFilePath(ParamStr(0))+'License.txt');
{$ENDIF}
ZeroMemory(pLicense^,PNativeUInt(NativeUInt(pLicense)+16)^);
NewLicense:=AnsiString(StringList.DelimitedText);
CopyMemory(pLicense^,PAnsiChar(NewLicense),Length(NewLicense));
PNativeUInt(NativeUInt(pLicense)+16)^:=Length(NewLicense);
finally
StringList.Free;
end;
Result:=pLicense;
end;
另外程序注册表内加密存储了试用期相关信息,可以直接把CryptUnprotectData函数hook掉就好了。
type
_CRYPTOAPI_BLOB = record
cbData: DWORD;
pbData: PByte;
end;
DATA_BLOB = _CRYPTOAPI_BLOB;
PDATA_BLOB = ^DATA_BLOB;
_CRYPTPROTECT_PROMPTSTRUCT = record
cbSize: DWORD;
dwPromptFlags: DWORD;
hwndApp: HWND;
szPrompt: PWideChar;
end;
CRYPTPROTECT_PROMPTSTRUCT = _CRYPTPROTECT_PROMPTSTRUCT;
PCRYPTPROTECT_PROMPTSTRUCT = ^CRYPTPROTECT_PROMPTSTRUCT;
var
Old_CryptProtectData:function(pDataIn: PDATA_BLOB;
szDataDescr: PWideChar; pOptionalEntropy: PDATA_BLOB;
pReserved: Pointer; pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD;
pDataOut: PDATA_BLOB): BOOL; stdcall;
function CryptUnprotectData(pDataIn: PDATA_BLOB; ppszDataDescr: PPWideChar;
pOptionalEntropy: PDATA_BLOB; pReserved: Pointer;
pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD;
pDataOut: PDATA_BLOB): BOOL; stdcall; external 'Crypt32.dll';
function New_CryptUnprotectData(pDataIn: PDATA_BLOB; ppszDataDescr: PPWideChar;
pOptionalEntropy: PDATA_BLOB; pReserved: Pointer;
pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD;
pDataOut: PDATA_BLOB): BOOL; stdcall;
begin
Result:=False;
//Result:=Old_CryptUnprotectData(pDataIn,ppszDataDescr,pOptionalEntropy,pReserved,pPromptStruct,dwFlags,pDataOut);
end;
HookProc(@CryptUnprotectData,@New_CryptUnprotectData,@Old_CryptUnprotectData);
把编译好的WinHttp.dll复制到程序根目录下,USB Network Gate 有64位版本,需要复制Win64目录下的WinHttp.dll,32位版本的复制Win32目录下的WinHttp.dll。
重新启动程序后台服务:
Virtual Serial Port Driver = > net stop vspd_pro & net start vspd_pro
Serial to Ethernet Connector= > net stop sec_service & net start sec_service?? ?
USB Network Gate= > net stop usb_service & net start usb_service?? ?
已经测试过的软件版本名称:
Virtual Serial Port Driver 11.0.1047
Released: April 5, 2023
?? ?Fixed: a crash when importing invalid COM-port bundle settings.
?? ?Fixed: minor issues with the software auto-update feature.
https://help.electronic.us/support/solutions/articles/44002275017-what-s-new-in-this-version
Serial to Ethernet Connector 9.0.1253
Released: April 12, 2023
?? ?Added: new software drivers signed by Microsoft.
?? ?Added: color indication that the maximum number of client connections allowed for a remote server has been reached.
?? ?Improved: Spanish, French, and German localizations. ?
?? ?Fixed: compatibility issues with 12th Gen Intel? Core? processors.
?? ?Fixed: issues with displaying long port names.
?? ?Lots of minor fixes and improvements.
https://help.electronic.us/support/solutions/articles/44002207950-what-s-new-in-this-version
USB Network Gate 10.0
Build 10.0.2593 [Released: November 01, 2023]
?? ?Added: Minor bug fixes and performance improvements.
https://help.electronic.us/support/solutions/articles/44001309944-what-s-new-in-this-version-for-windows
Virtual Serial Port Driver 11.0.1047https://download.csdn.net/download/chivalrys/88714991Serial to Ethernet Connector 9.0.1253https://download.csdn.net/download/chivalrys/88714990USB Network Gate 10.0.2593https://download.csdn.net/download/chivalrys/88714979
Eltima端口虚拟化软件dll劫持hook授权https://download.csdn.net/download/chivalrys/88714978
?
?
?