
Скриншот программы Cool MP3 Splitter
Программа Cool MP3 Splitter предназначена для вырезания фрагментов нужной длины из MP3-файлов. Особой практической ценности не представляет, потому что есть более мощные и бесплатные аналоги, но в качестве подопытного будет интересна.
Скачиваем дистрибутив, устанавливаем, запускаем. Сразу появляется окно с предложением зарегистрировать программу. На ввод любых левых данных программа никак не реагирует, окно с регистрацией просто закрывается. Разработчики предусмотрели такой вариант и все сделали правильно. Из внешних триальных признаков только две кнопки "Reg" и "BuyNow", в About ничего интересного, в заголовке триальных надписей нет. Тоже не густо для исследования. Поищем хотя бы текст сообщения из наг-скрина. С этим проблем нет, текст лежит в открытом виде.

Триальный текст найден
Теперь выясняем условия его появления. Для этого достаточно посмотреть перекрестные ссылки в дизассемблере.
...
.text:004047DF add ebx, 1F0h
.text:004047E5 mov eax, [ebx]
.text:004047E7 mov edx, [eax]
.text:004047E9 call dword ptr [edx+14h]
; Сравнивается с 0 двойное слово в памяти, на основании результата
; этого принимается решение о триальности
.text:004047EC cmp dword ptr [esi+43Ch], 0
.text:004047F3 mov ebx, eax
; Не равно 0, значит программа зарегистрирована
.text:004047F5 jnz loc_404884
.text:004047FB mov [ebp+var_40], 38h
.text:00404801 mov edx, offset aThisIsA14DayTr
; " This is a 14-day trial version of Coo"...
.text:00404806 lea eax, [ebp+var_14]
.text:00404809 call sub_4A8660
.text:0040480E inc [ebp+var_34]
.text:00404811 mov [ebp+var_40], 2Ch
.text:00404817 push 40h
.text:00404819 mov ecx, offset aThisIsA14Day_0
; "This is a 14-day trial version."
.text:0040481E cmp [ebp+var_14], 0
.text:00404822 jz short loc_404829
.text:00404824 mov edx, [ebp+var_14]
.text:00404827 jmp short loc_40482E
...
Сравнивается двойное слово по адресу [reg+43Ch], если оно не равно 0, то программа зарегистрирована. Этот адрес не фиксированный, при каждом запуске может меняться, поэтому до отладчика попробуем поискать в листинге все строчки, в которых содержится сигнатура "+43Ch]". Пугаться не надо, их не так много, как кажется. Со второго раза находится вот такой любопытный код:
...
; Указатели на какие-то две строки
.text:004045C6 lea edx, [ebp+var_4]
.text:004045C9 lea eax, [ebp+var_24]
; Выполнить функцию сравнения строк
.text:004045CC call @System@AnsiString@$beql$xqqrrx17
; System::AnsiString::operator==(System::AnsiString &)
.text:004045D1 test al, al
; Если она вернула AL=0, то переход
.text:004045D3 jz short loc_4045E2
.text:004045D5 mov ecx, [ebp+var_58]
; Иначе записать 1 в двойное слово по адресу [reg+43Ch]
.text:004045D8 mov dword ptr [ecx+43Ch], 1
.text:004045E2 loc_4045E2:
...
Ничего не напоминает? Правильно, это и есть проверка регистрации. Сравниваются две строки, если они равны, то в память записывается признак зарегистрированности. Найденный фрагмент кода находится внутри отдельной процедуры, посмотрим на нее повнимательнее. Для удобства восприятия некоторые фрагменты я сократил.
.text:004043FC push ebp
.text:004043FD mov ebp, esp
.text:004043FF add esp, 0FFFFFF8Ch
.text:00404402 push ebx
.text:00404403 push esi
.text:00404404 push edi
; Установить собственный обработчик исключений
.text:00404405 mov [ebp+var_58], eax
.text:00404408 mov eax, offset stru_4AC238
.text:0040440D call @__InitExceptBlockLDTC
.text:00404412 mov [ebp+var_44], 8
.text:00404418 lea edi, [ebp+var_74]
; Это какой-то хитрый ключ реестра. Ради интереса можете потом поискать
; его у себя, может быть там есть еще что-нибудь интересное :)
.text:0040441B mov esi, offset aQplaksjdhfgwoi
; "qplaksjdhfgwoieurytmznxbcv"
.text:00404420 mov ecx, 6
.text:00404425 mov edx, offset aUser ; "User"
.text:0040442A rep movsd
.text:0040442C movsw
.text:0040442E movsb
...
...
.text:00404456 mov eax, [eax+444h]
; Прочитать ключ реестра, в котором содержится имя пользователя
.text:0040445C call @TRegistry@ReadString$qqrx10AnsiString
; TRegistry::ReadString(AnsiString)
.text:00404461 lea edx, [ebp+var_10]
.text:00404464 mov eax, [ebp+var_58]
.text:00404467 add eax, 468h
.text:0040446C call sub_4A87E8
.text:00404471 dec [ebp+var_38]
.text:00404474 lea eax, [ebp+var_10]
.text:00404477 mov edx, 2
.text:0040447C call sub_4A87B8
.text:00404481 dec [ebp+var_38]
.text:00404484 lea eax, [ebp+var_C]
.text:00404487 mov edx, 2
.text:0040448C call sub_4A87B8
.text:00404491 mov [ebp+var_44], 2Ch
.text:00404497 mov edx, offset aCode ; "Code"
.text:0040449C lea eax, [ebp+var_14]
.text:0040449F call sub_4A8660
.text:004044A4 inc [ebp+var_38]
.text:004044A7 mov edx, [eax]
.text:004044A9 xor eax, eax
.text:004044AB mov [ebp+var_4], eax
.text:004044AE lea ecx, [ebp+var_4]
.text:004044B1 inc [ebp+var_38]
.text:004044B4 mov eax, [ebp+var_58]
.text:004044B7 mov eax, [eax+444h]
; Прочитать ключ реестра, в котором содержится ключ регистрации
.text:004044BD call @TRegistry@ReadString$qqrx10AnsiString
; TRegistry::ReadString(AnsiString)
.text:004044C2 dec [ebp+var_38]
.text:004044C5 lea eax, [ebp+var_14]
.text:004044C8 mov edx, 2
...
...
.text:00404509 inc [ebp+var_38]
.text:0040450C mov eax, [ebp+lpLibFileName]
; Получить каталог, из которого запущена программа
.text:0040450F call @Sysutils@ExtractFilePath$qqrx17
; Sysutils::ExtractFilePath(System::AnsiString)
.text:00404514 lea ecx, [ebp+var_18]
.text:00404517 push ecx
; Дописать к нему строку "key.dll"
.text:00404518 mov edx, offset aKey_dll ; "key.dll"
.text:0040451D lea eax, [ebp+var_1C]
.text:00404520 call sub_4A8660
...
...
.text:00404587 push ecx ; lpLibFileName
; Попытаться загрузить key.dll из папки программы. В стандартной поставке
; этого файла нет, видимо он высылается пользователям после покупки или
; его можно скачать из какой-нибудь закрытой зоны сайта. В любом случае
; у меня его нет.
.text:00404588 call LoadLibraryA
; Пипец, а если dll нет? Никаких обработчиков ошибок нет. No comments
.text:0040458D mov ebx, eax
; Получить адрес функции "myfun" из загруженной библиотеки
.text:0040458F push offset aMyfun ; "myfun"
.text:00404594 push ebx ; hModule
.text:00404595 call GetProcAddress
.text:0040459A test eax, eax
; Если функции "myfun" не найдено, то программа считается триальной
.text:0040459C jz short loc_4045F8
.text:0040459E mov [ebp+var_44], 5Ch
.text:004045A4 lea edx, [ebp+var_74]
.text:004045A7 push edx
.text:004045A8 mov ecx, [ebp+var_58]
.text:004045AB mov edx, [ecx+468h]
.text:004045B1 xor ecx, ecx
.text:004045B3 push edx
.text:004045B4 lea edx, [ebp+var_24]
.text:004045B7 mov [ebp+var_24], ecx
.text:004045BA push edx
.text:004045BB inc [ebp+var_38]
; Вызвать функцию "myfun", передав ей в параметрах имя из реестра
.text:004045BE call eax
.text:004045C0 mov [ebp+var_44], 50h
; Сравнить полученную строчку с ключом
.text:004045C6 lea edx, [ebp+var_4]
.text:004045C9 lea eax, [ebp+var_24]
.text:004045CC call @System@AnsiString@$beql$xqqrrx17
; System::AnsiString::operator==(System::AnsiString &)
.text:004045D1 test al, al
.text:004045D3 jz short loc_4045E2
; Если они совпали, то программа зарегистрирована
.text:004045D5 mov ecx, [ebp+var_58]
.text:004045D8 mov dword ptr [ecx+43Ch], 1
.text:004045E2 loc_4045E2:
.text:004045E2 dec [ebp+var_38]
.text:004045E5 lea eax, [ebp+var_24]
.text:004045E8 mov edx, 2
.text:004045ED call sub_4A87B8
.text:004045F2 mov [ebp+var_44], 20h
.text:004045F8 loc_4045F8:
.text:004045F8 push ebx ; hLibModule
; Выгрузить библиотеку "key.dll"
.text:004045F9 call FreeLibrary
.text:004045FE dec [ebp+var_38]
.text:00404601 lea eax, [ebp+lpLibFileName]
.text:00404604 mov edx, 2
.text:00404609 call sub_4A87B8
...
...
.text:00404673 mov edx, [ebp+var_58]
; Программа зарегистрирована?
.text:00404676 cmp dword ptr [edx+43Ch], 1
.text:0040467D jnz short loc_4046EC
; Убрать с формы кнопки "Reg" и "BuyNow"
.text:0040467F mov eax, [ebp+var_58]
.text:00404682 xor edx, edx
.text:00404684 mov eax, [eax+388h]
.text:0040468A call @Controls@TControl@SetVisible$qqro
; Controls::TControl::SetVisible(bool)
.text:0040468F mov ecx, [ebp+var_58]
.text:00404692 xor edx, edx
.text:00404694 mov eax, [ecx+384h]
.text:0040469A call @Controls@TControl@SetVisible$qqro
; Controls::TControl::SetVisible(bool)
.text:0040469F mov [ebp+var_44], 80h
.text:004046A5 xor ecx, ecx
; Записать куда-то строку, что программа зарегистрирована на кого-то
.text:004046A7 mov eax, offset aThisProductIsL
; "This Product Is Licensed To : "
.text:004046AC mov [ebp+var_30], ecx
.text:004046AF lea ecx, [ebp+var_30]
.text:004046B2 inc [ebp+var_38]
.text:004046B5 mov edx, [ebp+var_58]
...
...
; Здесь мы окажемся, если программа не зарегистрирована
.text:004046EC loc_4046EC:
.text:004046EC mov eax, [ebp+var_58]
.text:004046EF call sub_4058E8
...
Теперь, когда мы знаем, как работает защита, можно попробовать ее обойти. Сложный путь: написать свою key.dll с функцией myfun, пройти в отладчике сравнение строк и доработать key.dll так, чтобы она всегда возвращала нужные значения. Но для такой ненужной программы тратить столько усилий - слишком велика честь. Поэтому пойдем по простому пути - пропатчим исполняемый файл. Обычным патчем условных переходов тут не отделаться, ведь признак зарегистрированности заносится в память после проверок только при наличии файла key.dll, а у нас его нет, значит эта ветка алгоритма будет проигнорирована еще раньше, а принудительный заход в нее вызовет ошибку при обращении к несуществующей функции myfun по адресу 004045BE. Зато ниже по коду сразу же есть проверка:
.text:00404676 cmp dword ptr [edx+43Ch], 1
.text:0040467D jnz short loc_4046EC
Ее можно использовать для патча, сделав из проверки инициализацию двойного слова в памяти. Для этого надо заменить команду cmp на inc dword ptr [edx+43Ch] (команда mov dword ptr [edx+43Ch], 1 не поместится), а остаток команды и условный переход забить NOP'ами. Получится вот такой код:
; Записать в [reg+43Ch] единицу
.text:00404676 inc dword ptr [edx+43Ch]
.text:0040467C nop
.text:0040467D nop
.text:0040467E nop
Запускаем, проверяем, все работает замечательно. Никаких наг-скринов, никаких ограничений по количеству нарезаемых фрагментов.

Программа успешно "зарегистрирована"
Для спортивного интереса можно посмотреть еще и триальный алгоритм программы. Он вызывается по адресу 004046EF, вызываемая процедура расписана и прокомментирована ниже. Для удобства я также сократил некоторые фрагменты:
.text:004058E8 push ebp
.text:004058E9 mov ebp, esp
.text:004058EB add esp, 0FFFFFE54h
.text:004058F1 mov eax, offset stru_4AC9D0
.text:004058F6 push ebx
.text:004058F7 push esi
.text:004058F8 push edi
.text:004058F9 call @__InitExceptBlockLDTC
.text:004058FE push 104h ; uSize
.text:00405903 lea edx, [ebp+Buffer]
.text:00405909 push edx ; lpBuffer
; Получить папку, в которую установлена WINDOWS
.text:0040590A call GetWindowsDirectoryA
.text:0040590F mov esi, eax
.text:00405911 lea edi, [esi+1]
...
...
.text:00405944 call sub_4A8660
.text:00405949 inc [ebp+var_60]
; Дописать к ней строку "\Syskernel12.dll"
.text:0040594C mov edx, offset aSyskernel12_dl
; "\Syskernel12.dll"
.text:00405951 mov [ebp+var_6C], 14h
.text:00405957 mov [ebp+var_6C], 20h
.text:0040595D lea eax, [ebp+var_C]
...
...
; Получить текущую дату и время
.text:004059BD call @System@TDateTime@CurrentDate$qqrv
; System::TDateTime::CurrentDate(void)
.text:004059C2 fstp [ebp+var_88]
.text:004059C8 mov [ebp+var_6C], 2Ch
.text:004059CE xor eax, eax
...
...
; Пропущена куча кода по определению размера файла и чтению его в память
...
...
.text:00405A4C mov [ebp+var_6C], 44h
.text:00405A52 lea eax, [ebp+var_14]
.text:00405A55 mov edx, ebx
.text:00405A57 call sub_4A8660
.text:00405A5C inc [ebp+var_60]
.text:00405A5F mov edx, offset aEnd ; "-----END-----"
.text:00405A64 mov [ebp+var_6C], 50h
.text:00405A6A mov [ebp+var_6C], 5Ch
.text:00405A70 lea eax, [ebp+var_18]
.text:00405A73 call sub_4A8660
.text:00405A78 inc [ebp+var_60]
; Сравнить прочитанные данные со строкой "-----END-----"
.text:00405A7B lea edx, [ebp+var_18]
.text:00405A7E lea eax, [ebp+var_14]
.text:00405A81 call @System@AnsiString@$beql$xqqrrx17
; System::AnsiString::operator==(System::AnsiString &)
.text:00405A86 push eax
.text:00405A87 dec [ebp+var_60]
.text:00405A8A lea eax, [ebp+var_18]
.text:00405A8D mov edx, 2
.text:00405A92 call sub_4A87B8
.text:00405A97 pop ecx
.text:00405A98 test cl, cl
; Если в файле записана строка "-----END-----", то триальный срок закончился
.text:00405A9A jz loc_405B2B
.text:00405AA0 mov [ebp+var_6C], 68h
.text:00405AA6 mov edx, offset aTheTrialVersio
; "The trial version of Cool MP3 Splitter "...
.text:00405AAB lea eax, [ebp+var_1C]
.text:00405AAE call sub_4A8660
.text:00405AB3 inc [ebp+var_60]
.text:00405AB6 mov edx, [eax]
...
...
.text:00405B19 mov edx, off_4BA1AC
.text:00405B1F mov eax, [edx]
; Выйти из программы
.text:00405B21 call @Forms@TApplication@Terminate$qqrv
; Forms::TApplication::Terminate(void)
.text:00405B26 jmp loc_405D24
.text:00405B2B ; -------------------------------------------------
; Иначе триальный срок продолжается
.text:00405B2B mov [ebp+var_6C], 98h
.text:00405B31 lea eax, [ebp+var_28]
.text:00405B34 mov edx, ebx
.text:00405B36 call sub_4A8660
.text:00405B3B inc [ebp+var_60]
.text:00405B3E lea edx, [ebp+var_28]
.text:00405B41 lea eax, [ebp+var_90]
.text:00405B47 mov cl, 2
; Получить текущую дату и время в виде строки
.text:00405B49 call @System@TDateTime
.text:00405B4E dec [ebp+var_60]
.text:00405B51 lea eax, [ebp+var_28]
...
...
.text:00405C0C mov eax, [ebp+var_80]
; Записать в файл
.text:00405C0F call @Classes@TStream@WriteBuffer$qqrpxvi
; Classes::TStream::WriteBuffer(void *,int)
.text:00405C14 mov ebx, [ebp+var_80]
.text:00405C17 mov [ebp+var_34], ebx
.text:00405C1A test ebx, ebx
...
А теперь то же самое, но только словами. При запуске программа открывает и читает файл %WINDIR%\Syskernel12.dll, если в нем записана строка "-----END-----", то триальный срок истек и переустановка программы не поможет. Во время триального срока в этом файле записана дата первого запуска программы. Если при очередной проверке даты триальный срок истек, то в файл вместо даты записывается строка "-----END-----". Для вечного триала достаточно время от времени удалять этот файл, программа будет создавать его заново, как будто только что установлена. А вообще меня бесят криворукие шароварщики, которые пытаются запрятать регистрацию или триальные счетчики своего говнософта в системные папки, да еще и в файл с именем динамической библиотеки. Не делайте так никогда.