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

发布时间:2024年01月08日

目录

Eltima 端口虚拟化软件

授权文件加密方式

授权文件格式

key_type 授权类型

errorCode 授权状态

hid 硬件编码

授权许可

1、替换公钥

2、dll劫持hook

测试验证

成品


Eltima 端口虚拟化软件

  1. USB Network Gate
    通过局域网和互联网共享和接入USB端口
    最新版本:https://cdn.electronic.us/products/usb-over-ethernet/windows/update/settings.xml
  2. Serial to Ethernet Connector
    通过以太网远程连接串口设备
    最新版本:https://cdn.electronic.us/products/sec/windows/update/settings.xml
  3. Virtual Serial Port Driver
    创建使用虚拟零调制解调线连接的虚拟串口
    最新版本:https://cdn.electronic.us/products/vspd/windows/update/settings.xml

授权文件加密方式

??????授权文件保存在{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的位置。

{$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.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

?

?

?

文章来源:https://blog.csdn.net/chivalrys/article/details/135445575
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。