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

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

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

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

Строка сообщения
Файл ничем не упакован, строки обнаруживаются легко. Обратите внимание, что рядом находится еще один вариант сообщения о неправильном серийном номере. Отправляем исполняемый файл в дизассемблер, затем по перекрестным ссылкам находим код, где эти строчки используются.
Code (Assembler) : Убрать нумерацию
- CODE:0054839C call sub_435C38
- CODE:005483A1 mov eax, [ebp+var_24]
- ; Вызывать функцию проверки
- CODE:005483A4 call sub_543A84
- CODE:005483A9 test eax, eax
- ; Если она вернула EAX=0, то сообщение выводить не надо
- CODE:005483AB jz short loc_5483B9
- CODE:005483AD push ebp
- CODE:005483AE mov eax, offset aWrongKey_
- ; "Wrong key."
- CODE:005483B3 call sub_548268
- CODE:005483B8 pop ecx
- CODE:005483B9 loc_5483B9:
- CODE:005483B9 mov eax, [ebp+var_C]
- CODE:005483BC call sub_543AF0
- CODE:005483C1 mov eax, [ebp+var_4]
- CODE:005483C4 mov edx, [eax+308h]
- CODE:005483CA mov eax, [ebp+var_C]
- ; Вызывать функцию проверки
- CODE:005483CD call sub_5438C0
- CODE:005483D2 test eax, eax
- ; Если она вернула EAX=0, то сообщение выводить не надо
- CODE:005483D4 jz short loc_5483E2
- CODE:005483D6 push ebp
- CODE:005483D7 mov eax, offset aWrongKeyForThi
- ; "Wrong key for this application."
- CODE:005483DC call sub_548268
- CODE:005483E1 pop ecx
Code (Assembler) : Убрать нумерацию
- CODE:00543A84 push ebp
- ...
- ...
- CODE:00543A98 push offset loc_543AE0
- CODE:00543A9D push dword ptr fs:[eax]
- CODE:00543AA0 mov fs:[eax], esp
- ; Обнулить глобальное значение результата
- CODE:00543AA3 xor eax, eax
- CODE:00543AA5 mov [ebp+var_8], eax
- ; Количество итераций цикла
- CODE:00543AA8 mov [ebp+var_C], 1
- CODE:00543AAF loc_543AAF:
- CODE:00543AAF mov eax, [ebp+var_4]
- ; Вызвать какую-то функцию
- CODE:00543AB2 call sub_5437B4
- ; Прибавить к глобальному результату результат этой функции
- CODE:00543AB7 add [ebp+var_8], eax
- ; Пауза для имитации напряженной деятельности
- CODE:00543ABA push 64h ; dwMilliseconds
- CODE:00543ABC call Sleep
- ; Увеличить счетчик итераций цикла
- CODE:00543AC1 inc [ebp+var_C]
- ; По всей видимости длина серийника - 0Bh
- CODE:00543AC4 cmp [ebp+var_C], 0Bh
- CODE:00543AC8 jnz short loc_543AAF
- CODE:00543ACA xor eax, eax
- ...
- ...
- ; Записать в EAX глобальный результат
- CODE:00543AE7 mov eax, [ebp+var_8]
- CODE:00543AEA mov esp, ebp
- CODE:00543AEC pop ebp
- CODE:00543AED retn
Самый простой способ обхода этой проверки - пропатчить функцию по адресу 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". Когда точка останова сработает, пошаговой трассировкой выходим на следующий код:
Code (Assembler) : Убрать нумерацию
- CODE:0054381B sub eax, 2
- CODE:0054381E jl short loc_543841
- CODE:00543820 inc eax
- CODE:00543821 mov [ebp+var_1C], eax
- CODE:00543824 mov [ebp+var_10], 2
- ; В цикле перебираются символы серийника, начиная с "3" и до "7"
- CODE:0054382B loc_54382B:
- CODE:0054382B mov eax, [ebp+var_4]
- CODE:0054382E mov edx, [ebp+var_10]
- ; Получить отдельно символ
- CODE:00543831 movzx eax, byte ptr [eax+edx-1]
- ; ПроXORить этим байтом первый символ серийника
- CODE:00543836 xor [ebp+var_18], eax
- CODE:00543839 inc [ebp+var_10]
- CODE:0054383C dec [ebp+var_1C]
- ; Следующий символ
- CODE:0054383F jnz short loc_54382B
- CODE:00543841 loc_543841:
- ; Загрузить в EDX:EAX значение первого байта серийника после всех XOR
- CODE:00543841 mov eax, [ebp+var_18]
- CODE:00543844 mov ecx, 1Eh
- CODE:00543849 cdq
- ; Поделить на 1Eh
- CODE:0054384A idiv ecx
- ; Нафига этот INC, если EDX все равно потом уменьшается на 1???
- CODE:0054384C inc edx
- ; Загрузить символ из строки, который стоит на позиции EDX-1
- CODE:0054384D mov eax, offset a2345679qwert_0
- ; Контрольная строка "2345679qwertyupadfghjkzxcvbnms"
- CODE:00543852 mov al, [eax+edx-1]
- CODE:00543856 mov [ebp+var_9], al
- CODE:00543859 mov eax, [ebp+var_4]
- CODE:0054385C mov edx, [ebp+var_14]
- CODE:0054385F mov al, [eax+edx-1]
- ; Сравнить загруженный символ с символом "9" из серийника
- CODE:00543863 cmp al, [ebp+var_9]
- CODE:00543866 jz short loc_54386B
- ; Значение результата функции проверки будет не равно нулю
- CODE:00543868 inc [ebp+var_8]
- CODE:0054386B loc_54386B:
- CODE:0054386B xor eax, eax
- CODE:0054386D pop edx
Переходим ко второй проверке. Она длиннее, чем первая, поэтому разберем ее по частям. Ставим точку останова на адрес 005438C0, тут будет первая часть:
Code (Assembler) : Убрать нумерацию
- CODE:00543933 xor eax, eax
- CODE:00543935 mov [ebp+var_18], eax
- CODE:00543938 mov eax, [ebp+var_8]
- CODE:0054393B call sub_404178
- CODE:00543940 dec eax
- CODE:00543941 test eax, eax
- CODE:00543943 jle short loc_543965
- CODE:00543945 mov [ebp+var_20], eax
- CODE:00543948 mov [ebp+var_14], 1
- CODE:0054394F loc_54394F:
- CODE:0054394F mov eax, [ebp+var_8]
- CODE:00543952 mov edx, [ebp+var_14]
- ; Загрузить символ из строки
- CODE:00543955 movzx eax, byte ptr [eax+edx-1]
- ; Просуммировать символы
- CODE:0054395A add [ebp+var_18], eax
- CODE:0054395D inc [ebp+var_14]
- CODE:00543960 dec [ebp+var_20]
- ; Следующий символ
- CODE:00543963 jnz short loc_54394F
- CODE:00543965 loc_543965:
- ; Загрузить сумму в EDX:EAX
- CODE:00543965 mov eax, [ebp+var_18]
- CODE:00543968 mov ecx, 1Eh
- CODE:0054396D cdq
- ; Поделить на 1Eh
- CODE:0054396E idiv ecx
- CODE:00543970 inc edx
- ; Загрузить символ из строки, который стоит на позиции EDX-1
- CODE:00543971 mov eax, offset a2345679qwert_1
- ; Контрольная строка "2345679qwertyupadfghjkzxcvbnms"
- CODE:00543976 mov al, [eax+edx-1]
- CODE:0054397A mov [ebp+var_D], al
- CODE:0054397D mov eax, [ebp+var_4]
- CODE:00543980 mov al, [eax+1]
- ; Сравнить загруженный символ с символом "3" из серийника
- CODE:00543983 cmp al, [ebp+var_D]
- CODE:00543986 jz short loc_54398B
- CODE:00543988 inc [ebp+var_C]
- CODE:0054398B loc_54398B:
- CODE:0054398B lea edx, [ebp+var_1C]
Code (Assembler) : Убрать нумерацию
- CODE:005439C5 xor eax, eax
- CODE:005439C7 mov [ebp+var_18], eax
- CODE:005439CA mov eax, [ebp+var_8]
- CODE:005439CD call sub_404178
- CODE:005439D2 dec eax
- CODE:005439D3 test eax, eax
- CODE:005439D5 jle short loc_5439F7
- CODE:005439D7 mov [ebp+var_20], eax
- CODE:005439DA mov [ebp+var_14], 1
- CODE:005439E1 loc_5439E1:
- CODE:005439E1 mov eax, [ebp+var_8]
- CODE:005439E4 mov edx, [ebp+var_14]
- ; Загрузить символ из строки
- CODE:005439E7 movzx eax, byte ptr [eax+edx-1]
- ; ПроXORить символы
- CODE:005439EC xor [ebp+var_18], eax
- CODE:005439EF inc [ebp+var_14]
- CODE:005439F2 dec [ebp+var_20]
- ; Следующий символ
- CODE:005439F5 jnz short loc_5439E1
- CODE:005439F7 loc_5439F7:
- ; Загрузить сумму в EDX:EAX
- CODE:005439F7 mov eax, [ebp+var_18]
- CODE:005439FA mov ecx, 1Eh
- CODE:005439FF cdq
- ; Поделить на 1Eh
- CODE:00543A00 idiv ecx
- CODE:00543A02 inc edx
- ; Загрузить символ из строки, который стоит на позиции EDX-1
- CODE:00543A03 mov eax, offset a2345679qwert_1
- ; Контрольная строка "2345679qwertyupadfghjkzxcvbnms"
- CODE:00543A08 mov al, [eax+edx-1]
- CODE:00543A0C mov [ebp+var_D], al
- CODE:00543A0F mov eax, [ebp+var_4]
- CODE:00543A12 mov al, [eax]
- ; Сравнить загруженный символ с символом "2" из серийника
- CODE:00543A14 cmp al, [ebp+var_D]
- CODE:00543A17 jz short loc_543A1C
- CODE:00543A19 inc [ebp+var_C]
- CODE:00543A1C loc_543A1C:
- CODE:00543A1C xor eax, eax

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

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

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 или переводишь в псевдокод? Или делфи по идр смотришь? Мне очень помогает выделение в ИДЕ подозрительных блоков разным цветом. Какие ещё "секреты" используешь? Может, условные бряки в ОЛЕ? Дебажишь ли непосредственно в ИДЕ? Имхо, это должно быть лучше ОЛИ, при определенных скиллах..
п.с. регистрация проги прописалась в реестр и деинсталяция не помогает удалить регистрацию. Хотел ещё покрутить прогу, но лень вручную чистить реестр..
п.с. регистрация проги прописалась в реестр и деинсталяция не помогает удалить регистрацию. Хотел ещё покрутить прогу, но лень вручную чистить реестр..

Добавить комментарий
Заполните форму для добавления комментария

CPU Disasm
Address Hex dump Command Comments
00533333 00 DB 00
00533334 31C0 ( XOR EAX,EAX ) !!! ; iconextract.00533334(guessed void)
00533336 C3 RETN
00533337 |. 33C9 XOR ECX,ECX
00533339 |. 51 PUSH ECX
0053333A |. 51 PUSH ECX
0053333B |. 51 PUSH ECX
0053333C |. 51 PUSH ECX
0053333D |. 51 PUSH ECX
0053333E |. 53 PUSH EBX
0053333F |. 56 PUSH ESI
00533340 |. 8955 F8 MOV DWORD PTR SS:[EBP-8],EDX
00533343 |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
00533346 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
00533349 |. E8 C20FEDFF CALL 00404310 ; [iconextract.00404310
0053334E |. 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
00533351 |. E8 BA0FEDFF CALL 00404310 ; [iconextract.00404310
00533356 |. 33C0 XOR EAX,EAX
00533358 |. 55 PUSH EBP
?Зачем патчить в нескольких местах?