Eltima 端口虚拟化软件授权分析

目录

Eltima 端口虚拟化软件

授权文件加密方式

授权文件格式

key_type 授权类型

errorCode 授权状态

hid 硬件编码

授权许可

1、替换公钥

2、dll劫持hook

测试验证

成品


Eltima 端口虚拟化软件

  1. USB Network Gate
    通过局域网和互联网共享和接入USB端口
  2. Serial to Ethernet Connector
    通过以太网远程连接串口设备
  3. Virtual Serial Port Driver
    创建使用虚拟零调制解调线连接的虚拟串口

授权文件加密方式

      授权文件保存在{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:

  • key_type 授权类型

0 = Single License 单机版许可授权

1 = Limited func (license_options) 表示根据功能授权,license_options的值表示功能的限制数量

2 = Limited time(key_options=days) 表示根据时间授权,key_options的值表示授权天数。

3 = OEM

  • errorCode 授权状态

0 = ALREADY_ACTIVATED 

DEMO_IS_OVER

NO_AVAILABLE_ACTIVATIONS

KEY_BANNED

+REACTIVATE

+STARTING

  • nextActivation 下次联网验证key的时间
  • hid 硬件编码

硬件编码由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文件的函数,修改返回的内容,既可实现本地授权许可。

1、替换公钥

//替换公钥
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.

2、dll劫持hook

通过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的位置。

{$IF Defined(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 
}
{$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 Length 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;
{$IF Defined(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.1047icon-default.png?t=N7T8https://download.csdn.net/download/chivalrys/88714991Serial to Ethernet Connector 9.0.1253icon-default.png?t=N7T8https://download.csdn.net/download/chivalrys/88714990USB Network Gate 10.0.2593icon-default.png?t=N7T8https://download.csdn.net/download/chivalrys/88714979

Eltima端口虚拟化软件dll劫持hook授权icon-default.png?t=N7T8https://download.csdn.net/download/chivalrys/88714978

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值