Blog. Just Blog

Исследование защиты программы ICL-Icon Extractor

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

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

Как обычно, любое исследование начинается с дистрибутива. Скачиваем, устанавливаем, запускаем. Сразу открывается огромное наг-окно, затем окно "О программе" с кнопкой регистрации, и только потом запускается сама программа. При завершении работы снова вылазит наг-окно. Кроме этого в главное окно программы добавляются ссылки на покупку и регистрацию. Это из визуальных признаков незарегистрированной программы. Ограничение по функционалу в том, что триальная версия не может сохранять иконки.

Ограничения незарегистрированной программы
Ограничения незарегистрированной программы

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

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

На разные варианты неправильного серийного номера мы получаем разные сообщения: на слишком короткий серийник это будет "Key is required", на более длинный "Wrong key". Поищем что-нибудь похожее в исполняемом файле.

Строка сообщения
Строка сообщения

Файл ничем не упакован, строки обнаруживаются легко. Обратите внимание, что рядом находится еще один вариант сообщения о неправильном серийном номере. Отправляем исполняемый файл в дизассемблер, затем по перекрестным ссылкам находим код, где эти строчки используются.
  1. CODE:0054839C                 call    sub_435C38
  2. CODE:005483A1                 mov     eax, [ebp+var_24]
  3. ; Вызывать функцию проверки
  4. CODE:005483A4                 call    sub_543A84
  5. CODE:005483A9                 test    eax, eax
  6. ; Если она вернула EAX=0, то сообщение выводить не надо
  7. CODE:005483AB                 jz      short loc_5483B9
  8. CODE:005483AD                 push    ebp
  9. CODE:005483AE                 mov     eax, offset aWrongKey_
  10. ; "Wrong key."
  11. CODE:005483B3                 call    sub_548268
  12. CODE:005483B8                 pop     ecx
  13. CODE:005483B9 loc_5483B9:
  14. CODE:005483B9                 mov     eax, [ebp+var_C]
  15. CODE:005483BC                 call    sub_543AF0
  16. CODE:005483C1                 mov     eax, [ebp+var_4]
  17. CODE:005483C4                 mov     edx, [eax+308h]
  18. CODE:005483CA                 mov     eax, [ebp+var_C]
  19. ; Вызывать функцию проверки
  20. CODE:005483CD                 call    sub_5438C0
  21. CODE:005483D2                 test    eax, eax
  22. ; Если она вернула EAX=0, то сообщение выводить не надо
  23. CODE:005483D4                 jz      short loc_5483E2
  24. CODE:005483D6                 push    ebp
  25. CODE:005483D7                 mov     eax, offset aWrongKeyForThi
  26. ; "Wrong key for this application."
  27. CODE:005483DC                 call    sub_548268
  28. CODE:005483E1                 pop     ecx
Смотрим первую функцию проверки. Как мы видим из листинга, она должна вернуть EAX=0, чтобы сообщение о неправильном серийнике не выводилось. Часть кода я пропущу для наглядности.
  1. CODE:00543A84                 push    ebp
  2. ...
  3. ...
  4. CODE:00543A98                 push    offset loc_543AE0
  5. CODE:00543A9D                 push    dword ptr fs:[eax]
  6. CODE:00543AA0                 mov     fs:[eax], esp
  7. ; Обнулить глобальное значение результата
  8. CODE:00543AA3                 xor     eax, eax
  9. CODE:00543AA5                 mov     [ebp+var_8], eax
  10. ; Количество итераций цикла
  11. CODE:00543AA8                 mov     [ebp+var_C], 1
  12. CODE:00543AAF loc_543AAF:
  13. CODE:00543AAF                 mov     eax, [ebp+var_4]
  14. ; Вызвать какую-то функцию 
  15. CODE:00543AB2                 call    sub_5437B4
  16. ; Прибавить к глобальному результату результат этой функции
  17. CODE:00543AB7                 add     [ebp+var_8], eax
  18. ; Пауза для имитации напряженной деятельности
  19. CODE:00543ABA                 push    64h             ; dwMilliseconds
  20. CODE:00543ABC                 call    Sleep
  21. ; Увеличить счетчик итераций цикла
  22. CODE:00543AC1                 inc     [ebp+var_C]
  23. ; По всей видимости длина серийника - 0Bh
  24. CODE:00543AC4                 cmp     [ebp+var_C], 0Bh
  25. CODE:00543AC8                 jnz     short loc_543AAF
  26. CODE:00543ACA                 xor     eax, eax
  27. ...
  28. ...
  29. ; Записать в EAX глобальный результат
  30. CODE:00543AE7                 mov     eax, [ebp+var_8]
  31. CODE:00543AEA                 mov     esp, ebp
  32. CODE:00543AEC                 pop     ebp
  33. CODE:00543AED                 retn
Что мы видим? Здесь в цикле вызывается какая-то функция по адресу 005437B4 и суммируются ее результаты. Итоговая сумма должна быть равна нулю, чтобы функция проверки вернула нужный нам результат. Особенно умиляет принудительная задержка между итерациями цикла, видимо для того, чтобы пользователь успел проникнуться всей торжественностью момента. Этакая "театральная пауза" в программном исполнении.

Самый простой способ обхода этой проверки - пропатчить функцию по адресу 005437B4, чтобы она всегда возвращала 0. Если записать в ее начало команды XOR EAX,EAX и RET, то первая проверка всегда будет успешно проходить, но на второй мы получим текст как на скриншоте - "Wrong key for this application". Смотрим вторую функцию проверки по адресу 005438C0. Она очень напоминает функцию, которая использовалась в первой проверке для суммирования. Поэтому поступим с ней аналогичным образом - пропатчим начало парой команд XOR EAX,EAX и RET, чтобы условный переход сработал. Как вариант, можно пропатчить сам условный переход, заменив его на безусловный. Почему так? Потому что в первой проверке функция вызывается несколько раз и из разных мест, то есть, скорее всего, это и есть основная проверка серийника при запуске. Во второй проверке функция вызывается только один раз, то есть с большой вероятностью больше нигде не используется и можно ограничиться патчем условного перехода. Сохраняем изменения, запускаем программу и пробуем зарегистрироваться с любыми данными. Единственное условие, чтобы серийник был длиннее 10 символов. Эта проверка тоже легко обходится патчем, но на сегодня хирургии и так больше чем достаточно.

Сообщение об успешной регистрации
Сообщение об успешной регистрации

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

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

С патчами закончили, теперь попробуем отреверсить алгоритм проверки, чтобы подобрать правильный регистрационный номер или же выяснить алгоритм генерации таких серийников. Начнем с первой функции проверки. Выше я уже написал, где она находится и что должна возвращать, теперь посмотрим, что она из себя представляет. Для этого запустим программу под отладчиком и поставим точку останова по адресу 005437B4, то есть на начало функции. После этого попробуем зарегистрировать, например, с такими данными: имя = "ManHunter / PCL", email = "manhunter@pcl.pcl", серийник = "1234567890". Когда точка останова сработает, пошаговой трассировкой выходим на следующий код:
  1. CODE:0054381B                 sub     eax, 2
  2. CODE:0054381E                 jl      short loc_543841
  3. CODE:00543820                 inc     eax
  4. CODE:00543821                 mov     [ebp+var_1C], eax
  5. CODE:00543824                 mov     [ebp+var_10], 2
  6. ; В цикле перебираются символы серийника, начиная с "3" и до "7"
  7. CODE:0054382B loc_54382B:
  8. CODE:0054382B                 mov     eax, [ebp+var_4]
  9. CODE:0054382E                 mov     edx, [ebp+var_10]
  10. ; Получить отдельно символ
  11. CODE:00543831                 movzx   eax, byte ptr [eax+edx-1]
  12. ; ПроXORить этим байтом первый символ серийника
  13. CODE:00543836                 xor     [ebp+var_18], eax
  14. CODE:00543839                 inc     [ebp+var_10]
  15. CODE:0054383C                 dec     [ebp+var_1C]
  16. ; Следующий символ
  17. CODE:0054383F                 jnz     short loc_54382B
  18. CODE:00543841 loc_543841:
  19. ; Загрузить в EDX:EAX значение первого байта серийника после всех XOR
  20. CODE:00543841                 mov     eax, [ebp+var_18]
  21. CODE:00543844                 mov     ecx, 1Eh
  22. CODE:00543849                 cdq
  23. ; Поделить на 1Eh
  24. CODE:0054384A                 idiv    ecx
  25. ; Нафига этот INC, если EDX все равно потом уменьшается на 1???
  26. CODE:0054384C                 inc     edx
  27. ; Загрузить символ из строки, который стоит на позиции EDX-1
  28. CODE:0054384D                 mov     eax, offset a2345679qwert_0
  29. ; Контрольная строка "2345679qwertyupadfghjkzxcvbnms"
  30. CODE:00543852                 mov     al, [eax+edx-1]
  31. CODE:00543856                 mov     [ebp+var_9], al
  32. CODE:00543859                 mov     eax, [ebp+var_4]
  33. CODE:0054385C                 mov     edx, [ebp+var_14]
  34. CODE:0054385F                 mov     al, [eax+edx-1]
  35. ; Сравнить загруженный символ с символом "9" из серийника
  36. CODE:00543863                 cmp     al, [ebp+var_9]
  37. CODE:00543866                 jz      short loc_54386B
  38. ; Значение результата функции проверки будет не равно нулю
  39. CODE:00543868                 inc     [ebp+var_8]
  40. CODE:0054386B loc_54386B:
  41. CODE:0054386B                 xor     eax, eax
  42. CODE:0054386D                 pop     edx
Это первая проверка, опишу ее словами. Если взять на примере нашего серийника, то по берется первый символ и по очереди XORится с каждым символом от "3" до "7" включительно. Полученное значение делится на константу 1Eh, остаток от деления является позицией символа из контрольной строки "2345679qwertyupadfghjkzxcvbnms". Этот символ должен совпадать с тем, который находится в нашем серийнике на позиции символа "9".

Переходим ко второй проверке. Она длиннее, чем первая, поэтому разберем ее по частям. Ставим точку останова на адрес 005438C0, тут будет первая часть:
  1. CODE:00543933                 xor     eax, eax
  2. CODE:00543935                 mov     [ebp+var_18], eax
  3. CODE:00543938                 mov     eax, [ebp+var_8]
  4. CODE:0054393B                 call    sub_404178
  5. CODE:00543940                 dec     eax
  6. CODE:00543941                 test    eax, eax
  7. CODE:00543943                 jle     short loc_543965
  8. CODE:00543945                 mov     [ebp+var_20], eax
  9. CODE:00543948                 mov     [ebp+var_14], 1
  10. CODE:0054394F loc_54394F:
  11. CODE:0054394F                 mov     eax, [ebp+var_8]
  12. CODE:00543952                 mov     edx, [ebp+var_14]
  13. ; Загрузить символ из строки
  14. CODE:00543955                 movzx   eax, byte ptr [eax+edx-1]
  15. ; Просуммировать символы
  16. CODE:0054395A                 add     [ebp+var_18], eax
  17. CODE:0054395D                 inc     [ebp+var_14]
  18. CODE:00543960                 dec     [ebp+var_20]
  19. ; Следующий символ
  20. CODE:00543963                 jnz     short loc_54394F
  21. CODE:00543965 loc_543965:
  22. ; Загрузить сумму в EDX:EAX
  23. CODE:00543965                 mov     eax, [ebp+var_18]
  24. CODE:00543968                 mov     ecx, 1Eh
  25. CODE:0054396D                 cdq
  26. ; Поделить на 1Eh
  27. CODE:0054396E                 idiv    ecx
  28. CODE:00543970                 inc     edx
  29. ; Загрузить символ из строки, который стоит на позиции EDX-1
  30. CODE:00543971                 mov     eax, offset a2345679qwert_1
  31. ; Контрольная строка "2345679qwertyupadfghjkzxcvbnms"
  32. CODE:00543976                 mov     al, [eax+edx-1]
  33. CODE:0054397A                 mov     [ebp+var_D], al
  34. CODE:0054397D                 mov     eax, [ebp+var_4]
  35. CODE:00543980                 mov     al, [eax+1]
  36. ; Сравнить загруженный символ с символом "3" из серийника
  37. CODE:00543983                 cmp     al, [ebp+var_D]
  38. CODE:00543986                 jz      short loc_54398B
  39. CODE:00543988                 inc     [ebp+var_C]
  40. CODE:0054398B loc_54398B:
  41. CODE:0054398B                 lea     edx, [ebp+var_1C]
Код прокомментирован, но тоже продублирую его словами. Под отладчиком видно, что берется строка "ICL-Icon Extractor", а затем суммируются все ее символы. Полученная сумма также делится на константу 1Eh, остаток от деления является позицией символа из контрольной строки "2345679qwertyupadfghjkzxcvbnms". Этот символ должен совпадать с тем, который находится в нашем серийнике на позиции символа "3". А точнее, символ всегда будет один и тот же - буква "m". Значит промежуточный серийник приобретает вид "12m4567890". Но поскольку изменилось значение третьего символа, первая проверка его не пропустит. Придется повторить регистрацию и в первой проверке выяснить, какой символ должен стоять вместо символа "9". Под отладчиком видно, что это символ "h". Чтобы успешно пройти первую проверку и первую часть второй проверки, серийник должен быть "12m45678h0". Двигаемся дальше. Вторая часть второй проверки:
  1. CODE:005439C5                 xor     eax, eax
  2. CODE:005439C7                 mov     [ebp+var_18], eax
  3. CODE:005439CA                 mov     eax, [ebp+var_8]
  4. CODE:005439CD                 call    sub_404178
  5. CODE:005439D2                 dec     eax
  6. CODE:005439D3                 test    eax, eax
  7. CODE:005439D5                 jle     short loc_5439F7
  8. CODE:005439D7                 mov     [ebp+var_20], eax
  9. CODE:005439DA                 mov     [ebp+var_14], 1
  10. CODE:005439E1 loc_5439E1:
  11. CODE:005439E1                 mov     eax, [ebp+var_8]
  12. CODE:005439E4                 mov     edx, [ebp+var_14]
  13. ; Загрузить символ из строки
  14. CODE:005439E7                 movzx   eax, byte ptr [eax+edx-1]
  15. ; ПроXORить символы
  16. CODE:005439EC                 xor     [ebp+var_18], eax
  17. CODE:005439EF                 inc     [ebp+var_14]
  18. CODE:005439F2                 dec     [ebp+var_20]
  19. ; Следующий символ
  20. CODE:005439F5                 jnz     short loc_5439E1
  21. CODE:005439F7 loc_5439F7:
  22. ; Загрузить сумму в EDX:EAX
  23. CODE:005439F7                 mov     eax, [ebp+var_18]
  24. CODE:005439FA                 mov     ecx, 1Eh
  25. CODE:005439FF                 cdq
  26. ; Поделить на 1Eh
  27. CODE:00543A00                 idiv    ecx
  28. CODE:00543A02                 inc     edx
  29. ; Загрузить символ из строки, который стоит на позиции EDX-1
  30. CODE:00543A03                 mov     eax, offset a2345679qwert_1
  31. ; Контрольная строка "2345679qwertyupadfghjkzxcvbnms"
  32. CODE:00543A08                 mov     al, [eax+edx-1]
  33. CODE:00543A0C                 mov     [ebp+var_D], al
  34. CODE:00543A0F                 mov     eax, [ebp+var_4]
  35. CODE:00543A12                 mov     al, [eax]
  36. ; Сравнить загруженный символ с символом "2" из серийника
  37. CODE:00543A14                 cmp     al, [ebp+var_D]
  38. CODE:00543A17                 jz      short loc_543A1C
  39. CODE:00543A19                 inc     [ebp+var_C]
  40. CODE:00543A1C loc_543A1C:
  41. CODE:00543A1C                 xor     eax, eax
Под отладчиком тоже хорошо видно, что берется строка "ICL-Icon Extractor", затем все символы из нее XORятся друг на друга. Полученный результат делится на константу 1Eh, остаток от деления является позицией символа из контрольной строки "2345679qwertyupadfghjkzxcvbnms". Этот символ должен совпадать с тем, который находится в нашем серийнике на позиции символа "2". Поскольку строка неизменная, то и итоговое значение тоже всегда будет одинаковым - это символ "9". После всех проверок серийник принимает окончательный вид - "19m45678h0". Зачем было городить все эти городушки со строками, если они все равно постоянные - непонятно. Ведь куда проще было сравнить второй и третий символы серийника с "9" и "m", соответственно. При этом имя и email нигде не используются. Проверяем найденный серийник:

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

Перезапускаем программу и видим, что все ограничения сняты. Наг-окон нет, иконки и библиотеки сохраняются. Кейген вы теперь можете написать самостоятельно.

Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Добавить в закладки Memori Добавить в закладки Google
Просмотров: 3853 | Комментариев: 4

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

Комментарии

Отзывы посетителей сайта о статье
ManHunter (13.02.2015 в 14:32):
ida pro
md5 (13.02.2015 в 13:35):
Спасибо за статьи, очень познавательно! Хочу спросить, какими инструментами вы чаще всего пользуетесь, в особенности какой дизассемблер предпочитаете (порекомендуйте хороший)?
ManHunter (03.02.2015 в 10:51):
Здесь asm хватает, иногда хексреем перевожу в псевдокод, но это редко. Остальные инструменты как написано в статье, больше ничего не использовал.
brute (03.02.2015 в 10:43):
адрес 005437B4 нашел сам, пытался долго патчить следствие (сообщения о неправильном ключе), а не причину. Удалось заставить выводить сообщение о правильном ключе, но прога не регистрировалась после перезапуска.. Как анализируешь процедуры? Читаешь asm или переводишь в псевдокод? Или делфи по идр смотришь? Мне очень помогает выделение в ИДЕ подозрительных блоков разным цветом. Какие ещё "секреты" используешь? Может, условные бряки в ОЛЕ? Дебажишь ли непосредственно в ИДЕ? Имхо, это должно быть лучше ОЛИ, при определенных скиллах..
п.с.  регистрация проги прописалась в реестр и деинсталяция не помогает удалить регистрацию. Хотел ещё покрутить прогу, но лень вручную чистить реестр..

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

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

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