Geschrieben von: Claudius Ceteras
Kategorie: ASP Tricks
This printed page brought to you by AlphaSierraPapa
Textdateien im allgemeinen und ASP Skripte im speziellen lassen sich nicht wie konventionelle Software sichern. Dieser Artikel soll einen Weg aufzeigen, wie man, wenn auch nicht das eigentliche Kopieren verhindern, so doch Lizenzverstöße nachvollziehen kann.
Die Beispiels-Anwendung, die für diesen Artikel implementiert wurde, nutzt ActiveFile von Infomentum um ZIP-Archive zu erzeugen, sowie für den Datei-Download. Die Installation sowie grundsätzliche Verwendung dieser Komponente ist aber nicht primäres Thema dieses Artikels und kann im Artikel Zippen und entzippen von Dateien nachgelesen werden.
Eine beliebte Möglichkeit ASP Skripts zu schützen ist es, diese in COM Komponenten zu kapseln, wobei man dann die Möglichkeit hat Funktionalitäten z.B. erst durch Eingabe eines Lizenzcodes freizuschalten. Diese Lizenzcodes sind meist aus dem Namen des Käufers berechnet und nur damit verwendbar, was effektiv das Weitergeben der Software inklusive Freischaltcode verhindert - jedenfalls solange der Berechnungs-Algorithmus nicht geknackt wird. Solche Möglichkeiten hat man mit Skripten nicht, denn jeglicher Schutzmechanismus wäre ohne Probleme aus dem unkompilierten Code zu entfernen.
Warum dann überhaupt ASP-Funktionalität in Form von Skript anbieten und nicht immer als Komponente? Ein möglicher Grund ist z.B. die größere Käufergruppe, die man mit Skripten erreicht, denn nicht jeder hat die Möglichkeit Komponenten auf den verwendeten Servern zu registrieren.
Also, wie schütze ich dann meine Software, die ich in Form von Skripten verkaufen möchte?
Die verkaufte Kopie einer Software muß wiedererkennbar werden. Dazu wird eine Seriennummer in die ASP Skripte eingebaut. Diese Seriennummer sollte folgende Eigenschaften haben:
Die erste Eigenschaft ist dadurch zu erreichen, daß man die Nummer nicht als VBS-Kommentar innerhalb des ASP-Codes, sondern z.B. als HTML-Kommentar, als Kommentar in clientseitigem Skript-Code oder in CSS-Dateien einträgt.
Die zweite Eigenschaft erreicht man dadurch, daß die Seriennummer lang genug ist und auf keinen Fall fortlaufend sein darf. Man sollte hier also eher zB eine GUID wählen.
Für die letzte Eigenschaft muß man die Seriennummer verstecken und tarnen. Dazu kann man die Seriennummer auch auseinanderschneiden und auf mehrere Stellen innerhalb einer Datei oder sogar auf mehrere Dateien verteilen. Tarnmöglichkeiten gibt es viele, z.B. als Versionsnummer:
<!-- My ASP Application - Version 2.73.1254 -->
Oder als Farben in ungenutzten Klassen innerhalb von CSS-Dateien:
td.data1 { color: #12aabb; background-color: #3a5cd8}
Natürlich gibt es hier noch viel mehr Möglichkeiten, und man kann sich selbst ein geeignetes Versteck für die Seriennummer ausdenken.
Basierend auf obiger Spezifikation soll ein Prototyp eines Systems entstehen, mit dem man registrierten Kunden per Seriennummer personalisierte Software-Pakete zum Download anbieten kann. Für das on-the-fly zippen und den Download wird ActiveFile von Infomentum verwendet, wobei man natürlich beliebig andere ZIP- bzw. Download-Komponenten nutzen kann. Die Seriennummer-Funktionalität wird in einer VBS-Klasse gekapselt, um sie auch außerhalb dieses Prototyps wiederverwenden zu können.
Die zu erzeugende Klasse soll aus einem Quell-Verzeichnis einige Dateien (CopyFiles-Array) nur in ein Ziel-Verzeichnis kopieren und andere (ParseFiles-Array) Dateien beim Kopieren nach bestimmte Formulierungen suchen und diese durch Seriennummern ersetzen. Dies führt zu folgendem Klassen-Design:
class SoftwarePackagePersonalizer property let/get SourceDirectory property let/get DestinationDirectory property let/get CopyFiles property let/get ParseFiles function Go() end class
Die Function Go() führt den Kopierprozess durch und gibt dann die komplette Seriennummer zurück. Beim Parsen der Dateien soll nach folgendenden Formulierungen gesucht werden, welche dann durch Seriennummernfragmenten ersetzt werden:
@@number:type:length@@
number gibt die Position des Seriennummernfragments innerhalb der Seriennummer an, type bestimmt den Typ des zu erzeugenden Fragments, wobei hier folgende Typen implementiert sind:
Typ | Bedeutung |
---|---|
n | Numerisch: Dieser Typ erzeugt Fragmente aus den Zeichen 0-9 |
a | Alpha: Dieser Typ erzeugt Fragmente aus den Zeichen a-z |
an | Alphanumerisch: Dieser Typ erzeugt Fragmente aus den Zeichen 0-9 und a-z |
h | Hex: Dieser Typ erzeugt Fragmente aus den Zeichen 0-9 und a-f |
Auch andere Typen sind denkbar, z.B. welche, die ein Zufallsdatum erzeugen etc. length letztendlich bestimmt die Länge des zu erzeugenden Fragments.
Zum leichteren Verständnis hier einige Beispiele:
Code | Erzeugtes Beispiels-Fragment | Beschreibung |
---|---|---|
@@1:h:6@@ | 12a4f6 | Gut um Seriennummern als Farbe in CSS-Dateien zu tarnen |
@@2:n:4@@ | 3968 | Möglicher Einsatz: Erzeugung von Versionsnummern |
Vorgenanntes Beispiel würde dann zur Seriennummer 12a4f63968; die Seriennummerfragmente werden also in der richtigen Reihenfolge zur Seriennummer zusammengesetzt.
Wie sieht das ganze als Code aus?
Alles außer der Go-Funkion ist trivial. Das einzig bemerkenswerte ist, dass darauf geachtet wird, daß der Verzeichnispfad mit einem "\" abschließt.
<% class SoftwarePackagePersonalizer private m_SrcDir private m_DestDir private m_CopyFiles private m_ParseFiles private sub Class_Initialize() m_CopyFiles = Array() m_ParseFiles = Array() end sub property let SourceDirectory(value) m_SrcDir = value if right(m_SrcDir,1)<>"\" then m_SrcDir = m_SrcDir & "\" end if end property property get SourceDirectory SourceDirectory = m_SrcDir end property property let DestinationDirectory(value) m_DestDir = value if right(m_DestDir,1)<>"\" then m_DestDir = m_DestDir & "\" end if end property property get DestinationDirectory DestinationDirectory = m_DestDir end property property let CopyFiles(value) m_CopyFiles = value end property property get CopyFiles CopyFiles = m_CopyFiles end property property let ParseFiles(value) m_ParseFiles = value end property property get ParseFiles ParseFiles = m_ParseFiles end property function Go() dim fso, stream, txt, regEx, matches, match dim num, typ, leng, randStr, serials, dummy dim i, j, result, filename dim forReading : forReading = 1 dim forWriting : forWriting = 2 Set fso = Server.CreateObject("Scripting.FileSystemObject")
Nach dem Erstellen des FileSystemObjects werden zuerst die Dateien kopiert, die nicht verändert werden müssen:
' 1 Copy Files for each filename in m_CopyFiles fso.CopyFile m_SrcDir & filename, m_DestDir, true next
Danach werden die zu parsenden Dateien einzeln eingelesen und nach dem erwähnten Muster durchsucht:
' 2 Parse Files for each filename in m_ParseFiles ' 2.1 read file content Set stream = fso.OpenTextFile(m_SrcDir & filename,forReading) txt = stream.readAll() stream.close ' 2.2 search for @@number:type:length@@ … Set regEx = new RegExp regEx.Global = true regEx.Pattern = "@@([^:]*):([^:]*):([^@]*)@@" Set matches = regEx.Execute(txt)
Die gefundenen Muster werden in ihre Bestandteile zerlegt und an die Funktion RandomString übergeben, die wir später noch sehen werden (diese kann uns die verschiedenen Typen der Seriennummernfragmente erzeugen). Das erzeugte Fragment wird an die Stelle des gefundenen Musters im eingelesenen Text gesetzt und zusätzlich zusammen mit der Position in der Seriennummer in der Variable serials gespeichert. Damit können wir später daraus die Seriennummer zusammensetzen.
Zum Speichern der Seriennummerfragmente wird ein CSV-Format benutzt.
for each match in matches num = CInt(match.subMatches(0)) typ = match.subMatches(1) leng = CInt(match.subMatches(2)) randStr = randomString(typ,leng) ' 2.3 replace token with random String txt = replace(txt,match.Value,randStr) serials = serials & vbCr & num & vbTab & randStr next
Jetzt kann man den geänderten Dateininhalt in die Zieldatei schreiben und das gesamte Software-Paket ist personalisiert.
Set stream = fso.OpenTextFile(m_DestDir & filename,forWriting,true) ' 2.4 ... and write the altered content into the created file stream.write txt stream.close next Set fso = Nothing
Das einzige, was jetzt noch zu tun bleibt, ist die Seriennummern aus den Fragmenten zusammenzusetzen und zurückzugeben. Hierzu wird das CSV-Format in einem Array von Arrays expandiert, die einzelnen Fragmente mit einem Bubble-Sort-Derivat nach der Position sortiert und schließlich zur fertigen Seriennummer zusammengefügt.
' 3 return serial serials = mid(serials,2) ' delete first vbCr ' 3.1 unpack serial parts serials = split(serials, vbCr) for i = 0 to ubound(serials) serials(i) = split(serials(i), vbTab) next ' 3.2 bubble-sort serial parts for i = 0 to ubound(serials)-1 for j = ubound(serials) to i+1 step -1 if serials(i)(0)>serials(j)(0) then dummy = serials(j) serials(j) = serials(i) serials(i) = dummy end if next next ' 3.3 compile serial result = "" for i = 0 to ubound(serials) result = result & serials(i)(1) next Go = result end function
Die eben erstellte Klasse soll eingesetzt werden um personalisierte gezippte Software-Pakete anzubieten. In diesem Fall ist es eine Beispiels-ASP-Applikation, die zwei Zahlen addieren kann.
Als erstes brauchen wir ein Registrierungs-Formular. Zur Demonstration reicht die einfache Abfrage nach Namen und Email-Adresse. Auch verzichten wir hier auf besondere Überprüfungen der Eingaben - Hilfe hierzu bekommt man unter anderem im Artikel Überprüfen von HTML-Formularen mit ASP und den weiteren Links dazu.
<html> <body> Zum Download des SuperDuper-ASP-Addierers musst Du Dich hier registrieren: <form action="download.asp" method="post"> name: <input name="name"><br> email: <input name="email"><br> <input type="submit" value="abschicken"> </form> </body> </html>
In download.asp wird zuerst ein temporäres Verzeichnis erstellt, damit sich die einzelnen Downloads des Pakets nicht gegenseitig stören:
<%option explicit%> <!--#include file="SoftwarePackagePersonalizer.asp" --> <% dim fso, tmp, serial Set fso = CreateObject("Scripting.FileSystemObject") ' generate temp folder... tmp = Server.MapPath(fso.GetTempName) fso.CreateFolder tmp
Dann wird die erstellte Klasse benutzt, um das personalisierte Paket in diesem Verzeichnis zu erstellen. Das Paket besteht aus der Datei readme.txt, die nur kopiert wird und default.asp und style.css, die insgesamt drei Seriennummernfragmente enthalten (vollständiger Code im heutigen Download enthalten).
' ... and create the software package there. dim spp Set spp = new SoftwarePackagePersonalizer spp.SourceDirectory = Server.MapPath("calculator-package") spp.DestinationDirectory = tmp spp.CopyFiles = Array("readme.txt") spp.ParseFiles = Array("default.asp","style.css") serial = spp.Go()
Nun wird noch alles mit den ActiveFile-Komponenten gezippt und zum Client geschickt:
' now put everything in a zipfile, ... dim Archive Set Archive = Server.CreateObject("ActiveFile.Archive") Archive.NewArchive tmp & "\calculator.zip" Archive.Add tmp & "\*.*" Archive.SaveArchive Set Archive = Nothing ' ... download the ZIP archive ... dim File Set File = Server.CreateObject("ActiveFile.File") File.Name = tmp & "\calculator.zip" Response.Clear Response.AddHeader "Content-Disposition", "inline; filename=calculator.zip" File.Download "application/x-zip-compressed", Now() Set File = Nothing
Zum Schluß löscht man nur noch das temporäre Verzeichnis mit allen Dateien darin und merkt sich die erstellte Seriennummer zusammen mit den Registrierungsdaten. In diesem Beispiel speichern wir alles der Einfachheit halber nur in einer Textdatei. Im Produktions-Einsatz nimmt man hierzu natürlich eine Datenbank.
' and delete everything. (comment this to see the temp folder and its content) fso.DeleteFolder tmp, true ' write serial and name to a log-file. ' this would be a database on a production site of course... const ForAppending = 8 dim stream Set stream = fso.OpenTextFile(Server.MapPath(".") & _ "\log.txt", ForAppending, true) stream.writeLine "----------------------------" stream.writeLine "Name: " & Request("name") & _ " (" & Request("email") & ")" stream.writeLine "Serial: " & serial stream.close Set stream = Nothing Set fso = Nothing %>
Entdeckt man nun irgendwo seine Software, braucht man sich nur noch aus den entsprechenden Dateien die Seriennummerfragmente zusammenzusuchen, und sie dann mit den Einträgen in der eigenen Datenbank/Logdatei zu vergleichen, um herauszufinden an wen das Paket lizensiert wurde. Natürlich kann man hierzu auch eine Web-Applikation erstellen, die einem diese Vergleichsarbeit abnimmt, vielleicht sogar teilweise manipulierte Seriennummern noch erkennt. Eine weitere nützliche Erweiterungen wäre es bei Eingabe einer URL die Dateien mit den Seriennummerfragmenten herunterzuladen ( z.B. mit ASPTear oder XMLHTTP) und die Seriennummer automatisch zu extrahieren.
Wir haben gesehen, wie man mit einfachen Mitteln durch Seriennummern abgesicherte Pakete zum Download anbieten kann. Eine Beschränkung dieser Technologie ist es natürlich, daß die Seriennummer nicht funktionaler Bestandteil des Skripts ist, also bei Entdeckung entfernt werden könnte. Es kommt also darauf an, die Seriennummer gut zu verstecken.
This printed page brought to you by AlphaSierraPapa
Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20020411.zip
Überprüfen von HTML-Formularen mit ASP
http:/www.aspheute.com/artikel/20000522.htm
ASP Scripts verschlüsseln
http:/www.aspheute.com/artikel/20000510.htm
Klassen in VBScript
http:/www.aspheute.com/artikel/20000526.htm
Laden von Dateien aus dem Web mit ASP
http:/www.aspheute.com/artikel/20000519.htm
MS Script Encoder dekodiert
http:/www.aspheute.com/artikel/20011123.htm
Sonderzeichen korrekt grabben mit XmlHttp
http:/www.aspheute.com/artikel/20011113.htm
Webpage-Grabbing mit dem XML Parser
http:/www.aspheute.com/artikel/20010328.htm
Webseiten automatisiert scrapen
http:/www.aspheute.com/artikel/20010910.htm
Webseiten automatisiert scrapen, Teil 2
http:/www.aspheute.com/artikel/20010911.htm
Zippen und entzippen von Dateien
http:/www.aspheute.com/artikel/20001005.htm
Infomentum ActiveFile
http://www.infomentum.com/activefile/
©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.