Blog. Just Blog

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

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Темная сторона Силы | Автор: ManHunter
Скриншот программы Cool MP3 Splitter
Скриншот программы Cool MP3 Splitter

Программа Cool MP3 Splitter предназначена для вырезания фрагментов нужной длины из MP3-файлов. Особой практической ценности не представляет, потому что есть более мощные и бесплатные аналоги, но в качестве подопытного будет интересна.

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

Триальный текст найден
Триальный текст найден

Теперь выясняем условия его появления. Для этого достаточно посмотреть перекрестные ссылки в дизассемблере.
  1. ...
  2. .text:004047DF                 add     ebx, 1F0h
  3. .text:004047E5                 mov     eax, [ebx]
  4. .text:004047E7                 mov     edx, [eax]
  5. .text:004047E9                 call    dword ptr [edx+14h]
  6. ; Сравнивается с 0 двойное слово в памяти, на основании результата
  7. ; этого принимается решение о триальности
  8. .text:004047EC                 cmp     dword ptr [esi+43Ch], 0
  9. .text:004047F3                 mov     ebx, eax
  10. ; Не равно 0, значит программа зарегистрирована
  11. .text:004047F5                 jnz     loc_404884
  12. .text:004047FB                 mov     [ebp+var_40], 38h
  13. .text:00404801                 mov     edx, offset aThisIsA14DayTr
  14. ; "  This is a 14-day trial version of Coo"...
  15. .text:00404806                 lea     eax, [ebp+var_14]
  16. .text:00404809                 call    sub_4A8660
  17. .text:0040480E                 inc     [ebp+var_34]
  18. .text:00404811                 mov     [ebp+var_40], 2Ch
  19. .text:00404817                 push    40h
  20. .text:00404819                 mov     ecx, offset aThisIsA14Day_0
  21. ; "This is a 14-day trial version."
  22. .text:0040481E                 cmp     [ebp+var_14], 0
  23. .text:00404822                 jz      short loc_404829
  24. .text:00404824                 mov     edx, [ebp+var_14]
  25. .text:00404827                 jmp     short loc_40482E
  26. ...
Сравнивается двойное слово по адресу [reg+43Ch], если оно не равно 0, то программа зарегистрирована. Этот адрес не фиксированный, при каждом запуске может меняться, поэтому до отладчика попробуем поискать в листинге все строчки, в которых содержится сигнатура "+43Ch]". Пугаться не надо, их не так много, как кажется. Со второго раза находится вот такой любопытный код:
  1. ...
  2. ; Указатели на какие-то две строки
  3. .text:004045C6                 lea     edx, [ebp+var_4]
  4. .text:004045C9                 lea     eax, [ebp+var_24]
  5. ; Выполнить функцию сравнения строк
  6. .text:004045CC                 call    @System@AnsiString@$beql$xqqrrx17
  7. ; System::AnsiString::operator==(System::AnsiString &)
  8. .text:004045D1                 test    al, al
  9. ; Если она вернула AL=0, то переход
  10. .text:004045D3                 jz      short loc_4045E2
  11. .text:004045D5                 mov     ecx, [ebp+var_58]
  12. ; Иначе записать 1 в двойное слово по адресу [reg+43Ch]
  13. .text:004045D8                 mov     dword ptr [ecx+43Ch], 1
  14. .text:004045E2 loc_4045E2:
  15. ...
Ничего не напоминает? Правильно, это и есть проверка регистрации. Сравниваются две строки, если они равны, то в память записывается признак зарегистрированности. Найденный фрагмент кода находится внутри отдельной процедуры, посмотрим на нее повнимательнее. Для удобства восприятия некоторые фрагменты я сократил.
  1. .text:004043FC                 push    ebp
  2. .text:004043FD                 mov     ebp, esp
  3. .text:004043FF                 add     esp, 0FFFFFF8Ch
  4. .text:00404402                 push    ebx
  5. .text:00404403                 push    esi
  6. .text:00404404                 push    edi
  7. ; Установить собственный обработчик исключений
  8. .text:00404405                 mov     [ebp+var_58], eax
  9. .text:00404408                 mov     eax, offset stru_4AC238
  10. .text:0040440D                 call    @__InitExceptBlockLDTC
  11. .text:00404412                 mov     [ebp+var_44], 8
  12. .text:00404418                 lea     edi, [ebp+var_74]
  13. ; Это какой-то хитрый ключ реестра. Ради интереса можете потом поискать
  14. ; его у себя, может быть там есть еще что-нибудь интересное :)
  15. .text:0040441B                 mov     esi, offset aQplaksjdhfgwoi
  16. ; "qplaksjdhfgwoieurytmznxbcv"
  17. .text:00404420                 mov     ecx, 6
  18. .text:00404425                 mov     edx, offset aUser ; "User"
  19. .text:0040442A                 rep movsd
  20. .text:0040442C                 movsw
  21. .text:0040442E                 movsb
  22. ...
  23. ...
  24. .text:00404456                 mov     eax, [eax+444h]
  25. ; Прочитать ключ реестра, в котором содержится имя пользователя
  26. .text:0040445C                 call    @TRegistry@ReadString$qqrx10AnsiString
  27. ; TRegistry::ReadString(AnsiString)
  28. .text:00404461                 lea     edx, [ebp+var_10]
  29. .text:00404464                 mov     eax, [ebp+var_58]
  30. .text:00404467                 add     eax, 468h
  31. .text:0040446C                 call    sub_4A87E8
  32. .text:00404471                 dec     [ebp+var_38]
  33. .text:00404474                 lea     eax, [ebp+var_10]
  34. .text:00404477                 mov     edx, 2
  35. .text:0040447C                 call    sub_4A87B8
  36. .text:00404481                 dec     [ebp+var_38]
  37. .text:00404484                 lea     eax, [ebp+var_C]
  38. .text:00404487                 mov     edx, 2
  39. .text:0040448C                 call    sub_4A87B8
  40. .text:00404491                 mov     [ebp+var_44], 2Ch
  41. .text:00404497                 mov     edx, offset aCode ; "Code"
  42. .text:0040449C                 lea     eax, [ebp+var_14]
  43. .text:0040449F                 call    sub_4A8660
  44. .text:004044A4                 inc     [ebp+var_38]
  45. .text:004044A7                 mov     edx, [eax]
  46. .text:004044A9                 xor     eax, eax
  47. .text:004044AB                 mov     [ebp+var_4], eax
  48. .text:004044AE                 lea     ecx, [ebp+var_4]
  49. .text:004044B1                 inc     [ebp+var_38]
  50. .text:004044B4                 mov     eax, [ebp+var_58]
  51. .text:004044B7                 mov     eax, [eax+444h]
  52. ; Прочитать ключ реестра, в котором содержится ключ регистрации
  53. .text:004044BD                 call    @TRegistry@ReadString$qqrx10AnsiString
  54. ; TRegistry::ReadString(AnsiString)
  55. .text:004044C2                 dec     [ebp+var_38]
  56. .text:004044C5                 lea     eax, [ebp+var_14]
  57. .text:004044C8                 mov     edx, 2
  58. ...
  59. ...
  60. .text:00404509                 inc     [ebp+var_38]
  61. .text:0040450C                 mov     eax, [ebp+lpLibFileName]
  62. ; Получить каталог, из которого запущена программа
  63. .text:0040450F                 call    @Sysutils@ExtractFilePath$qqrx17
  64. ; Sysutils::ExtractFilePath(System::AnsiString)
  65. .text:00404514                 lea     ecx, [ebp+var_18]
  66. .text:00404517                 push    ecx
  67. ; Дописать к нему строку "key.dll"
  68. .text:00404518                 mov     edx, offset aKey_dll ; "key.dll"
  69. .text:0040451D                 lea     eax, [ebp+var_1C]
  70. .text:00404520                 call    sub_4A8660
  71. ...
  72. ...
  73. .text:00404587                 push    ecx             ; lpLibFileName
  74. ; Попытаться загрузить key.dll из папки программы. В стандартной поставке
  75. ; этого файла нет, видимо он высылается пользователям после покупки или
  76. ; его можно скачать из какой-нибудь закрытой зоны сайта. В любом случае
  77. ; у меня его нет.
  78. .text:00404588                 call    LoadLibraryA
  79. ; Пипец, а если dll нет? Никаких обработчиков ошибок нет. No comments
  80. .text:0040458D                 mov     ebx, eax
  81. ; Получить адрес функции "myfun" из загруженной библиотеки
  82. .text:0040458F                 push    offset aMyfun   ; "myfun"
  83. .text:00404594                 push    ebx             ; hModule
  84. .text:00404595                 call    GetProcAddress
  85. .text:0040459A                 test    eax, eax
  86. ; Если функции "myfun" не найдено, то программа считается триальной
  87. .text:0040459C                 jz      short loc_4045F8
  88. .text:0040459E                 mov     [ebp+var_44], 5Ch
  89. .text:004045A4                 lea     edx, [ebp+var_74]
  90. .text:004045A7                 push    edx
  91. .text:004045A8                 mov     ecx, [ebp+var_58]
  92. .text:004045AB                 mov     edx, [ecx+468h]
  93. .text:004045B1                 xor     ecx, ecx
  94. .text:004045B3                 push    edx
  95. .text:004045B4                 lea     edx, [ebp+var_24]
  96. .text:004045B7                 mov     [ebp+var_24], ecx
  97. .text:004045BA                 push    edx
  98. .text:004045BB                 inc     [ebp+var_38]
  99. ; Вызвать функцию "myfun", передав ей в параметрах имя из реестра
  100. .text:004045BE                 call    eax
  101. .text:004045C0                 mov     [ebp+var_44], 50h
  102. ; Сравнить полученную строчку с ключом
  103. .text:004045C6                 lea     edx, [ebp+var_4]
  104. .text:004045C9                 lea     eax, [ebp+var_24]
  105. .text:004045CC                 call    @System@AnsiString@$beql$xqqrrx17
  106. ; System::AnsiString::operator==(System::AnsiString &)
  107. .text:004045D1                 test    al, al
  108. .text:004045D3                 jz      short loc_4045E2
  109. ; Если они совпали, то программа зарегистрирована
  110. .text:004045D5                 mov     ecx, [ebp+var_58]
  111. .text:004045D8                 mov     dword ptr [ecx+43Ch], 1
  112. .text:004045E2 loc_4045E2:
  113. .text:004045E2                 dec     [ebp+var_38]
  114. .text:004045E5                 lea     eax, [ebp+var_24]
  115. .text:004045E8                 mov     edx, 2
  116. .text:004045ED                 call    sub_4A87B8
  117. .text:004045F2                 mov     [ebp+var_44], 20h
  118. .text:004045F8 loc_4045F8:
  119. .text:004045F8                 push    ebx             ; hLibModule
  120. ; Выгрузить библиотеку "key.dll"
  121. .text:004045F9                 call    FreeLibrary
  122. .text:004045FE                 dec     [ebp+var_38]
  123. .text:00404601                 lea     eax, [ebp+lpLibFileName]
  124. .text:00404604                 mov     edx, 2
  125. .text:00404609                 call    sub_4A87B8
  126. ...
  127. ...
  128. .text:00404673                 mov     edx, [ebp+var_58]
  129. ; Программа зарегистрирована?
  130. .text:00404676                 cmp     dword ptr [edx+43Ch], 1
  131. .text:0040467D                 jnz     short loc_4046EC
  132. ; Убрать с формы кнопки "Reg" и "BuyNow"
  133. .text:0040467F                 mov     eax, [ebp+var_58]
  134. .text:00404682                 xor     edx, edx
  135. .text:00404684                 mov     eax, [eax+388h]
  136. .text:0040468A                 call    @Controls@TControl@SetVisible$qqro
  137. ; Controls::TControl::SetVisible(bool)
  138. .text:0040468F                 mov     ecx, [ebp+var_58]
  139. .text:00404692                 xor     edx, edx
  140. .text:00404694                 mov     eax, [ecx+384h]
  141. .text:0040469A                 call    @Controls@TControl@SetVisible$qqro
  142. ; Controls::TControl::SetVisible(bool)
  143. .text:0040469F                 mov     [ebp+var_44], 80h
  144. .text:004046A5                 xor     ecx, ecx
  145. ; Записать куда-то строку, что программа зарегистрирована на кого-то
  146. .text:004046A7                 mov     eax, offset aThisProductIsL
  147. ; "This Product Is Licensed To : "
  148. .text:004046AC                 mov     [ebp+var_30], ecx
  149. .text:004046AF                 lea     ecx, [ebp+var_30]
  150. .text:004046B2                 inc     [ebp+var_38]
  151. .text:004046B5                 mov     edx, [ebp+var_58]
  152. ...
  153. ...
  154. ; Здесь мы окажемся, если программа не зарегистрирована
  155. .text:004046EC loc_4046EC:
  156. .text:004046EC                 mov     eax, [ebp+var_58]
  157. .text:004046EF                 call    sub_4058E8
  158. ...
Теперь, когда мы знаем, как работает защита, можно попробовать ее обойти. Сложный путь: написать свою key.dll с функцией myfun, пройти в отладчике сравнение строк и доработать key.dll так, чтобы она всегда возвращала нужные значения. Но для такой ненужной программы тратить столько усилий - слишком велика честь. Поэтому пойдем по простому пути - пропатчим исполняемый файл. Обычным патчем условных переходов тут не отделаться, ведь признак зарегистрированности заносится в память после проверок только при наличии файла key.dll, а у нас его нет, значит эта ветка алгоритма будет проигнорирована еще раньше, а принудительный заход в нее вызовет ошибку при обращении к несуществующей функции myfun по адресу 004045BE. Зато ниже по коду сразу же есть проверка:
  1. .text:00404676                 cmp     dword ptr [edx+43Ch], 1
  2. .text:0040467D                 jnz     short loc_4046EC
Ее можно использовать для патча, сделав из проверки инициализацию двойного слова в памяти. Для этого надо заменить команду cmp на inc dword ptr [edx+43Ch] (команда mov dword ptr [edx+43Ch], 1 не поместится), а остаток команды и условный переход забить NOP'ами. Получится вот такой код:
  1. ; Записать в [reg+43Ch] единицу
  2. .text:00404676                 inc     dword ptr [edx+43Ch]
  3. .text:0040467C                 nop
  4. .text:0040467D                 nop
  5. .text:0040467E                 nop
Запускаем, проверяем, все работает замечательно. Никаких наг-скринов, никаких ограничений по количеству нарезаемых фрагментов.

Программа успешно "зарегистрирована"
Программа успешно "зарегистрирована"

Для спортивного интереса можно посмотреть еще и триальный алгоритм программы. Он вызывается по адресу 004046EF, вызываемая процедура расписана и прокомментирована ниже. Для удобства я также сократил некоторые фрагменты:
  1. .text:004058E8                 push    ebp
  2. .text:004058E9                 mov     ebp, esp
  3. .text:004058EB                 add     esp, 0FFFFFE54h
  4. .text:004058F1                 mov     eax, offset stru_4AC9D0
  5. .text:004058F6                 push    ebx
  6. .text:004058F7                 push    esi
  7. .text:004058F8                 push    edi
  8. .text:004058F9                 call    @__InitExceptBlockLDTC
  9. .text:004058FE                 push    104h            ; uSize
  10. .text:00405903                 lea     edx, [ebp+Buffer]
  11. .text:00405909                 push    edx             ; lpBuffer
  12. ; Получить папку, в которую установлена WINDOWS
  13. .text:0040590A                 call    GetWindowsDirectoryA
  14. .text:0040590F                 mov     esi, eax
  15. .text:00405911                 lea     edi, [esi+1]
  16. ...
  17. ...
  18. .text:00405944                 call    sub_4A8660
  19. .text:00405949                 inc     [ebp+var_60]
  20. ; Дописать к ней строку "\\Syskernel12.dll"
  21. .text:0040594C                 mov     edx, offset aSyskernel12_dl
  22. ; "\\Syskernel12.dll"
  23. .text:00405951                 mov     [ebp+var_6C], 14h
  24. .text:00405957                 mov     [ebp+var_6C], 20h
  25. .text:0040595D                 lea     eax, [ebp+var_C]
  26. ...
  27. ...
  28. ; Получить текущую дату и время
  29. .text:004059BD                 call    @System@TDateTime@CurrentDate$qqrv
  30. ; System::TDateTime::CurrentDate(void)
  31. .text:004059C2                 fstp    [ebp+var_88]
  32. .text:004059C8                 mov     [ebp+var_6C], 2Ch
  33. .text:004059CE                 xor     eax, eax
  34. ...
  35. ...
  36. ; Пропущена куча кода по определению размера файла и чтению его в память
  37. ...
  38. ...
  39. .text:00405A4C                 mov     [ebp+var_6C], 44h
  40. .text:00405A52                 lea     eax, [ebp+var_14]
  41. .text:00405A55                 mov     edx, ebx
  42. .text:00405A57                 call    sub_4A8660
  43. .text:00405A5C                 inc     [ebp+var_60]
  44. .text:00405A5F                 mov     edx, offset aEnd ; "-----END-----"
  45. .text:00405A64                 mov     [ebp+var_6C], 50h
  46. .text:00405A6A                 mov     [ebp+var_6C], 5Ch
  47. .text:00405A70                 lea     eax, [ebp+var_18]
  48. .text:00405A73                 call    sub_4A8660
  49. .text:00405A78                 inc     [ebp+var_60]
  50. ; Сравнить прочитанные данные со строкой "-----END-----"
  51. .text:00405A7B                 lea     edx, [ebp+var_18]
  52. .text:00405A7E                 lea     eax, [ebp+var_14]
  53. .text:00405A81                 call    @System@AnsiString@$beql$xqqrrx17
  54. ; System::AnsiString::operator==(System::AnsiString &)
  55. .text:00405A86                 push    eax
  56. .text:00405A87                 dec     [ebp+var_60]
  57. .text:00405A8A                 lea     eax, [ebp+var_18]
  58. .text:00405A8D                 mov     edx, 2
  59. .text:00405A92                 call    sub_4A87B8
  60. .text:00405A97                 pop     ecx
  61. .text:00405A98                 test    cl, cl
  62. ; Если в файле записана строка "-----END-----", то триальный срок закончился
  63. .text:00405A9A                 jz      loc_405B2B
  64. .text:00405AA0                 mov     [ebp+var_6C], 68h
  65. .text:00405AA6                 mov     edx, offset aTheTrialVersio
  66. ; "The trial version of Cool MP3 Splitter "...
  67. .text:00405AAB                 lea     eax, [ebp+var_1C]
  68. .text:00405AAE                 call    sub_4A8660
  69. .text:00405AB3                 inc     [ebp+var_60]
  70. .text:00405AB6                 mov     edx, [eax]
  71. ...
  72. ...
  73. .text:00405B19                 mov     edx, off_4BA1AC
  74. .text:00405B1F                 mov     eax, [edx]
  75. ; Выйти из программы
  76. .text:00405B21                 call    @Forms@TApplication@Terminate$qqrv
  77. ; Forms::TApplication::Terminate(void)
  78. .text:00405B26                 jmp     loc_405D24
  79. .text:00405B2B ; -------------------------------------------------
  80. ; Иначе триальный срок продолжается
  81. .text:00405B2B                 mov     [ebp+var_6C], 98h
  82. .text:00405B31                 lea     eax, [ebp+var_28]
  83. .text:00405B34                 mov     edx, ebx
  84. .text:00405B36                 call    sub_4A8660
  85. .text:00405B3B                 inc     [ebp+var_60]
  86. .text:00405B3E                 lea     edx, [ebp+var_28]
  87. .text:00405B41                 lea     eax, [ebp+var_90]
  88. .text:00405B47                 mov     cl, 2
  89. ; Получить текущую дату и время в виде строки
  90. .text:00405B49                 call    @System@TDateTime
  91. .text:00405B4E                 dec     [ebp+var_60]
  92. .text:00405B51                 lea     eax, [ebp+var_28]
  93. ...
  94. ...
  95. .text:00405C0C                 mov     eax, [ebp+var_80]
  96. ; Записать в файл
  97. .text:00405C0F                 call    @Classes@TStream@WriteBuffer$qqrpxvi
  98. ; Classes::TStream::WriteBuffer(void *,int)
  99. .text:00405C14                 mov     ebx, [ebp+var_80]
  100. .text:00405C17                 mov     [ebp+var_34], ebx
  101. .text:00405C1A                 test    ebx, ebx
  102. ...
А теперь то же самое, но только словами. При запуске программа открывает и читает файл %WINDIR%\Syskernel12.dll, если в нем записана строка "-----END-----", то триальный срок истек и переустановка программы не поможет. Во время триального срока в этом файле записана дата первого запуска программы. Если при очередной проверке даты триальный срок истек, то в файл вместо даты записывается строка "-----END-----". Для вечного триала достаточно время от времени удалять этот файл, программа будет создавать его заново, как будто только что установлена. А вообще меня бесят криворукие шароварщики, которые пытаются запрятать регистрацию или триальные счетчики своего говнософта в системные папки, да еще и в файл с именем динамической библиотеки. Не делайте так никогда.

Поделиться ссылкой ВКонтакте
Просмотров: 5333 | Комментариев: 5

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Pp (30.11.2010 в 12:16):
Всё работает сам затупил.
Pp (30.11.2010 в 10:58):
Ограничение на "60-секундность" фрагмента не снимает.
AyTkACT (15.04.2010 в 20:20):
"А вообще меня бесят криворукие шароварщики, которые пытаются запрятать регистрацию или триальные счетчики своего говнософта в системные папки, да еще и в файл с именем динамической библиотеки." © ManHunter    Подпишусь под каждым словом. Понравилось решение с cmp -> inc. Вообщем как всегда приятно почитать. Спасибо.
Чтец (13.04.2010 в 17:09):
Как всегда отлично, особенно финальная часть. :)
INC. (13.04.2010 в 13:53):
Thanks

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

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0066 сек.) / Память: 4.5 Mb
Наверх