PDA

Просмотр полной версии : Разбор и анализ патчера GenP: безопасный кряк для всех приложений Adobe?


ROP
29.09.2024, 01:15
1.0 Введение
Хоть компания Adobe и ушла из России, но потребность в их софте всё ещё остаётся. Решают её по-разному: кто-то использует другие аккаунты, кто-то использует ключи, а кто-то одевает повязку и спускает на воду свой цифровой фрегат.
На последнем мы как раз и остановимся. В этой статье проанализируем Adobe GenP.

Adobe GenP — это патчер, предназначенный для активации продуктов Adobe Creative Cloud, начиная с версий 2019 года и до текущих релизов. Он позволяет устанавливать и использовать приложения Adobe: Photoshop, Illustrator, Premiere Pro и другие, без необходимости приобретения лицензии.
Патчер — это программа, предназначенная для модификации программного обеспечения. Чаще всего используемая для обхода проверки лицензий и других защитных механизмов.
В отличие от многих других патчеров у него есть даже своё комьюнити на Reddit!

https://forum.antichat.xyz/attachments/4937599/1727474570191.png

Кстати, помимо основного сообщества рекомендую обратить внимание на Community bookmarks. Там много полезных туториалов и дополнительной информации.

План статьи:

1.0 Введение

2.0 Файлы

3.0 Установка Creative Cloud и первый запуск
3.1 Про установку Adobe
3.2 Запуск GenP
4.0 Разбираемся в исходном коде
4.1 Начало скрипта
4.2 Основной цикл патчера
4.2.1. Обработчик кнопки поиска
4.2.2. Обработчик кнопки выбора папки
4.2.3. Обработчик кнопки выбора/отмены всех элементов
4.2.4. Обработчик кнопки Pop-up
4.2.5. Обработчик кнопки "Patch CC" и как работает эта магия
4.2.5.1. Весь цикл поиска и патчинга кратко
4.2.5.2. Вернёмся к обработчику кнопки "Patch CC"
4.2.5.3. Сравниваем пропатченные байты с оригинальными и ищем их
4.2.6. Обработчик кнопки "Restore"
4.2.7. Обработчик кнопки "Patch"
5.0 Изучаем патчи из INI-файла
5.1. Секция CustomPatterns
5.2. Секция DefaultPatterns
5.3. Оставшиеся патчи
6.0 Вывод

https://forum.antichat.xyz/attachments/4937599/1727474582204.png

2.0 Файлы
Архив с GenP, который будет разбирать, был загружен с Reddit.

https://forum.antichat.xyz/attachments/4937599/1727474644705.png

Его контрольные суммы:


iz3lne.zip


MD5:

6b104ba9deb749a6b6ce88b9c6997dae


SHA1:

19d9b52477606b78bdce568235c0acb9321c1bc4


SHA256:

14ce93ae01d50b9d2ff3c36c3edd574a9f8bcec56451f3a865 fcc210c617a77b


Архив с GenP состоит из следующих частей:

https://forum.antichat.xyz/attachments/4937599/1727474740656.png

Исполняемый файл и исходник со всем необходимым, если мы хотим собрать патчер сами. Исходник написан в виде скрипта для AutoIt.

https://forum.antichat.xyz/attachments/4937599/1727474755438.png

3.0 Установка Creative Cloud и первый запуск
3.1 Про установку Adobe
Установщик для Creative Cloud (далее CC) загружаем с официального сайта. На момент написания статьи его MD5:

0011ec2b0f49f83ccf67aa706a638ccc

. Но и с более новыми версиями всё работает (дата проверки 27.09.2024).

https://forum.antichat.xyz/attachments/4937599/1727474775016.png

ОБЯЗАТЕЛЬНО снимаем тут галочку, чтобы патчи не слетали. Должно быть так, как на скрине.

3.2 Запуск GenP
После установки видим магазин приложений, где нам предлагают купить софт.

Попробуем запустить GenP.

Он предлагает повысить привилегии GenP, чтобы нормально пропатчить XD и UWP-приложения. Откажемся.
Патчинг происходит таким образом:

Патчим Adobe Creative Cloud, чтобы скачивать приложения - кнопка "Patch CC"

Патчим отдельные приложения - кнопки "Search" и "Patch".


Нажмём "Patch CC".

Все приложения Adobe CC закрываются. Выводится лог патча: какой файл патчится и что в нём ищется/заменяется - это первая/вторая строка после названия файла. Попробуем запустить Adobe CC.

Появилась возможность скачивать любое приложение, как будто у нас есть подписка. Скачаем и запустим Adobe Audition для теста.

Нас просят купить подписку. Вернёмся в GenP и выполним поиск.

Пропатчим. Перед этим желательно закрыть Audition.

Нам вывелся лог патча. Попробуем запустить снова программу.

Теперь можем спокойно использовать софт. Также есть возможность заблокировать всплывающие окна (pop-up's). Adobe Illustrator также успешно патчится.

Более того, мы можем восстановить оригинальные файлы до патча через кнопку "Restore".

4.0 Разбираемся в исходном коде
Код представлен в виде скрипта для AutoIt.

Читается как многие скриптовые языки вроде того же Python.

4.1 Начало скрипта
Первые две строчки кода указывают, что в трее не будет иконки от скрипта и то что он выполняется с правами админа.

Visual Basic:



#
NoTrayIcon
#
RequireAdmin


Затем идут настройки конкретно для AutoIT.

Visual Basic:



#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#
AutoIt3Wrapper_Icon
=
ICONS
/
Logo
.
ico
#
AutoIt3Wrapper_Outfile_x64
=
GenP
-
3.4
.14
.1
.
exe
#
AutoIt3Wrapper_Res_Comment
=
GenP v3
.4
.14
.1
#
AutoIt3Wrapper_Res_Description
=
GenP v3
.4
.14
.1
#
AutoIt3Wrapper_Res_Fileversion
=
3.4
.14
.1
#
AutoIt3Wrapper_Res_ProductName
=
GenP v3
.4
.14
.1
#
AutoIt3Wrapper_Res_ProductVersion
=
3.4
.14
.1
#
AutoIt3Wrapper_Res_CompanyName
=
GenP
#
AutoIt3Wrapper_Res_LegalCopyright
=
GenP
#
AutoIt3Wrapper_Res_LegalTradeMarks
=
GenP
#
AutoIt3Wrapper_Res_requestedExecutionLevel
=
asInvoker
#
AutoIt3Wrapper_Run_Au3Stripper
=
y
#
Au3Stripper_Parameters
=
/
pe
/
sf
/
sv
/
rm
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****




#NoTrayIcon

- отключает иконку в системном трее для этого скрипта.


#RequireAdmin

- требует права администратора для выполнения скрипта.


#AutoIt3Wrapper_Icon=ICONS/Logo.ico

- задает иконку для исполняемого файла.


#AutoIt3Wrapper_Outfile_x64=GenP-3.4.14.1.exe

- указывает имя выходного файла для 64-битной системы.


#AutoIt3Wrapper_Res_*

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


#AutoIt3Wrapper_Res_requestedExecutionLevel=asInvo ker

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


#AutoIt3Wrapper_Run_Au3Stripper=y

- включает использование Au3Stripper, инструмента для уменьшения размера скрипта.


#Au3Stripper_Parameters=/pe /sf /sv /rm

- параметры для Au3Stripper, которые включают удаление комментариев, пустых строк и других ненужных элементов.
Затем идут include'ы.

Visual Basic:



#
include

#
include

#
include

#
include

#
include

#
include

#
include

#
include

#
include

#
include



Далее небольшой блок кода:

Visual Basic:



AutoItSetOption
(
"GUICloseOnESC"
,
0
)
;
1
=
ESC closes
,
0
=
ESC won
't close
Global
Const
$
g_AppWndTitle
=
"GenP v3.4.14.1"
,
$
g_AppVersion
=
"Original version by uncia/CGP - GenP Community Edition - v3.4.14.1"
If
_Singleton
(
$
g_AppWndTitle
,
1
)
=
0
Then
Exit
EndIf


Первая строка указывает, что при нажатии ESC окно программы не будет закрываться. Вторая строка указывает заголовок приложения и дополнительную информацию о приложении.
Блок

if

служит для запуска только одного экземпляра приложения.

Затем объявляются глобальные переменные:

Visual Basic:



Global
$
MyLVGroupIsExpanded
=
True
Global
$
fInterrupt
=
0
Global
$
FilesToPatch[
0
][
1
]
,
$
FilesToPatchNull[
0
][
1
]
Global
$
FilesToRestore[
0
][
1
]
,
$
fFilesListed
=
0
Global
$
MyhGUI
,
$
hTab
,
$
hMainTab
,
$
hLogTab
,
$
idMsg
,
$
idListview
,
$
g_idListview
,
$
idButtonSearch
,
$
idButtonStop
Global
$
idButtonCustomFolder
,
$
idBtnCure
,
$
idBtnDeselectAll
,
$
ListViewSelectFlag
=
1
Global
$
idBtnBlockPopUp
,
$
idBtnPatchCC
,
$
idMemo
,
$
timestamp
,
$
idLog
,
$
idBtnRestore
,
$
idBtnCopyLog


Далее идёт проверка наличия

config.ini

. Если его нет, то он создаётся по указанному пути.

Visual Basic:



Global
$
sINIPath
=
@
ScriptDir
&
"\config.ini"
If
Not
FileExists
(
$
sINIPath
)
Then
FileInstall
(
"config.ini"
,
@
ScriptDir
&
"\config.ini"
)
EndIf


Этот конфиг-файл выглядит так:

Часть контента скрыл, чтобы скрин поместился на экран. Этот файл важен для работы патчера, так как содержит названия файлов, что будет искать патчер, сами патчи и указания, какие конкретно патчи к чему применяются.

Секция

[Default]

:
Содержит ключ Path, который указывает путь по умолчанию до папки с программами Adobe.


Секция

[TargetFiles]

:
Содержит список файлов, которые будет искать патчер.


Секция

[DefaultPatterns]

:
Содержит ключ Values с набором имён применяемых патчей. Используется для тех программ, что нет в [CustomPatterns].


Секция

[CustomPatterns]

:
Содержит ключи для различных файлов и их соответствующие имена патчей.


Секция

[Patches]

:
Содержит ключи (имена патчей) и значения (сами патчи в виде искомых и заменяемых байт).

Затем мы читаем из INI-файла путь до папки с программами Adobe по умолчанию. Если указанного пути нет, то используем путь по умолчанию - это

"C:\Program Files\Adobe"

. Также мы пишем этот путь в INI-файл.

Visual Basic:



Global
$
MyDefPath
=
IniRead
(
$
sINIPath
,
"Default"
,
"Path"
,
"C:\Program Files\Adobe"
)
If
Not
FileExists
(
$
MyDefPath
)
Or
Not
StringInStr
(
FileGetAttrib
(
$
MyDefPath
)
,
"D"
)
Then
IniWrite
(
$
sINIPath
,
"Default"
,
"Path"
,
"C:\Program Files\Adobe"
)
$
MyDefPath
=
"C:\Program Files\Adobe"
EndIf


Далее идёт работа с Nsudo - инструмент, позволяющий запускать процессы с правами System или TrustedInstaller. По описанию она нужна для корректной работы Adobe XD и приложений UWP.

Вот краткое объяснение функции:

Сначала происходит копирование файла

NSudoLG.exe

во временную директорию (обычно

C:\Users\\AppData\Local\Temp

). Далее проверка.

Если скрипт не запущен от имени

SYSTEM

и файл

NSudoLG.exe

существует:
Выводится диалоговое окно с вопросом о повышении привилегий до TrustedInstaller.
Если пользователь соглашается, скрипт перезапускается с повышенными привилегиями.
Если условия не выполнены:
Проверяется существование директории

"C:\Windows\System32\config\systemprofile\Desktop"

. Нужна для нормальной работы приложений, чьи привилегии были повышены.
Если директория не существует, она создаётся.
В конце удаление временного файла

NSudoLG.exe

.

Затем снова объявляются глобальные переменные:

Visual Basic:



Global
$
MyRegExpGlobalPatternSearchCount
=
0
,
$
Count
=
0
,
$
idProgressBar
Global
$
aOutHexGlobalArray[
0
]
,
$
aNullArray[
0
]
,
$
aInHexArray[
0
]
Global
$
MyFileToParse
=
""
,
$
MyFileToParsSweatPea
=
""
,
$
MyFileToParseEaclient
=
""
Global
$
sz_type
,
$
bFoundAcro32
=
False
,
$
bFoundLrARM
=
False
,
$
bFoundCCARM
=
False
,
$
bFoundPsARM
=
False
,
$
bFoundGenericARM
=
False
,
$
aSpecialFiles
,
$
sSpecialFiles
=
"|"
Global
$
ProgressFileCountScale
,
$
FileSearchedCount


Далее идёт код, что читает содержимое секций

TargetFiles

и

CustomPatterns

из

config.ini

.

Visual Basic:



Local
$
tTargetFileList_Adobe
=
IniReadSection
(
$
sINIPath
,
"TargetFiles"
)
Global
$
TargetFileList_Adobe[
0
]
If
Not
@
error
Then
ReDim
$
TargetFileList_Adobe[
$
tTargetFileList_Adobe[
0
][
0
]]
For
$
i
=
1
To
$
tTargetFileList_Adobe[
0
][
0
]
$
TargetFileList_Adobe[
$
i
-
1
]
=
StringReplace
(
$
tTargetFileList_Adobe[
$
i][
1
]
,
'"', "")
Next
EndIf
;_ArrayDisplay
(
$
TargetFileList_Adobe
,
"TargetFileList_Adobe"
)
$
aSpecialFiles
=
IniReadSection
(
$
sINIPath
,
"CustomPatterns"
)
;_ArrayDisplay
(
$
aSpecialFiles
)
For
$
i
=
1
To
UBound
(
$
aSpecialFiles
)
-
1
$
sSpecialFiles
=
$
sSpecialFiles
&
$
aSpecialFiles[
$
i][
0
]
&
"|"
Next
;MsgBox
(
0
,
""
,
$
sSpecialFiles
)


Тут есть даже отладочный код. Можно убрать комментарии и запустить скрипт с ним. Для дальнейшего понимания патчера важно запомнить, что:


$TargetFileList_Adobe

-

TargetFiles

из конфига.


$sSpecialFiles

-

CustomPatterns

из конфига.

Затем идёт блок кода для настройки GUI-части:

Visual Basic:



GUIRegisterMsg
(
$
WM_COMMAND
,
"WM_COMMAND"
)
MainGui
(
)


Функция

MainGui

находится в самом скрипте и создает графический интерфейс для приложения: табы, ListView, кнопки для выбора пути, поиска, патчинга, блокировки всплывающих окон и восстановления файлов, а также прогресс-бар и лог активности.
После этого идёт главный код в цикле

While 1

.

4.2 Основной цикл патчера

Visual Basic:



$
idMsg
=
GUIGetMsg
(
)
Select
Case
$
idMsg
=
$
GUI_EVENT_CLOSE
GUIDelete
(
$
MyhGUI
)
Exit
Case
$
idMsg
=
$
GUI_EVENT_RESIZED
ContinueCase
Case
$
idMsg
=
$
GUI_EVENT_RESTORE
ContinueCase
Case
$
idMsg
=
$
GUI_EVENT_MAXIMIZE
Local
$
iWidth
Local
$
aGui
=
WinGetPos
(
$
MyhGUI
)
Local
$
aRect
=
_GUICtrlListView_GetViewRect
(
$
g_idListview
)
If
(
$
aRect[
2
]
>
$
aGui[
2
]
)
Then
$
iWidth
=
$
aGui[
2
]
-
75
Else
$
iWidth
=
$
aRect[
2
]
-
25
EndIf
GUICtrlSendMsg
(
$
idListview
,
$
LVM_SETCOLUMNWIDTH
,
1
,
$
iWidth
)


Этот фрагмент настраивает закрытие окна, изменение его размера и максимизацию. Далее идёт обработка кнопки "Stop" в поиске.

Далее в коде будет обработка событий для кнопки "Search".

4.2.1. Обработчик кнопки поиска
В самом начале идёт настройка списка файлов (в

ListView

) под вывод и появление надписи, что нам нужно подождать:

Visual Basic:



Case
$
idMsg
=
$
idButtonSearch
$
fInterrupt
=
0
GUICtrlSetState
(
$
idButtonSearch
,
$
GUI_HIDE
)
GUICtrlSetState
(
$
idButtonStop
,
$
GUI_SHOW
)
ToggleLog
(
0
)
GUICtrlSetState
(
$
idBtnDeselectAll
,
128
)
GUICtrlSetState
(
$
idBtnBlockPopUp
,
128
)
GUICtrlSetState
(
$
idListview
,
128
)
GUICtrlSetState
(
$
idBtnCure
,
128
)
GUICtrlSetState
(
$
idButtonCustomFolder
,
128
)
GUICtrlSetState
(
$
idBtnPatchCC
,
128
)
;Search through all files
and
folders
in
directory
and
fill ListView
_GUICtrlListView_DeleteAllItems
(
$
g_idListview
)
_GUICtrlListView_SetExtendedListViewStyle
(
$
idListview
,
BitOR
(
$
LVS_EX_FULLROWSELECT
,
$
LVS_EX_GRIDLINES
,
$
LVS_EX_DOUBLEBUFFER
)
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
0
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
1
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
2
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
2
)
_GUICtrlListView_RemoveAllGroups
(
$
idListview
)
_GUICtrlListView_InsertGroup
(
$
idListview
,
-
1
,
1
,
""
,
1
)
; Group
1
_GUICtrlListView_SetGroupInfo
(
$
idListview
,
1
,
"Info"
,
1
,
$
LVGS_COLLAPSIBLE
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
0
,
""
,
1
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
1
,
"Preparing..."
,
1
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
2
,
""
,
1
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
3
,
"Be patient, please."
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
0
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
1
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
2
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
3
,
1
)
_Expand_All_Click
(
)
_GUICtrlListView_SetGroupInfo
(
$
idListview
,
1
,
"Info"
,
1
,
$
LVGS_COLLAPSIBLE
)


Функция

_Expand_All_Click

самописная:

Visual Basic:



Func _Expand_All_Click
(
)
Local
$
aInfo
,
$
aCount
=
_GUICtrlListView_GetGroupCount
(
$
idListview
)
; Group Count
If
$
aCount
>
0
Then
; Change group information
For
$
i
=
1
To
28
$
aInfo
=
_GUICtrlListView_GetGroupInfo
(
$
idListview
,
$
i
)
_GUICtrlListView_SetGroupInfo
(
$
idListview
,
$
i
,
$
aInfo[
0
]
,
$
aInfo[
1
]
,
$
LVGS_NORMAL
)
_GUICtrlListView_SetGroupInfo
(
$
idListview
,
$
i
,
$
aInfo[
0
]
,
$
aInfo[
1
]
,
$
LVGS_COLLAPSIBLE
)
Next
EndIf
EndFunc ;
=
=
>
_Expand_All_Click


Также есть обратная функция:

Visual Basic:



Func _Collapse_All_Click
(
)
Local
$
aInfo
,
$
aCount
=
_GUICtrlListView_GetGroupCount
(
$
idListview
)
; Group Count
If
$
aCount
>
0
Then
If
$
MyLVGroupIsExpanded
=
1
Then
; Change group information
For
$
i
=
1
To
28
$
aInfo
=
_GUICtrlListView_GetGroupInfo
(
$
idListview
,
$
i
)
_GUICtrlListView_SetGroupInfo
(
$
idListview
,
$
i
,
$
aInfo[
0
]
,
$
aInfo[
1
]
,
$
LVGS_COLLAPSED
)
Next
Else
_Expand_All_Click
(
)
EndIf
$
MyLVGroupIsExpanded
=
Not
$
MyLVGroupIsExpanded
EndIf
EndFunc ;
=
=
>
_Collapse_All_Click



_Expand_All_Click

: Раскрывает все группы в ListView, делая их видимыми и сворачиваемыми.


_Collapse_All_Click

: Сворачивает все группы, если они были раскрыты, и наоборот, раскрывает все группы, если они были свернуты.

Далее идут подготовительные блоки:

Visual Basic:



; Clear previous results
$
FilesToPatch
=
$
FilesToPatchNull
$
FilesToRestore
=
$
FilesToPatchNull
$
timestamp
=
TimerInit
(
)


Очищаем предыдущие результаты и настраиваем таймер. Затем идёт блок, что ищет файлы, считает их количество, настраивает прогресс-бар и некоторые переменные.

Visual Basic:



Local
$
FileCount
Local
$
aSize
=
DirGetSize
(
$
MyDefPath
,
$
DIR_EXTENDED
)
; extended mode
If
UBound
(
$
aSize
)
>
=
2
Then
$
FileCount
=
$
aSize[
1
]
$
ProgressFileCountScale
=
100
/
$
FileCount
$
FileSearchedCount
=
0
ProgressWrite
(
0
)
RecursiveFileSearch
(
$
MyDefPath
,
0
,
$
FileCount
)
;Search through all files
and
folders
Sleep
(
100
)
ProgressWrite
(
0
)
EndIf


Параметр

$MyDefPath

в этот момент содержит содержимое секции

[Default]

из INI-файла:

INI:



[Default]
Path
=C:\Program Files\Adobe


Функция

RecursiveFileSearch

самописная:

Visual Basic:



Func RecursiveFileSearch
(
$
INSTARTDIR
,
$
DEPTH
,
$
FileCount
)
;_FileListToArrayEx
_GUICtrlListView_SetItemText
(
$
idListview
,
1
,
"Searching for files."
,
1
)
;_GUICtrlListView_SetItemGroupID
(
$
idListview
,
0
,
1
)
Local
$
RecursiveFileSearch_MaxDeep
=
6
; Local
$
RecursiveFileSearch_WhenFoundRaiseToLevel
=
0
;
0
to
disable raising
If
$
DEPTH
>
$
RecursiveFileSearch_MaxDeep
Then
Return
Local
$
STARTDIR
=
$
INSTARTDIR
&
"\"
$
FileSearchedCount
+
=
1
Local
$
HSEARCH
=
FileFindFirstFile
(
$
STARTDIR
&
"*.*"
)
If
@
error
Then
Return
Local
$
NEXT
,
$
IPATH
,
$
isDir
While
$
fInterrupt
=
0
$
NEXT
=
FileFindNextFile
(
$
HSEARCH
)
$
FileSearchedCount
+
=
1
If
@
error
Then
ExitLoop
$
isDir
=
StringInStr
(
FileGetAttrib
(
$
STARTDIR
&
$
NEXT
)
,
"D"
)
If
$
isDir
Then
Local
$
targetDepth
$
targetDepth
=
RecursiveFileSearch
(
$
STARTDIR
&
$
NEXT
,
$
DEPTH
+
1
,
$
FileCount
)
; raise up
in
recursion
to
wanted level
;~
if
(
$
targetDepth
>
0
)
and
_
;~
(
$
targetDepth

1
Then
If
Not
StringInStr
(
$
IPATH
,
".bak"
)
Then
;_ArrayAdd
(
$
FilesToPatch
,
$
DEPTH
&
" - "
&
$
IPATH
)
If
StringInStr
(
$
IPATH
,
"Adobe"
)
Or
StringInStr
(
$
IPATH
,
"Acrobat"
)
Then
If
StringInStr
(
$
IPATH
,
"4.js"
)
And
Not
StringInStr
(
$
IPATH
,
"UXP\com.adobe.ccx.start\js\4.js"
)
Then
Return
EndIf
If
StringInStr
(
$
IPATH
,
"manifest.json"
)
And
Not
StringInStr
(
$
IPATH
,
"UXP\com.adobe.ccx.start\manifest.json"
)
Then
Return
EndIf
_ArrayAdd
(
$
FilesToPatch
,
$
IPATH
)
EndIf
Else
_ArrayAdd
(
$
FilesToRestore
,
$
IPATH
)
EndIf
; File Found
and
stored
-
Quit search
in
current dir
;~
return
$
RecursiveFileSearch_WhenFoundRaiseToLevel
EndIf
Next
EndIf
EndIf
WEnd
;Lazy screenupdates
If
1
=
Random
(
0
,
10
,
1
)
Then
MemoWrite
(
@
CRLF
&
"Searching in "
&
$
FileCount
&
" files"
&
@
TAB
&
@
TAB
&
"Found : "
&
UBound
(
$
FilesToPatch
)
&
@
CRLF
&
_
"---"
&
@
CRLF
&
_
"Level: "
&
$
DEPTH
&
" Time elapsed : "
&
Round
(
TimerDiff
(
$
timestamp
)
/
1000
,
0
)
&
" second(s)"
&
@
TAB
&
@
TAB
&
"Excluded because of *.bak: "
&
UBound
(
$
FilesToRestore
)
&
@
CRLF
&
_
"---"
&
@
CRLF
&
_
$
INSTARTDIR
_
)
ProgressWrite
(
$
ProgressFileCountScale
*
$
FileSearchedCount
)
EndIf
FileClose
(
$
HSEARCH
)
EndFunc ;
=
=
>
RecursiveFileSearch


Она выполняет поиск файлов из

$TargetFileList_Adobe

(секция

[TargetFiles]

в INI) и добавляет пути до них в массив

$FilesToPatch

- файла для патчинга, которые нашлись из

[TargetFiles]

. Также если где-то нашлись бекапы оригинальных файлов (с постфиксом

.bak

), то они добавляются в

$FilesToRestore

.
Для теста можно добавить вывод массива в процессе работы.

Или так:

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

Visual Basic:



If
$
MyDefPath
=
"C:\Program Files"
Or
$
MyDefPath
=
"C:\Program Files\Adobe"
Then
Local
$
sProgramFiles
=
EnvGet
(
'ProgramFiles(x86)') & "\Common Files\Adobe"
$
aSize
=
DirGetSize
(
$
sProgramFiles
,
$
DIR_EXTENDED
)
; extended mode
If
UBound
(
$
aSize
)
>
=
2
Then
$
FileCount
=
$
aSize[
1
]
RecursiveFileSearch
(
$
sProgramFiles
,
0
,
$
FileCount
)
;Search through all files
and
folders
ProgressWrite
(
0
)
EndIf
EndIf


Затем идёт вывод найденных файлов через самописную функцию

FillListViewWithFiles

:

Visual Basic:



Func FillListViewWithFiles
(
)
_GUICtrlListView_DeleteAllItems
(
$
g_idListview
)
_GUICtrlListView_SetExtendedListViewStyle
(
$
idListview
,
BitOR
(
$
LVS_EX_FULLROWSELECT
,
$
LVS_EX_GRIDLINES
,
$
LVS_EX_DOUBLEBUFFER
,
$
LVS_EX_CHECKBOXES
)
)
; Two column load
If
UBound
(
$
FilesToPatch
)
>
0
Then
Global
$
aItems[UBound
(
$
FilesToPatch
)
][
2
]
For
$
i
=
0
To
UBound
(
$
aItems
)
-
1
$
aItems[
$
i][
0
]
=
$
i
$
aItems[
$
i][
1
]
=
$
FilesToPatch[
$
i][
0
]
Next
_GUICtrlListView_AddArray
(
$
idListview
,
$
aItems
)
MemoWrite
(
@
CRLF
&
UBound
(
$
FilesToPatch
)
&
" File(s) were found in "
&
Round
(
TimerDiff
(
$
timestamp
)
/
1000
,
0
)
&
" second(s) at:"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
MyDefPath
&
@
CRLF
&
"---"
&
@
CRLF
&
"Press the 'Patch Files'"
)
LogWrite
(
1
,
UBound
(
$
FilesToPatch
)
&
" File(s) were found in "
&
Round
(
TimerDiff
(
$
timestamp
)
/
1000
,
0
)
&
" second(s)"
&
@
CRLF
)
;_ArrayDisplay
(
$
FilesToPatch
)
$
fFilesListed
=
1
Else
MemoWrite
(
@
CRLF
&
"Nothing was found in"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
MyDefPath
&
@
CRLF
&
"---"
&
@
CRLF
&
"waiting for user action"
)
LogWrite
(
1
,
"Nothing was found in "
&
$
MyDefPath
)
$
fFilesListed
=
0
EndIf
EndFunc ;
=
=
>
FillListViewWithFiles


Информация выводится в логах. В конце обработчика кнопки будет настройка GUI-частей.
Также есть такие небольшие функции:

Visual Basic:



; Write a line
to
the memo control
Func MemoWrite
(
$
sMessage
)
GUICtrlSetData
(
$
idMemo
,
$
sMessage
)
EndFunc ;
=
=
>
MemoWrite

Func LogWrite
(
$
bTS
,
$
sMessage
)
GUICtrlSetDataEx
(
$
idLog
,
$
sMessage
,
$
bTS
)
EndFunc ;
=
=
>
LogWrite

Func ToggleLog
(
$
bShow
)
If
$
bShow
=
1
Then
GUICtrlSetState
(
$
idMemo
,
$
GUI_HIDE
)
GUICtrlSetState
(
$
idLog
,
$
GUI_SHOW
)
Else
GUICtrlSetState
(
$
idLog
,
$
GUI_HIDE
)
GUICtrlSetState
(
$
idMemo
,
$
GUI_SHOW
)
EndIf
EndFunc ;
=
=
>
ToggleLog

Func SendToClipBoard
(
)
If
BitAND
(
GUICtrlGetState
(
$
idMemo
)
,
$
GUI_HIDE
)
=
$
GUI_HIDE
Then
ClipPut
(
GUICtrlRead
(
$
idLog
)
)
Else
ClipPut
(
GUICtrlRead
(
$
idMemo
)
)
EndIf
EndFunc ;
=
=
>
SendToClipBoard


Краткое описание этих функций:


MemoWrite

записывает строку текста в элемент управления

Memo

(текстовое поле для многострочного ввода).


LogWrite

записывает строку текста в элемент управления

Log

(журнал) со временной меткой к сообщению или без неё.


ToggleLog

переключает видимость между элементами управления

Memo

и

Log

.


SendToClipBoard

копирует текст из видимого элемента управления (либо

Memo

, либо

Log

) в буфер обмена. Если элемент

Memo

скрыт, копируется текст из

Log

, иначе копируется текст из

Memo

.
Тут можно увидеть использование функции

GUICtrlSetDataEx

. Её код прилагается:

Visual Basic:



Func GUICtrlSetDataEx
(
$
hWnd
,
$
sText
,
$
bTS
)
If
Not
IsHWnd
(
$
hWnd
)
Then
$
hWnd
=
GUICtrlGetHandle
(
$
hWnd
)
Local
$
iLength
=
DllCall
(
"user32.dll"
,
"lresult"
,
"SendMessageW"
,
"hwnd"
,
$
hWnd
,
"uint"
,
0
x000E
,
"wparam"
,
0
,
"lparam"
,
0
)
DllCall
(
"user32.dll"
,
"lresult"
,
"SendMessageW"
,
"hwnd"
,
$
hWnd
,
"uint"
,
0
xB1
,
"wparam"
,
$
iLength[
0
]
,
"lparam"
,
$
iLength[
0
]
)
;
$
EM_SETSEL
If
$
bTS
=
1
Then
Local
$
iData
=
@
CRLF
&
@
YEAR
&
"-"
&
@
MON
&
"-"
&
@
MDAY
&
" "
&
@
HOUR
&
":"
&
@
MIN
&
":"
&
@
SEC
&
"."
&
@
MSEC
&
" "
&
$
sText
Else
Local
$
iData
=
$
sText
EndIf
DllCall
(
"user32.dll"
,
"lresult"
,
"SendMessageW"
,
"hwnd"
,
$
hWnd
,
"uint"
,
0
xC2
,
"wparam"
,
True
,
"wstr"
,
$
iData
)
;
$
EM_REPLACESEL
EndFunc ;
=
=
>
GUICtrlSetDataEx


Она выполняет запись текста в элемент управления (например, текстовое поле) с возможностью добавления временной метки.

4.2.2. Обработчик кнопки выбора папки
Далее идёт обработчик выбора своей папки с программами Adobe вместо стандартной.

Visual Basic:



Case
$
idMsg
=
$
idButtonCustomFolder ;
Select
Custom Path

ToggleLog
(
0
)
MyFileOpenDialog
(
)
_Expand_All_Click
(
)
If
$
fFilesListed
=
0
Then
GUICtrlSetState
(
$
idBtnCure
,
128
)
GUICtrlSetState
(
$
idBtnDeselectAll
,
128
)
GUICtrlSetState
(
$
idButtonSearch
,
64
)
GUICtrlSetState
(
$
idButtonSearch
,
256
)
;
Set
focus
Else
GUICtrlSetState
(
$
idButtonSearch
,
128
)
GUICtrlSetState
(
$
idBtnDeselectAll
,
64
)
GUICtrlSetState
(
$
idBtnCure
,
64
)
GUICtrlSetState
(
$
idBtnCure
,
256
)
;
Set
focus
EndIf


Функция

MyFileOpenDialog

самописная.

Visual Basic:



Func MyFileOpenDialog
(
)
; Create a constant variable
in
Local scope
of
the message
to
display
in
FileOpenDialog
.
Local
Const
$
sMessage
=
"Select a Path"
; Display an open dialog
to
select
a file
.
FileSetAttrib
(
"C:\Program Files\WindowsApps"
,
"-H"
)
Local
$
MyTempPath
=
FileSelectFolder
(
$
sMessage
,
$
MyDefPath
,
0
,
$
MyDefPath
,
$
MyhGUI
)
If
@
error
Then
; Display the
error
message
.
;MsgBox
(
$
MB_SYSTEMMODAL
,
""
,
"No folder was selected."
)
FileSetAttrib
(
"C:\Program Files\WindowsApps"
,
"+H"
)
MemoWrite
(
@
CRLF
&
"Path"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
MyDefPath
&
@
CRLF
&
"---"
&
@
CRLF
&
"waiting for user action"
)
Else
GUICtrlSetState
(
$
idBtnCure
,
128
)
$
MyDefPath
=
$
MyTempPath
IniWrite
(
$
sINIPath
,
"Default"
,
"Path"
,
$
MyDefPath
)
_GUICtrlListView_DeleteAllItems
(
$
g_idListview
)
_GUICtrlListView_SetExtendedListViewStyle
(
$
idListview
,
BitOR
(
$
LVS_EX_GRIDLINES
,
$
LVS_EX_FULLROWSELECT
,
$
LVS_EX_SUBITEMIMAGES
)
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
0
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
1
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
2
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
3
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
4
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
5
)
_GUICtrlListView_AddItem
(
$
idListview
,
""
,
6
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
0
,
""
,
1
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
1
,
"Path:"
,
1
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
2
,
" "
&
$
MyDefPath
,
1
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
3
,
"Step 1:"
,
1
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
4
,
" Press 'Search Files' - wait until GenP finds all files"
,
1
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
5
,
"Step 2:"
,
1
)
_GUICtrlListView_AddSubItem
(
$
idListview
,
6
,
" Press 'Patch Files' - wait until GenP will do it's job"
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
0
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
1
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
2
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
3
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
4
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
5
,
1
)
_GUICtrlListView_SetItemGroupID
(
$
idListview
,
6
,
1
)
_GUICtrlListView_SetGroupInfo
(
$
idListview
,
1
,
"Info"
,
1
,
$
LVGS_COLLAPSIBLE
)
FileSetAttrib
(
"C:\Program Files\WindowsApps"
,
"+H"
)
MemoWrite
(
@
CRLF
&
"Path"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
MyDefPath
&
@
CRLF
&
"---"
&
@
CRLF
&
"Press the Search button"
)
; Display the selected folder
.
;MsgBox
(
$
MB_SYSTEMMODAL
,
""
,
"You chose the following folder:"
&
@
CRLF
&
$
MyDefPath
)
GUICtrlSetState
(
$
idBtnBlockPopUp
,
$
GUI_SHOW
)
GUICtrlSetState
(
$
idBtnRestore
,
$
GUI_HIDE
)
$
fFilesListed
=
0
EndIf
EndFunc ;
=
=
>
MyFileOpenDialog


Она отображает диалоговое окно для выбора папки и снимает скрытый атрибут с папки

C:\Program Files\WindowsApps

. Если папка выбрана, обновляет путь, сохраняет его в INI-файле, очищает и обновляет

ListView

с новыми элементами, восстанавливает скрытый атрибут и выводит сообщение с инструкцией.

4.2.3. Обработчик кнопки выбора/отмены всех элементов
Затем идёт обработчик для выбора или отмены выбора всех элементов:

Visual Basic:



Case
$
idMsg
=
$
idBtnDeselectAll ; Deselect
-
Select
All
ToggleLog
(
0
)
If
$
ListViewSelectFlag
=
1
Then
For
$
i
=
0
To
_GUICtrlListView_GetItemCount
(
$
idListview
)
-
1
_GUICtrlListView_SetItemChecked
(
$
idListview
,
$
i
,
0
)
Next
$
ListViewSelectFlag
=
0
;
Set
Flag
to
Deselected State
Else
For
$
i
=
0
To
_GUICtrlListView_GetItemCount
(
$
idListview
)
-
1
_GUICtrlListView_SetItemChecked
(
$
idListview
,
$
i
,
1
)
Next
$
ListViewSelectFlag
=
1
;
Set
Flag
to
Selected State
EndIf


4.2.4. Обработчик кнопки Pop-up
Следующий обработчик для кнопки "Pop-up":

Visual Basic:



Case
$
idMsg
=
$
idBtnBlockPopUp ; Pop
-
up button
ToggleLog
(
0
)
BlockPopUp
(
)


Функцию

BlockPopUp

нам нужно разобрать.

Visual Basic:



Func BlockPopUp
(
)
GUICtrlSetState
(
$
hLogTab
,
$
GUI_SHOW
)
GUICtrlSetState
(
$
idBtnBlockPopUp
,
128
)
MemoWrite
(
@
CRLF
&
"Checking for an active internet connection..."
&
@
CRLF
&
""
&
@
CRLF
&
""
)
Local
$
sCmdInfo
=
"""C:\Windows\System32\WindowsPowerShell\v1.0\PowerSh ell.exe"" -Command ""Test-Connection 8.8.8.8 -Count 1 -Quiet"""
Local
$
iPID
=
Run
(
$
sCmdInfo
,
""
,
@
SW_HIDE
,
BitOR
(
$
STDERR_CHILD
,
$
STDOUT_CHILD
)
)
Local
$
sOutput
=
""
While
1
$
sOutput
&
=
StdoutRead
(
$
iPID
)
If
@
error
Then
ExitLoop
WEnd
ProcessWaitClose
(
$
iPID
)
If
StringReplace
(
$
sOutput
,
@
CRLF
,
""
)
=
"True"
Then


Тут происходит настройка GUI и проверка наличия интернет-соединения через Powershell-команду:

Bash:



Test-Connection
8.8
.8.8 -Count
1
-Quiet


Она вернёт нам True, если интернет есть.
Затем идёт блок кода для разрешения IP-адресов доменов

adobe.io

и

3u6k9as4bj.adobestats.io

.

Visual Basic:



If
StringReplace
(
$
sOutput
,
@
CRLF
,
""
)
=
"True"
Then
MemoWrite
(
@
CRLF
&
"Resolving ip-addresses..."
&
@
CRLF
&
""
&
@
CRLF
&
""
)
$
sCmdInfo
=
; Powershell
-
скрипт
$
iPID
=
Run
(
$
sCmdInfo
,
""
,
@
SW_HIDE
,
BitOR
(
$
STDERR_CHILD
,
$
STDOUT_CHILD
)
)
$
sOutput
=
""
While
1
$
sOutput
&
=
StdoutRead
(
$
iPID
)
If
@
error
Then
ExitLoop
WEnd
ProcessWaitClose
(
$
iPID
)
If
StringInStr
(
$
sOutput
,
"False"
)
Then
MemoWrite
(
@
CRLF
&
"Failed to resolve ip-addresses, try using a VPN..."
&
@
CRLF
&
""
&
@
CRLF
&
""
)
Sleep
(
2000
)
Else
.
.
.


Разрешение IP-адресов (или "резолвинг" IP-адресов) — это процесс преобразования доменного имени (например,

example.com

) в его соответствующий IP-адрес (например,

101.34.200.8

).
В функции выполняется PowerShell-скрипт для разрешения IP-адресов доменов

adobe.io

и

3u6k9as4bj.adobestats.io

:

Читается вывод скрипта в переменную

$sOutput

.

Если в выводе содержится "False", записывает сообщение о неудаче в журнал и предлагает использовать VPN.

Иначе добавляется правило в брандмауэр Windows для блокировки IP-адресов. Это разберём далее.
Пока перейдём к самому скрипту. Изначально он был в однострочном виде, но для нашего удобства представим его так:

Bash:



$currentDate
=
Get-Date
$ipAddresses
=
@
(
)
try
{
$SOA
=
(
Resolve-DnsName -Name adobe.io -Type SOA -ErrorAction Stop
)
.PrimaryServer
}
catch
{
$SOA
=
$null
}
if
(
$SOA
)
{
Do
{
if
((New-TimeSpan -Start $currentDate -End (Get-Date))
.TotalSeconds -gt
5
)
{
if
(
$ipAddresses
.Count -eq
0
)
{
$ipAddresses
+=
'False'
}
break
}
try
{
$ipAddress
=
(
Resolve-DnsName -Name adobe.io -Server
$SOA
-ErrorAction Stop
)
.IPAddress
}
catch
{
$ipAddress
=
$null
}
if
(
$ipAddress
)
{
$ipAddresses
+=
$ipAddress
}
$ipAddresses
=
$ipAddresses
|
Select -Unique
|
Sort-Object
}
While
(
$ipAddresses
.Count -lt
8
)
}
else
{
$ipAddresses
+=
'False'
}
Do
{
if
((New-TimeSpan -Start $currentDate -End (Get-Date))
.TotalSeconds -gt
5
-or
$ipAddresses
[
0
]
-eq
'False'
)
{
break
}
try
{
$ipAddress
=
(
Resolve-DnsName -Name 3u6k9as4bj.adobestats.io -ErrorAction Stop
)
.IPAddress
}
catch
{
$ipAddress
=
$null
}
if
(
$ipAddress
)
{
$ipAddresses
+=
$ipAddress
}
$ipAddresses
=
$ipAddresses
|
Select -Unique
|
Sort-Object
}
While
(
$ipAddresses
.Count -lt
12
-and
$ipAddresses
[
0
]
-ne
'False'
)
$ipAddresses
=
$ipAddresses
-ne
'False'
|
Select -Unique
|
Sort-Object
$ipAddressList
=
if
(
$ipAddresses
.Count -eq
0
)
{
'False'
}
else
{
$ipAddresses
-join
','
}
$ipAddressList


Скрипт определяет авторитетный сервер (SOA) для домена

adobe.io

. Затем собирает уникальные IP-адреса для доменов

adobe.io

и

3u6k9as4bj.adobestats.io

.
В итоге мы получим список уникальных IP-адресов такого вида:

Код:



107.22.247.231,18.207.85.246,23.22.254.206...


Затем идёт блок Else, что добавляет правило в брандмауэр Windows для блокировки IP-адресов:

Visual Basic:



If
StringInStr
(
$
sOutput
,
"False"
)
Then
MemoWrite
(
@
CRLF
&
"Failed to resolve ip-addresses, try using a VPN..."
&
@
CRLF
&
""
&
@
CRLF
&
""
)
Sleep
(
2000
)
Else
MemoWrite
(
@
CRLF
&
"Adding Windows Firewall rule..."
&
@
CRLF
&
""
&
@
CRLF
&
""
)
$
sCmdInfo
=
"netsh advfirewall firewall delete rule name=""Adobe Unlicensed Pop-up"""
$
iPID
=
Run
(
$
sCmdInfo
,
""
,
@
SW_HIDE
,
BitOR
(
$
STDERR_CHILD
,
$
STDOUT_CHILD
)
)
ProcessWaitClose
(
$
iPID
)
$
sCmdInfo
=
"netsh advfirewall firewall add rule name=""Adobe Unlicensed Pop-up"" dir=out action=block remoteip="""
&
StringReplace
(
$
sOutput
,
@
CRLF
,
""
)
&
""""
$
iPID
=
Run
(
$
sCmdInfo
,
""
,
@
SW_HIDE
,
BitOR
(
$
STDERR_CHILD
,
$
STDOUT_CHILD
)
)
ProcessWaitClose
(
$
iPID
)
EndIf


Данный код удаляет старое правило в брандмауэре, если оно было:

Bash:



netsh advfirewall firewall delete rule
name
=
"Adobe Unlicensed Pop-up"


Затем уже добавляет своё:

Bash:



netsh advfirewall firewall
add
rule
name
=
"Adobe Unlicensed Pop-up"
dir
=
out
action
=
block
remoteip
=
"..."


Вместо "..." подставятся IP из прошлого ps-скрипта. Получим команду подобного вида:

Bash:



netsh advfirewall firewall
add
rule
name
=
"Adobe Unlicensed Pop-up"
dir
=
out
action
=
block
remoteip
=
"107.22.247.231,18.207.85.246,23.22.254.206..."


Далее идёт блок кода, что работает с файлом

hosts

.

Visual Basic:



MemoWrite
(
@
CRLF
&
"Blocking Hosts..."
&
@
CRLF
&
""
&
@
CRLF
&
""
)
$
sCmdInfo
=
; Powershell
-
скрипт
$
iPID
=
Run
(
$
sCmdInfo
,
""
,
@
SW_HIDE
,
$
STDOUT_CHILD
)
$
sOutput
=
""
While
1
$
sLine
=
StdoutRead
(
$
iPID
)
If
@
error
Then
ExitLoop
$
sOutput
&
=
$
sLine
WEnd
If
StringInStr
(
$
sOutput
,
"Script execution complete."
)
Then
MemoWrite
(
@
CRLF
&
"All Hosts blocked."
&
@
CRLF
&
""
&
@
CRLF
&
""
)
Else
MemoWrite
(
@
CRLF
&
"Failed to block Hosts, try using a VPN..."
&
@
CRLF
&
""
&
@
CRLF
&
""
)
EndIf


Тут снова используется Powershell-скрипт:

Bash:



try
{
if
(
-not
(
[
Security.Principal.WindowsPrincipal
]
[
Security.Principal.WindowsIdentity
]
::GetCurrent
(
))
.IsInRole
(
[
Security.Principal.WindowsBuiltInRole
]
::Administrator
))
{
Write-Host
'Script execution failed...'
return
}
$hostsPath
=
'C:\Windows\System32\drivers\etc\hosts'
$bakPath
=
$hostsPath
+
'.bak'
if
(
-not
(
Test-Path
$bakPath
))
{
Copy-Item -Path
$hostsPath
-Destination
$bakPath
}
$webContent
=
(
Invoke-RestMethod -Uri
'https://a.dove.isdumb.one/list.txt'
-UseBasicParsing
)
.Split
(
$([char]0x0A)
)
|
ForEach-Object
{
$_
.Trim
(
)
}
$currentHostsContent
=
Get-Content -Path
$hostsPath
$startMarker
=
'# '
$endMarker
=
'# '
$blockStart
=
$currentHostsContent
.IndexOf
(
$startMarker
)
$blockEnd
=
$currentHostsContent
.IndexOf
(
$endMarker
)
if
(
$blockStart
-eq -1 -or
$blockEnd
-eq -1
)
{
$currentHostsContent
+=
$startMarker
$currentHostsContent
+=
$endMarker
$blockStart
=
$currentHostsContent
.IndexOf
(
$startMarker
)
$blockEnd
=
$currentHostsContent
.IndexOf
(
$endMarker
)
}
$newBlock
=
@
(
$startMarker
)
+
$webContent
+
$endMarker
$newHostsContent
=
$currentHostsContent
[
0
..
(
$blockStart
-
1
)
]
+
$newBlock
+
$currentHostsContent
[
(
$blockEnd
+
1
)
..
$currentHostsContent
.Length
]
Set-Content -Path
$hostsPath
-Value
$newHostsContent
Write-Host
'Script execution complete.'
}
catch
{
Write-Host
'Script execution failed...'
}


Данный скрипт выполняет предварительные действия:

Проверка наличие прав администратора. Если нет, то завершает выполнение.

Создание резервной копии файла

hosts

:

C:\Windows\System32\drivers\etc\hosts.bak


Загрузка файла

https://a.dove.isdumb.one/list.txt

и разделяет загруженный контент по строкам и удаляет пробелы в начале и конце каждой строки.
Файл

list.txt

можно взять из этого репозитория: GitHub - ignaciocastro/a-dove-is-dumb: Easily block Adobe telemetry checking domains. Continuously Updated. Useful for HostsMan / SwitchHosts / Pi-hole users
А далее происходит работа с файлом

hosts

. Мы записываем содержимое

list.txt

между строками



.

До выполнения скрипта:

Код:



127.0.0.1 localhost


После:

Код:



127.0.0.1 localhost
#
# Данные из list.txt
#


Таким образом, функция

BlockPopUp

просто добавляет правила в брандмауэр и записи в файл

hosts

. Теперь настало время самого интересного - начинаем разбор патчинга Creative Cloud и программ Adobe.

4.2.5. Обработчик кнопки "Patch CC" и как работает эта магия

Обработкой кнопки "Patch CC" занимается вот этот обработчик:

Visual Basic:



Case
$
idMsg
=
$
idBtnPatchCC ; Patch Creative Cloud button
Global
$
appsPanelFile
Global
$
containerBLFile
Global
$
adobeDesktopServiceFile
.
.
.


Первое, что патчит код - это

AppsPanelBL.dll

.

Visual Basic:



If
FileExists
(
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
)
Then
$
appsPanelFile
=
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
ElseIf
FileExists
(
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
)
Then
$
appsPanelFile
=
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
Else
$
appsPanelFile
=
FileOpenDialog
(
"Select a File"
,
@
ScriptDir
,
"AppsPanelBL.dll (AppsPanelBL.dll)"
)
EndIf
ProgressWrite
(
0
)
If
FileExists
(
$
appsPanelFile
)
Then
MyGlobalPatternSearch
(
$
appsPanelFile
)
Sleep
(
100
)
MemoWrite
(
@
CRLF
&
"File Path:"
&
@
CRLF
&
""
&
@
CRLF
&
$
appsPanelFile
&
@
CRLF
&
""
&
@
CRLF
&
""
)
Sleep
(
100
)
MyGlobalPatternPatch
(
$
appsPanelFile
,
$
aOutHexGlobalArray
)
Sleep
(
500
)
EndIf


Выполняется проверка на существование файла. А затем в нём ищутся паттерны, которые будут взяты из INI-файла через

MyGlobalPatternSearch

.

Visual Basic:



Func MyGlobalPatternSearch
(
$
MyFileToParse
)
;ConsoleWrite
(
$
MyFileToParse
&
@
CRLF
)
$
aInHexArray
=
$
aNullArray ; Nullifay Array that will contain Hex later
$
aOutHexGlobalArray
=
$
aNullArray ; Nullifay Array that will contain Hex later

ProgressWrite
(
0
)
$
MyRegExpGlobalPatternSearchCount
=
0
$
Count
=
15
Local
$
sFileName
=
StringRegExpReplace
(
$
MyFileToParse
,
"^.*\\"
,
""
)
Local
$
sExt
=
StringRegExpReplace
(
$
sFileName
,
"^.*\."
,
""
)
MemoWrite
(
@
CRLF
&
$
MyFileToParse
&
@
CRLF
&
"---"
&
@
CRLF
&
"Preparing to Analyze"
&
@
CRLF
&
"---"
&
@
CRLF
&
"*****"
)
LogWrite
(
1
,
"Checking File: "
&
$
sFileName
&
" "
)
;MsgBox
(
$
MB_SYSTEMMODAL
,
""
,
"$sFileName = "
&
$
sFileName
&
@
CRLF
&
"$sExt = "
&
$
sExt
)
If
$
sExt
=
"exe"
Then
_ProcessCloseEx
(
""""
&
$
sFileName
&
""""
)
EndIf
If
$
sFileName
=
"AppsPanelBL.dll"
Or
$
sFileName
=
"ContainerBL.dll"
Or
$
sFileName
=
"Adobe Desktop Service.exe"
Then
_ProcessCloseEx
(
"""Creative Cloud.exe"""
)
_ProcessCloseEx
(
"""Adobe Desktop Service.exe"""
)
Sleep
(
100
)
EndIf
If
StringInStr
(
$
sSpecialFiles
,
$
sFileName
)
Then
;MsgBox
(
$
MB_SYSTEMMODAL
,
""
,
"Special File: "
&
$
sFileName
)
LogWrite
(
0
,
" - using Custom Patterns"
)
ExecuteSearchPatterns
(
$
sFileName
,
0
,
$
MyFileToParse
)
Else
LogWrite
(
0
,
" - using Default Patterns"
)
ExecuteSearchPatterns
(
$
sFileName
,
1
,
$
MyFileToParse
)
;MsgBox
(
$
MB_SYSTEMMODAL
,
""
,
"File: "
&
$
sFileName
&
@
CRLF
&
"Not in Special Files"
)
EndIf
Sleep
(
100
)
EndFunc ;
=
=
>
MyGlobalPatternSearch


Функции передаётся путь до файла. Затем из него извлекается:

Имя файла вместе с расширением;

Отдельно расширение файла.


Visual Basic:



If
$
sExt
=
"exe"
Then
_ProcessCloseEx
(
""""
&
$
sFileName
&
""""
)
EndIf
If
$
sFileName
=
"AppsPanelBL.dll"
Or
$
sFileName
=
"ContainerBL.dll"
Or
$
sFileName
=
"Adobe Desktop Service.exe"
Then
_ProcessCloseEx
(
"""Creative Cloud.exe"""
)
_ProcessCloseEx
(
"""Adobe Desktop Service.exe"""
)
Sleep
(
100
)
EndIf


Затем закрываются процессы Creative Cloud, чтобы успешно пропатчить нужный файл. В конце функции вызывается

ExecuteSearchPatterns

в зависимости от того, есть ли искомый файл в

$sSpecialFiles

- это секция

[CustomPatterns]

в INI-файле.

Для дальнейшего понимания патчера важно запомнить, что:


$TargetFileList_Adobe

-

TargetFiles

из конфига.


$sSpecialFiles

-

CustomPatterns

из конфига.

Если такой файл есть, то вызывается

ExecuteSearchPatterns($sFileName, 0, $MyFileToParse)

.
Иначе -

ExecuteSearchPatterns($sFileName, 1, $MyFileToParse)

.

Сейчас как раз разберём её.

Visual Basic:



Func ExecuteSearchPatterns
(
$
FileName
,
$
DefaultPatterns
,
$
MyFileToParse
)
Local
$
aPatterns
,
$
sPattern
,
$
sData
,
$
aArray
,
$
sSearch
,
$
sReplace
,
$
iPatternLength
If
$
DefaultPatterns
=
0
Then
$
aPatterns
=
IniReadArray
(
$
sINIPath
,
"CustomPatterns"
,
$
FileName
,
""
)
Else
$
aPatterns
=
IniReadArray
(
$
sINIPath
,
"DefaultPatterns"
,
"Values"
,
""
)
EndIf
;_ArrayDisplay
(
$
aPatterns
,
"Patterns for "
&
$
FileName
)
For
$
i
=
0
To
UBound
(
$
aPatterns
)
-
1
$
sPattern
=
$
aPatterns[
$
i]
$
sData
=
IniRead
(
$
sINIPath
,
"Patches"
,
$
sPattern
,
""
)
If
StringInStr
(
$
sData
,
"|"
)
Then
$
aArray
=
StringSplit
(
$
sData
,
"|"
)
If
UBound
(
$
aArray
)
=
3
Then
$
sSearch
=
StringReplace
(
$
aArray[
1
]
,
'"', '')
$
sReplace
=
StringReplace
(
$
aArray[
2
]
,
'"', '')
$
iPatternLength
=
StringLen
(
$
sSearch
)
If
$
iPatternLength

StringLen
(
$
sReplace
)
Or
Mod
(
$
iPatternLength
,
2
)

0
Then
MsgBox
(
$
MB_SYSTEMMODAL
,
"Error"
,
"Pattern Error in config.ini:"
&
$
sPattern
&
@
CRLF
&
$
sSearch
&
@
CRLF
&
$
sReplace
)
Exit
EndIf
;MsgBox
(
0
,
0
,
$
MyFileToParse
&
@
CRLF
&
$
sSearch
&
@
CRLF
&
$
aReplace
&
@
CRLF
&
$
sPattern
)
LogWrite
(
1
,
"Searching for: "
&
$
sPattern
&
": "
&
$
sSearch
)
MyRegExpGlobalPatternSearch
(
$
MyFileToParse
,
$
sSearch
,
$
sReplace
,
$
sPattern
)
;
Exit
;
STOP
AT FIRST VALUE
-
COMMENT
OUT
TO
CONTINUE
EndIf
;
Exit
EndIf
Next
EndFunc ;
=
=
>
ExecuteSearchPatterns



$FileName

- имя файла


$DefaultPatterns

- 1 или 0 (True или False)


$MyFileToParse

- путь до файла

Если передаётся 0, то используется

[CustomPatterns]

. Иначе -

[DefaultPatterns]

. Затем начинается основной цикл, где происходит обработка данных из INI-файла и передача в

MyRegExpGlobalPatternSearch

.

4.2.5.1. Весь цикл поиска и патчинга кратко
Рассмотрим работу всего цикла поиска и патчинга, забегая немного вперёд. Посмотрим на INI-файл:

INI:



...
[DefaultPatterns]
Values
="ProfileExpired1","ProfileExpired3","ProfileExpired4","ProfileExpired5","ProfileExpired6","ValidateLicense1","ValidateLicense2","ValidateLicense3","CmpEax61","CmpEax62","CmpEax63","CmpEax64","Profile1","Profile2","Banner1","Banner2","InstantShutdown1"
[CustomPatterns]
Acrobat.dll
="Acrobat3","Acrobat5"
acrodistdll.dll
="Acrodist2","Acrodist3","Acrodist4","Acrodist5","AcroRegistry1","AcroNew1"
acrotray.exe
="AcroTray2","AcroTray3","AcroTray4","AcroTray5","AcroRegistry1","AcroNew1"
AppsPanelBL.dll
="CreativeCloud1","CreativeCloud2"
ContainerBL.dll
="CreativeCloud3"
...
[Patches]
CreativeCloud1
="8378????0F84????????8378????0F84????????8378????0F 84????????33C0"|"C640????0F84????????C640????0F84????????C640????0F 84????????33C0"
CreativeCloud2
="E8????????85C00F85????????83EC??8BCC89"|"E8????????FEC00F85????????83EC??8BCC89"
CreativeCloud3
="??????0F85890300"|"??????E98A030000"
Acrobat1
="488BCFE8????????85C00F84????????488D??????????488B CFE8????????85C075??8D"|"488BCFE8????????FFC00F84????????488D??????????488B CFE8????????31C075??8D"
Acrobat2
="6685C0740FE8????????6685C07405BB01000000"|"6685C07400E8????????6685C07400BB01000000"
Acrobat3
="6685C0741A6685??0F85??020000"|"6685C090906685??0F85??020000"
...


В секции

[CustomPatterns]

есть ключи, имена которых - это имена файлов для патчинга. У имён-файлов (ключей) есть значения. Значения используются в секции

[Patches]

, которая содержит байты для поиска и патчинга.
Например, если патчится файл

AppsPanelBL.dll

, то используются патчи

"CreativeCloud1"

,

"CreativeCloud2"

(из-за прямого указания в строке

AppsPanelBL.dll="CreativeCloud1","CreativeCloud2"

):

В секции патчей это будет:

INI:



CreativeCloud1
="8378????0F84????????8378????0F84????????8378????0F 84????????33C0"|"C640????0F84????????C640????0F84????????C640????0F 84????????33C0"
CreativeCloud2
="E8????????85C00F85????????83EC??8BCC89"|"E8????????FEC00F85????????83EC??8BCC89"


Байты для поиска и замены разделены символов

|

. Для

CreativeCloud1

будут искаться байты

"8378????0F84????????8378????0F84????????8378????0F 84????????33C0"

, а заменой им будет

"C640????0F84????????C640????0F84????????C640????0F 84????????33C0"

.
Затем поиск и замена будут выполнены для

CreativeCloud2

. До знаков вопроса в патчах мы сейчас дойдём. Для понимания нам нужно изучить

MyRegExpGlobalPatternSearch

. Вот так она вызывается в

MyRegExpGlobalPatternSearch

:

Visual Basic:



MyRegExpGlobalPatternSearch
(
$
MyFileToParse
,
$
sSearch
,
$
sReplace
,
$
sPattern
)



$MyFileToParse

- путь до файла


$sSearch

- байты, которые ищутся (до символа | в секции с патчами)


$sReplace

- байты, на которые произойдёт замена (после символа | в секции с патчами)


$sPattern

- значение ключа из набора в секции

[CustomPatterns]

(

CreativeCloud1

,

CreativeCloud2

и другие).

Функция

MyRegExpGlobalPatternSearch

:

Visual Basic:



Func MyRegExpGlobalPatternSearch
(
$
FileToParse
,
$
PatternToSearch
,
$
PatternToReplace
,
$
PatternName
)
; Path
to
a file
to
parse
;MsgBox
(
$
MB_SYSTEMMODAL
,
"Path"
,
$
FileToParse
)
;ConsoleWrite
(
$
FileToParse
&
@
CRLF
)
Local
$
hFileOpen
=
FileOpen
(
$
FileToParse
,
$
FO_READ
+
$
FO_BINARY
)
FileSetPos
(
$
hFileOpen
,
60
,
0
)
$
sz_type
=
FileRead
(
$
hFileOpen
,
4
)
FileSetPos
(
$
hFileOpen
,
Number
(
$
sz_type
)
+
4
,
0
)
$
sz_type
=
FileRead
(
$
hFileOpen
,
2
)
If
$
sz_type
=
"0x4C01"
And
StringInStr
(
$
FileToParse
,
"Acrobat"
,
2
)
>
0
Then
; Acrobat x86 won
't work with this script
MemoWrite
(
@
CRLF
&
$
FileToParse
&
@
CRLF
&
"---"
&
@
CRLF
&
"File is 32bit. Aborting..."
&
@
CRLF
&
"---"
)
FileClose
(
$
hFileOpen
)
Sleep
(
100
)
$
bFoundAcro32
=
True
ElseIf
$
sz_type
=
"0x64AA"
Then
; AArch64
(
ARM64
)
and
~~AArch32
(
ARM32
)
architectures~~
(
big
-
endian
)
.
only exist
as
photoshop
,
lightroom
,
and
ccdesktop at time
of
writing
If
StringInStr
(
$
FileToParse
,
"Lightroom"
,
2
)
>
0
Then
; Lightroom ARM
MemoWrite
(
@
CRLF
&
$
FileToParse
&
@
CRLF
&
"---"
&
@
CRLF
&
"Lightroom is ARM. Aborting..."
&
@
CRLF
&
"---"
)
FileClose
(
$
hFileOpen
)
Sleep
(
100
)
$
bFoundLrARM
=
True
ElseIf
StringInStr
(
$
FileToParse
,
"AppsPanelBL.dll"
,
2
)
Or
StringInStr
(
$
FileToParse
,
"ContainerBL.dll"
,
2
)
Or
StringInStr
(
$
FileToParse
,
"Adobe Desktop Service.exe"
,
2
)
>
0
Then
; CC Desktop ARM
MemoWrite
(
@
CRLF
&
$
FileToParse
&
@
CRLF
&
"---"
&
@
CRLF
&
"Creative Cloud is ARM. Aborting..."
&
@
CRLF
&
"---"
)
FileClose
(
$
hFileOpen
)
Sleep
(
100
)
$
bFoundCCARM
=
True
ElseIf
StringInStr
(
$
FileToParse
,
"Photoshop"
,
2
)
>
0
Then
; Photoshop ARM
MemoWrite
(
@
CRLF
&
$
FileToParse
&
@
CRLF
&
"---"
&
@
CRLF
&
"Photoshop is ARM. Aborting..."
&
@
CRLF
&
"---"
)
FileClose
(
$
hFileOpen
)
Sleep
(
100
)
$
bFoundPsARM
=
True
Else
; Other ARM
MemoWrite
(
@
CRLF
&
$
FileToParse
&
@
CRLF
&
"---"
&
@
CRLF
&
"File is ARM. Aborting..."
&
@
CRLF
&
"---"
)
FileClose
(
$
hFileOpen
)
Sleep
(
100
)
$
bFoundGenericARM
=
True
EndIf
Else
FileSetPos
(
$
hFileOpen
,
0
,
0
)
Local
$
sFileRead
=
FileRead
(
$
hFileOpen
)
Local
$
GeneQuestionMark
,
$
AnyNumOfBytes
,
$
OutStringForRegExp
For
$
i
=
256
To
1
Step
-
2
; limiting
to
256
-
?
-
$
GeneQuestionMark
=
_StringRepeat
(
"??"
,
$
i
/
2
)
; Repeat the
string
-
?
?
-
$
i
/
2
times
.
$
AnyNumOfBytes
=
"(.{"
&
$
i
&
"})"
$
OutStringForRegExp
=
StringReplace
(
$
PatternToSearch
,
$
GeneQuestionMark
,
$
AnyNumOfBytes
)
$
PatternToSearch
=
$
OutStringForRegExp
Next
Local
$
sSearchPattern
=
$
OutStringForRegExp ;
string
Local
$
aReplacePattern
=
$
PatternToReplace ;
string
Local
$
sWildcardSearchPattern
=
""
,
$
sWildcardReplacePattern
=
""
,
$
sFinalReplacePattern
=
""
Local
$
aInHexTempArray[
0
]
Local
$
sSearchCharacter
=
""
,
$
sReplaceCharacter
=
""
$
aInHexTempArray
=
$
aNullArray
$
aInHexTempArray
=
StringRegExp
(
$
sFileRead
,
$
sSearchPattern
,
$
STR_REGEXPARRAYGLOBALFULLMATCH
,
1
)
For
$
i
=
0
To
UBound
(
$
aInHexTempArray
)
-
1
$
aInHexArray
=
$
aNullArray
$
sSearchCharacter
=
""
$
sReplaceCharacter
=
""
$
sWildcardSearchPattern
=
""
$
sWildcardReplacePattern
=
""
$
sFinalReplacePattern
=
""
$
aInHexArray
=
$
aInHexTempArray[
$
i]
;_ArrayDisplay
(
$
aInHexArray
)
If
@
error
=
0
Then
$
sWildcardSearchPattern
=
$
aInHexArray[
0
] ; full founded Search Pattern index
0
$
sWildcardReplacePattern
=
$
aReplacePattern

;MsgBox
(
-
1
,
""
,
$
sWildcardSearchPattern
&
@
CRLF
&
$
sWildcardReplacePattern
)
; full search
and
full patch
with
?
?
symbols
If
StringInStr
(
$
sWildcardReplacePattern
,
"?"
)
Then
;MsgBox
(
$
MB_SYSTEMMODAL
,
"Found ? symbol"
,
"Constructing new Replace string"
)
For
$
j
=
1
To
StringLen
(
$
sWildcardReplacePattern
)
+
1
; Retrieve a characters from the
$
jth position
in
each
string
.
$
sSearchCharacter
=
StringMid
(
$
sWildcardSearchPattern
,
$
j
,
1
)
$
sReplaceCharacter
=
StringMid
(
$
sWildcardReplacePattern
,
$
j
,
1
)
If
$
sReplaceCharacter

"?"
Then
$
sFinalReplacePattern
&
=
$
sReplaceCharacter
Else
$
sFinalReplacePattern
&
=
$
sSearchCharacter
EndIf
Next
Else
$
sFinalReplacePattern
=
$
sWildcardReplacePattern
EndIf
_ArrayAdd
(
$
aOutHexGlobalArray
,
$
sWildcardSearchPattern
)
_ArrayAdd
(
$
aOutHexGlobalArray
,
$
sFinalReplacePattern
)
ConsoleWrite
(
$
PatternName
&
"---"
&
@
TAB
&
$
sWildcardSearchPattern
&
" "
&
@
CRLF
)
ConsoleWrite
(
$
PatternName
&
"R"
&
"--"
&
@
TAB
&
$
sFinalReplacePattern
&
" "
&
@
CRLF
)
MemoWrite
(
@
CRLF
&
$
FileToParse
&
@
CRLF
&
"---"
&
@
CRLF
&
$
PatternName
&
@
CRLF
&
"---"
&
@
CRLF
&
$
sWildcardSearchPattern
&
@
CRLF
&
$
sFinalReplacePattern
)
LogWrite
(
1
,
"Replacing with: "
&
$
sFinalReplacePattern
)
Else
ConsoleWrite
(
$
PatternName
&
"---"
&
@
TAB
&
"No"
&
" "
&
@
CRLF
)
MemoWrite
(
@
CRLF
&
$
FileToParse
&
@
CRLF
&
"---"
&
@
CRLF
&
$
PatternName
&
"---"
&
"No"
)
EndIf
$
MyRegExpGlobalPatternSearchCount
+
=
1
Next
FileClose
(
$
hFileOpen
)
$
sFileRead
=
""
ProgressWrite
(
Round
(
$
MyRegExpGlobalPatternSearchCount
/
$
Count
*
100
)
)
Sleep
(
100
)
EndIf
;
=
=
>
If
$
sz_type
=
"0x4C01"
EndFunc ;
=
=
>
MyRegExpGlobalPatternSearch


Первое, что проверяет функция - патчим ли мы Adobe Acrobat с архитектурой x32. Если да, то функция завершается, так как скрипт не может обработать такой случай. Далее проверяется, используем ли мы файл с архитектурой ARM. Если да, то снова завершение функции. GenP рассчитан в основном на x64.

Далее функция читает файл, путь до которого был ей передан, и находит точные байты в нём (без знаков ?), добавляя их в массив

$aOutHexGlobalArray

:

Visual Basic:



_ArrayAdd
(
$
aOutHexGlobalArray
,
$
sWildcardSearchPattern
)
_ArrayAdd
(
$
aOutHexGlobalArray
,
$
sFinalReplacePattern
)


В первый элемент массива будут добавлены точные байты, которые были найдены в файле.
Во второй элемент массива будут добавлены точные байты, на которые произойдёт замена.

Функция заменяет знаки

??

на байты из самого файла, чтобы получились "точные байты". Забегая вперёд, потом благодаря массиву

$aOutHexGlobalArray

функция

MyGlobalPatternPatch

просто пропатчит точные искомые байты на точные заменяемые. Но перед этим создаст резервную копию.

Рассмотрим схематично замену байтов в файле. Значения

CreativeCloud1

для файла

AppsPanelBL.dll

:

INI:



CreativeCloud1
="8378????0F84????????8378????0F84????????8378????0F 84????????33C0"|"C640????0F84????????C640????0F84????????C640????0F 84????????33C0"


Более понятная запись:

INI:



"8378????0F84????????8378????0F84????????8378????0F 84????????33C0" ; байты, которые ищем
"C640????0F84????????C640????0F84????????C640????0F 84????????33C0" ; байты, на которые заменяем


2 знака вопроса выступают в роли одного любого байта, который не будет изменён. Или по-другому это часть регулярного выражения. Отсюда и Reg в названии функции.

INI:



"83 78 ?? ?? 0F 84 ?? ?? ?? ?? 83 78 ?? ?? 0F 84 ..." ; байты, которые ищем
"C6 40 ?? ?? 0F 84 ?? ?? ?? ?? C6 40 ?? ?? 0F 84..." ; байты, на которые заменяем


Вот так записано более понятно. Функция

MyRegExpGlobalPatternSearch

сделает из вопросов точные байты:

INI:



"83 78 FF FF 0F 84 FF FF FF FF 83 78 FF FF 0F 84 ..." ; байты, которые ищем
"C6 40 FF FF 0F 84 FF FF FF FF C6 40 FF FF 0F 84..." ; байты, на которые заменяем


Для примера представим, что в самом файле на месте вопросов были байты 0xFF.
Байт 0x83 заменится на 0xC6,
Байт 0x78 заменится на 0x40,
Байт 0xFF заменится на 0xFF,
Байт 0xFF заменится на 0xFF,
Байт 0x0F заменится на 0x0F,
Байт 0x84 заменится на 0x84,
и так далее

4.2.5.2. Вернёмся к обработчику кнопки "Patch CC"
Теперь снова вернёмся к обработчику нажатия кнопки "Patch CC".

Visual Basic:



If
FileExists
(
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
)
Then
$
appsPanelFile
=
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
ElseIf
FileExists
(
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
)
Then
$
appsPanelFile
=
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
Else
$
appsPanelFile
=
FileOpenDialog
(
"Select a File"
,
@
ScriptDir
,
"AppsPanelBL.dll (AppsPanelBL.dll)"
)
EndIf
ProgressWrite
(
0
)
If
FileExists
(
$
appsPanelFile
)
Then
MyGlobalPatternSearch
(
$
appsPanelFile
)
Sleep
(
100
)
MemoWrite
(
@
CRLF
&
"File Path:"
&
@
CRLF
&
""
&
@
CRLF
&
$
appsPanelFile
&
@
CRLF
&
""
&
@
CRLF
&
""
)
Sleep
(
100
)
MyGlobalPatternPatch
(
$
appsPanelFile
,
$
aOutHexGlobalArray
)
Sleep
(
500
)
EndIf


После того как вызов

MyGlobalPatternSearch

пропатчит файл, запустится функция

MyGlobalPatternPatch

.

Visual Basic:



Func MyGlobalPatternPatch
(
$
MyFileToPatch
,
$
MyArrayToPatch
)
;MsgBox
(
$
MB_SYSTEMMODAL
,
""
,
$
MyFileToPatch
)
;_ArrayDisplay
(
$
MyArrayToPatch
)
ProgressWrite
(
0
)
;MemoWrite
(
"Current path"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
MyFileToPatch
&
@
CRLF
&
"---"
&
@
CRLF
&
"medication :)"
)
Local
$
iRows
=
UBound
(
$
MyArrayToPatch
)
; Total number
of
rows

MsgBox
(
0
,
"q"
,
$
MyArrayToPatch
)
MsgBox
(
0
,
"q"
,
$
iRows
)
If
$
iRows
>
0
Then
MemoWrite
(
@
CRLF
&
"Path"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
MyFileToPatch
&
@
CRLF
&
"---"
&
@
CRLF
&
"medication :)"
)
Local
$
hFileOpen
=
FileOpen
(
$
MyFileToPatch
,
$
FO_READ
+
$
FO_BINARY
)
Local
$
sFileRead
=
FileRead
(
$
hFileOpen
)
Local
$
sStringOut
For
$
i
=
0
To
$
iRows
-
1
Step
2
MsgBox
(
0
,
"q"
,
$
MyArrayToPatch[
$
i]
)
MsgBox
(
0
,
"q"
,
$
MyArrayToPatch[
$
i
+
1
]
)
$
sStringOut
=
StringReplace
(
$
sFileRead
,
$
MyArrayToPatch[
$
i]
,
$
MyArrayToPatch[
$
i
+
1
]
,
0
,
1
)
$
sFileRead
=
$
sStringOut
$
sStringOut
=
$
sFileRead
ProgressWrite
(
Round
(
$
i
/
$
iRows
*
100
)
)
Next
;MsgBox
(
$
MB_SYSTEMMODAL
,
""
,
"binary: "
&
Binary
(
$
sStringOut
)
)
FileClose
(
$
hFileOpen
)
FileMove
(
$
MyFileToPatch
,
$
MyFileToPatch
&
".bak"
,
$
FC_OVERWRITE
)
Local
$
hFileOpen1
=
FileOpen
(
$
MyFileToPatch
,
$
FO_OVERWRITE
+
$
FO_BINARY
)
FileWrite
(
$
hFileOpen1
,
Binary
(
$
sStringOut
)
)
FileClose
(
$
hFileOpen1
)
ProgressWrite
(
0
)
Sleep
(
100
)
;MemoWrite1
(
@
CRLF
&
"---"
&
@
CRLF
&
"Waitng for your command :)"
&
@
CRLF
&
"---"
)
LogWrite
(
1
,
"File patched."
&
@
CRLF
)
Else
;Empty array
-
>
no search
-
replace patterns
;File
is
already patched
or
no patterns were found
.
MemoWrite
(
@
CRLF
&
"No patterns were found"
&
@
CRLF
&
"---"
&
@
CRLF
&
"or"
&
@
CRLF
&
"---"
&
@
CRLF
&
"file is already patched."
)
Sleep
(
100
)
LogWrite
(
1
,
"No patterns were found or file already patched."
&
@
CRLF
)
EndIf
;Sleep
(
100
)
;MemoWrite2
(
"***"
)
EndFunc ;
=
=
>
MyGlobalPatternPatch


Данная функция открывает файл и благодаря глобальному массиву

$aOutHexGlobalArray

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

.bak

, чтобы в случае чего его можно было бы восстановить. Таким образом,

MyGlobalPatternSearch

и все функции, что вызываются внутри неё, ищут точные байты в файле для замены и на основе них создают точные байты, на которые произойдёт замена. Саму замену байтов выполняет

MyGlobalPatternPatch

. Связаны 2 эти функции через

$aOutHexGlobalArray

.

Таким образом, во всём обработчике "Patch CC" прослеживается общий шаблон:

Код:



1. Проверка, существует ли обрабатываемый файл в Program Files (x86) или в Program Files. Иначе выбираем сами.
2. Функцией MyGlobalPatternSearch ищутся точные исходные и заменяемые байты и записываются в глобальный массив $aOutHexGlobalArray.
3. Функция MyGlobalPatternPatch использует $aOutHexGlobalArray и заменяет точные искомые байты на точные заменяемые. Также создаёт бекап исходного файла.


В коде это:

Visual Basic:



If
FileExists
(
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
)
Then
$
appsPanelFile
=
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
ElseIf
FileExists
(
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
)
Then
$
appsPanelFile
=
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll"
Else
$
appsPanelFile
=
FileOpenDialog
(
"Select a File"
,
@
ScriptDir
,
"AppsPanelBL.dll (AppsPanelBL.dll)"
)
EndIf
ProgressWrite
(
0
)
If
FileExists
(
$
appsPanelFile
)
Then
MyGlobalPatternSearch
(
$
appsPanelFile
)
Sleep
(
100
)
MemoWrite
(
@
CRLF
&
"File Path:"
&
@
CRLF
&
""
&
@
CRLF
&
$
appsPanelFile
&
@
CRLF
&
""
&
@
CRLF
&
""
)
Sleep
(
100
)
MyGlobalPatternPatch
(
$
appsPanelFile
,
$
aOutHexGlobalArray
)
Sleep
(
500
)
EndIf
ProgressWrite
(
0
)
If
$
bFoundCCARM
=
False
Then
If
FileExists
(
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\ADS\ContainerBL.dll"
)
Then
$
containerBLFile
=
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\ADS\ContainerBL.dll"
ElseIf
FileExists
(
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\ADS\ContainerBL.dll"
)
Then
$
containerBLFile
=
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\ADS\ContainerBL.dll"
Else
$
containerBLFile
=
FileOpenDialog
(
"Select a File"
,
@
ScriptDir
,
"ContainerBL.dll (ContainerBL.dll)"
)
EndIf
ProgressWrite
(
0
)
If
FileExists
(
$
containerBLFile
)
Then
MyGlobalPatternSearch
(
$
containerBLFile
)
Sleep
(
100
)
MemoWrite
(
@
CRLF
&
"File Path:"
&
@
CRLF
&
""
&
@
CRLF
&
$
containerBLFile
&
@
CRLF
&
""
&
@
CRLF
&
""
)
Sleep
(
100
)
MyGlobalPatternPatch
(
$
containerBLFile
,
$
aOutHexGlobalArray
)
Sleep
(
500
)
EndIf
ProgressWrite
(
0
)
If
FileExists
(
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\ADS\Adobe Desktop Service.exe"
)
Then
$
adobeDesktopServiceFile
=
"C:\Program Files\Common Files\Adobe\Adobe Desktop Common\ADS\Adobe Desktop Service.exe"
ElseIf
FileExists
(
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\ADS\Adobe Desktop Service.exe"
)
Then
$
adobeDesktopServiceFile
=
"C:\Program Files (x86)\Common Files\Adobe\Adobe Desktop Common\ADS\Adobe Desktop Service.exe"
Else
$
adobeDesktopServiceFile
=
FileOpenDialog
(
"Select a File"
,
@
ScriptDir
,
"Adobe Desktop Service.exe (Adobe Desktop Service.exe)"
)
EndIf
If
FileExists
(
$
adobeDesktopServiceFile
)
Then
MyGlobalPatternSearch
(
$
adobeDesktopServiceFile
)
Sleep
(
100
)
MemoWrite
(
@
CRLF
&
"File Path:"
&
@
CRLF
&
""
&
@
CRLF
&
$
adobeDesktopServiceFile
&
@
CRLF
&
""
&
@
CRLF
&
""
)
Sleep
(
100
)
MyGlobalPatternPatch
(
$
adobeDesktopServiceFile
,
$
aOutHexGlobalArray
)
Sleep
(
500
)
EndIf
ProgressWrite
(
0
)
MemoWrite
(
@
CRLF
&
"All files patched."
&
@
CRLF
&
""
&
@
CRLF
&
""
)


Кроме этого проверяется, что мы не используем ARM-версии файлов. Ну и всё пишется в лог
Теперь посмотрим, какие именно изменения вносятся. Как мы узнали, меняются эти файлы:

Код:



C:\Program Files\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll
C:\Program Files\Common Files\Adobe\Adobe Desktop Common\ADS\ContainerBL.dll
C:\Program Files\Common Files\Adobe\Adobe Desktop Common\ADS\Adobe Desktop Service.exe


Вместо

Program Files

может быть

Program Files (x86)

. У меня как раз этот случай. Патчинг этих файлов позволяет скачивать платные программы Adobe из Creative Cloud.

До патча:

После патча:

Экспериментально у себя я выяснил, что для достижения такого результата обязательно нужно пропатчить

ContainerBL.dll

и

AppsPanelBL.dll

. Вероятно, патчинг

Adobe Desktop Service.exe

нужен для подстраховки.

4.2.5.3. Сравниваем пропатченные байты с оригинальными и ищем их
Мы сравним файл до патча и после патча. Я буду делать это через IDA. Но сначала нужно найти точные байты.

Это можно сделать этой утилитой на C++:

C++:



#include
#include
#include
#include
#include
#include
#include
#include
struct
Pattern
{
std
::
string name
;
std
::
vector
>
patterns
;
}
;
// Функция для парсинга паттернов
std
::
vector

parsePatterns
(
)
{
std
::
vector

patterns
=
{
{
"CreativeCloud1"
,
{
{
"8378????0F84????????8378????0F84????????8378????0F 84????????33C0"
,
"C640????0F84????????C640????0F84????????C640????0F 84????????33C0"
}
}
}
,
{
"CreativeCloud2"
,
{
{
"E8????????85C00F85????????83EC??8BCC89"
,
"E8????????FEC00F85????????83EC??8BCC89"
}
}
}
,
{
"CreativeCloud3"
,
{
{
"??????0F85890300"
,
"??????E98A030000"
}
}
}
,
{
"Acrobat1"
,
{
{
"488BCFE8????????85C00F84????????488D??????????488B CFE8????????85C075??8D"
,
"488BCFE8????????FFC00F84????????488D??????????488B CFE8????????31C075??8D"
}
}
}
,
{
"Acrobat2"
,
{
{
"6685C0740FE8????????6685C07405BB01000000"
,
"6685C07400E8????????6685C07400BB01000000"
}
}
}
,
{
"Acrobat3"
,
{
{
"6685C0741A6685??0F85??020000"
,
"6685C090906685??0F85??020000"
}
}
}
,
{
"Acrobat4"
,
{
{
"00E8????E7FF66893D??????03381D??????03751F4533C048 8D15??????02488D0D??????02E8????E7FF6685C00F84BAFE FFFF8BDFE9B3FEFFFFB801000000"
,
"00E8????E7FF66893D??????03381D??????03EB1F4533C048 8D15??????02488D0D??????02E8????E7FF6685C00F84BAFE FFFF8BDFE9B3FEFFFFB801000000"
}
}
}
,
{
"Acrobat5"
,
{
{
"753FE8????????6685C0740933C9E8????????EB25488D05?? ??????48894424204C8D0D????????33D2448D423C488D0D?? ????00E8????????66893D????????381D????????75"
,
"EB3FE8????????6685C0740933C9E8????????EB25488D05?? ??????48894424204C8D0D????????33D2448D423C488D0D?? ????00E8????????66893D????????381D????????EB"
}
}
}
,
{
"Acrodist1"
,
{
{
"85C00F84????????E8????????85C00F85????????E8"
,
"85C00F84????????E8????????FFC00F85????????E8"
}
}
}
,
{
"Acrodist2"
,
{
{
"00908BC8E8??????00CCCCCCCCCCCC48895C24104889"
,
"00908BC8E8??????00CCCCCCCCCCCC31C0C324104889"
}
}
}
,
{
"Acrodist3"
,
{
{
"5E5B5DC3CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC405553565741 5441554156415748"
,
"5E5B5DC3CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC31C0C3565741 5441554156415748"
}
}
}
,
{
"Acrodist4"
,
{
{
"00005072696E745F447269766572"
,
"0000B001906E745F447269766572"
}
}
}
,
{
"Acrodist5"
,
{
{
"51??????????????????????????????48895C???????????? ????????????55"
,
"51??????????????????????????????33C0C3???????????? ????????????55"
}
}
}
,
{
"AcroNew1"
,
{
{
"C3488BC1C3CC48895C24084889??2410"
,
"C3488BC1C3CCC3895C24084889??2410"
}
}
}
,
{
"AcroRegistry1"
,
{
{
"0FB6C3488B5C2458????????????????895C"
,
"B801000000909090????????????????895C"
}
}
}
,
{
"AcroTray1"
,
{
{
"E8????????84C074??E8????????84C075??33DB"
,
"E8????????FEC074??E8????????FEC075??33DB"
}
}
}
,
{
"AcroTray2"
,
{
{
"0F8459030000803D??????00000F8545030000"
,
"E95A03000090803D??????0000E94603000090"
}
}
}
,
{
"AcroTray3"
,
{
{
"0F85A302000048397C24400F84"
,
"E9A40200009048397C24400F85"
}
}
}
,
{
"AcroTray4"
,
{
{
"244884DB0F84C70100"
,
"244884DB0F85C70100"
}
}
}
,
{
"AcroTray5"
,
{
{
"????????E86791D9FFE862B2D8FF????"
,
"????????9090909090B801000000????"
}
}
}
,
{
"DesktopService1"
,
{
{
"68008B40148985CCFEFFFF85C0"
,
"68008B40148985CCFEFFFF33C0"
}
}
}
,
{
"Banner1"
,
{
{
"72656C6174696F6E7368697050726F66696C65"
,
"78656C6174696F6E7368697050726F66696C65"
}
}
}
,
{
"Banner2"
,
{
{
"000000000000000072656C6174696F6E"
,
"000000000000000078656C6174696F6E"
}
}
}
,
{
"BridgeCamRaw1"
,
{
{
"84C074??8B??83??0174??83??0174??83??01"
,
"84C074??8B??83??01EB??83??0174??83??01"
}
}
}
,
{
"BridgeCamRaw2"
,
{
{
"4084??0F85????????4084??0F84"
,
"4084??0F85????????40FEC60F85"
}
}
}
,
{
"CmpEax61"
,
{
{
"8B??85C074??83F80674????83????007D"
,
"C7??0300000083F8067400??83????00EB"
}
}
}
,
{
"CmpEax62"
,
{
{
"8B??85C074??83F80674????83??????007D"
,
"C7??0300000083F8067400??83??????00EB"
}
}
}
,
{
"CmpEax63"
,
{
{
"8B????85C074??83F80674????83????007D"
,
"C7????0300000083F8067400??83????00EB"
}
}
}
,
{
"CmpEax64"
,
{
{
"8B????85C074??83F80674????83??????007D"
,
"C7????0300000083F8067400??83??????00EB"
}
}
}
,
{
"HevcMpegEnabler1"
,
{
{
"FF50100FB6"
,
"FFC0900FB6"
}
}
}
,
{
"HevcMpegEnabler2"
,
{
{
"FF5010??0FB6"
,
"FFC090??0FB6"
}
}
}
,
{
"HevcMpegEnabler3"
,
{
{
"FF50??0FB6"
,
"FFC0900FB6"
}
}
}
,
{
"HevcMpegEnabler4"
,
{
{
"FF50????0FB6"
,
"FFC090??0FB6"
}
}
}
,
{
"Profile1"
,
{
{
"00007504488D4850"
,
"00007500488D4850"
}
}
}
,
{
"Profile2"
,
{
{
"00007504488D5050"
,
"00007500488D5050"
}
}
}
,
{
"ProfileExpired1"
,
{
{
"85C075??????????75??B892010000E9"
,
"31C075004883FF0F7500B800000000E9"
}
}
}
,
{
"ProfileExpired2"
,
{
{
"85C075??B892010000E9"
,
"85C07500B800000000E9"
}
}
}
,
{
"ProfileExpired3"
,
{
{
"85C075????????75??B892010000E9"
,
"31C075????????7500B800000000E9"
}
}
}
,
{
"ProfileExpired4"
,
{
{
"488D4D??483B??0F??????000048????4889??4885C9"
,
"488D4D??483B??C700000000004831C94889??4885C9"
}
}
}
,
{
"ProfileExpired5"
,
{
{
"488B0B4889034885C974??BA04000000E8????????B00148"
,
"C7000000000048890348C7C1000000000F1F440000B00148"
}
}
}
,
{
"ProfileExpired6"
,
{
{
"4885C974??BA04000000E8????????4C8D5C"
,
"C700000000004889034831C90F1F004C8D5C"
}
}
}
,
{
"TeamProjectEnabler1"
,
{
{
"488379????740A488379????7403B001C332C0C3"
,
"488379????740A488379????7403B001C3B001C3"
}
}
}
,
{
"ValidateLicense1"
,
{
{
"83F80175??BA94010000"
,
"83F80175??BA00000000"
}
}
}
,
{
"ValidateLicense2"
,
{
{
"83F8040F95C281C293010000"
,
"83F8040F95C2BA0000000090"
}
}
}
,
{
"ValidateLicense3"
,
{
{
"83F8040F95C181C193010000"
,
"83F8040F95C1B90000000090"
}
}
}
,
{
"InstantShutdown1"
,
{
{
"00??????????E875000000????????C0"
,
"00??????????9090909090????????C0"
}
}
}
,
{
"PluginVerification1"
,
{
{
"5F5E????????????????????????????48895C2410??8974"
,
"5F5E????????????????????????????C390909090??8974"
}
}
}
,
{
"LoginVerification1"
,
{
{
"4C8B??????????????????????????????????????742C48"
,
"4C8B??????????????????????????????????????EB2C48"
}
}
}
,
{
"JS1"
,
{
{
"52656C6174696F6E7368697050726F66696C653A612E??2E6F 7074696F6E616C"
,
"58656C6174696F6E7368697050726F66696C653A612E??2E6F 7074696F6E616C"
}
}
}
,
{
"JS2"
,
{
{
"52656C6174696F6E7368697050726F66696C652E66696E6428 653D3E652E75736564466F724C656761637950726F66696C65 293B72657475726E20692E6D61696E53746F7265"
,
"58656C6174696F6E7368697050726F66696C652E66696E6428 653D3E652E75736564466F724C656761637950726F66696C65 293B72657475726E20692E6D61696E53746F7265"
}
}
}
,
{
"JS3"
,
{
{
"52656C6174696F6E7368697050726F66696C652E66696E6428 653D3E652E75736564466F724C656761637950726F66696C65 297D7D2929"
,
"58656C6174696F6E7368697050726F66696C652E66696E6428 653D3E652E75736564466F724C656761637950726F66696C65 297D7D2929"
}
}
}
,
{
"JS4"
,
{
{
"52656C6174696F6E7368697050726F66696C65297C7C766F69 6420303D3D3D69"
,
"58656C6174696F6E7368697050726F66696C65297C7C766F69 6420303D3D3D69"
}
}
}
,
{
"Version"
,
{
{
"6363782E7374617274222C0A20202276657273696F6E223A20 22372E"
,
"6363782E7374617274222C0A20202276657273696F6E223A20 22392E"
}
}
}
}
;
return
patterns
;
}
// Функция для преобразования паттерна в байтовый массив и маску
void
parseHexPattern
(
const
std
::
string
&
pattern
,
std
::
vector

&
bytes
,
std
::
vector

&
mask
)
{
for
(
size_t i
=
0
;
i

(
std
::
stoi
(
byteString
,
nullptr
,
16
)
)
;
bytes
.
push_back
(
byte
)
;
mask
.
push_back
(
0xFF
)
;
}
catch
(
const
std
::
invalid_argument
&
e
)
{
std
::
cerr

&
data
,
const
std
::
vector

&
pattern
,
const
std
::
vector

&
mask
,
size_t pos
)
{
for
(
size_t i
=
0
;
i

&
data
,
const
std
::
vector

&
pattern
,
const
std
::
vector

&
mask
,
const
std
::
string
&
patternType
,
bool
&
headerPrinted
,
const
std
::
string
&
patternName
)
{
bool
found
=
false
;
for
(
size_t i
=
0
;
i
"

(
data
[
i
+
j
]
)
"

data
(
(
std
::
istreambuf_iterator

(
file
)
)
,
std
::
istreambuf_iterator

(
)
)
;
auto
patterns
=
parsePatterns
(
)
;
for
(
const
auto
&
pattern
:
patterns
)
{
bool
anyFound
=
false
;
bool
headerPrinted
=
false
;
for
(
const
auto
&
[
searchPattern
,
replacePattern
]
:
pattern
.
patterns
)
{
std
::
vector

searchBytes
,
searchMask
;
parseHexPattern
(
searchPattern
,
searchBytes
,
searchMask
)
;
std
::
vector

replaceBytes
,
replaceMask
;
parseHexPattern
(
replacePattern
,
replaceBytes
,
replaceMask
)
;
if
(
searchAndPrintPattern
(
data
,
searchBytes
,
searchMask
,
"Искомый "
,
headerPrinted
,
pattern
.
name
)
)
{
anyFound
=
true
;
}
if
(
searchAndPrintPattern
(
data
,
replaceBytes
,
replaceMask
,
"Заменяемый"
,
headerPrinted
,
pattern
.
name
)
)
{
anyFound
=
true
;
}
}
if
(
anyFound
)
{
std
::
cout
search.exe AppsPanelBL.dll

Искомый : 83 78 2c 00 0f 84 95 01 00 00 83 78 44 00 0f 84 8b 01 00 00 83 78 5c 00 0f 84 81 01 00 00 33 c0

Искомый : e8 76 47 10 00 85 c0 0f 85 9d 00 00 00 83 ec 18 8b cc 89
Искомый : e8 01 a9 0e 00 85 c0 0f 85 8f 02 00 00 83 ec 18 8b cc 89
Искомый : e8 09 b7 02 00 85 c0 0f 85 1f 02 00 00 83 ec 18 8b cc 89
Искомый : e8 83 fd ff ff 85 c0 0f 85 bd 04 00 00 83 ec 18 8b cc 89
Искомый : e8 f2 f7 ff ff 85 c0 0f 85 74 01 00 00 83 ec 18 8b cc 89
Искомый : e8 ef f5 ff ff 85 c0 0f 85 76 02 00 00 83 ec 18 8b cc 89
Искомый : e8 cf f2 ff ff 85 c0 0f 85 76 02 00 00 83 ec 18 8b cc 89

Искомый : ff 84 c0 0f 85 89 03 00
Искомый : ff 84 c0 0f 85 89 03 00
Искомый : 74 24 38 0f 85 89 03 00
Заменяемый: ff 32 c0 e9 8a 03 00 00
Заменяемый: 0a 01 00 e9 8a 03 00 00


Только иногда она находит лишние байты. Но нам нужно искать только те, что патчатся. Для

AppsPanelBL.dll

это будет

CreativeCloud1

и

CreativeCloud2

. На

CreativeCloud3

просто не смотрим.

INI:



AppsPanelBL.dll
="CreativeCloud1","CreativeCloud2"


Далее файл нужно открыть в IDA, нажать ALT+B и ввести первые 5-10 найденных байт:

Нажимаем 2 раза на запись.

Попали сюда. Теперь отобразим опкоды инструкций.

Таким образом можно сравнивать файлы до патча и после. Так мы посмотрим, как поменяются инструкции.
Так как GenP делает бекапы (.bak), то можно сравнивать бекапы с пропатченными файлами.


C:\Program Files\Common Files\Adobe\Adobe Desktop Common\ADS\Adobe Desktop Service.exe



DesktopService1


Слева я буду располагать файл до патча, а справа после. Если хотите перейти в режим листинга IDA (как на скрине), нажмите пробел. И снова в настройках включите отображение опкодов.


C:\Program Files\Common Files\Adobe\Adobe Desktop Common\AppsPanel\AppsPanelBL.dll



CreativeCloud1



CreativeCloud2



C:\Program Files\Common Files\Adobe\Adobe Desktop Common\ADS\ContainerBL.dll



CreativeCloud3


Проанализировав различия между этими файлами, видим, что ничего вредоносного в них он не добавляет. Просто меняет ход выполнения программы. Где-то через замену инструкций условных переходов, где-то через замену инструкций перед ними. Можно даже сделать всё это вручную и получить тот же результат.

Далее нам нужно будет рассмотреть, как работает обработчик патчинга файлов самих программ Adobe. Но перед этим рассмотрим обработчик кнопки восстановления оригинального файла - "Restore".

4.2.6. Обработчик кнопки "Restore"
Она появляется, если после поиска файлов для патча (кнопка "Search") мы выберем какой-либо или какие-либо из них, и есть файл

.bak

для выбранного.

Начало его обработчика:

Visual Basic:



Case
$
idMsg
=
$
idBtnRestore
GUICtrlSetData
(
$
idLog
,
"Activity Log"
&
@
CRLF
)
ToggleLog
(
0
)
GUICtrlSetState
(
$
idListview
,
128
)
GUICtrlSetState
(
$
idBtnDeselectAll
,
128
)
GUICtrlSetState
(
$
idButtonSearch
,
128
)
GUICtrlSetState
(
$
idBtnCure
,
128
)


Основная функция там - это

RestoreFile

:

Visual Basic:



If
_GUICtrlListView_GetItemChecked
(
$
idListview
,
$
i
)
=
True
Then
_GUICtrlListView_SetItemSelected
(
$
idListview
,
$
i
)
$
ItemFromList
=
_GUICtrlListView_GetItemText
(
$
idListview
,
$
i
,
1
)
$
iCheckedItems
=
_GUICtrlListView_GetSelectedCount
(
$
idListview
)
$
iProgress
=
100
/
$
iCheckedItems
ProgressWrite
(
0
)
RestoreFile
(
$
ItemFromList
)
ProgressWrite
(
$
iProgress
)
Sleep
(
100
)
MemoWrite
(
@
CRLF
&
"Path"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
ItemFromList
&
@
CRLF
&
"---"
&
@
CRLF
&
"restoring :)"
)
Sleep
(
100
)
; Scroll control
10
pixels
-
1
line
_GUICtrlListView_Scroll
(
$
idListview
,
0
,
10
)
_GUICtrlListView_EnsureVisible
(
$
idListview
,
$
i
,
0
)
Sleep
(
100
)
EndIf


Этой функции передаётся путь до файла. Её код:

Visual Basic:



Func RestoreFile
(
$
MyFileToDelete
)
If
FileExists
(
$
MyFileToDelete
&
".bak"
)
Then
FileDelete
(
$
MyFileToDelete
)
FileMove
(
$
MyFileToDelete
&
".bak"
,
$
MyFileToDelete
,
$
FC_OVERWRITE
)
Sleep
(
100
)
MemoWrite
(
@
CRLF
&
"File restored"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
MyFileToDelete
)
LogWrite
(
1
,
$
MyFileToDelete
)
LogWrite
(
1
,
"File restored."
)
Else
Sleep
(
100
)
MemoWrite
(
@
CRLF
&
"No backup file found"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
MyFileToDelete
)
LogWrite
(
1
,
$
MyFileToDelete
)
LogWrite
(
1
,
"No backup file found."
)
EndIf
EndFunc ;
=
=
>
RestoreFile


Тут всё просто
Удаляем пропатченный файл и переименовываем

.bak-файл

в файл без постфикса

.bak

. Конечно же, если бекап этого файла существует.

4.2.7. Обработчик кнопки "Patch"
Ну а теперь переходим к обработчику кнопки "Patch", что доступна после "Search".

Visual Basic:



Case
$
idMsg
=
$
idBtnCure
ToggleLog
(
0
)
GUICtrlSetState
(
$
idListview
,
128
)
GUICtrlSetState
(
$
idBtnDeselectAll
,
128
)
GUICtrlSetState
(
$
idButtonSearch
,
128
)
GUICtrlSetState
(
$
idBtnCure
,
128
)
GUICtrlSetState
(
$
idBtnBlockPopUp
,
128
)
GUICtrlSetState
(
$
idBtnRestore
,
128
)
GUICtrlSetState
(
$
idButtonCustomFolder
,
128
)
GUICtrlSetState
(
$
idBtnPatchCC
,
128
)
_Expand_All_Click
(
)
_GUICtrlListView_EnsureVisible
(
$
idListview
,
0
,
0
)
.
.
.


Это начало кода функции.

Visual Basic:



For
$
i
=
0
To
_GUICtrlListView_GetItemCount
(
$
idListview
)
-
1
If
_GUICtrlListView_GetItemChecked
(
$
idListview
,
$
i
)
=
True
Then
_GUICtrlListView_SetItemSelected
(
$
idListview
,
$
i
)
$
ItemFromList
=
_GUICtrlListView_GetItemText
(
$
idListview
,
$
i
,
1
)
MyGlobalPatternSearch
(
$
ItemFromList
)
ProgressWrite
(
0
)
Sleep
(
100
)
MemoWrite
(
@
CRLF
&
"Path"
&
@
CRLF
&
"---"
&
@
CRLF
&
$
ItemFromList
&
@
CRLF
&
"---"
&
@
CRLF
&
"medication :)"
)
LogWrite
(
1
,
$
ItemFromList
)
Sleep
(
100
)
MyGlobalPatternPatch
(
$
ItemFromList
,
$
aOutHexGlobalArray
)
; Scroll control
10
pixels
-
1
line
_GUICtrlListView_Scroll
(
$
idListview
,
0
,
10
)
_GUICtrlListView_EnsureVisible
(
$
idListview
,
$
i
,
0
)
Sleep
(
100
)
EndIf


При выборе какого-либо элемента и при нажатии кнопки "Patch" в функцию

MyGlobalPatternSearch

передаётся путь до файла. В

MyGlobalPatternSearch

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

$aOutHexGlobalArray

, вызывается

MyGlobalPatternPatch

. Тут всё, как в патчинге самого Creative Cloud. Но отличие в том, что при патчинге Creative Cloud искомые файлы были в секции

[CustomPatterns]

:

INI:



[CustomPatterns]
...
AppsPanelBL.dll
="CreativeCloud1","CreativeCloud2"
ContainerBL.dll
="CreativeCloud3"
Adobe Desktop Service.exe
="DesktopService1"
...


Но теперь нам нужно пропатчить сами программы, а не Creative Cloud. Вот полный список файлов, что будет искать GenP:

INI:



[TargetFiles]
1
="acrobat.dll"
2
="acrodistdll.dll"
3
="acrotray.exe"
4
="aero.exe"
5
="afterfxlib.dll"
6
="animate.exe"
7
="animator.exe"
8
="animator (beta).exe"
9
="auui.dll"
10
="adobe bridge.exe"
11
="designer.exe"
12
="dreamweaver.exe"
13
="dvaappsupport.dll"
14
="encoder.exe"
15
="encoder (beta).exe"
16
="euclid-core"
17
="gemini_uwp_bridge.dll"
18
="illustrator.exe"
19
="lightroom.exe"
20
="lightroomcc.exe"
21
="modeler.exe"
22
="modeler beta.exe"
23
="ngl-lib.dll"
24
="painter.exe"
25
="photoshop.exe"
26
="public.dll"
27
="registration.dll"
28
="sampler.exe"
29
="sampler beta.exe"
30
="stager.exe"
31
="sweetpeasupport.dll"
32
="xd.exe"
33
="appframework.rpln"
34
="objectmodel.dll"
35
="4.js"
36
="manifest.json"


А вот список специальных файлов:

INI:



[CustomPatterns]
Acrobat.dll
="Acrobat3","Acrobat5"
acrodistdll.dll
="Acrodist2","Acrodist3","Acrodist4","Acrodist5","AcroRegistry1","AcroNew1"
acrotray.exe
="AcroTray2","AcroTray3","AcroTray4","AcroTray5","AcroRegistry1","AcroNew1"
AppsPanelBL.dll
="CreativeCloud1","CreativeCloud2"
ContainerBL.dll
="CreativeCloud3"
Adobe Bridge.exe
="ProfileExpired1","ValidateLicense1","ValidateLicense2","ValidateLicense3","CmpEax61","CmpEax62","CmpEax63","CmpEax64","Profile1","Profile2","Banner1","BridgeCamRaw1","BridgeCamRaw2","InstantShutdown1"
dvaappsupport.dll
="TeamProjectEnabler1"
SweetPeaSupport.dll
="HevcMpegEnabler3","HevcMpegEnabler4"
AppFramework.rpln
="LoginVerification1"
ObjectModel.dll
="PluginVerification1"
Adobe Desktop Service.exe
="DesktopService1"
4.js
="JS1","JS2","JS3","JS4"
manifest.json
="Version"


Файлов из

TargetFiles

явно больше. Как быть с теми, что не в секции со специальными файлами?
В таком случае используется набор патчей из секции

[DefaultPatterns]

:

INI:



[DefaultPatterns]
Values
="ProfileExpired1","ProfileExpired3","ProfileExpired4","ProfileExpired5","ProfileExpired6","ValidateLicense1","ValidateLicense2","ValidateLicense3","CmpEax61","CmpEax62","CmpEax63","CmpEax64","Profile1","Profile2","Banner1","Banner2","InstantShutdown1"


Патчи по умолчанию применяются для всех файлов, что не находятся в секции

[CustomPatterns]

. В функции

MyGlobalPatternSearch

и

ExecuteSearchPatterns

как раз можно это увидеть:

Visual Basic:



Func MyGlobalPatternSearch
(
$
MyFileToParse
)
.
.
.
If
StringInStr
(
$
sSpecialFiles
,
$
sFileName
)
Then
;MsgBox
(
$
MB_SYSTEMMODAL
,
""
,
"Special File: "
&
$
sFileName
)
LogWrite
(
0
,
" - using Custom Patterns"
)
ExecuteSearchPatterns
(
$
sFileName
,
0
,
$
MyFileToParse
)
Else
LogWrite
(
0
,
" - using Default Patterns"
)
ExecuteSearchPatterns
(
$
sFileName
,
1
,
$
MyFileToParse
)
;MsgBox
(
$
MB_SYSTEMMODAL
,
""
,
"File: "
&
$
sFileName
&
@
CRLF
&
"Not in Special Files"
)
EndIf
.
.
.


Visual Basic:



Func ExecuteSearchPatterns
(
$
FileName
,
$
DefaultPatterns
,
$
MyFileToParse
)
Local
$
aPatterns
,
$
sPattern
,
$
sData
,
$
aArray
,
$
sSearch
,
$
sReplace
,
$
iPatternLength
If
$
DefaultPatterns
=
0
Then
$
aPatterns
=
IniReadArray
(
$
sINIPath
,
"CustomPatterns"
,
$
FileName
,
""
)
Else
$
aPatterns
=
IniReadArray
(
$
sINIPath
,
"DefaultPatterns"
,
"Values"
,
""
)
EndIf
.
.
.


Явно видно, что в случае если

$DefaultPatterns

равен 1, то читается значение ключа

Values

из секции

[DefaultPatterns]

. А если же файл будет из секции

CustomPatterns

, то и использоваться будут те байты, что указаны в этой секции. Например, как с

Acrobat.dll

:

INI:



[CustomPatterns]
Acrobat.dll
="Acrobat3","Acrobat5"


5.0 Изучаем патчи из INI-файла
Теперь рассмотрим патчи из секции

[Patches]

. Как и с файлами Creative Cloud, слева на скринах будет файл до патча, а справа - после. Сначала рассмотрим оставшиеся значения для ключей из

[CustomPatterns]

, а затем из

[DefaultPatterns]

. Важно учесть то, что не все значения для ключей будут найдены и применены. Некоторые останутся ненайденными и байты из них не будут использоваться для патча. Скорее всего, они использовались у некоторых старых версий приложений. Я покажу в IDA только то, что нашлось у меня. Также в файлах могут встречаться байты из других. Дубликаты я буду пропускать.

5.1. Секция CustomPatterns


C:\Program Files\Adobe\Acrobat DC\Acrobat\Acrobat.dll



Acrobat3



Acrobat5



C:\Program Files\Adobe\Acrobat DC\Acrobat\acrodistdll.dll



Acrodist4



AcroRegistry1



AcroNew1



C:\Program Files\Adobe\Acrobat DC\Acrobat\acrotray.exe


Новых нет.


C:\Program Files\Adobe\Adobe Bridge 2024\Adobe Bridge.exe



Banner1



BridgeCamRaw2



CmpEax63



Profile2



ProfileExpired1



ValidateLicense2



C:\Program Files\Adobe\Adobe After Effects 2024\Support Files\dvaappsupport.dll



TeamProjectEnabler1



C:\Program Files\Adobe\Adobe After Effects 2024\Support Files\SweetPeaSupport.dll



HevcMpegEnabler3



HevcMpegEnabler4



C:\Program Files\Adobe\Adobe Photoshop 2024\Required\UXP\com.adobe.ccx.start\js\4.js



JS1



JS2



JS3



JS4



C:\Program Files\Adobe\Adobe Photoshop 2024\Required\UXP\com.adobe.ccx.start\manifest.jso n



Version


5.2. Секция DefaultPatterns


C:\Program Files\Adobe\Adobe Photoshop 2024\Photoshop.exe



Banner2



Profile1



ProfileExpired4



C:\Program Files\Adobe\Adobe Illustrator 2024\Support Files\Contents\Windows\Illustrator.exe



ProfileExpired6



C:\Program Files\Adobe\Adobe Substance 3D Designer\Adobe Substance 3D Designer.exe



ProfileExpired5



C:\Program Files\Adobe\Adobe Dreamweaver 2021\Dreamweaver.exe



ValidateLicense3


5.3. Оставшиеся патчи
После анализа всех программ у меня остались нетронутыми такие байты:

INI:



Acrobat1
="488BCFE8????????85C00F84????????488D??????????488B CFE8????????85C075??8D"|"488BCFE8????????FFC00F84????????488D??????????488B CFE8????????31C075??8D"
Acrobat2
="6685C0740FE8????????6685C07405BB01000000"|"6685C07400E8????????6685C07400BB01000000"
Acrobat4
="00E8????E7FF66893D??????03381D??????03751F4533C048 8D15??????02488D0D??????02E8????E7FF6685C00F84BAFE FFFF8BDFE9B3FEFFFFB801000000"|"00E8????E7FF66893D??????03381D??????03EB1F4533C048 8D15??????02488D0D??????02E8????E7FF6685C00F84BAFE FFFF8BDFE9B3FEFFFFB801000000"
Acrodist1
="85C00F84????????E8????????85C00F85????????E8"|"85C00F84????????E8????????FFC00F85????????E8"
Acrodist2
="00908BC8E8??????00CCCCCCCCCCCC48895C24104889"|"00908BC8E8??????00CCCCCCCCCCCC31C0C324104889"
Acrodist3
="5E5B5DC3CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC405553565741 5441554156415748"|"5E5B5DC3CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC31C0C3565741 5441554156415748"
Acrodist5
="51??????????????????????????????48895C???????????? ????????????55"|"51??????????????????????????????33C0C3???????????? ????????????55"
AcroTray1
="E8????????84C074??E8????????84C075??33DB"|"E8????????FEC074??E8????????FEC075??33DB"
AcroTray2
="0F8459030000803D??????00000F8545030000"|"E95A03000090803D??????0000E94603000090"
AcroTray3
="0F85A302000048397C24400F84"|"E9A40200009048397C24400F85"
AcroTray4
="244884DB0F84C70100"|"244884DB0F85C70100"
AcroTray5
="????????E86791D9FFE862B2D8FF????"|"????????9090909090B801000000????"
BridgeCamRaw1
="84C074??8B??83??0174??83??0174??83??01"|"84C074??8B??83??01EB??83??0174??83??01"
CmpEax61
="8B??85C074??83F80674????83????007D"|"C7??0300000083F8067400??83????00EB"
CmpEax62
="8B??85C074??83F80674????83??????007D"|"C7??0300000083F8067400??83??????00EB"
CmpEax64
="8B????85C074??83F80674????83??????007D"|"C7????0300000083F8067400??83??????00EB"
HevcMpegEnabler1
="FF50100FB6"|"FFC0900FB6"
HevcMpegEnabler2
="FF5010??0FB6"|"FFC090??0FB6"
ProfileExpired2
="85C075??B892010000E9"|"85C07500B800000000E9"
ProfileExpired3
="85C075????????75??B892010000E9"|"31C075????????7500B800000000E9"
ValidateLicense1
="83F80175??BA94010000"|"83F80175??BA00000000"
InstantShutdown1
="00??????????E875000000????????C0"|"00??????????9090909090????????C0"
PluginVerification1
="5F5E????????????????????????????48895C2410??8974"|"5F5E????????????????????????????C390909090??8974"
LoginVerification1
="4C8B??????????????????????????????????????742C48"|"4C8B??????????????????????????????????????EB2C48"


В этом случае можно поступить по-другому: мы используем онлайн-дизассемблер (Online x86 and x64 Intel Instruction Assembler). Передадим в него искомые и заменяемые байты, чтобы сравнить. Знаки

??

я заменю

00

вот так:
https://cyberchef.org/#recipe=Find_/_Replace(%7B'option':'Simple%20string','string':'? ?'%7D,'00',true,false,true,false)From_Hex('Auto')T o_Hex('Space',0)&input=NDg4QkNGRTg/Pz8/Pz8/P0ZGQzAwRjg0Pz8/Pz8/Pz80ODhEPz8/Pz8/Pz8/PzQ4OEJDRkU4Pz8/Pz8/Pz8zMUMwNzU/PzhE
Этот способ даст не на 100% точный вывод дизассемблера, а приблизительный. Но точный нам тут и не нужен. Для проверки того, вредоносен ли патч, хватит и приблизительного вывода. К тому же изменения в патчах маленькие, обычно меняют инструкции перед условным переходом, сам условный переход или что-то заменяют чем-то. Это всё не будет вредоносным.


Acrobat1



Acrobat2



Acrobat4



Acrodist1



Acrodist2



Acrodist3



Acrodist5



AcroTray1



AcroTray2



AcroTray3



AcroTray4



AcroTray5



BridgeCamRaw1



CmpEax61



CmpEax62



CmpEax64



HevcMpegEnabler1



HevcMpegEnabler2



ProfileExpired2



ProfileExpired3



ValidateLicense1



InstantShutdown1



PluginVerification1



LoginVerification1


6.0 Вывод
Мы вместе с вами изучили этот патчер и не нашли ничего особо вредоносного внутри него. Даже рассмотрели все патчи и все функции. Но важный момент - мы работали с исходником GenP. Помимо исходника в архиве идёт ещё и EXE-файл. Насчёт его ничего сказать не могу, так как ещё не ревёрсил его. Поэтому если вы хотите тоже его изучить, для безопасности ЗАГРУЖАЙТЕ AutoIt С ОФИЦИАЛЬНОГО САЙТА И СОБИРАЙТЕ СВОЙ EXE ИЗ ИСХОДНИКА! Также обращайте внимание на версию GenP. Ниже я прикреплю контрольные суммы файлов, что были рассмотрены в статье.

Архив с GenP:


iz3lne.zip


MD5:

6b104ba9deb749a6b6ce88b9c6997dae


SHA1:

19d9b52477606b78bdce568235c0acb9321c1bc4


SHA256:

14ce93ae01d50b9d2ff3c36c3edd574a9f8bcec56451f3a865 fcc210c617a77b



GenP-3.4.14.1.au3


MD5:

42c434f0a040132e37eddb5b1d886f8e


SHA1:

3e94d309d3c1dbd4dd9077082e9caf5926bc0fde


SHA256:

3dd6cf96e38768110c8f0e64ae8c698e43931ff9fb57b4a147 6b63f4e5d45554



config.ini


MD5:

add427035968bc6f8bcdf0c5d7580495


SHA1:

7c1d13771b0546c31b87b36d1f158665ba9f793b


SHA256:

66232a4d8677cd50612eaebc664b2f2f3556b497d5bf865796 7c259ef4723b68

UBUNTUM
09.02.2025, 22:41
Это сильно чувак)

UBUNTUM
06.05.2025, 18:20
Ссылка на Reddit не актуальна!

velvetx
06.05.2025, 21:35
спасибо
успокоил)

koekaker
07.05.2025, 11:56
А подобного разбора патча massgrave никто не делал?

Don Reverso
18.05.2025, 11:21
Бомба =]

ROP
01.06.2025, 14:07
UBUNTUM сказал(а):

Ссылка на Reddit не актуальна!


Спасибо, что написал! Всё руки не доходили заменить(
Нашёл у себя файлы GenP из статьи. Ссылка ниже

Скачать (Mega.nz)

Либо можно скачать со сторонних сайтов, но проверяйте хеш-суммы.

Рекомендую запускать в образовательных целях из исходника, как в статье, а не EXE. Скомпилированный EXE не разобран в статье.

kerikush
15.01.2026, 08:44
на момент 2026 года у тебя остался только хеш-суммы приложений.

kerikush
15.01.2026, 08:48
kerikush сказал(а):

на момент 2026 года у тебя остался только хеш-суммы приложений.


или сделай подробный разбор Release GenP 3,7,1 · Mojszli/GenP