目录
Eltima 端口虚拟化软件
授权文件加密方式
授权文件格式
key_type 授权类型
errorCode 授权状态
hid 硬件编码
授权许可
1、替换公钥
2、dll劫持hook
测试验证
成品
Eltima 端口虚拟化软件
- USB Network Gate
通过局域网和互联网共享和接入USB端口 - Serial to Ethernet Connector
通过以太网远程连接串口设备 - 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.1047https://download.csdn.net/download/chivalrys/88714991Serial to Ethernet Connector 9.0.1253https://download.csdn.net/download/chivalrys/88714990USB Network Gate 10.0.2593https://download.csdn.net/download/chivalrys/88714979
Eltima端口虚拟化软件dll劫持hook授权https://download.csdn.net/download/chivalrys/88714978