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;implementationtypeTRegisters = recordEAX: DWORD;EBX: DWORD;ECX: DWORD;EDX: DWORD;end;
procedure GetCPUID(Param: Cardinal; var Registers: TRegisters);
asmPUSH    EBX                         { save affected registers }PUSH    EDIMOV     EDI, RegistersXOR     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;
varregs1,regs2:TRegisters;i:Cardinal;
beginGetCPUID(0,regs1);Result:= regs1.EBX xor regs1.ECX xor regs1.EDX;if regs1.EAX>=1 thenbeginfor i := 1 to 13 dobeginGetCPUID(i,regs2);case i of2,3,4,5,8,9,12,13:beginResult:=Result xor regs2.EAX xor regs2.EBX xor regs2.ECX xor regs2.EDX;end;11:beginResult:=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 dobeginGetCPUID(i,regs2);case i of$80000001,$80000002,$80000003,$80000004,$80000006,$80000008,$8000000A,$8000001A:beginResult:=Result xor regs2.EAX xor regs2.EBX xor regs2.ECX xor regs2.EDX;end;end;end;
end;function GetHid11():Integer;
varSystemInfo:TSystemInfo;
beginGetSystemInfo(SystemInfo);if SystemInfo.wProcessorArchitecture>=0 thenResult:=2*SystemInfo.wProcessorArchitectureelseResult:=2*SystemInfo.wProcessorArchitecture+1;Result:=SystemInfo.dwNumberOfProcessors xor Result;if Result>=0 thenResult := 2 * ResultelseResult := 2 * Result+1;Result := SystemInfo.dwProcessorType xor Result;if Result>=0 thenResult := 2 * ResultelseResult := 2 * Result+1;Result := SystemInfo.wProcessorLevel xor Result;if Result>=0 thenResult := 2 * ResultelseResult := 2 * Result+1;Result := SystemInfo.wProcessorRevision xor Result;if Result>=0 thenResult := 2 * ResultelseResult := 2 * Result+1;
end;function GetHid2:Integer;
typeTDiskGeometry=recordCylinders:LARGE_INTEGER;MediaType:DWORD;TracksPerCylinder:DWORD;SectorsPerTrack:DWORD;BytesPerSector:DWORD;end;
varhDevice : THandle;DiskGeometry:TDiskGeometry;cbBytesReturned:DWORD;Value1,Value2:LARGE_INTEGER;
constIOCTL_DISK_GET_DRIVE_GEOMETRY = $00070000;
beginResult:=0;hDevice := CreateFile( '\\.\PhysicalDrive0',GENERIC_READ,FILE_SHARE_READ or FILE_SHARE_WRITE,nil, OPEN_EXISTING, 0, 0 );if hDevice<>INVALID_HANDLE_VALUE thenbeginif DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY,nil,0, @DiskGeometry,SizeOf(TDiskGeometry), cbBytesReturned, nil ) thenbeginValue1.QuadPart:=DiskGeometry.BytesPerSector * DiskGeometry.SectorsPerTrack*DiskGeometry.TracksPerCylinder*DiskGeometry.Cylinders.QuadPart;if (Value1.QuadPart and $80000000)=0 thenValue2.QuadPart:=2*Value1.QuadPartelseValue2.QuadPart:=(2*Value1.QuadPart) or 1;Result:=Value2.LowPart xor Value1.HighPart;if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;end;CloseHandle(hDevice);end;
end;function GetHid3:Integer;
typeTStorgePropertyQuery=recordPropertyId:DWORD;QueryType:DWORD;AdditionalParameters:Byte;end;TStorgeDeviceDescriptor=recordVersion: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=recordbVersion:Byte;bRevision:Byte;bReserved:Byte;bIDEDeviceMap:Byte;fCapabilities:DWORD;dwReserved:array[0..3] of DWORD;end;_IDEREGS=recordbFeaturesReg:Byte;bSectorCountReg:Byte;bSectorNumberReg:Byte;bCylLowReg:Byte;bCylHighReg:Byte;bDriveHeadReg:Byte;bCommandReg:Byte;bReserved:Byte;end;TSendCmdInParams=recordcBufferSize:DWORD;irDriveRegs:_IDEREGS;bDriveNumber:Byte;bReserved:array[0..2] of Byte;dwReserved:array[0..3] of DWORD;bBuffer:Byte;end;_DRIVERSTATUS=recordbDriverError:Byte;bIDEError:Byte;bReserved:array[0..1] of Byte;dwReserved:array[0..1] of DWORD;end;TSendCmdOutParams=recordcBufferSize:DWORD;DriverStatus:_DRIVERSTATUS;bBuffer:Byte;end;PSendCmdOutParams=^TSendCmdOutParams;
varhDevice : THandle;StorgePropertyQuery:TStorgePropertyQuery;StorgeDeviceDescriptor:PStorgeDeviceDescriptor;GetVersionInParams:TGetVersionInParams;SendCmdInParams:TSendCmdInParams;SendCmdOutParams:PSendCmdOutParams;cbBytesReturned,i:DWORD;P:PChar;W:PWORD;Str:string;
constIOCTL_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.
beginResult:=0;hDevice := CreateFile( '\\.\PhysicalDrive0',GENERIC_READ,FILE_SHARE_READ or FILE_SHARE_WRITE,nil, OPEN_EXISTING, 0, 0 );if hDevice<>INVALID_HANDLE_VALUE thenbeginZeroMemory(@StorgePropertyQuery,SizeOf(TStorgePropertyQuery));ZeroMemory(@GetVersionInParams,SizeOf(TGetVersionInParams));StorgeDeviceDescriptor:=AllocMem(10000);if DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,@StorgePropertyQuery,SizeOf(TStorgePropertyQuery),StorgeDeviceDescriptor,10000, cbBytesReturned, nil ) thenbeginStr:='';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) dobeginResult:=Result xor Ord(Str[i]);if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;end;endelse if DeviceIoControl(hDevice, SMART_GET_VERSION,nil,0,@GetVersionInParams,SizeOf(TGetVersionInParams), cbBytesReturned, nil) thenbeginif GetVersionInParams.bIDEDeviceMap>0 thenbeginSendCmdOutParams:=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 thenSendCmdInParams.irDriveRegs.bCommandReg:=ATAPI_ID_CMDelseSendCmdInParams.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 ) thenbeginW:=@SendCmdOutParams^.bBuffer;Inc(W,10);Result:=W^;if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;Inc(W,1);Result:=Result xor W^;if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;Inc(W,1);Result:=Result xor W^;if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;Inc(W,1);Result:=Result xor W^;if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;Inc(W,1);Result:=Result xor W^;if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;Inc(W,1);Result:=Result xor W^;if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;Inc(W,1);Result:=Result xor W^;if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;Inc(W,1);Result:=Result xor W^;if Result>=0 thenResult:=2*ResultelseResult:=(2*Result) or 1;Inc(W,1);Result:=Result xor W^;if Result>=0 thenResult:=2*ResultelseResult:=(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;
varReg:TRegistry;Size,i:Integer;B:TBytes;
beginResult:=0;Reg:=TRegistry.Create;tryReg.RootKey:=HKEY_LOCAL_MACHINE;if Reg.OpenKeyReadOnly('HARDWARE\DESCRIPTION\System') thenbeginSize:=Reg.GetDataSize('SystemBiosVersion');if Size>0 thenbeginSetLength(B,Size);if Reg.ReadBinaryData('SystemBiosVersion',B[0],Size)<>0 thenbeginfor i := 0 to Size - 1 dobeginResult:=Result xor B[i];if Result>=0 thenResult:=2*ResultelseResult:=2*Result or 1;//UNICODE STRINGResult:=Result xor $00;if Result>=0 thenResult:=2*ResultelseResult:=2*Result or 1;end;end;end;end;finallyFreeAndNil(Reg);end;
end;function GetHid():string;
varregs:TRegisters;hid1,hid2,hid3,hid4,hid5:DWORD;
beginGetCPUID(0,regs);if regs.EAX>0 thenhid1:=GetHid1elsehid1:=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);
varStream:TMemoryStream;SubStr,Str:string;P:PByte;Offset:Integer;
beginSubStr:='-----BEGIN PUBLIC KEY-----';Stream:=TMemoryStream.Create;tryStream.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);finallyStream.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;typeRSA_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;
constRSA_PKCS1_PADDING_SIZE  =  11;function RSA_padding_check_PKCS1_type_1(Src:string):string;
varp: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 dobeginif p^<>$FF thenbeginif p^=0 thenbeginInc(p);Break;endelseraise 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;
varp:PByte;len:Integer;
beginif 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;
varp:PByte;i,len:Integer;
beginp:=PByte(@Src[1]);if p^ = $6B thenbeginInc(p);for i := 0 to Length(Src) - 3 dobeginif p^ = $BA thenbegininc(p);Break;endelse if p^<>$BB then raise Exception.Create('RSA_R_INVALID_PADDING');Inc(p);end;len:=Length(Src) - (Cardinal(p)-Cardinal(@Src[1])) - 1;endelse if p^ = $6A thenbeginInc(p);len:=Length(Src) - 2;endelseraise 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;
varp:PByte;len:Integer;
beginlen:= 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 thenbeginp^:=$6A;Inc(p);endelsebeginp^:=$6B;Inc(p);if len > 1 thenbeginFillMemory(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;
vartempstr:string;c,e,n,temp:TFGInt;i,j,num:Integer;
beginBase256StringToFGInt(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 dobegintempstr := copy(Src, 1, num);delete(Src, 1, num);Base256StringToFGInt(tempstr,c);FGIntModExp(c, e, n, temp);FGIntToBase256String(temp, tempstr);case Padding ofRSA_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;
vari, j ,num,padding_size:Integer;tempstr:string;c,d,n,temp:TFGInt;
beginBase256StringToFGInt(StrD,d);Base256StringToFGInt(StrN,n);Result:='';FGIntToBase2String(n,tempstr);num:=Length(tempstr) div 8;padding_size:=0;case Padding ofRSA_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 dobegintempstr := copy(Src, 1, num - padding_size);delete(Src, 1, num - padding_size);case Padding ofRSA_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.pyimport 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 = 0while True:_,value,_ = winreg.EnumValue(key, j)if value.upper() == dllname.upper():return Truej += 1except WindowsError as e:passreturn Falseif __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 = Nonefor path in dll_paths:if os.path.exists(os.path.join(path,name)):dll_path = os.path.join(path,name)breakif 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};
usesSysUtils,Classes,Windows,u{name} in 'u{name}.pas';    
{{$R *.res}}
procedure DLLHandler(Reason: Integer);
begincase Reason ofDLL_PROCESS_ATTACH:beginOnLoad();end;DLL_PROCESS_DETACH:beginend;DLL_THREAD_ATTACH:;DLL_THREAD_DETACH:;end;
end;
beginDLLProc := @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;
varSysPath:array[0..MAX_PATH-1] of Char ;LibPath:string ;LibHandle: THandle ;
beginGetSystemDirectory(SysPath,MAX_PATH);LibPath:= SysPath+'\{name}.dll';LibHandle := LoadLibrary(PChar(LibPath));if LibHandle <> 0 thenbegin
$code6endelseExitProcess(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;
vari:Integer;P:PByte;WildcardBytes,SearchBytes:array of Byte;
beginResult:=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 dobeginif Search[2*i+1] + Search[2*i+2]  = '??' thenbeginWildcardBytes[i]:=$00;SearchBytes[i]:=$00;endelse if Search[2*i+1] = '?' thenbeginWildcardBytes[i]:=$0F;SearchBytes[i]:=StrToInt('$'+Search[2*i+2]);endelse if Search[2*i+2] = '?' thenbeginWildcardBytes[i]:=$F0;SearchBytes[i]:=StrToInt('$'+Search[2*i+1]) shl 8;endelsebeginWildcardBytes[i]:=$FF;SearchBytes[i]:=StrToInt('$'+Search[2*i+1] + Search[2*i+2]);end;end;while Size>0 dobeginP:=Addr;for i := 0 to Length(SearchBytes) - 1 dobeginif P^ and WildcardBytes[i] <> SearchBytes[i] then Break;Inc(P);end;if i = Length(SearchBytes) thenbeginResult:=Addr;Break;end;Inc(Addr);Dec(Size);end;
end;

根据特征值,搜索确定解密函数位置后进行hook

function HookDecryptLicense:Boolean;
varP:PByte;ModuleInfo:TModuleInfo;
beginResult:=False;if GetModuleInformation(GetCurrentProcess,GetModuleHandle(nil),@ModuleInfo,SizeOf(TModuleInfo)) thenbeginP:=SearchMemory(ModuleInfo.lpBaseOfDll,ModuleInfo.SizeOfImage,SEARCH);if P<>nil thenbeginHookProc(P,@New_DecryptLicense,Old_DecryptLicense);Result:=True;end;end;
end;

hook后跳转到新的函数

procedure New_DecryptLicense;assembler;
{$IF Defined(CPUX86)}
asmPUSH EBPMOV EBP,ESPPUSH [EBP+$10]PUSH [EBP+$0C]PUSH [EBP+$08]CALL Old_DecryptLicenseCALL ModifyLicensePOP EBPRET $0C
end;
{$ELSEIF Defined(CPUX64)}
asmSUB RSP,$18CALL Old_DecryptLicenseMOV RCX,RAXCALL ModifyLicenseADD RSP,$18
end;
{$ENDIF}

新的函数首先调用原来的解密函数,得到返回值后通过ModifyLicense函数,修改返回的license再返回给程序。

function GenerateKey(LicenseKey:string):string;
constDEFAULT_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
vari,j,Count,Size:Integer;StringList:TStringList;
beginRandomize;Result:='';StringList:=TStringList.Create;tryStringList.Delimiter:='-';StringList.StrictDelimiter:=True;StringList.DelimitedText:=LicenseKey;Count:=StringList.Count;for i := 0 to Count - 1 dobeginSize:=Length(StringList[i]);for j := 0 to Size - 1 dobeginResult:=Result+UpCase(DEFAULT_CHAR[Random(Length(DEFAULT_CHAR) - 1) + 1])end;if i <> Count - 1 then Result := Result + '-';end;finallyStringList.Free;end;
end;function ModifyLicense(pLicense:PPAnsiChar):PPAnsiChar;
varStringList:TStringList;NewLicense:AnsiString;
beginStringList:=TStringList.Create;tryStringList.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=OEMStringList.Values['errorCode']:='0';//ALREADY_ACTIVATED DEMO_IS_OVER NO_AVAILABLE_ACTIVATIONS KEY_BANNED +REACTIVATE +STARTINGStringList.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 = 1293724800StringList.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);finallyStringList.Free;end;Result:=pLicense;
end;

另外程序注册表内加密存储了试用期相关信息,可以直接把CryptUnprotectData函数hook掉就好了。

type_CRYPTOAPI_BLOB = recordcbData: DWORD;pbData: PByte;end;DATA_BLOB = _CRYPTOAPI_BLOB;PDATA_BLOB = ^DATA_BLOB;_CRYPTPROTECT_PROMPTSTRUCT = recordcbSize: DWORD;dwPromptFlags: DWORD;hwndApp: HWND;szPrompt: PWideChar;end;CRYPTPROTECT_PROMPTSTRUCT = _CRYPTPROTECT_PROMPTSTRUCT;PCRYPTPROTECT_PROMPTSTRUCT = ^CRYPTPROTECT_PROMPTSTRUCT;
varOld_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;
beginResult:=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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/605453.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

80/20法则-扫盲和复习篇

80/20法则-扫盲和复习篇 一、80/20法则二、对于目标三、时间管理应用四、“二八定律”基本内容总结 一、80/20法则 “80/20法则”是20世纪初意大利统计学家、经济学家维尔弗雷多帕累托提出的&#xff0c;他指出&#xff1a;在任何特定群体中&#xff0c;重要的因子通常只占少数…

Vue脚手架及组件开发

组件插槽: 路由数据传递&#xff1a;

静态网页设计——科学家网(HTML+CSS+JavaScript)(dw、sublime Text、webstorm、HBuilder X)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a;https://www.bilibili.com/video/BV1wg4y1Q7qm/?vd_source5f425e0074a7f92921f53ab87712357b 源码&#xff1a;https://space.bilibili.com…

在群晖NAS上搭建私有部署笔记软件——Blossom

一、Blossom 简介 Blossom 是一个需要私有部署的笔记软件&#xff0c;虽然本身定位是一个云端软件&#xff0c;但你仍然可以在本地部署&#xff0c;数据和图片都将保存在本地&#xff0c;不依赖任何的图床或者对象存储。 Blossom | Blossom (wangyunf.com)https://www.wangyun…

01第一个Mybatis程序+引入Junit+引入日志文件logback

Mybatis MyBatis本质上就是对JDBC的封装&#xff0c;通过MyBatis完成CRUD。而对于JDBC&#xff0c;SQL语句写死在Java程序中&#xff0c;不灵活。改SQL的话就要改Java代码。违背开闭原则OCP。对于事务机制&#xff0c;MyBatis支持 或managed模式&#xff0c;JDBC模式中MyBatis…

基于SSM图书管理系统【源码】【最详细运行文档】

SSM图书管理系统【源码】【最详细运行文档】 系统简介系统涉及系统运行系统演示源码获取 系统简介 以往的图书馆管理事务处理主要使用的是传统的人工管理方式&#xff0c;这种管理方式存在着管理效率低、操作流程繁琐、保密性差等缺点&#xff0c;长期的人工管理模式会产生大量…

python 写自动点击爬取数据

今天来点不一样的&#xff01;哥们 提示&#xff1a; 这里只是用于自己学习的 &#xff0c;请勿用违法地方 效果图 会进行点击下一页 进行抓取 需要其他操作也可以自己写 文章目录 今天来点不一样的&#xff01;哥们前言一、上代码&#xff1f;总结 前言 爬虫是指通过编程自动…

Spark基础原理

Spark On Yarn Spark On Yarn的本质 Spark专注于分布式计算,Yarn专注于资源管理,Spark将资源管理的工作交给了Yarn来负责 Spark On Yarn两种部署方式 Spark中有两种部署方式&#xff0c;Client和Cluster方式&#xff0c;默认是Client方式。这两种方式的本质区别&#xff0c…

[C#]winform利用seetaface6实现C#人脸检测活体检测口罩检测年龄预测性别判断眼睛状态检测

【官方框架地址】 https://github.com/ViewFaceCore/ViewFaceCore 【算法介绍】 SeetaFace6是由中国科技公司自主研发的一款人脸识别技术&#xff0c;它基于深度学习算法&#xff0c;能够快速、准确地识别出人脸&#xff0c;并且支持多种应用场景&#xff0c;如门禁系统、移动…

智慧校园电子班牌管理系统源码 Java Android原生

智慧校园电子班牌系统源码是一种智能化的教育管理解决方案&#xff0c;它可以在学校内实现信息共享、教学管理、学生管理、家校互通等各个方面的协调与配合&#xff0c;帮助教师、学生和家长更加高效地开展教学活动、管理学生、协同合作&#xff0c;从而推动学校教育水平的提高…

Redis内存策略:「过期Key删除策略」+ 「内存淘汰策略」

Redis之所以性能强&#xff0c;最主要的原因就是基于内存存储&#xff0c;然而单节点的Redis其内存大小不宜过大&#xff0c;否则会影响持久化或主从同步的性能。 Redis内存满了&#xff0c;会发生什么&#xff1f; 在Redis的运行内存达到了某个阈值&#xff0c;就会触发内存…

图神经网络入门

图神经网络&#xff08;GNN&#xff09;是一组在图领域工作的深度学习方法。 这些网络最近已应用于多个领域&#xff0c;包括&#xff1a; 组合优化、推荐系统、计算机视觉—仅举几例。 这些网络还可用于对大型系统进行建模&#xff0c;例如社交网络、蛋白质-蛋白质相互作用网络…

软件测试方法分类-按测试对象划分

接上一篇,下来我们再细讲,第四个维度的分类, 软件测试方法分类-按测试对象划分 本章节重点介绍非功能测试的相关知识,因为功能测试的基本在之前的分类都是有涉及的。 一、非功能测试 1,性能测试(Performance Testing) 检查系统是否满足需求规格说明书中规定的性能。 …

pytorch07:损失函数与优化器

目录 一、损失函数是什么二、常见的损失函数2.1 nn.CrossEntropyLoss交叉熵损失函数2.1.1 交叉熵的概念2.2.2 交叉熵代码实现2.2.3 加权重损失 2.2 nn.NLLLoss2.2.1 代码实现 2.3 nn.BCELoss2.3.1 代码实现 2.4 nn.BCEWithLogitsLoss2.4.1 代码实现 三、优化器Optimizer3.1 什么…

Redis入门-五种数据类型

大家好我是苏麟 , 今天来说说Reids五种基本数据类型 . 五种常用数据类型介绍 大纲 字符串操作命令哈希操作命令列表操作命令集合操作命令有序集合操作命令通用命令 Redis存储的是key-value结构的数据&#xff0c;其中key是字符串类型&#xff0c;value有5种常用的数据类型&…

【图神经网络导论】之第9章模型变体(刘知远)

第9章不同图类型的模型变体 文章目录 第9章不同图类型的模型变体9.1 有向图9.2 异构图9.3 带有边信息的图9.4 动态图9.5 多维图 第4章介绍的基础GNN模型"被用于处理无向图&#xff0c;这些图包含具有标签的节点&#xff0c;是最简单的图。然而&#xff0c;在现实世界中还有…

Linux基础知识点(八-POSXI互斥锁)

目录 一、互斥锁基本概念 1.1 互斥相关背景概念 1.2 互斥锁(mutex) 1.3 死锁 二、初始化互斥锁 2.1 静态初始化 2.2 动态初始化 三、获取与释放互斥锁 四、销毁互斥锁 一、互斥锁基本概念 1.1 互斥相关背景概念 临界资源&#xff1a;多线程执行流共享的资源就叫做…

06.函数和模块的使用

函数和模块的使用 在讲解本章节的内容之前&#xff0c;我们先来研究一道数学题&#xff0c;请说出下面的方程有多少组正整数解。 事实上&#xff0c;上面的问题等同于将8个苹果分成四组每组至少一个苹果有多少种方案。想到这一点问题的答案就呼之欲出了。 可以用Python的程序来…

基于ssm的大湾区旅游推荐系统的设计与实现+vue论文

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统大湾区旅游景点信息管理难度大&#xff0c;容错率低&…

美创科技葛宏彬:夯实安全基础,对医疗数据风险“逐个击破”

导读 解决医疗机构“临床业务数据合规流动”与“重要数据安全防护”两大难题。 2023年11月11日&#xff0c;在2023年南湖HIT论坛上&#xff0c;HIT专家网联合杭州美创科技股份有限公司&#xff08;以下简称美创科技&#xff09;发布《医疗数据安全风险分析及防范实践》白皮书…