Патч на NSIS (с возможностью отмены).

Преамбула

На самом деле, многими реверсерскими командами создана куча генераторов автопатчеров, подсовываешь ему оригинальный и измененный экзешник, и тебе генерируется автопатчер, даже с красивой картинкой и восьмибитной музыкой. Но «есть проблема» (ц) Патриарх Кирилл:

— Не на все автопатчеры хорошо реагируют антивирусы
— Автопатчеры нестандартны, кто в лес, кто по дрова.

Неплохо бы сделать так, чтоб и антивирусы не ругались, и оно хоть как-то относительно стандартно выглядело. И такое решение есть — модуль VPatch для системы установки NSIS. Далее расскажу, как этим модулем воспользоваться, чтобы пропатчить уже установленную программу, а также, немного расширю пример, покажу как в патч «зашить» еще и отмену патча. Естественно, чтобы все повторить, у вас должен быть установлен NSIS хотя бы в минимальной комплектации.

Подготовка #1

Устанавливаем модуль VPatch, лучше воспользоваться ссылкой на установщик, потом скачать родной пример и попытаться его скомпилировать. Если все сработало — модуль установился правильно, можно работать дальше.

Для генерации данных для патча, VPatch использует свои утилиты командной строки, которые в комплекте идут, но устанавливаются криво, так что качаем утилиты отдельно и устанавливаем их (установятся в %WINDIR%).

Скачать установщик здесь

Подготовка #2

Теперь нужна лабораторная крыса, т.е. программа, которую будем патчить, возьмем оригинальный 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

Все, осталось только написать инсталлер и скормить сгенерированные файлы плагину-патчеру.

Создаем патчер с возможностью восстановления оригинала на NSIS

Сначала пишем стандартную «болванку» для инсталлятора:

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

Отступление про 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: Получение даты и времени. (копия)

Исходник примера

Пример полностью на GitHub
Основной исходник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *