Исследование защиты программы Cool MP3 Splitter


Скриншот программы 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-----". Для вечного триала достаточно время от времени удалять этот файл, программа будет создавать его заново, как будто только что установлена. А вообще меня бесят криворукие шароварщики, которые пытаются запрятать регистрацию или триальные счетчики своего говнософта в системные папки, да еще и в файл с именем динамической библиотеки. Не делайте так никогда.
Теги:
защита, взлом
Добавлено: 21 Сентября 2013 01:35:48 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...