Donnerstag, 22. Dezember 2016

Systeminformationen sammeln um einen eindeutigen Daumenabdruck zu generieren / Collecting Systeminformation to generate a unique fingerprint

Wenn für unsere Applikation der Bedarf besteht, dass sie nicht unkontrolliert auf ein anderes System kopierbar sein soll, dann können wir mit Hilfe der WMI (Windows Management Instrumentation) diverse Informationen aus Windows herauskitzeln.
Im Folgenden werden wir eine ID basierend auf CPU, Netzwerkadaptern und Festplatten zusammenstellen. Bei den Festplatten erfolgt eine Beschränkung auf fest installierte Datenträger. D.h. keine RAM Disks, Netzlaufwerke, CD/DVD-ROMs, SD-Karten.

Der Mustercode geht von einer verbauten CPU, mehreren Netzwerkadaptern sowie mehreren Festplatten bzw. Partitionen.

Aus dem generierten String wird im Anschluß eine Checksumme gebildet.

Um gezielt auf den Wechsel/Wegfall/Einbau einzelner Komponenten reagieren zu können kann der im Beispiel konkatenierte String auch mit Hilfe von festen Blocklängen untergliedert werden so dass bei Änderung eines einzelnen Segmentes/Blocks nicht sofort Alarm geschlagen wird.

Im Echteinsatz sollten die mit '?' beginnenden Infoausgaben natürlich auskommentiert/entfernt werden :)

CLEAR 

LOCAL lcSystemID as String
lcSystemID = []

* // retrieve CPU ID
LOCAL    lcComputerName as String, loWMI as Object, ;
        lowmiWin32Objects as Object, lowmiWin32Object as Object
lcComputerName = GETWORDNUM( SYS( 0 ) , 1 )
loWMI = GETOBJECT( [WinMgmts://] + lcComputerName )
lowmiWin32Objects = loWMI.InstancesOf( [Win32_Processor] )
FOR EACH lowmiWin32Object IN lowmiWin32Objects
    WITH lowmiWin32Object
        ? [ProcessorId: ] + TRANSFORM( .ProcessorId )
        lcSystemID = TRANSFORM( .ProcessorId )
    ENDWITH
ENDFOR
?
RELEASE lcComputerName, loWMI, lowmiWin32Objects, lowmiWin32Object

* // retrieve the MAC Address(es)
* // usually more than one (BT,WLAN,LAN,VNA)
LOCAL    lcComputerName as String, loWMIService as Object, ;
        loItems as Object, loItem as Object, lcMACAddress as String
lcComputerName = [.]
loWMIService = GETOBJECT( [winmgmts:\\] + lcComputerName + [\root\cimv2] )
loItems = loWMIService.ExecQuery( [Select * from Win32_NetworkAdapter] , , 48 )
FOR EACH loItem IN loItems
    lcMACAddress = loItem.MACAddress
    IF !ISNULL( lcMACAddress )
        ? [MAC Address: ] + loItem.MACAddress
        lcSystemID = lcSystemID + CHRTRAN( loItem.MACAddress , [:] , [] )
    ENDIF
ENDFOR
?
RELEASE lcComputerName, loWMIService, loItems, loItem, lcMACAdress

* // retrieve Volume Serial Number(s)
* // maybe more than one, even HQ NBs often have SSD and HD
LOCAL    lcComputerName as String, loWMIService as Object, ;
        loItems as Object, loItem as Object, lcVolumeSerial as String
lcComputerName = [.]
loWMIService = GETOBJECT( [winmgmts:\\] + lcComputerName + [\root\cimv2] )
loItems = loWMIService.ExecQuery( [Select * from Win32_LogicalDisk] )

FOR EACH loItem IN loItems
    lcVolumeSerial = loItem.VolumeSerialNumber
    IF !ISNULL( lcVolumeSerial ) AND CheckDriveType4( loItem.DeviceID ) = .T.
        ? [DeviceID / VSN: ] + loItem.DeviceID
        ?? [ / ] + loItem.VolumeSerialNumber
        lcSystemID = lcSystemID + loItem.VolumeSerialNumber
    ENDIF
ENDFOR
?

? [SystemID Pure:] + PADL( TRANSFORM( LEN( lcSystemID ) ) , 4 , [ ] ) + [ Chars - ] 
?? lcSystemID
? [Prüfziffer:] + SYS( 2007 , lcSystemID , 1 , 1 )
?
lcSystemID = STRCONV( lcSystemID,13)
? [SystemID MIME:] + PADL( TRANSFORM( LEN( lcSystemID ) ) , 4 , [ ] ) + [ Chars - ] 
?? lcSystemID 
? [Prüfziffer:] + SYS( 2007 , lcSystemID , 1 , 1 )

RELEASE lcComputerName, loWMIService, loItems, loItem, lcVolumeSerial, lcSystemID

FUNCTION CheckDriveType4 as Boolean
LPARAMETERS vName as String

    * // Returnvalues of GetDriveType:    
    * // 0 = DRIVE_UNKNOWN                
    * // 1 = DRIVE_NO_ROOT_DIR            
    * // 2 = DRIVE_REMOVABLE            
    * // 3 = DRIVE_FIXED                
    * // 4 = DRIVE_REMOTE                
    * // 5 = DRIVE_CDROM                
    * // 6 = DRIVE_RAMDISK                
    vName = ADDBS( EVL( vName , [C:] ) )
    
    LOCAL llReturn as Boolean
    llReturn = .F.
    
    DECLARE INTEGER GetDriveType IN kernel32 String lpszRootPathName
    IF GetDriveType( vName ) = 3
        llReturn = .T.
    ENDIF 
    
    RETURN llReturn

ENDFUNC 

SystemID und Checksumme sollten an unterschiedlichen Stellen hinterlegt sein.

Dieser Ansatz ist sicherlich nicht 'bulletproof', aber der Aufwand hält sich in Grenzen und ist somit kostengünstig umzusetzen.