Blog. Just Blog

Исследование защиты программы Xilisoft Video Cutter

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

Компания Xilisoft на протяжении многих лет занимает лидирующие позиции по производству разнообразных мультимедийных "ужа-в-ежа конвертеров". Достаточно лишь посмотреть список и наименования их продуктов. Остальные их программы, не относящиеся к конвертерам, все равно так или иначе связаны с обработкой мультимедийных файлов, например, Xilisoft Video Cutter. Главное, что за каждое свое поделие они хотят отдельную денежку. Это при том, что все действия можно выполнить с помощью гораздо более мощных и бесплатных инструментов. Ну да ладно, считать чужие деньги неприлично, лучше сделать так, чтобы ничего считать не пришлось.

Забираем с сайта дистрибутив, устанавливаем, запускаем, смотрим. К слову, просто так дистрибутив с сайта не скачать, ссылка на загрузку с офсайта ведет на какую-то софтопомойку с кучей рекламы и адвары, с которой тоже фиг скачаешь. Так что пользуйтесь моей прямой ссылкой. Похоже, что доходов от продаж не хватает, вот они и решили получить немного дополнительного профита с инсталляции адвары. В папке с установленной программой обнаруживается куча динамических библиотек с характерным префиксом "Qt". Я уже не первый год пересекаюсь с продуктами Xilisoft. Раньше им вполне хватало обычных виндовых компиляторов, но в какой-то момент они решили притянуть пользователей Mac, поэтому перешли на использование кроссплатформенного Qt. Запускаем программу и пробуем зарегистрировать ее каким-нибудь левым серийником.

Сообщение о неправильной регистрации
Сообщение о неправильной регистрации

Вот, уже хорошо. У нас есть текст сообщения о неправильной регистрации. Поищем по фразе "Invalid license info", в каком файле это сообщение находится. Оказывается, что базовая строчка обнаруживается в файле "imfc0.dll", плюс к этому в пачке файлов локализации. Но нас интересует только файл imfc0.dll. Отправляем его на анализ в дизассемблер. В файле не все просто, оказывается, что это не обычная строка, а целая html-страница. Ну да, Qt так умеет.

Текст сообщения
Текст сообщения

Посмотрим, где вся эта красота используется. По перекрестным ссылкам в дизассемблере выходим на код, где это сообщение формируется и выводится диалоговое окно. Там ничего для нас интересного нет, поэтому прокручиваем код вверх и переходим на код, откуда это окно вызывается:
  1. .text:100226DC loc_100226DC:
  2. .text:100226DC                 cmp     [esp+5Ch+arg_4], ebp
  3. .text:100226E0                 jnz     short loc_10022753
  4. .text:100226E2                 call    ?instance@regLib@@SAPAV1@XZ
  5. ; regLib::instance(void)
  6. .text:100226E7                 mov     edi, eax
  7. .text:100226E9                 mov     ecx, edi
  8. .text:100226EB                 call    ?updateState@regLib@@QAEXXZ
  9. ; regLib::updateState(void)
  10. .text:100226F0                 cmp     dword ptr [edi+0Ch], 0
  11. .text:100226F4                 jz      short loc_10022753
  12. ...
  13. ...
  14. ...
  15. .text:10022753 loc_10022753:
  16. .text:10022753                 mov     ecx, esi
  17. ; Окно с сообщением
  18. .text:10022755                 call    loc_1001F1F0
А что это у нас за "regLib" такая? Давайте посмотрим, какие еще функции в ней присутствуют.

Функции регистрации
Функции регистрации

Можно сколько угодно не любить Qt, но за такие структурированные подарки приходится быть ему благодарным. Готов поспорить на что угодно, что функция GetRegInfo запрашивает регистрационные данные, SaveRegInfo сохраняет их куда надо, а самая интересная функция IsValidRegInfo отвечает за проверку корректности введенных данных. Вот ее и посмотрим, точнее самый ее конец:
  1. .text:1008035E                 push    eax
  2. .text:1008035F                 mov     byte ptr [esp+50h+var_4], 0Ch
  3. ; Вызвать функцию проверки
  4. .text:10080364                 call    sub_10071DC0
  5. .text:10080369                 add     esp, 8
  6. .text:1008036C                 lea     ecx, [esp+48h+var_20]
  7. ; Сохранить ее результат из AL в BL
  8. .text:10080370                 mov     bl, al
  9. .text:10080372                 mov     byte ptr [esp+48h+var_4], 0Bh
  10. .text:10080377                 call    ds:??1QByteArray@@QAE@XZ
  11. .text:1008037D                 lea     ecx, [esp+48h+var_28]
  12. .text:10080381                 mov     byte ptr [esp+48h+var_4], 0Ah
  13. .text:10080386                 call    ds:??1QByteArray@@QAE@XZ
  14. .text:1008038C                 lea     ecx, [esp+48h+var_2C]
  15. .text:10080390                 mov     [esp+48h+var_4], ebp
  16. .text:10080394                 call    ds:??1QString@@QAE@XZ
  17. ; Сохраненный результат нулевой?
  18. .text:1008039A                 test    bl, bl
  19. .text:1008039C                 jz      short loc_100803A3
  20. ; Записать во временную переменную 1
  21. .text:1008039E                 mov     byte ptr [esp+48h+arg_8], 1
  22. .text:100803A3 loc_100803A3:
  23. ; Записать в AL значение из переменной
  24. .text:100803A3                 mov     al, byte ptr [esp+48h+arg_8]
  25. .text:100803A7 loc_100803A7:
  26. .text:100803A7                 mov     ecx, [esp+48h+var_C]
  27. .text:100803AB                 mov     large fs:0, ecx
  28. .text:100803B2                 pop     ecx
  29. .text:100803B3                 pop     edi
  30. .text:100803B4                 pop     esi
  31. .text:100803B5                 pop     ebp
  32. .text:100803B6                 pop     ebx
  33. .text:100803B7                 add     esp, 34h
  34. ; На выход
  35. .text:100803BA                 retn    0Ch
Очевидно, что в случае с правильной регистрацией функций IsValidRegInfo должна вернуть AL=1. Самый простой путь решения проблемы с регистрацией - пропатчить проверку, чтобы она всегда возвращала AL=1. Чтобы не тратить время на условные переходы, записываем в самое начало функции IsValidRegInfo пару команд MOV AL,1 и RET 0Ch. Сохраняем изменения, запускаем, пробуем зарегистрировать любыми данными.

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

Отлично теперь программа принимает любое регистрационное имя и любой серийник. Это быстрый вариант "для дома, для семьи", когда надо просто пару раз воспользоваться программой и не заморачиваться на красоте решений.

Теперь давайте попробуем отреверсить алгоритм генерации серийных номеров, ну или, как вариант, найти валидную пару для регистрации. Оба решения можно считать "красивыми", ведь ничего патчить в этом случае не придется. К сожалению, анализировать код на Qt в дизассемблере - это ужас, конструкции типа "динамического указателя на динамический указатель на указатель" тут обычное дело. Поэтому запустим программу под отладчиком и поставим точку останова в модуле imfc0.dll на адрес 10071DC0. Помните, по этому адресу располагается финальная функция проверки из функции IsValidRegInfo? Запускаем программу, пробуем зарегистрировать любым левым серийником. Когда сработает точка останова, продолжим выполнение под управлением отладчика в пошаговом режиме.
  1. .text:10071E0C                 mov     [esp+0A4h+var_60], edi
  2. .text:10071E10                 mov     [esp+0A4h+var_81], 0
  3. .text:10071E15                 call    ds:?left@QByteArray@@QBE?AV1@H@Z
  4. .text:10071E1B                 mov     ecx, edi
  5. .text:10071E1D                 mov     [esp+9Ch+var_4], 0
  6. .text:10071E28                 call    ds:?size@QByteArray@@QBEHXZ
  7. ; Длина строки серийника должна быть 27h символов
  8. .text:10071E2E                 cmp     eax, 27h
  9. .text:10071E31                 jnz     loc_1007228D
  10. .text:10071E37                 mov     ecx, esi
  11. .text:10071E39                 call    ds:?isEmpty@QByteArray@@QBE_NXZ
  12. .text:10071E3F                 test    al, al
  13. .text:10071E41                 jnz     loc_1007228D
Первая проверка: длина серийника должна быть 27h (39 в десятичной системе) символов. Трассируем дальше. Какие-то строки рассматриваются как массив символов, с ними последовательно выполняются преобразования, затем результаты куда-то сохраняются. Конкретики тут я не привожу, так как все действия с памятью выполняются по указателям на указатели на указатели на указатели... Разбирать это спагетти даже под отладчиком очень трудоемко, да и бессмысленно. А вот после всех преобразований вызывается следующий код:
  1. .text:100720FF                 lea     ecx, [esp+0A0h+var_88]
  2. .text:10072103                 call    ds:?data@QByteArray@@QAEPADXZ
  3. .text:10072109                 push    eax             ; int
  4. .text:1007210A                 lea     ecx, [esp+0A0h+var_58] ; this
  5. .text:1007210E                 call    ??0CMD5@@QAE@PBDI@Z
  6. ; CMD5::CMD5(char const *,uint)
  7. .text:10072113                 lea     ecx, [esp+98h+var_58] ; this
  8. .text:10072117                 mov     byte ptr [esp+98h], 0Ah
  9. .text:1007211F                 call    ?getMD5Digest@CMD5@@QAEPBDXZ
  10. ; CMD5::getMD5Digest(void)
  11. .text:10072124                 push    eax
  12. .text:10072125                 lea     ecx, [esp+9Ch+var_68]
  13. .text:10072129                 call    ds:??0QByteArray@@QAE@PBD@Z
Очевидно, что здесь формируется хеш MD5 из какой-то строки, и вот эта строка нас интересует больше всего. Под отладчиком этот фрагмент выглядит так же:

Вызов генерации MD5
Вызов генерации MD5

Если посмотреть на стек, то там находятся два параметра: указатель на указатель на хешируемую строку и длина строки.

Параметры для генерации хеша
Параметры для генерации хеша

Итого, длина хешируемых данных 53h символов. Переходим в дампе на адрес из стека (значение динамическое, поэтому у вас будет другой адрес). Там находится вот такая строка:

Блок для генерации хеша
Блок для генерации хеша

Если отсчитать 53h символов, то как раз получится выделенный на скриншоте фрагмент. Дальше обратите внимание, из чего он состоит. Сперва идет символ "1", затем 10 нечетных символов из строки "xilisoftvideocutter2", чередующиеся с номерами этих символов, затем 10 четных символов этой же строки по очереди с их номерами, затем два нуля "00", затем первые 20 символов серийного номера и строка "xilisoftvideocutter2". Можно проверить с разными серийниками, алгоритм составления строки остается неизменным. Затем от этого блока данных считается хеш MD5 и преобразуется в строку.

Строка хеша MD5
Строка хеша MD5

После этого с полученной строкой выполняются преобразования и в финале выполняется самое обычное сравнение строк:
  1. .text:100721FB                 mov     edx, [esp+9Ch+var_60]
  2. ; Указатель на одну строку
  3. .text:100721FF                 push    edx
  4. .text:10072200                 lea     eax, [esp+0A0h+var_88]
  5. ; Указатель на вторую строку
  6. .text:10072204                 push    eax
  7. ; Вызвать функцию сравнения строк
  8. .text:10072205                 call    sub_10070A00
  9. .text:1007220A                 add     esp, 8
  10. ; Сохранить результат сравнения
  11. .text:1007220D                 mov     [esp+9Ch+var_81], al
  12. .text:10072211 loc_10072211:
  13. .text:10072211                 lea     ecx, [esp+9Ch+var_6C]
  14. .text:10072215                 mov     byte ptr [esp+9Ch+var_4], 0Ah
  15. .text:1007221D                 call    ds:??1QByteArray@@QAE@XZ
  16. ...
  17. ...
  18. ...
  19. ; Записать результат сравнения в AL, он же является результатом всей функции
  20. .text:100722A2                 mov     al, [esp+9Ch+var_81]
  21. .text:100722A6                 mov     ecx, [esp+9Ch+var_C]
  22. .text:100722AD                 mov     large fs:0, ecx
  23. .text:100722B4                 pop     ecx
  24. .text:100722B5                 pop     edi
  25. .text:100722B6                 pop     esi
  26. .text:100722B7                 pop     ebp
  27. .text:100722B8                 pop     ebx
  28. .text:100722B9                 mov     ecx, [esp+88h+var_10]
  29. .text:100722BD                 xor     ecx, esp
  30. .text:100722BF                 call    @__security_check_cookie@4
  31. .text:100722C4                 add     esp, 88h
  32. .text:100722CA                 retn
Если посмотреть под отладчиком на сравниваемые данные, то в памяти обнаружатся следующие строчки:

Первая строка
Первая строка

Вторая строка
Вторая строка

Получается, что валидный серийник "12345678908234567890954D-DA1C-1C5D-8821". Ненадолго вернемся к хешу MD5. Если посмотреть на него, то можно увидеть, что вторая половина серийника составлена из нечетных символов строки хеша. Регистрационное имя не используется. Вот и весь алгоритм генерации серийных номеров.

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

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

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

Точно так же нейтрализуется защита и на остальных продуктах Xilisoft. Вся разница только в строке, которая используется для хеширования. Универсальный патч или кейген для любой программы от Xilisoft вы теперь можете написать самостоятельно.

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

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

Комментарии

Отзывы посетителей сайта о статье
xussr (24.01.2018 в 23:23):
Пособие отличное спасибо...
arina-23 (24.01.2018 в 03:49):
Перед проверкой валидности регистрационных данных есть еще проверка длины номера и наличия в номере букв. Я, в свое время, на этом срезался. Если есть хотя-бы одна буква, сразу на выход. Должны быть только шестнадцатеричные цифры.
ManHunter (23.01.2018 в 21:02):
ЦитатаА как быть, если строки сообщений исключительно в qt-шных языковых файлах? По каким данным их в иде искать?

Проверить другой путь, например, работу с реестром, ввод-вывод, все равно же где-то должно быть критическое место. Монстрячность Qt иногда играет в нашу пользу.
pawel97 (23.01.2018 в 20:41):
"А еще можно патчить функцию isFree в regLib, тоже интересно получается. Часть продуктов, видимо, планировались как халявные, а модуль защиты везде одинаковый."
Помнится у ashampoo ещё интересней было, там и патчить не надо было, просто один дворд в реестре менялся (shareware_type кажется). Но потом в отдельные фичи добавили онлайн активацию.
А как быть, если строки сообщений исключительно в qt-шных языковых файлах? По каким данным их в иде искать?
Там ещё id какие-то числовые есть, но только рядом с этими строками они и используются. Типа: присвоить такой-то строке такой-то id. Но мест, где по нему запрашивается нужная строка, не нахожу.
ManHunter (23.01.2018 в 13:16):
Noobie, не хочу разочаровывать, но по длине он точно такой же :))) И генерится точно по такому же алгоритму. Только разделители добавлены, вот и вся разница :)

А еще можно патчить функцию isFree в regLib, тоже интересно получается. Часть продуктов, видимо, планировались как халявные, а модуль защиты везде одинаковый.
Noobie (23.01.2018 в 13:10):
Познавательно, спасибо! С практической точки зрения, для тех, кто не желает ничему учиться, есть старый, но поныне актуальный кейген от Lz0, там для получения данных используется файл pd.cfg из папки программы. Серийник получается короче, например: C322-1076-54A9-8FE3-DDFC-76D0-F138-6A73
ManHunter (23.01.2018 в 13:08):
Совершенно верно.
user (23.01.2018 в 12:54):
То есть, если патчится начало функции, RET делаем точно такой, как у оригинальной функции?
ManHunter (23.01.2018 в 11:56):
Чтобы стек восстановить
user (23.01.2018 в 11:48):
Для чего делается RET именно 0Ch?

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

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

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