Вы только что разработали замечательный сценарий и уже собрались сделать его достоянием общественности, но что-то заставляет вас медлить? Да новый сценарий позволит сэкономить время, деньги и силы вам, вашим пользователям и компании в целом. Все вроде бы хорошо, однако вы не можете полностью избавиться от ощущения, будто что-то не так. Через некоторое время вы начинаете понимать, что написанный сценарий действительно может быть очень полезен, если его использовать надлежащим образом, однако кто-то по незнанию или злому умыслу может изменить его код и в результате наделать бед.

Сама природа сценариев такова, что они доступны для всех. Любой человек может запустить обычный текстовый редактор и увидеть в нем все нюансы вашей прекрасно выполненной работы. Это одновременно и хорошо, и плохо. Хорошо потому, что дает возможность свободно распространять сценарии среди специалистов в области ИТ. Обычно такие специалисты держат под рукой коллекцию различных сценариев, чтобы использовать те или иные их фрагменты при создании новых программных инструментов. Плохо то, что, если написанный вами сценарий используется и другими людьми, они могут препарировать его код как им угодно. А хуже всего, что сделать это весьма просто. Данная ситуация даже может послужить потенциальной причиной серьезного ущерба, в зависимости от того, для каких задач этот сценарий разрабатывался.

Поэтому, прежде чем тиражировать свои сценарии, следует подумать о мерах их защиты. Конечно, можно воспользоваться стандартными методиками для защиты кода сценариев от изменения, однако существует и более эффективный способ.

Стандартные методики

Для защиты сценариев, предназначенных для публичного использования, разработчики, как правило, применяют один из трех методов. Первый состоит в том, чтобы использовать для сценариев уровни безопасности, относящиеся к файлам или папкам. Если на файл сценария предоставить права только на «чтение» (read) и «выполнение» (execute), это поможет предотвратить внесение кем-либо изменений в данный файл. Недостаток такого подхода состоит в том, что любой по-прежнему может просматривать код сценария, а также копировать и сохранять его в любом другом месте.

Второй способ состоит в шифровании кода сценария. В свое время Microsoft выпустила утилиту Script Encoder (http://www.microsoft.com/downloads/details.aspx?familyid=E7877F67-C447–4873-B1B0–21F0626A6329&displaylang=en).
После применения данной утилиты сценарий становится нечитаемым, поэтому любые внесенные в него изменения приводят его в нерабочее состояние. Однако Script Editor может работать только с определенными типами сценариев, а именно с файлами VBScript, JScript и HTML. При этом в некоторых ситуациях, например в случае сценариев VBScript, можно легко догадаться, что сценарий был обработан утилитой Script Encoder, поскольку здесь расширение файла сценария изменяется с.vbs на .vbe. Быстрый поиск в Internet выдаст нужные ссылки, описывающие методики по приведению шифрованного текста сценария в исходное состояние. И наконец, третий способ состоит в том, чтобы переписать сценарий с интерпретируемого языка на компилируемый, а затем скомпилировать его в исполняемый файл. После этого уже никто не сможет ни прочитать код, ни внести в него какие-либо изменения. Однако в данном случае нужно будет полностью переписать сценарий в соответствии с требованиями компилируемого языка. К тому же если в вашем распоряжении нет необходимого инструментария и среды разработки, то вы не сможете даже попытаться применить этот подход.

Наилучший путь

Для реализации оптимального метода защиты мною был разработан сценарий DotNetWrapper.vbs. Он не только шифрует исходный код сценария, но и компилирует его в исполняемый файл, и при этом в исходном сценарии не требуется изменять ни одной строки. В отличие от Script Encoder, DotNetWrapper.vbs не ограничивает вас несколькими типами обрабатываемых им файлов, он может использоваться с любым сценарием, сохраненным в виде текстового файла и запускаемым через сервер сценариев или из командной строки. Инструмент DotNetWrapper.vbs может работать с различными типами сценариев, включая .vbs, .js,.htm, .bat, .cmd и .hta.

В DotNetWrapper.vbs используется vbc.exe — компилятор командной строки из Visual Basic.NET, который входит в состав Microsoft.NET Framework. В ходе работы DotNetWrapper.vbs создается исполняемый файл Visual Basic.NET, который запускает «на лету» тот сценарий, код которого мы защищаем. Это осуществляется с помощью записи содержимого сценария в переменную внутри исполняемой среды. Далее исполняемый файл сохраняет сценарий во временном файле и вызывает для его запуска соответствующий сервер сценариев либо интерпретатор командной строки. После того как сценарий был запущен, временный файл удаляется.

Данный подход сам по себе уже обеспечивает некоторую степень защиты, однако если просмотреть содержимое исполняемого файла .NET в текстовом редакторе, то можно увидеть, что он все равно содержит некоторое количество простого текста. В частности, исходный текст сценария по-прежнему остается видимым. Данный текст, конечно, не будет столь легкочитаемым (все символы будут разделены пробелами, а переносы строк будут отсутствовать) и тем не менее он будет видимым.

Для реализации дополнительной защиты кода в сценарий включена функция EncodeScript, реализующая простой алгоритм циклического сдвига символов. Как показано в листинге 1, данная функция смещает буквенные символы на 128 позиций вверх. В результате остаются только цифровые и другие небуквенные символы. Алгоритм декодирования встроен в исполняемый файл .NET.

Метод «упаковки» сценария внутрь исполняемого файла .NET имеет, помимо уже отмеченных преимуществ, еще ряд достоинств, свойственных всем файлам .exe. Например, этому файлу можно присвоить какую-то особую пиктограмму. Кроме того, для запуска такого файла можно задействовать инструментарий RunAs, тем самым исключив возможность запуска соответствующего сценария от имени какой-либо учетной записи, отличной от той, под которой вы вошли в систему.

Несмотря на все преимущества, сценарий DotNetWrapper.vbs тоже имеет ряд ограничений. В первую очередь, он может запускаться только на том компьютере, на котором установлен компонент.NET Framework. Следует также учитывать, что исполняемый файл должен запускаться на локальном компьютере. Если вы попытаетесь запустить его с сетевого диска либо используя в описании пути имена UNC, то он работать не будет. И последнее: поскольку защищенный сценарий после его запуска удаляется, любые действия, имеющие обратную ссылку на этот сценарий, будут выполняться некорректно. Рассмотрим пример. Допустим, мы используем DotNetWrapper.vbs для защиты приложения HTML Application (HTA) или страницы HTML. Если вы попытаетесь просмотреть исходный код страницы HTML или HTA, выбрав в меню View Internet Explorer (IE) пункт Source, то вы увидите пустое окно. Любые попытки обновить содержимое страницы HTML или HTA приведут к ее зависанию. Еще нужно отметить, что DotNetWrapper.vbs можно использовать и со сценариями, требующими интерактивного взаимодействия. В частности, можно применять DotNetWrapper.vbs для запуска сценария WScript, в котором задействована функция InputBox.

Использование DotNetWrapper.vbs

Для того чтобы можно было работать с DotNetWrapper.vbs, на компьютере, на котором он будет запускаться, должны быть установлены дополнительные компоненты Windows Script Host (WSH) 5.6 и.NET Framework 1.1 (или более поздняя версия). Чтобы определить, какая версия .NET Framework установлена на компьютере, нужно просмотреть содержимое папки%systemroot%Microsoft.NETFramework. Каждая версия.NET Framework размещается в подкаталоге с названием, соответствующим номеру версии. Я разрабатывал данный сценарий для версии 1.1.4322. Если у вас установлена более свежая версия, то вы должны изменить приведенную ниже строку сценария DotNetWrapper.vbs, указав в ней номер версии, установленной на вашей системе:

dotNetVersion = «v1.1.4322»

Еще одно изменение, которое может потребоваться, касается следующей строки кода:

System.Threading.Thread.Sleep
  (1000)

В сценарии DotNetWrapper.vbs данный код используется дважды, он служит для вставки в ход выполнения паузы длительностью в одну секунду. Первая пауза возникает между моментом закрытия временного файла и моментом запуска сценария. Вторая пауза разделяет моменты запуска сценария и удаления его файла. Может оказаться (хотя это и маловероятно), что нужно увеличить длительность паузы в зависимости от объема сценария, код которого необходимо защитить. Должен сказать, что паузы в одну секунду обычно хватало для работы с достаточно большими сценариями (объемом около 500 строк или 14 Kбайт), которые я запускал через DotNetWrapper.vbs. Однако если вам все-таки потребуется увеличить длительность паузы, то измените в приведенной выше строке значение с 1000, скажем, на 2000 — в результате длительность паузы будет увеличена до 2 секунд.

Никаких других изменений, кроме двух описанных выше, в сценарий DotNetWrapper.vbs вносить не требуется. Текст сценария приведен в листинге 2.

Для запуска DotNetWrapper.vbs используется следующий синтаксис:

DotNetWrapper.vbs
Script_File Script_Type Script_Engine
Output_File [Icon_File]

Здесь аргумент Script_File содержит путь к сценарию, код которого мы хотим защитить. Если этот аргумент содержит пробелы, то его необходимо заключать в кавычки (то же самое относится и ко всем остальным аргументам). Пример: «C:My Scriptsmyscript.vbs».

Аргумент Script_Type определяет расширение файла запускаемого сценария (например, .vbs). Не забудьте указать точку (.) в начале расширения.

Аргумент Script_Engine задает тот сервер (исполнительную среду), через который будет запускаться сценарий. Для описания серверов сценариев, которые размещаются в каталоге операционной системы, достаточно указать только имя соответствующего программного файла (например, cscript.exe для CSCript или wscript.exe для WSCript). В других случаях необходимо указывать полный путь к исполняемому файлу сервера сценариев. Для вызова интерпретатора командной строки Windows следует указать путь в виде «%COMSPEC%/k». Наличие ключа/k делает окно командной строки видимым для возможности отображения в нем информации. Если же сценарий должен запускаться в скрытом окне, то в этом случае данный аргумент должен указываться в виде «%COMSPEC%/c».

В аргументе Output_File указывается имя, которое присваивается исполняемому файлу, создаваемому.NET Framework. Здесь следует указывать полный путь (например, «C:Programsmyscript.exe»).

В отличие от четырех предыдущих, аргумент Icon_File является необязательным. Если в нем указан некоторый путь (например, C:Iconsmypicture.ico), тогда для исполняемого файла .NET будет выбрана соответствующая иконка.

Защищайте код своих сценариев

После написания очередного великолепного сценария, прежде чем делать его достоянием широких масс, подумайте, не следует ли защитить его код. Если вы решите, что код сценария требует принятия мер по его защите, возможно, DotNetWrapper.vbs — это именно то, что вам нужно.

Джейсон Джой (flajason@gmail.com) — системный инженер в строительной компании на юго-востоке США. Специализируется на обслуживании настольных компьютеров, Web-приложениях и решениях на основе баз данных


Листинг 1. Функция EncodeScript

Function EncodeScript(stringinfo)
Dim i, curchar, newstr
For i = 1 to Len(stringinfo)
curchar = Asc(Mid(stringinfo,i,1))
If (curchar >= 66 and curchar <= 122) or (curchar >=194 and curchar <= 250) Then
If curchar >= 66 and curchar <= 122 Then
newstr = newstr & chr(curchar+128)
Else
newstr = newstr & chr(curchar-128)
End If
Else
newstr = newstr & chr(curchar)
End If
Next
EncodeScript = newstr
End Function


Листинг 2. DotNetWrapper.vbs

Option Explicit

On error resume next

 

Dim dotNetVersion, ScriptFile, ScriptType, LaunchWith, EXEFile, icofile

Dim fso, WshShell, sysroot, ScriptSource, ScriptContent, workingdir, vbfile, vbsource

Dim vbcPath, vbcArgs, strCMDLine, debugger, objArgs

 

dotNetVersion = "v1.1.4322"

 

Set objArgs = WScript.Arguments

If objArgs.Count < 4 Then

MsgBox "Missing one or more arguments..." & vbCrLf & _

"Correct Usage: dotNetWrapper [Script File] [Script Type] [Script Engine] [Output File] [opt - Icon File]" & vbcrlf & vbcrlf & _

"Script File (ex: C:Scriptsmyscript.vbs)" & vbcrlf & _

"Script Type (ex: .vbs)" & vbcrlf & _

"Script Engine (ex: cscript.exe)" & vbcrlf & _

"Output File (ex: C:Programsmyscript.exe)" & vbcrlf & _

"Icon File --optional (ex. C:Iconsmypicture.ico)"

WScript.Quit

End If

ScriptFile = objArgs(0)

ScriptType = objArgs(1)

LaunchWith = objArgs(2)

EXEFile = objArgs(3)

If objArgs(4) <> "" Then

icofile = objArgs(4)

End If

 

Set fso = CreateObject ("Scripting.FileSystemObject")

Set WshShell = CreateObject("WScript.Shell")

 

Set sysroot = fso.GetSpecialFolder(0)

If Not(fso.FileExists("C:" & sysroot.name & "Microsoft.NetFramework" & dotNetVersion & "vbc.exe")) Then

MsgBox "Unable to locate vbc.exe compiler. Confirm your version of .NET", 16, "ERROR"

WScript.Quit

End If

 

Set ScriptSource = fso.OpenTextFile(ScriptFile, 1)

ScriptContent = ""

Do While Not ScriptSource.AtEndOfStream

ScriptContent = ScriptContent & CHR(34) & Replace(EncodeScript(ScriptSource.readline), CHR(34), CHR(34) & " & CHR(34) & " & CHR(34)) & CHR(34) & " & vbcrlf & _" & vbcrlf

Loop

ScriptContent = ScriptContent & CHR(34) & CHR(34)

ScriptContent = "ScriptContent = " & CHR(34) & CHR(34) & " & _" & vbcrlf & ScriptContent

 

Set workingdir = fso.GetFile(ScriptFile)

 

vbfile = "Module Module1" & vbcrlf & _

"Sub Main()" & vbcrlf & _

"On Error Resume Next" & vbcrlf & _

"Dim sPath As String" & vbcrlf & _

"sPath = System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)" & vbcrlf & _

"Dim ScriptContent As String" & vbcrlf & _

ScriptContent & vbcrlf & _

"Dim oFile As System.IO.File" & vbcrlf & _

"Dim oWrite As System.IO.StreamWriter" & vbcrlf & _

"oWrite = oFile.CreateText(sPath & ""compiledScript" & ScriptType & """)" & vbcrlf & _

"If Err.Number <> 0 Then" & vbcrlf & _

"MsgBox(""Unable to open Program. Please make sure you are running this locally."", MsgBoxStyle.Critical, ""Error"")" & vbcrlf & _

"Exit Sub" & vbcrlf & _

"End If" & vbcrlf & _

"oWrite.WriteLine(EncodeScript(ScriptContent))" & vbcrlf & _

"oWrite.Flush()" & vbcrlf & _

"oWrite.Close()" & vbcrlf & _

"System.Threading.Thread.Sleep(1000)" & vbcrlf & _

"Shell(""" & LaunchWith & " "" & Chr(34) & sPath & ""compiledScript" & ScriptType & """ & Chr(34), AppWinStyle.NormalFocus, False)" & vbcrlf & _

"System.Threading.Thread.Sleep(1000)" & vbcrlf & _

"oFile.Delete(sPath & ""compiledScript" & ScriptType & """)" & vbcrlf & _

"End Sub" & vbcrlf & _

"Function EncodeScript(ByVal stringinfo As String)" & vbcrlf & _

"Dim i As Int16" & vbcrlf & _

"Dim newstr As String" & vbcrlf & _

"Dim curchar As Int16" & vbcrlf & _

"For i = 1 to len(stringinfo)" & vbcrlf & _

"curchar = asc(mid(stringinfo,i,1))" & vbcrlf & _

"If (curchar >= 66 and curchar <= 122) or (curchar >=194 and curchar <= 250) then" & vbcrlf & _

"If curchar >= 66 and curchar <= 122 Then" & vbcrlf & _

"newstr = newstr & chr(curchar+128)" & vbcrlf & _

"else " & vbcrlf & _

"newstr = newstr & chr(curchar-128)" & vbcrlf & _

"End If" & vbcrlf & _

"Else" & vbcrlf & _

"newstr = newstr & chr(curchar)" & vbcrlf & _

"end if" & vbcrlf & _

"next" & vbcrlf & _

"EncodeScript = newstr" & vbcrlf & _

"End Function" & vbcrlf & _

"End Module" & vbcrlf

 

Set vbsource = fso.OpenTextFile(workingdir.ParentFolder & "compiledscript.vb", 2, True)

vbsource.Write vbfile

Set vbsource = Nothing

 

vbcPath = "C:" & sysroot.name & "Microsoft.NETFramework" & dotNetVersion & "vbc.exe"

vbcArgs = " /out:" & CHR(34) & exefile & CHR(34) & _

" /nowarn /nologo /quiet /debug- /optimize+ /optionstrict- /optionexplicit- " & _

"/imports:Microsoft.VisualBasic,System /t:winexe " & _

CHR(34) & workingdir.ParentFolder & "compiledscript.vb" & CHR(34) & " > " & _

CHR(34) & workingdir.ParentFolder & "debug.txt" & CHR(34)

If icofile <> "" Then

vbcargs = " /win32icon:" & CHR(34) & icofile.Value & CHR(34) & vbcArgs & CHR(34)

End If

 

strCMDLine = vbcPath & vbcArgs

debugger = WshShell.Run("cmd /c " & strCmdLine, 1, True)

If debugger <> 0 Then

WshShell.Run CHR(34) & workingdir.ParentFolder & "debug.txt" & CHR(34), 1, True

Else

MsgBox ".EXE Created Successfully!", 64, "Complete"

End If

 

fso.DeleteFile workingdir.ParentFolder & "debug.txt"

fso.DeleteFile workingdir.ParentFolder & "compiledscript.vb"

 

 

Function EncodeScript(stringinfo)

Dim i, curchar, newstr

for i = 1 to len(stringinfo)

curchar = asc(mid(stringinfo,i,1))

If (curchar >= 66 and curchar <= 122) or (curchar >=194 and curchar <= 250) then

If curchar >= 66 and curchar <= 122 Then

newstr = newstr & chr(curchar+128)

else

newstr = newstr & chr(curchar-128)

End If

Else

newstr = newstr & chr(curchar)

End If

Next

EncodeScript = newstr

End Function