На самом деле, многими реверсерскими командами создана куча генераторов автопатчеров, подсовываешь ему оригинальный и измененный экзешник, и тебе генерируется автопатчер, даже с красивой картинкой и восьмибитной музыкой. Но «есть проблема» (ц) Патриарх Кирилл:
— Не на все автопатчеры хорошо реагируют антивирусы
— Автопатчеры нестандартны, кто в лес, кто по дрова.
Неплохо бы сделать так, чтоб и антивирусы не ругались, и оно хоть как-то относительно стандартно выглядело. И такое решение есть — модуль VPatch для системы установки NSIS. Далее расскажу, как этим модулем воспользоваться, чтобы пропатчить уже установленную программу, а также, немного расширю пример, покажу как в патч «зашить» еще и отмену патча. Естественно, чтобы все повторить, у вас должен быть установлен NSIS хотя бы в минимальной комплектации.
Устанавливаем модуль VPatch, лучше воспользоваться ссылкой на установщик, потом скачать родной пример и попытаться его скомпилировать. Если все сработало — модуль установился правильно, можно работать дальше.
Для генерации данных для патча, VPatch использует свои утилиты командной строки, которые в комплекте идут, но устанавливаются криво, так что качаем утилиты отдельно и устанавливаем их (установятся в %WINDIR%
).
Теперь нужна лабораторная крыса, т.е. программа, которую будем патчить, возьмем оригинальный CrackMe из предыдущего примера (копия) и напишем ему стандартный инсталлятор:
Исходник инсталлятора
Готовый инсталлятор тестовой программы
Исходник CarckMe
Устанавливаем ее.
Для патча нужен оригинальный экзешник
Кладем оригинальный и пропатченный вручную файл в один каталог, например, создадим в каталоге проекта подкаталог exe-files
, в него переместим scrackme.exe
и scrackme.patched.exe
.
Теперь необходимо сгенерировать патч-файл с помощью genpat.exe
:
genpat.exe /V <оригинальный_файл> <измененный_файл> <патч_файл>
где:
/V
— отображение процесса (включим для наглядности)
<оригинальный_файл>
— оригинальный файл
<измененный_файл>
— пропатченная вручную копия оригинального файла
<патч_файл>
— файл с инструкцией для патчинга, который будет использован патч-модулем NSIS
genpat /V scrackme.exe scrackme.patched.exe change.pat
Вывод:
[Source] scrackme.exe
[Target] scrackme.patched.exe
[PatchFile] change.pat
Patch file does not yet exist: File could not be read (getFileSize), it will be created.
[Checksum] Kind of checksums used: MD5
[BlockSize] 64 bytes
[FindBlockMatchLimit] 500 matches
[Debug] Verbose output during patch generation activated.
[ChunkedFile] Filesize of 2560 gives 40 chunks.
[ChunkedFile] Memory to be used by those chunks: 960 bytes... allocated.
[ChunkedFile] Sorting chunks... done.
[CacheReload] File position = 0
Block found: 0 1409 (source offset=0)
Block found: 1411 1149 (source offset=1411)
Генерируем патч-файл для отмены:
genpat /V scrackme.patched.exe scrackme.exe restore.pat
Можно проверить информацию .pat
-файла:
listpat change.pat
Вывод:
[PatchFile] change.pat
[Checksums] MD5 mode
[NumberOfPatches] 1 patch
[1] MD5{34477cabd7eccbff756ce5280e3db} to MD5{172073a0e5a098ea8e4d521889a2c840}
has 4 steps using 67 bytes (offset 8..75)
[Total] 75 bytes
Переносим *.pat
-файлы в каталог будущего проекта инсталлятора.
Получаем MD5-хэши (копия) для оригинального и измененного exe-файлов:
scrackme.exe: 34477c0a0bd7eccbff756ce50280e3db scrackme.patched.exe: 172073a0e5a098ea8e4d521889a2c840
Все, осталось только написать инсталлер и скормить сгенерированные файлы плагину-патчеру.
Сначала пишем стандартную «болванку» для инсталлятора:
Unicode true
!include LogicLib.nsh
!include "FileFunc.nsh"
!insertmacro GetTime
; Main Install settings
Name "Simple Crackme Patch And Restore"
InstallDir "$PROGRAMFILES\Patch Test"
OutFile "SimpleCrackmePatch.exe"
DirText "Choose the folder in which to patch scrackme.exe:"
ShowInstDetails show
Section "TestPatch"
...
SectionEnd
Все как обычно, только стоит обратить внимание на подключаемые файлы:
LogicLib.nsh
— понадобится, чтобы сделать более удобоваримые if
‘ы в NSIS, а FileFunc.nsh
и !insertmacro GetTime
— для функции бэкапа, чтобы пользователь мог восстановить файл и в ручном режиме.
Что ж, рассмотрим, что происходит внутри секции TestPatch
:
А внутри секции начинаем подготовку. Создаем переменную TARGETFILE
:
Var /GLOBAL "TARGETFILE"
И присваиваем ей имя нашего файла, который будем патчить:
StrCpy $TARGETFILE "scrackme.exe"
Далее, проверяем, существует ли файл по указанному пользователем пути в окне
DetailPrint "Find $TARGETFILE in dir $INSTDIR:"
;check if file for patching exist
IfFileExists "$INSTDIR\$TARGETFILE" 0 NoTargetFile
DetailPrint "File $TARGETFILE found. OK"
Параллельно сообщаем пользователю о том, что мы делаем, в псевдоконсоли NSIS.
Если файла нет, уходим на метку NoTargetFile:
, где выводим сообщение, о том, что файл отсутствует:
DetailPrint "ERROR: File $TARGETFILE NOT FOUND in $INSTDIR!"
MessageBox MB_ICONSTOP "No file $INSTDIR\$TARGETFILE. Not installed or wrong directory selected?"
И завершаем программу.
Если же все прошло успешно, переход по команде IfFileExists "$INSTDIR\$TARGETFILE"
перейдет на следующую строчку (второй параметр команды 0
) и выполнение скрипта продолжится.
Файл существует, все хорошо, теперь можно получить его MD5-хэш и записать его в отдельную переменную:
;create user variable for checksum
;for target file
Var /GLOBAL "FILEMD5"
;Get checksum for patching file
md5dll::GetMD5File "$INSTDIR\$TARGETFILE"
Pop $FILEMD5
DetailPrint "Target file MD5: $FILEMD5"
Далее заводим отдельные переменные и сохраняем в них MD5-хэши для оригинального и пропатченного файла (см. выше, где мы получали хэши):
;Add checksums
Var /GLOBAL "MD5ORIG"
StrCpy $MD5ORIG "34477c0a0bd7eccbff756ce50280e3db" ;Original file MD5
Var /GLOBAL "MD5PATCH"
StrCpy $MD5PATCH "172073a0e5a098ea8e4d521889a2c840" ;Patched file MD5
В переменной $MD5ORIG
содержится хэш оригинального файла, а в переменной MD5PATCH
— уже измененного (пропатченного).
Отступление про backup оригинального файла. Хоть наш инсталлятор-патчер и будет иметь возможность все откатить назад автоматически, но не надо лишать пользователя возможности сделать это вручную.
Так что перед секцией создадим макрос, который будет делать бэкап программы, перед ее изменением:
!macro BackupFile ${GetTime} "" "L" $0 $1 $2 $3 $4 $5 $6 StrCpy $0 "$TARGETFILE.$2$1$0$4$5$6.bak" DetailPrint "Backup $TARGETFILE --> $0..." CopyFiles "$INSTDIR\$TARGETFILE" "$INSTDIR\$0" !macroend
Этот макрос добавляет локальное время и дату (копия), расширение .bak
к файлу, который должен быть изменен, и копирует его.
Формируем условия:
1. Если контрольная сумма соответствует оригинальному файлу — пропатчиваем его.
2. Не соответствует — проверяем, может быть файл уже пропатчен (по контрольной сумме пропатченного файла). Тогда предоставляем пользователю возможность откатить это назад.
3. Контрольная сумма файла не совпадает с $MD5ORIG
или $MD5PATCH
— файл поврежден, изменен, или вообще не тот. Отменяем операцию.
;Patch/Restore... ${If} $FILEMD5 == $MD5ORIG ;Original file. Patch. ... Здесь будет код ${Else} ${If} $FILEMD5 == $MD5PATCH ; Patched file. Restore. ... Здесь будет код ${Else} ;Other checksum, wrong file ... Здесь будет код ${EndIf} ${EndIf}
Примечание: Да, VPatch сам умеет проверять контрольную сумму, но у него довольно слабая внутренняя обработка ошибок, так что лучше все сделать самому.
Т.е. это и не исходный, и не пропатченный файл. Код самый простой, надо известить пользователя и завершить программу:
${Else} ;Other checksum, wrong file
MessageBox MB_ICONSTOP "Unknown or wrong file $INSTDIR\$TARGETFILE. Bad checksum."
DetailPrint "ERROR: Unknown or wrong file $TARGETFILE. Bad checksum. "
Goto EndProg
${EndIf}
1. Спрашиваем подтверждение у пользователя:
MessageBox MB_YESNO|MB_ICONQUESTION "Patch file?" IDYES 0 IDNO EndProg
2. Бэкапим оригинальный файл:
!insertmacro BackupFile
3. Получаем имя временного файла:
GetTempFileName $R0
В $R0
будет что-то типа %TEMP%\nsvDC3D.tmp
, где %TEMP%
— пользовательская переменная окружения, указывающая на путь ко временной папке пользователя (обычно "C:\Users\ВашеИмяПользователя\Local Settings\Temp"
)
4. Пропатчиваем файл:
vpatch::vpatchfile "change.pat" "$INSTDIR\$TARGETFILE" "$R0"
где:
vpatch::vpatchfile
— вызов библиотеки VPatch. Можно, конечно, воспользоваться и макросом !insertmacro VPatchFile
из VPatchLib.nsh
, но он, на мой взгляд, слишком много думает за программиста, и часто думает не в кассу.
"change.pat"
— pat
-файл, полученный на этапе подготовки (см. выше).
"$INSTDIR\$TARGETFILE"
— целевой (оригинальный) файл для изменения (в примере C:\Program Files (x86)\Patch Test\scrackme.exe
)
"$R0"
— полученный выше временный файл для записи туда измененного (пропатченного) файла. Сразу изменить оригинальный файл не получится, да оно и к лучшему, есть больше возможностей, чтоб проверить возможные ошибки.
5. Показываем сообщение самой библиотеки VPatch, которое она помещает в стек:
Pop $R1
DetailPrint "Patch:"
DetailPrint "$R1"
Я выше говорил, что у VPatch довольно слабый контроль ошибок, и в качестве результата своей работы, он помещает в стек строку, имеющую всего несколько вариантов содержимого:
OK
— все прошло успешно, файл пропатчен.
OK, new version already installed
— файл был пропатчен уже, применение патча не требуется (этого мы избежали самостоятельной проверкой контрольных сумм).
Unable to open source file
— в случае, если файл отсутствует или недоступен.
No suitable patches were found
— не совпадает контрольная сумма оригинального файла.
6. Копируем временный (пропатченный) файл на место оригинального:
CopyFiles "$R0" "$INSTDIR\$TARGETFILE"
7. Проверяем, не произошло ли при копировании ошибки:
IfErrors CopyError 0 ;check copy file error
Если ошибка произошла, переходим на метку CopyError
, где сообщаем пользователю об ошибке и завершаем программу:
CopyError: MessageBox MB_ICONSTOP "Copy error! Target file not changed!" Goto EndProg
Ошибки нет — удаляем временный файл:
Delete "$R0" ;remove temporary file
Алгоритм такой же, как и при патче, меняется лишь вызов библиотеки vpatch
:
vpatch::vpatchfile "restore.pat" "$INSTDIR\$TARGETFILE" "$R0"
Фрагмент кода целиком на PasteBin
Патч:
Восстановление:
— VPatch — Free Patch Generator
— VPatch plug-in
— Документация и исходники оригинального примера
— NSIS: контрольная сумма (MD5), сравнение файлов. (копия)
— NSIS: Получение списка файлов с MD5-хешами. (копия)
— NSIS: Получение даты и времени. (копия)