Исследование защиты программы PDF Password Cracker


Скриншот программы PDF Password Cracker

Программа PDF Password Cracker предназначена для подбора и удаления паролей из PDF-файлов. Поддерживается стандартное 40-битное шифрование, улучшенное 128-битное шифрование и шифрование по алгоритму AES. Подбор пароля выполняется по заданному диапазону символов, по словарю или прямым поиском ключа. Кроме пароля позволяет удалять из PDF-файлов ограничения на печать и копирование, различные приватные данные, такие как мета-данные, формы, скрипты, вложенные файлы, цифровые подписи и т.п. В общем реально нужная и полезная программа. Есть несколько вариантов дистрибутивов, в зависимости от количества наворотов растет и цена, доходя на версии Enterprise аж до 60 баксов. Бесплатных версий нет, по крайней мере пока.

Раз уж взялись изучать защиту этого поделия, так начнем сразу с его максимально навороченной редакции. Загружаем дистрибутив версии Enterprise, устанавливаем, запускаем. Получаем вот такое симпатичное окно:


Окно регистрации

На ввод левых данных программа реагирует сообщением "Your registration key is wrong, please check it and try again". Где-то я все это уже видел. Назначение программы и внешний вид окон очень сильно напоминает мне разобранную ранее программу PDF Password Remover. Там было такое же грозное окно с фейковым Machine ID и регистрационным ключом. Обе программы также используют одинаковый скиновый движок, и это наводит на мысли, что автор у них один. Значит и используемые методы защиты могут совпадать. Проверим это на практике.

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


Нехорошая строка найдена

Да, наша догадка полностью подтвердилась, внутри программа PDF Password Cracker очень похожа на PDF Password Remover. Ну или наоборот, это сути не меняет :) Посмотрим на фрагмент кода, который отвечает за вывод сообщения о неправильном серийном номере.

...
.text:004086AA                 push    (offset Src+20h) ; lpString
.text:004086AF                 push    414h            ; nIDDlgItem
.text:004086B4                 push    esi             ; hDlg
.text:004086B5                 rep stosd
.text:004086B7                 call    ds:GetDlgItemTextA
; Получить введенный текст серийного номера
.text:004086BD                 push    4F5C94h
; Вызвать процедуру проверки
.text:004086C2                 call    sub_408060   ; <---- Проверка
.text:004086C7                 add     esp, 4
.text:004086CA                 test    eax, eax
; Если процедура проверки вернула EAX=0, то серийный номер неправильный
.text:004086CC                 jz      short loc_408712
; Вывести сообщение о правильной регистрации
.text:004086CE                 push    40h             ; uType
.text:004086D0                 push    offset aThankYou_ ; "Thank you."
.text:004086D5                 push    offset aThankYouForPur
; "Thank you for purchase the PDF Password"...
.text:004086DA                 push    esi             ; hWnd
.text:004086DB                 call    ds:MessageBoxA
.text:004086E1                 push    ecx             ; lpData
.text:004086E2                 mov     ecx, esp
.text:004086E4                 mov     [esp+0E8h+var_D8], esp
.text:004086E8                 push    (offset Src+20h) ; Src
.text:004086ED                 call    sub_47F96A
.text:004086F2                 call    sub_408310
.text:004086F7                 add     esp, 4
.text:004086FA                 mov     dword ptr Src+0E8h, 1
.text:00408704                 push    1               ; nResult
.text:00408706                 push    esi             ; hDlg
.text:00408707                 call    ds:EndDialog
.text:0040870D                 jmp     loc_408982
.text:00408712 ; --------------------------------------------------
.text:00408712 loc_408712:
; Вывести сообщение о неправильной регистрации
.text:00408712                 push    10h             ; uType
.text:00408714                 push    0               ; lpCaption
.text:00408716                 push    offset aYourRegistrati
; "Your registration key is wrong, please "...
.text:0040871B                 push    esi             ; hWnd
.text:0040871C                 call    ds:MessageBoxA
.text:00408722                 push    414h            ; nIDDlgItem
.text:00408727                 push    esi             ; hDlg
...

Как и в прошлый раз, здесь есть два варианта. Можно пропатчить процедуру проверки серийного номера по адресу 00408060, чтобы она всегда возвращала EAX=1, в этом случае программа станет портативной и ее можно будет таскать с собой на флешке. Для собственный нужд я сделал именно такой вариант. А чтобы немного порадовать приверженцев кейгенов, разберем алгоритм проверки серийного номера. Он очень похож на алгоритм, который использовался в программе PDF Password Remover, ну это и не удивительно.
.text:00408060                 sub     esp, 18h
.text:00408063                 or      ecx, 0FFFFFFFFh
.text:00408066                 xor     eax, eax
.text:00408068                 push    ebx
.text:00408069                 push    esi
.text:0040806A                 mov     esi, [esp+20h+arg_0]
.text:0040806E                 push    edi
; Самопальная функция получения длины строки
.text:0040806F                 mov     edi, esi
.text:00408071                 repne scasb
.text:00408073                 not     ecx
.text:00408075                 dec     ecx
; После отработки в регистре ECX содержится значение длины строки
.text:00408076                 cmp     ecx, 14h
; Должно быть 14h (20 в десятичной системе) символов
.text:00408079                 jz      short loc_408082
.text:0040807B                 pop     edi
.text:0040807C                 pop     esi
.text:0040807D                 pop     ebx
.text:0040807E                 add     esp, 18h
; Если это не так, то на выход
.text:00408081                 retn

Длина серийного номера, как и в прошлый раз, равна 20 символам.
; Первая проверка. Берутся 14-й и 15-й символы серийного номера
.text:00408082                 mov     al, [esi+0Eh]
.text:00408085                 mov     cl, [esi+0Fh]
.text:00408088                 lea     edx, [esp+24h+Str]
.text:0040808C                 xor     bl, bl
.text:0040808E                 push    edx             ; Str
.text:0040808F                 mov     [esp+28h+var_C], al
.text:00408093                 mov     [esp+28h+var_B], bl
.text:00408097                 mov     [esp+28h+Str], cl
.text:0040809B                 mov     [esp+28h+var_17], bl
; Оба символа преобразуются в числа и складываются
.text:0040809F                 call    _atoi
.text:004080A4                 mov     edi, eax
.text:004080A6                 lea     eax, [esp+28h+var_C]
.text:004080AA                 push    eax             ; Str
.text:004080AB                 call    _atoi
.text:004080B0                 add     edi, eax
.text:004080B2                 add     esp, 8
; В сумме должно получиться 10
.text:004080B5                 cmp     edi, 0Ah
.text:004080B8                 jz      short loc_4080C3
.text:004080BA                 pop     edi
.text:004080BB                 pop     esi
.text:004080BC                 xor     eax, eax
.text:004080BE                 pop     ebx
.text:004080BF                 add     esp, 18h
.text:004080C2                 retn

Первая проверка. Из серийного номера берутся 14-й и 15-й символы, переводятся в численное значение и складываются. В сумме должно получиться 10.
; Вторая проверка. Берутся 1-й и 2-й символы серийного номера
.text:004080C3                 mov     cl, [esi]
.text:004080C5                 mov     dl, [esi+1]
.text:004080C8                 lea     eax, [esp+24h+Str]
.text:004080CC                 mov     [esp+24h+var_C], cl
.text:004080D0                 push    eax             ; Str
.text:004080D1                 mov     [esp+28h+var_B], bl
.text:004080D5                 mov     [esp+28h+Str], dl
.text:004080D9                 mov     [esp+28h+var_17], bl
; Оба символа преобразуются в числа и складываются
.text:004080DD                 call    _atoi
.text:004080E2                 lea     ecx, [esp+28h+var_C]
.text:004080E6                 mov     edi, eax
.text:004080E8                 push    ecx             ; Str
.text:004080E9                 call    _atoi
.text:004080EE                 add     edi, eax
.text:004080F0                 add     esp, 8
; В сумме должно получиться 11
.text:004080F3                 cmp     edi, 0Bh
.text:004080F6                 jz      short loc_408101
.text:004080F8                 pop     edi
.text:004080F9                 pop     esi
.text:004080FA                 xor     eax, eax
.text:004080FC                 pop     ebx
.text:004080FD                 add     esp, 18h
.text:00408100                 retn

Во второй проверке суммируются первый и второй символы серийного номера, в сумме они должны дать 11.
; Третья проверка: 6-й символ серийного номера должен быть "2"
.text:00408101                 cmp     byte ptr [esi+5], 32h
.text:00408105                 jz      short loc_408110
.text:00408107                 pop     edi
.text:00408108                 pop     esi
.text:00408109                 xor     eax, eax
.text:0040810B                 pop     ebx
.text:0040810C                 add     esp, 18h
.text:0040810F                 retn

Третья проверка - 6-й символ серийного номера должен быть "2".
; Последняя проверка. Берутся 8-й и 9-й символы серийного номера
.text:00408110                 mov     dl, [esi+7]
.text:00408113                 mov     al, [esi+8]
.text:00408116                 lea     ecx, [esp+24h+Str]
.text:0040811A                 mov     [esp+24h+var_C], dl
.text:0040811E                 push    ecx             ; Str
.text:0040811F                 mov     [esp+28h+var_B], bl
.text:00408123                 mov     [esp+28h+Str], al
.text:00408127                 mov     [esp+28h+var_17], bl
; Оба символа преобразуются в числа и складываются
.text:0040812B                 call    _atoi
.text:00408130                 lea     edx, [esp+28h+var_C]
.text:00408134                 mov     esi, eax
.text:00408136                 push    edx             ; Str
.text:00408137                 call    _atoi
.text:0040813C                 add     esp, 8
.text:0040813F                 add     esi, eax
.text:00408141                 xor     eax, eax
; В сумме должно получиться 15
.text:00408143                 cmp     esi, 0Fh
.text:00408146                 pop     edi
.text:00408147                 pop     esi
.text:00408148                 setz    al
.text:0040814B                 pop     ebx
.text:0040814C                 add     esp, 18h
.text:0040814F                 retn

В последней проверке складываются 8-й и 9-й символы серийного номера, в сумме они должны дать 15. Остальные символы серийного номера не учитываются и могут быть любыми. Готовый серийный номер может быть, например, таким: 56PCL2078MANHU55NTER. Рабочий кейген вы теперь можете написать самостоятельно.


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

Последний момент. Не знаю как вас, а меня просто вымораживают уродливые скины, которые используются для оформления программ. Для того, чтобы окна программ PDF Password Cracker или PDF Password Remover приняли обычный вид, достаточно удалить файл xpgrean.smf, который находится в папке с программой.

Добавлено: 20 Сентября 2013 09:07:39 Добавил: Андрей Ковальчук

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


Скриншот программы Diacrit

Программа Diacrit предназначена для удобного ввода многобайтных символов различных национальных алфавитов, математических символов и значков, которых нет на клавиатуре. Что-то типа стандартной таблицы символов Windows, только более специализированная. Символы заносятся в буфер обмена, а затем их можно вставить в нужное приложение. В незарегистрированной версии доступны только два первых символа, а лицензия стоит почти 10 килорублей. Будем это устранять.

Запускаем программу, сперва появляется наг-скрин. После этого выбираем какой-нибудь язык из предлагаемого списка, и попытаемся выбрать какой-нибудь символ, начиная с третьего. Получим вот такое триальное сообщение:


Сообщение незарегистрированной программы

Отлично, теперь поищем эту строчку в файле. Исполняемый файл ничем не упакован и не зашифрован, все находится в лучшем виде:


Нехорошая строка найдена

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

; DATA XREF: DATA:off_485754
DATA:00485234 off_485234      dd offset aOnly1st2Charac
; "Only 1st 2 characters allowed in unregi"...
Code (Assembler) : Убрать нумерацию
; DATA XREF: sub_47F938+48
DATA:00485754 off_485754      dd offset off_485234

На последний указатель ссылается следующий код:
...
; Проверить двойное слово по адресу 485624
CODE:0047F971                 mov     eax, ds:off_485624
CODE:0047F976                 cmp     dword ptr [eax], 0
; Если оно не равно 0, то сообщение о незарегистрированности не выводить
CODE:0047F979                 jnz     short loc_47F9A3
CODE:0047F97B                 push    0
CODE:0047F97D                 lea     eax, [ebp+var_10]
; Указатель на строку 'Only 1st 2 characters allowed in unregistered version'
CODE:0047F980                 mov     edx, ds:off_485754
CODE:0047F986                 mov     edx, [edx]
; Вывести сообщение на экран
CODE:0047F988                 call    sub_4046DC
CODE:0047F98D                 mov     eax, [ebp+var_10]
CODE:0047F990                 mov     cx, word_48094C
CODE:0047F997                 mov     dl, 2
...

Значит, что если в двойном слове по адресу 485624 записано ненулевое значение, то программа считается зарегистрированной. Осталось выяснить условие, при котором это значение туда заносится. Посмотрим по очереди все участки кода, которые ссылаются на адрес 485624. В основном это такие же или похожие проверки на нулевое значение, а вот один код очень интересный, в нем как раз и выполняется установка значений.
CODE:0047E1FB                 mov     edx, [ebp+var_2C]
CODE:0047E1FE                 lea     eax, [ebp+var_C]
CODE:0047E201                 call    sub_404584
CODE:0047E206                 mov     eax, ds:off_4856C4
CODE:0047E20B                 mov     edx, [ebp+var_C]
CODE:0047E20E                 call    sub_404540
; Указатель на флаг зарегистрированности
CODE:0047E213                 mov     eax, ds:off_485624
CODE:0047E218                 xor     edx, edx
; Записать туда дефолтное значение 0
CODE:0047E21A                 mov     [eax], edx
CODE:0047E21C                 mov     eax, [ebp+var_8]
CODE:0047E21F                 mov     al, [eax+5]
; Какая-то проверка
CODE:0047E222                 cmp     al, 80h
; Если она не пройдена, то программа не зарегистрирована
CODE:0047E224                 jb      short loc_47E231
; Записать значение 511h в флаг зарегистрированности
CODE:0047E226                 mov     eax, ds:off_485624
CODE:0047E22B                 mov     dword ptr [eax], 511h
CODE:0047E231 loc_47E231:

Получается, что если программа зарегистрирована, то в ячейку заносится значение 511h, если не зарегистрирована - то 0. Если посмотреть выше по коду, то там видны обращения ко ключам реестра, связанным с регистрацией. И все это дело оформлено в виде отдельной процедуры. Для того, чтобы программа считала себя зарегистрированной, в ячейку должно быть записано правильное значение - 511h. Это можно сделать, пропатчив все условные переходы, связанные с проверкой наличия ключа реестра, его значения и еще множества каких-то условий, чтобы получившийся линейный код доходил до адреса с инициализацией. Второй вариант - просто записать в самое начало процедуры команды: MOV eax, ds:off_485624; MOV dword ptr
, 511h; RET. В этом случае процедура будет сразу инициализировать флаг нужным значением и возвращать управление обратно.

[IMG]https://pp.vk.me/c424630/v424630982/2d8f/vgilzSxOFcA.jpg

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

После патча пропал стартовый наг-скрин, ограничение на символы, пункт покупки в меню, и появилась информация о регистрации в окне "О программе". Задача выполнена.

Добавлено: 20 Сентября 2013 09:02:11 Добавил: Андрей Ковальчук

Исследование защиты программы Sothink SWF Decompiler


Скриншот программы Sothink SWF Decompiler

Sothink SWF Decompiler от компании SourceTec Software - одна из лучших программа для декомпиляции Flash-файлов в формате SWF и Flash EXE, извлечения из них ресурсов, скриптов, графики, музыки, восстановления исходных кодов проектов и т.д. Незаменимый инструмент при изучении чужих разработок и восстановления своих исходников в случае их утери. Было бы странным предполагать, что авторы будут распространять такую прелесть "за красивые глазки", так оно и есть, стоимость Sothink SWF Decompiler почти 80 вечнозеленых денег.

Скачиваем дистрибутив и устанавливаем его. Предварительный анализ исполняемого файла показывает, что он упакован навесным протектором Armadillo. Для распаковки воспользуемся программой ArmaGeddon. К сожалению, она пока не работает под Windows 7, так что придется запускать ее под какой-нибудь виртуальной машиной с установленной Windows XP.


Распаковываем файл

В логе видно, что для защиты использованы почти все опции, но ArmaGeddon успешно с ними справляется. Через несколько секунд получаем распакованный файл размером почти в два раза превышающий исходный. Попробуем немного сократить размер, удалив из него уже ненужные секции протектора. Сделать это можно при помощи программы CFF Explorer, которая входит в состав Explorer Suite.


Удаляем из файла секции протектора

В нашем случае не использовалась опция протектора, когда имена секций заменяются на случайный набор символов, это немного облегчит работу. Секции протектора имеют имена, похожие на имена оригинальных секций, но с добавлением в конец цифры, к тому же они идут подряд: .text1, .adata, .data1, .pdata. Отрезайте их по одной, предварительно сделав резервную копию распакованного файла и проверяя каждый раз работоспособность полученного файла. Опытным путем установлено, что безболезненно можно отрезать секции .text1, .adata, .pdata, после чего файл сразу "похудел" наполовину. Загоняем его в дизассемблер и вернемся к теории. Протектор Armadillo предоставляет функции регистрации защищаемых программ своими средствами. Для взаимодействия с защищаемой программой используются переменные окружения, в которые записываются данные об имени пользователя, ключе регистрации (если он найден в системе), триальном времени работы, количестве одновременно запущенных копий и другую информацию. Эти переменные имеют неизменные имена, значит их можно поискать в файле.


Найдены имена переменных окружения

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

; Процедура проверки переменной окружения USERKEY. Возвращает EAX=1,
; если такая переменная установлена (т.е. программа зарегистрирована),
; и EAX=0, если нет.
.text:00446310                 sub     esp, 20Ch
.text:00446316                 mov     eax, dword_9F18B8
.text:0044631B                 xor     eax, esp
.text:0044631D                 mov     [esp+20Ch+var_4], eax
.text:00446324                 push    esi
.text:00446325                 mov     esi, [esp+210h+arg_0]
.text:0044632C                 push    104h            ; nSize
.text:00446331                 lea     eax, [esp+214h+Src]
.text:00446335                 push    eax             ; lpBuffer
; Получить переменную окружения USERKEY
.text:00446336                 push    offset aUserkey ; lpName
.text:0044633B                 call    ds:GetEnvironmentVariableW
.text:00446341                 test    eax, eax
; Такая переменная не установлена
.text:00446343                 jbe     short loc_446387
.text:00446345                 lea     eax, [esp+210h+Src]
.text:00446349                 lea     edx, [eax+2]
.text:0044634C                 lea     esp, [esp+0]
.text:00446350 loc_446350:
.text:00446350                 mov     cx, [eax]
.text:00446353                 add     eax, 2
.text:00446356                 test    cx, cx
.text:00446359                 jnz     short loc_446350
.text:0044635B                 sub     eax, edx
.text:0044635D                 sar     eax, 1
.text:0044635F                 push    eax             ; int
.text:00446360                 lea     ecx, [esp+214h+Src]
.text:00446364                 push    ecx             ; Src
.text:00446365                 mov     ecx, esi
.text:00446367                 call    sub_4024D0
; Переменная есть, установить флаг EAX=1
.text:0044636C                 mov     eax, 1
.text:00446371                 pop     esi
.text:00446372                 mov     ecx, [esp+20Ch+var_4]
.text:00446379                 xor     ecx, esp
.text:0044637B                 call    sub_7BE489
.text:00446380                 add     esp, 20Ch
.text:00446386                 retn
.text:00446387 ; -----------------------------------------------
.text:00446387 loc_446387:
.text:00446387                 mov     ecx, [esp+210h+var_4]
.text:0044638E                 pop     esi
.text:0044638F                 xor     ecx, esp
; Переменной нет, установить флаг EAX=0
.text:00446391                 xor     eax, eax
.text:00446393                 call    sub_7BE489
.text:00446398                 add     esp, 20Ch
.text:0044639E                 retn
.text:0044639E sub_446310      endp

По перекрестным ссылкам смотрим, что эта процедура проверки вызывается из другой процедуры, которая в свою очередь вызывается уже как минимум из трех мест. Логично предположить, что основной процедурой, возвращающей признак "зарегистрировано" является именно родительская процедура. Под отладчиком видно, что флаг зарегистрированности возвращается в регистре EAX. Значит пропатчим родительскую процедуру, записав в ее начало команды MOV EAX,1; RET. Сохраняем изменения, запускаем программу. Стартовое наг-окно пропало. Теперь проверяем функционал программы. Оппа, при попытке декомпиляции любого ролика выдается следующее сообщение:


Собщение незарегистрированной программы

То есть какая-то дополнительная проверка регистрации выполняется где-то еще. Поищем строчку этого сообщения в исполняемом файле. Она найдется в ресурсах.


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

Код строки 302, или в шестнадцатеричном виде 12Eh. Поищем в дизассемблированном листинге строку "12Eh".
; Вызвать какую-то функцию проверки чего-то
.text:00455C28                 call    sub_4466F0
.text:00455C2D                 test    al, al
; Если она вернула AL=0, то окно с сообщением о незарегистрированной
; программе не выводить
.text:00455C2F                 jz      short loc_455C5F
.text:00455C31                 cmp     byte ptr word_9FD804+1, 0
.text:00455C38                 jz      short loc_455C5F
.text:00455C3A                 lea     eax, [esp+14h+var_10]
; Загрузить строку с кодом 302 (12Eh) из ресурсов
.text:00455C3E                 push    12Eh
.text:00455C43                 push    eax
.text:00455C44                 call    sub_4629D0
.text:00455C49                 mov     ecx, [esp+1Ch+var_10]
.text:00455C4D                 add     esp, 8
; Вывести окно сообщения
.text:00455C50                 push    0               ; int
.text:00455C52                 push    41h             ; uType
.text:00455C54                 push    ecx             ; int
.text:00455C55                 call    ?AfxMessageBox@@YGHPB_WII@Z
; AfxMessageBox(wchar_t const *,uint,uint)
.text:00455C5A                 cmp     eax, 1
.text:00455C5D                 jnz     short loc_455C0D
.text:00455C5F loc_455C5F:

На вывод окна влияет результат процедуры sub_4466F0, для активации нужной нам ветки алгоритма она должна вернуть EAX=0:
.text:004466F0                 call    sub_445B50
.text:004466F5                 mov     ecx, eax
.text:004466F7                 call    sub_446690
.text:004466FC                 neg     eax
.text:004466FE                 sbb     eax, eax
.text:00446700                 inc     eax
.text:00446701                 retn

Что она делает? Первая процедура интереса не представляет, так как не влияет на результат, а вот вторая, sub_446690, как раз и проверяет по очереди установленные переменные навесной защиты - наличие ключа и окончание триального срока. Для обхода всей этой канители нужно, чтобы sub_446690 вернула EAX=1. Второй патч - записываем в начало sub_446690 команды MOV EAX,1; RET. Сохраняем изменения, запускаем. Все признаки триальности исчезли, программа работает на полную мощность. Цель достигнута. Очень похожим способом обходятся защиты и других программ SourceTec, хотя и со своими тонкостями.

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

Добавлено: 20 Сентября 2013 08:56:22 Добавил: Андрей Ковальчук

Исследование защиты программы PDF Encrypter


Скриншот программы PDF Encrypter

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

Скачиваем дистрибутив, устанавливаем, запускаем. В программе установлено ограничение по количеству запусков, после чего она вроде как должна перестать работать. Что же, постараемся уложиться в отведенное нам количество попыток.


Триальное окно

На левые регистрационные данные программа реагирует сообщением "Registration failed, please check the code and try again!" Исполняемый файл упакован ASPack последней версии, его можно распаковать вручную или воспользоваться одним из универсальных распаковщиков. После распаковки файла можно поискать в нем строку, тут ничего сложного нет.


Нехорошая строка найдена

Отправляем распакованный файл в дизассемблер и поищем условие, при котором появляется сообщение о неправильной регистрации. Тут тоже никаких сложностей возникнуть не должно.

...
.text:00405A0A                 mov     eax, dword ptr ValueName+4
.text:00405A0F                 mov     edx, [esp+8]
; Вызвать функцию сравнения введенного серийного номера и правильного.
; В регистре EAX - указатель на введенный серийный номер, в регистре EDX -
; указатель на правильный серийный номер.
.text:00405A13                 push    eax
.text:00405A14                 push    edx
.text:00405A15                 call    __mbsicmp
.text:00405A1A                 add     esp, 8
.text:00405A1D                 mov     byte ptr [esp+2Ch], 3
.text:00405A22                 test    eax, eax
.text:00405A24                 lea     ecx, [esp+8]
; Если строки не равны, то вывести сообщение о неудачной регистрации
.text:00405A28                 jnz     short loc_405A8D
.text:00405A2A                 call    sub_421E35
; Сообщение об удачной регистрации
.text:00405A2F                 push    offset aRegistrationSu
; "Registration successed, thank for your "...
.text:00405A34                 lea     ecx, [esp+0Ch]
.text:00405A38                 call    sub_421EA3
.text:00405A3D                 lea     eax, [esp+20h]
.text:00405A41                 mov     bl, 9
.text:00405A43                 push    eax
.text:00405A44                 mov     ecx, offset unk_441E98
.text:00405A49                 mov     [esp+30h], bl
.text:00405A4D                 call    sub_404450
.text:00405A52                 push    40h
.text:00405A54                 lea     ecx, [esp+0Ch]
.text:00405A58                 push    eax
.text:00405A59                 push    ecx
.text:00405A5A                 mov     ecx, esi
.text:00405A5C                 mov     byte ptr [esp+38h], 0Ah
.text:00405A61                 call    sub_405D20
.text:00405A66                 lea     ecx, [esp+20h]
.text:00405A6A                 mov     [esp+2Ch], bl
.text:00405A6E                 call    sub_421E35
.text:00405A73                 lea     ecx, [esp+8]
.text:00405A77                 mov     byte ptr [esp+2Ch], 3
.text:00405A7C                 call    sub_421E35
.text:00405A81                 mov     ecx, offset unk_441E98
.text:00405A86                 call    sub_404EE0
.text:00405A8B                 jmp     short loc_405AE4
.text:00405A8D ; --------------------------------------------------
.text:00405A8D loc_405A8D:
.text:00405A8D                 call    sub_421E35
.text:00405A92                 push    offset aRegistrationFa
; "Registration failed, please check the c"...
.text:00405A97                 lea     ecx, [esp+0Ch]
.text:00405A9B                 call    sub_421EA3
.text:00405AA0                 lea     edx, [esp+20h]
...

Если запустить программу под отладчиком, поставить точку останов адресу 00405A13, то после ввода имени и любого серийного номера в регистре EAX будет 16 символов введенного серийника (4 поля по 4 символа), а в регистре EDX - 16 символов серийного номера, который считается правильным для введенного имени. Для этого примера валидной парой будет регистрационное имя ManHunter / PCL и серийный номер 0d34-55b4-c7a3-858e.


Функция проверки серийного номера

На этом можно остановиться, а можно попробовать разобрать алгоритм генерации серийного номера. Для этого поставим точку останова в самое начало процедуры обработки введенных данных по адресу 004058C0 и повторим регистрацию с любыми неправильными данными. После срабатывания точки останова пройдем процедуру до сравнения строк в пошаговом режиме. На стеке хорошо видно, что сперва берутся символы из всех 4 блоков серийного номера и слепляются в одну строку, а затем на стеке появляется такие интересные данные:


Обработка введенного имени

Введенное имя переводится в верхний регистр, все пробелы в нем заменяются на букву "B" (наверное, от слова "blank"), затем от полученной строки берутся два первых символа и к ним дописывается какая-то бредятина "5dgdfrdr". По всей видимости это исходные данные для генерации правильного серийного номера. Хорошо, едем дальше.


Строка преобразована в хеш

Дальше строка хешируется в функции по адресу 00402D30 по какому-то алгоритму. Попробуем поискать по какому. Krypto ANALyzer не оставляет сомнений, что это MD5, причем без всяких модификаций.


Использован алгоритм MD5

На всякий случай проверим нашу догадку любым другим сторонним инструментом, посчитав MD5 от строки "MA5dgdfrdr", действительно, так оно и есть, после хеширования получается строка "0d3455b4c7a3858e0f23a456a40ab24e". Первые 16 символов полученной строки и являются серийным номером. Сама регистрация и триальные счетчики в зашифрованном виде хранятся в файле %ProgramData%\1pdfenc.dll. Еще одно уродское поделие, прячущее свои данные в файле с именем динамической библиотеки. Если кому интересно, то расшифровка выполняется здесь:
; Расшифровка регистрационных данных 
.text:00404FCC                 xor     eax, eax
.text:00404FCE loc_404FCE:
.text:00404FCE                 mov     bl, byte ptr [esp+eax+420h+Buffer]
.text:00404FD2                 xor     bl, 48h
.text:00404FD5                 mov     byte ptr [esp+eax+420h+Buffer], bl
.text:00404FD9                 inc     eax
.text:00404FDA                 cmp     eax, 200h
.text:00404FDF                 jl      short loc_404FCE

Подводим итог. Алгоритм генерации серийного номера следующий: сперва регистрационное имя переводится в верхний регистр, пробелы в заменяются на символ "B". От обработанной строки берутся два первых символа, к ним добавляется строка "5dgdfrdr" и от полученной строки считается MD5. Первые 16 символов результата хеширования являются серийным номером, для удобства они разбиты на группы по 4 символа. Кейген напишете сами.

Добавлено: 20 Сентября 2013 02:06:06 Добавил: Андрей Ковальчук

Исследование защиты программы PHP LockIt!


Скриншот программы PHP LockIt!

Программа PHP LockIt! предназначена для защиты исходных текстов PHP-скриптов от исследования и модификации. В качестве защиты применяется обфускация имен функций и переменных, а также шифрование исходного кода. В более ранних версиях была еще возможность сжимать полученные скрипты, но разработчики по какой-то причине от этого отказались. Зашифрованные скрипты не требуют установки на сервер дополнительного программного обеспечения, поэтому PHP LockIt! пользуется большой популярностью как у зарубежных, так и у отечественных копирастов. Я давно наблюдаю за развитием этой защиты, и среди других аналогичных поделок она кажется мне наиболее стабильной и надежной. Мне даже довелось встретить китайскую подделку PHP LockIt!, когда интерфейс программы был изменен в редакторе ресурсов, копирайты исправлены на свои, а затем на изуродованный файл был навешан протектор. При этом полученный китайский гибрид позиционировался как авторская разработка и распространялся как самостоятельный продукт с другим названием.

С офсайта PHP LockIt! можно скачать только демо-версию программы для ознакомления, или же раскошелиться на три десятка баксов для покупки ее коммерческого варианта. Поскольку к полной версии у нас доступа нет, попробуем что-нибудь сделать с демо-версией. Скачиваем дистрибутив, устанавливаем, запускаем. Получаем окно с таймером и сообщением сколько дней нам осталось:


Сообщение демо-версии

Как пишут сами разработчики программы, ограничение по времени - это единственное различие демонстрационной и полной версии. По истечение отведенного триального срока окно меняется на сообщение, что время вышло, и PHP LockIt! перестает запускаться.


Демонстрационный период закончился

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


Триальное окно в ресурсах


Окно окончания демонстрационного периода

Текстовые строки в них заменены заглушками, так как они подставляются потом, перед выводом окон на экран. Видимо авторы задумывали поддержу многоязычности, но так и не реализовали эту функцию. Мы знаем индекс окна со счетчиком триального периода - это число 133 или 85h в шестнадцатеричной системе счисления. Посмотрим в дизассемблере, где он используется. В сегменте кода найдется только одно вхождение:

.text:0040546A                 push    ebx
.text:0040546B                 push    ebp
.text:0040546C                 push    esi
.text:0040546D                 push    edi
.text:0040546E                 mov     esi, ecx
; Вызвать функцию открытия диалогового окна CDialog
.text:00405470                 push    eax  ; На стек хэндл владельца 
.text:00405471                 push    85h  ; На стек идентификатор ресурса
.text:00405476                 mov     [esp+28h+var_10], esi
.text:0040547A                 call    ??0CDialog@@QAE@IPAVCWnd@@@Z
; CDialog::CDialog(uint,CWnd *)
.text:0040547F                 lea     edi, [esi+5Ch]
.text:00405482                 mov     [esp+20h+var_4], 0
.text:0040548A                 mov     ecx, edi
.text:0040548C                 call    sub_4214F4
.text:00405491                 mov     dword ptr [edi], offset off_42CCA4
.text:00405497                 mov     ecx, off_44215C
.text:0040549D                 lea     edi, [esi+98h]

Дизассемблер показывает, что для открытия диалогового окна вызывается функция CDialog, значит если ее вырезать, то окно не появится. У функции CDialog два параметра, то есть предварительно перед вызовом на стек кладутся два числа. Чтобы корректно удалить это открытие диалога, придется забить NOP'ами не только сам вызов функции по адресу 0040547A, но и две команды PUSH по адресам 00405470 и 00405471. Сохраняем изменения, запускаем. Триальное окно при старте пропало. Едем дальше. После окончания демонстрационного периода появляется уже другое сообщение, но просто удалять его по аналогии с триальным окном нельзя, надо полностью отключить ветку алгоритма, которая получает управление после 30 дней пользования программой. Поищем в дизассемблере условие срабатывания. Диалоговое окно с нужным нам сообщением появляется по адресу:
.text:00405790 sub_405790      proc near
.text:00405790
.text:00405790 var_10          = dword ptr -10h
.text:00405790 var_C           = dword ptr -0Ch
.text:00405790 var_4           = dword ptr -4
.text:00405790 arg_0           = dword ptr  4
.text:00405790
.text:00405790                 push    0FFFFFFFFh
.text:00405792                 push    offset SEH_405790
.text:00405797                 mov     eax, large fs:0
.text:0040579D                 push    eax
.text:0040579E                 mov     large fs:0, esp
.text:004057A5                 push    ecx
.text:004057A6                 mov     eax, [esp+10h+arg_0]
.text:004057AA                 push    ebx
.text:004057AB                 push    esi
.text:004057AC                 push    edi
.text:004057AD                 mov     esi, ecx
; Вызвать функцию открытия диалогового окна CDialog
.text:004057AF                 push    eax  ; На стек хэндл владельца
.text:004057B0                 push    84h  ; На стек идентификатор ресурса
.text:004057B5                 mov     [esp+24h+var_10], esi
.text:004057B9                 call    ??0CDialog@@QAE@IPAVCWnd@@@Z
; CDialog::CDialog(uint,CWnd *)
.text:004057BE                 mov     ecx, off_44215C
.text:004057C4                 lea     edi, [esi+5Ch]
.text:004057C7                 mov     [esp+1Ch+var_4], 0
...

Эта процедура в свою очередь вызывается только из одного места:
.text:00409F27                 mov     ecx, dword ptr [esp+0BACh+cbData]
.text:00409F2B                 cmp     ecx, eax
; Если одно значение больше другого, то триальный срок окончен
.text:00409F2D                 jg      loc_40A0CC
.text:00409F33                 lea     edx, [eax-28DE80h]
.text:00409F39                 cmp     ecx, edx
; Если одно значение меньше другого, то триальный срок окончен
.text:00409F3B                 jl      loc_40A0CC
.text:00409F41 loc_409F41:
; Триальная ветка алгоритма
.text:00409F41                 sub     eax, ecx
.text:00409F43                 lea     esi, [edi+0C0h]
.text:00409F49                 mov     ecx, eax
.text:00409F4B                 mov     eax, 0C22E4507h
.text:00409F50                 imul    ecx
.text:00409F52                 add     edx, ecx
.text:00409F54                 mov     ecx, esi
.text:00409F56                 sar     edx, 10h
.text:00409F59                 mov     eax, edx
.text:00409F5B                 shr     eax, 1Fh
; ............
; Часть кода пропущена для экономии места
; ............
.text:0040A0A6                 call    sub_424911
.text:0040A0AB                 mov     byte ptr [esp+0BACh+var_4], 1Ch
.text:0040A0B3                 lea     ecx, [esp+0BACh+var_874]
.text:0040A0BA                 call    sub_401590
.text:0040A0BF                 mov     byte ptr [esp+0BACh+var_4], 1Bh
.text:0040A0C7                 jmp     loc_40A2E0
.text:0040A0CC ; -----------------------------------------------
; Нужная нам ветка алгоритма, в которой показывается сообщение об
; окончании триального периода, после чего программа завершает работу
.text:0040A0CC loc_40A0CC:
.text:0040A0CC                 push    esi
.text:0040A0CD                 lea     ecx, [esp+0BB0h+var_B8C]
; Вызвать функцию открытия диалогового окна
.text:0040A0D1                 call    sub_405790
.text:0040A0D6                 lea     ecx, [esp+0BACh+var_B8C]
.text:0040A0DA                 mov     byte ptr [esp+0BACh+var_4], 4
.text:0040A0E2                 call    sub_420FF7
...

Получается, что если забить NOP'ами два условных перехода по адресам 00409F2D и 00409F3B, то программа будет всегда считать, что ее демонстрационный период не закончился. Сохраняем изменения, переводим часы на год вперед, запускаем. Все работает как надо, у нас получилась почти что коммерческая версия. Почему почти? Потому что мы не заплатили за нее ни копейки :)

Напоследок попробуем разобраться с ограничением по времени. Запустим первоначальный вариант программу под управлением монитора RegMon или Process Monitor. В любом случае нас интересуют только обращения к реестру. Обращений не так много, в основном это стандартные вызовы. Но в списке присутствует один интересный ключ, к которому фиксируется обращение при старте программы и сразу перед появлением сообщения об истечении триального срока.

[HKEY_LOCAL_MACHINE\SOFTWARE\{A77633C1-E8C8-6A41-12CA-BADFE4FA2760}]
@="0x448ac29f"


Ключ вызывает интерес хотя бы даже потому, что ключей с таким именем в этой ветке реестра быть не должно. Видимо разработчики задумывали спрятать этот ключ где-то в дебрях ветки CLSID, но забыли или не смогли сделать это правильно. Если удалить этот ключ, то триальный срок снова сбрасывается на 30 дней. В принципе, можно обойтись и без вмешательства в исполняемый код, просто периодически удаляя этот ключ из реестра. В этом случае даже демо-версией можно будет пользоваться неограниченное время, если не обращать внимания на надоедливые наг-окна.

Разбирать снятие защиты с самих скриптов я не буду по личным соображениям. При наличии знаний языка PHP и свободного времени вы можете сделать это самостоятельно. Написать автоматический распаковщик для PHP LockIt! также не составит большого труда.

Добавлено: 20 Сентября 2013 01:45:41 Добавил: Андрей Ковальчук

Исследование защиты программы Advanced JPEG Compressor


Скриншот программы Advanced JPEG Compressor

Программа Advanced JPEG Compressor предназначена для оптимизации изображений, изменения размеров, повышения резкости, а также для выполнения многих других операций над картинками, в том числе и в режиме пакетной обработки. Важно, что все действия выполняются без значительной потери качества. Очень неплохая альтернатива тяжеленным графическим редакторам, особенно когда надо быстро подправить большое количество фотографий. Но стоимость лицензии колеблется от 35 до 1500 баксов, хотя единственно допустимая цена любой программы - это 0 (прописью: НОЛЬ) в любой валюте.

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


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

Это уже интереснее, мы узнали, что триальная версия программы не дает сохранять изменения. Главный исполняемый файл программы ajc.exe запакован ASPack, его можно снять или руками, или воспользовавшись каким-нибудь автоматическим распаковщиком. После распаковки файла загоняем его в дизассемблер, а сами в редакторе ресурсов поищем что-нибудь интересное. Вот что нашлось:


Строка о регистрации в ресурсах

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

; DATA XREF: DATA:off_5600F0
CODE:0043CE18 off_43CE18      dd offset hModule
CODE:0043CE1C                 dd 0FEDFh  ; Индекс строки "LICENSED TO:"
На него ссылается следующий указатель:
Code (Assembler) : Убрать нумерацию
; Dbtables::TBlobStream::TBlobStream(Db::TBlobField *,Db::TBlobStreamMode)+1BE
; sub_53BA2C+102
DATA:005600F0 off_5600F0      dd offset off_43CE18

А на него, в свою очередь, вот такой фрагмент кода:
...
CODE:0053BAE9     mov     eax, [ebp+var_4]
; Если байт по адресу [eax+0E89h] равен нулю, то данные о лицензии
; не загружать и не выводить
CODE:0053BAEC     cmp     byte ptr [eax+0E89h], 0
CODE:0053BAF3     jz      loc_53BB7F
; Загрузить строку из ресурсов и прописать данные в заголовок главного
; окна программы
CODE:0053BAF9     lea     edx, [ebp+var_14]
CODE:0053BAFC     mov     eax, ds:off_560034
CODE:0053BB01     call    @System@LoadResString$qqrp20System@TResStringRec
; System::LoadResString(System::TResStringRec *)
CODE:0053BB06     mov     edx, [ebp+var_14]
CODE:0053BB09     mov     eax, [ebp+var_4]
CODE:0053BB0C     mov     eax, [eax+6E4h]
CODE:0053BB12     call    @Menus@TMenuItem@SetCaption$qqrx17System@AnsiString
; Menus::TMenuItem::SetCaption(System::AnsiString)
CODE:0053BB17     lea     eax, [ebp+var_18]
CODE:0053BB1A     push    eax
CODE:0053BB1B     mov     eax, [ebp+var_4]
CODE:0053BB1E     mov     eax, [eax+9CB4h]
CODE:0053BB24     mov     [ebp+var_30], eax
CODE:0053BB27     mov     [ebp+var_2C], 0Bh
CODE:0053BB2B     lea     edx, [ebp+var_34]
; Указатель на указатель на строку "LICENSED TO:"
CODE:0053BB2E     mov     eax, ds:off_5600F0
CODE:0053BB33     call    @System@LoadResString$qqrp20System@TResStringRec
; System::LoadResString(System::TResStringRec *)
CODE:0053BB38     mov     eax, [ebp+var_34]
CODE:0053BB3B     mov     [ebp+var_28], eax
CODE:0053BB3E     mov     [ebp+var_24], 0Bh
...

При корректной регистрации байт по адресу [eax+0E89h] содержит ненулевое значение. Поищем место, где он инициализируется. Далеко ходить не пришлось, это место чуть выше по коду, прямо перед проверкой:
...
CODE:0053BA9C     call    @Idimap4$qqrx17System@AnsiStringp20Idmessage@TIdMessage
; Idimap4::TIdIMAP4::UIDRetrieve(System::AnsiString,Idmessage::TIdMessage *)
CODE:0053BAA1     mov     ebx, eax
CODE:0053BAA3     mov     eax, [ebp+var_4]
CODE:0053BAA6     mov     [eax+0E89h], bl
...

По адресу 0053BAA1 в коде находится двухбайтовая команда mov ebx, eax, заменим ее такой же двухбайтовой командой mov bl,1 и посмотрим что получилось.


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

В заголовке окна надпись, что программа на кого-то зарегистрирована, надоедливый файл помощи при старте не открывается. Попробуем отредактировать какую-нибудь фотографию и сохранить изменения. Все работает! Файл сохраняется, никаких предупреждений не выдается. Последний штрих: в меню "Help" пункт "Purchase a license..." сменился на "Purchase more licenses..." То есть программа считает, что какое-то количество лицензий у нас есть, но можно прикупить еще. Накормим ее регистрациями до отвала. Если предположить, что в байте по адресу [eax+0E89h] содержится количество лицензий, а мы туда записываем единичку, то максимум, что туда может быть записано - это 255 или 0FFh в шестнадцатеричной системе счисления. Заменим команду по адресу 0053BAA1 на mov bl,0FF и снова посмотрим что получилось. Пункт в меню с предложением купить дополнительные лицензии пропал. Все поставленные цели достигнуты. В пропатченном состоянии Advanced JPEG Compressor становится портативным и прекрасно работает со съемных носителей. Лично я его таскаю на флешке вместе с другим полезным инструментарием.

Добавлено: 20 Сентября 2013 01:28:53 Добавил: Андрей Ковальчук

Исследование защиты программы AWicons Pro


Скриншот программы AWicons Pro

AWicons Pro - лучшая, на мой взгляд, программа для создания и редактирования иконок и курсоров. Она включает в себя множество профессиональных инструментов, таких как прозрачность и многоцветный градиент, функции модификации (Контрастность, Яркость, Размытие, Поворот, 3D тень, Aqua и другие), импорт иконок из исполняемых файлов и графических файлов, сохранение готовых изображений в разные форматы, работа с библиотеками иконок и мультииконками, и еще много-много других полезных возможностей. Но почему-то стоимость программы превышает предельно допустимый уровень в ноль рублей, значит будем смотреть на нее поближе и желательно изнутри.

Скачиваем дистрибутив, устанавливаем, смотрим на главный исполняемый файл. Он упакован Armadillo. Не страшно, навесная защита легко снимается с помощью ArmaGeddon'а:


Распаковываем программу

Почистите распакованный файл от ненужных секций протектора при помощи CFF Explorer. Эта процедура уже не раз описывалась на сайте, в очередной раз дублировать не буду. Теперь обкатаем на жертве два новых инструмента. Первый - Armadillo Environment Variables Finder. Скармливаем ему распакованный файл, смотрим результат.


Используемые переменные окружения Armadillo

Ага, в программе содержится строка ALTUSERNAME, это одна из стандартных переменных окружения, устанавливаемых протектором Armadillo. Остальные данные нас не интересуют. По всей вероятности эта переменная устанавливается в случае зарегистрированной программы и должна содержать в себе имя пользователя. Пустим в дело нашу вторую новинку - Armadillo Environment Variables Injector.


Патчим переменные окружения Armadillo

Добавляем переменную окружения ALTUSERNAME с нужным вам значением имени, нажимаем кнопку Patch. В исполняемый файл будет добавлен код, который при запуске программы установит переменную окружения в нужное значение.


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

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

Добавлено: 19 Сентября 2013 12:38:20 Добавил: Андрей Ковальчук

Исследование защиты программы Sonne Flash Decompiler


Скриншот программы Sonne Flash Decompiler

Sonne Flash Decompiler - предназначена для извлечения объектов и других элементов, используемых во flash-файлах. Она также позволяет извлекать файлы SWF из EXE-контейнеров и редактировать ролики в формате SWF - изменять текст, шрифты, картинки, звуки, формы, скрипты и другие элементы, находящиеся во flash-файле. Естественно, за все это аффтары хотят денег.

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


Окно регистрации программы

На ввод всякого левака программа реагирует сообщением "Invalid registration key!". Исполняемый файл ничем не упакован, искомая строчка лежит в открытом виде:


Нехорошая строка найдена

Традиционно переходим к дизассемблированию с целью выяснить условия появления этой строки. Код достаточно понятный, но я постараюсь его максимально прокомментировать:

CODE:00644014      lea     edx, [ebp+var_28]
CODE:00644017      mov     eax, [ebx+300h]
; Прочитать введенный текст из поля ввода
CODE:0064401D      call    @Controls@TControl@GetText$qqrv
CODE:00644022      cmp     [ebp+var_28], 0
; Если его длина не нулевая, то продолжить
CODE:00644026      jnz     short loc_64406F
; Сообщение "Invalid registration key!"
CODE:00644028      push    30h             ; uType
CODE:0064402A      lea     edx, [ebp+var_2C]
; Указатель на указатель на строку "Invalid registration key!"
CODE:0064402D      mov     eax, ds:off_662AFC
CODE:00644032      mov     eax, [eax]
CODE:00644034      call    @Forms@TApplication@GetTitle$qqrv
CODE:00644039      mov     eax, [ebp+var_2C]
CODE:0064403C      call    @System@@LStrToPChar$qqrx...
CODE:00644041      push    eax             ; lpCaption
CODE:00644042      mov     eax, ds:off_66262C
CODE:00644047      mov     eax, [eax]
CODE:00644049      call    @System@@LStrToPChar$qqrx...
CODE:0064404E      push    eax             ; lpText
CODE:0064404F      mov     eax, ebx
CODE:00644051      call    @Controls@TWinControl@GetHandle$qqrv
CODE:00644056      push    eax             ; hWnd
CODE:00644057      call    MessageBoxA_0
CODE:0064405C      mov     eax, [ebx+300h]
CODE:00644062      mov     edx, [eax]
CODE:00644064      call    dword ptr [edx+0C4h]
CODE:0064406A      jmp     loc_644122
CODE:0064406F ; -------------------------------------------
; Подготовка всех трех строк (имя, email и серийник) к проверке
CODE:0064406F loc_64406F:
CODE:0064406F      lea     eax, [ebp+var_30]
CODE:00644072      mov     edx, [ebp+var_8]
CODE:00644075      call    @System@@LStrFromWStr$qqrr...
CODE:0064407A      mov     eax, [ebp+var_30]
CODE:0064407D      push    eax
CODE:0064407E      push    0
CODE:00644080      lea     eax, [ebp+var_34]
CODE:00644083      mov     edx, [ebp+var_4]
CODE:00644086      call    @System@@LStrFromWStr$qqrr...
CODE:0064408B      mov     eax, [ebp+var_34]
CODE:0064408E      push    eax
CODE:0064408F      lea     eax, [ebp+var_38]
CODE:00644092      mov     edx, [ebp+var_C]
CODE:00644095      call    @System@@LStrFromWStr$qqrr...
CODE:0064409A      mov     edx, [ebp+var_38]
CODE:0064409D      mov     eax, ds:off_662550
CODE:006440A2      mov     eax, [eax]
CODE:006440A4      pop     ecx
; Вызвать процедуру проверки серийного номера
CODE:006440A5      call    @Sqledit@EditSQL$qqrp16Classes...
; Если она вернула AL!=0, то регистрационные данные верные
CODE:006440AA      test    al, al
CODE:006440AC      jz      short loc_6440CB
; Регистрация прошла успешно
CODE:006440AE      mov     eax, ds:off_662550
CODE:006440B3      mov     eax, [eax]
CODE:006440B5      call    sub_626308
; Установить флажок "Зарегистрировано"
CODE:006440BA      mov     eax, ds:off_66284C
CODE:006440BF      mov     byte ptr [eax], 1
CODE:006440C2      mov     eax, ebx
; Вывести сообщение об удачной регистрации
CODE:006440C4      call    sub_643E88
CODE:006440C9      jmp     short loc_644122
CODE:006440CB ; ------------------------------------------------
CODE:006440CB loc_6440CB:
; Вывести сообщение "Invalid registration key!"
CODE:006440CB      mov     eax, ds:off_66284C
CODE:006440D0      mov     byte ptr [eax], 0
CODE:006440D3      push    10h             ; uType
CODE:006440D5      lea     edx, [ebp+var_3C]
CODE:006440D8      mov     eax, ds:off_662AFC
CODE:006440DD      mov     eax, [eax]
CODE:006440DF      call    @Forms@TApplication@GetTitle$qqrv
CODE:006440E4      mov     eax, [ebp+var_3C]
CODE:006440E7      call    @System@@LStrToPChar$qqrx17System@AnsiString
CODE:006440EC      push    eax             ; lpCaption
; Указатель на указатель на строку "Invalid registration key!"
CODE:006440ED      mov     eax, ds:off_66262C
CODE:006440F2      mov     eax, [eax]
CODE:006440F4      call    @System@@LStrToPChar$qqrx17System@AnsiString
CODE:006440F9      push    eax             ; lpText
CODE:006440FA      mov     eax, ebx
CODE:006440FC      call    @Controls@TWinControl@GetHandle$qqrv
CODE:00644101      push    eax             ; hWnd
CODE:00644102      call    MessageBoxA_0
...

Итого, что мы имеем. По адресу ds:off_66284C хранится флаг регистрации. Если байт по этому адресу равен 1, то программа считается зарегистрированной, иначе нет. Ссылок на него слишком много, чтобы патчить их все, поэтому попробуем пройти под отладчиком процедуру проверки регистрационных данных.
CODE:006260DC      push    ebp
CODE:006260DD      mov     ebp, esp
CODE:006260DF      add     esp, 0FFFFFFF4h
CODE:006260E2      push    ebx
CODE:006260E3      push    esi
CODE:006260E4      xor     ebx, ebx
CODE:006260E6      mov     [ebp+var_C], ebx
CODE:006260E9      mov     [ebp+var_8], ecx
CODE:006260EC      mov     [ebp+var_4], edx
CODE:006260EF      mov     esi, eax
CODE:006260F1      mov     eax, [ebp+var_4]
CODE:006260F4      call    @System@@LStrAddRef$qqrpv
CODE:006260F9      mov     eax, [ebp+var_8]
CODE:006260FC      call    @System@@LStrAddRef$qqrpv
CODE:00626101      mov     eax, [ebp+arg_4]
CODE:00626104      call    @System@@LStrAddRef$qqrpv
CODE:00626109      mov     eax, [ebp+arg_0]
CODE:0062610C      call    @System@@LStrAddRef$qqrpv
CODE:00626111      xor     eax, eax
CODE:00626113      push    ebp
CODE:00626114      push    offset loc_62619C
CODE:00626119      push    dword ptr fs:[eax]
CODE:0062611C      mov     fs:[eax], esp
CODE:0062611F      cmp     byte ptr [esi+34h], 0
CODE:00626123      jnz     short loc_626129
CODE:00626125      xor     ebx, ebx
CODE:00626127      jmp     short loc_626174
CODE:00626129 ; ------------------------------------
CODE:00626129 loc_626129:
CODE:00626129      lea     edx, [ebp+var_C]
CODE:0062612C      mov     eax, esi
CODE:0062612E      call    sub_626708
; Функция сравнения двух текстовых строк
CODE:00626133      mov     edx, [ebp+var_C]
CODE:00626136      mov     eax, [ebp+var_4]
CODE:00626139      call    @System@@LStrCmp$qqrv
CODE:0062613E      jnz     short loc_626172
CODE:00626140      mov     bl, 1
CODE:00626142      mov     dl, 2
CODE:00626144      mov     eax, esi
CODE:00626146      call    sub_6271E4
CODE:0062614B      mov     edx, [ebp+var_8]
CODE:0062614E      mov     eax, esi
CODE:00626150      call    sub_62683C
...

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


Серийный номер в отладчике

В окне регистров видно, что сравнивается наш левый серийник с какой-то красивой строкой "SFD2568741". Попробуем повторить регистрацию, но теперь вместо серийного номера используем строку из отладчика "SFD2568741". На этот раз никакой ругани нет, наоборот, программа радостно приняла серийник и поблагодарила:


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

В окне "About" также появились наши регистрационные данные, стартовое окно с предложением купить также исчезло:


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

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

Добавлено: 19 Сентября 2013 12:33:33 Добавил: Андрей Ковальчук

Исследование защиты программы Product Key Explorer


Скриншот программы Product Key Explorer

Программа Product Key Explorer предназначена для извлечения из реестра системы информации о регистрационных ключах более 3000 разных программных продуктов, игр и операционных систем (полный список выложен на офсайте). При наличии администраторских привилегий эти данные можно получать и с других машин локальной сети. Также очень удобно, что готовый список можно сохранить в текстовый файл. В общем, Product Key Explorer пригодится как системным администраторам, так и забывчивым пользователям. И опять бы все хорошо, если б разработчики не требовали за нее денег. Хотя для твердолобых поборников аффтарских прав есть другая причина: все было бы хорошо, если бы программа с навешенным протектором запускалась при наличии отладчика в системе. Как вам такой аргумент?

Скачиваем дистрибутив, устанавливаем, смотрим на главный исполняемый файл. Первичный анализ показывает, что он упакован навесным протектором Armadillo. На каждую хитрую защиту есть противодействие, для Armadillo это универсальный распаковщик ArmaGeddon. С его помощью без труда отдираем протектор от программы. Если триальный срок уже прошел, то перед распаковкой придется почистить реестр при помощи Registry Trash Keys Finder.


Распаковываем программу

Затем с помощью CFF Explorer VII удаляем ненужные секции протектора для сокращения размера файла. Восстановленную секцию импорта ".Geddon" переименуйте в ".idata" или оставьте как есть. Должны остаться только те секции, которые показаны на скриншоте. Файл, естественно, должен остаться работоспособным.


Удаляем секции протектора

После распаковки файла Product Key Explorer остается незарегистрированным, но в окне About пишет интересную фразу: "Program probably unprotected!". То есть программа как-то взаимодействует с навесным протектором, и скорее всего через переменные окружения Armadillo. Кнопка "Register" в окне About также перестала работать, это только лишний раз подтверждает, что для лицензирования используются средства навесного протектора. Поиск в распакованном файл по стандартным названиям переменных окружения Armadillo сразу же наводит нас на следующий блок данных:


Переменные окружения Armadillo в файле

Теперь загоняем файл в дизассемблер и посмотрим в нем поподробнее, как эти переменные используются. Обычно регистрационное имя записывается в USERNAME или ALTUSERNAME. На ALTUSERNAME есть только одна перекрестная ссылка из длиннющей процедуры, в которой кроме ALTUSERNAME запрашивается значение USERKEY, сравнивается со значением DEFAULT, куда-то записывается строка "No Key!", проверяется количество лицензий из установленной пользовательской переменной NSALIC, в общем идет активная движуха по проверке регистрации. На выходе из нее получаем EAX=1, если программа зарегистрирована, и EAX=0 в случае с триальным вариантом.

.text:0042C480                 push    0FFFFFFFFh
.text:0042C482                 push    offset SEH_42C480
.text:0042C487                 mov     eax, large fs:0
.text:0042C48D                 push    eax
.text:0042C48E                 mov     large fs:0, esp
.text:0042C495                 sub     esp, 31Ch
.text:0042C49B                 mov     dl, byte_486F70
.text:0042C4A1                 push    ebx
.text:0042C4A2                 push    ebp
.text:0042C4A3                 mov     ebx, ecx
.text:0042C4A5                 push    edi
.text:0042C4A6                 mov     ecx, 3Fh
.text:0042C4AB                 xor     eax, eax
.text:0042C4AD                 lea     edi, [esp+334h+var_20B]
.text:0042C4B4                 mov     [esp+334h+Str1], dl
.text:0042C4BB                 mov     [esp+334h+var_30C], dl
.text:0042C4BF                 rep stosd
.text:0042C4C1                 stosw
.text:0042C4C3                 stosb
.text:0042C4C4                 mov     ecx, 3Fh
.text:0042C4C9                 xor     eax, eax
.text:0042C4CB                 lea     edi, [esp+334h+var_30B]
.text:0042C4CF                 mov     [esp+334h+Src], dl
.text:0042C4D6                 rep stosd
.text:0042C4D8                 stosw
.text:0042C4DA                 stosb
.text:0042C4DB                 mov     ecx, 3Fh
.text:0042C4E0                 xor     eax, eax
.text:0042C4E2                 lea     edi, [esp+334h+var_10B]
.text:0042C4E9                 mov     ebp, ds:GetEnvironmentVariableA
.text:0042C4EF                 rep stosd
.text:0042C4F1                 stosw
.text:0042C4F3                 stosb
.text:0042C4F4                 mov     ecx, 40h
.text:0042C4F9                 xor     eax, eax
.text:0042C4FB                 lea     edi, [esp+334h+Str1]
.text:0042C502                 push    0FFh            ; nSize
.text:0042C507                 rep stosd
.text:0042C509                 mov     ecx, 40h
.text:0042C50E                 lea     edi, [esp+338h+var_30C]
.text:0042C512                 rep stosd
.text:0042C514                 mov     ecx, 40h
.text:0042C519                 lea     edi, [esp+338h+Src]
.text:0042C520                 rep stosd
.text:0042C522                 lea     eax, [esp+338h+Str1]
.text:0042C529                 push    eax             ; lpBuffer
.text:0042C52A                 push    offset aAltusername ; "ALTUSERNAME"
.text:0042C52F                 call    ebp ; GetEnvironmentVariableA
.text:0042C531                 test    eax, eax
.text:0042C533                 jnz     short loc_42C5AB
.text:0042C535                 mov     ecx, off_482C24
.text:0042C53B                 mov     [esp+334h+var_324], ecx
.text:0042C53F                 mov     edi, offset off_468914
.text:0042C544                 push    84h             ; uID
.text:0042C549                 lea     ecx, [esp+338h+var_324]
.text:0042C54D                 mov     [esp+338h+var_4], eax
.text:0042C554                 mov     [esp+338h+var_328], edi
.text:0042C558                 call    ?LoadStringA@CString@@QAEHI@Z
; CString::LoadStringA(uint)
.text:0042C55D                 lea     eax, [esp+334h+var_328]
.text:0042C561                 lea     edx, [esp+334h+var_324]
.text:0042C565                 neg     eax
.text:0042C567                 sbb     eax, eax
.text:0042C569                 lea     ecx, [ebx+0FCh]
.text:0042C56F                 and     eax, edx
.text:0042C571                 mov     [esp+334h+var_4], 1
.text:0042C57C                 push    eax
.text:0042C57D                 call    sub_456585
.text:0042C582                 lea     ecx, [esp+334h+var_328]
.text:0042C586                 lea     eax, [esp+334h+var_324]
.text:0042C58A                 neg     ecx
.text:0042C58C                 sbb     ecx, ecx
.text:0042C58E                 mov     [esp+334h+var_4], 0FFFFFFFFh
.text:0042C599                 and     ecx, eax
.text:0042C59B                 mov     [esp+334h+var_328], edi
.text:0042C59F                 call    sub_45644C
.text:0042C5A4                 xor     eax, eax
.text:0042C5A6                 jmp     loc_42CB09
.text:0042C5AB ; ------------------------------------------
.text:0042C5AB loc_42C5AB:
.text:0042C5AB                 push    esi
.text:0042C5AC                 lea     ecx, [esp+338h+var_30C]
.text:0042C5B0                 push    0FFh            ; nSize
.text:0042C5B5                 push    ecx             ; lpBuffer
.text:0042C5B6                 push    offset aUserkey_0 ; "USERKEY"
.text:0042C5BB                 call    ebp ; GetEnvironmentVariableA
.text:0042C5BD                 test    eax, eax
.text:0042C5BF                 jnz     short loc_42C5E5
.text:0042C5C1                 mov     edi, offset aNoKey ; "No key!"
.text:0042C5C6                 or      ecx, 0FFFFFFFFh
.text:0042C5C9                 repne scasb
.text:0042C5CB                 not     ecx
.text:0042C5CD                 sub     edi, ecx
.text:0042C5CF                 lea     edx, [esp+338h+var_30C]
.text:0042C5D3                 mov     eax, ecx
.text:0042C5D5                 mov     esi, edi
.text:0042C5D7                 mov     edi, edx
.text:0042C5D9                 shr     ecx, 2
.text:0042C5DC                 rep movsd
.text:0042C5DE                 mov     ecx, eax
.text:0042C5E0                 and     ecx, 3
.text:0042C5E3                 rep movsb
.text:0042C5E5 loc_42C5E5:
.text:0042C5E5                 lea     ecx, [esp+338h+Str1]
.text:0042C5EC                 push    offset aDefault ; "DEFAULT"
.text:0042C5F1                 push    ecx             ; Str1
.text:0042C5F2                 call    __strcmpi
.text:0042C5F7                 add     esp, 8
.text:0042C5FA                 test    eax, eax
.text:0042C5FC                 jnz     short loc_42C678
.text:0042C5FE                 mov     edx, off_482C24
.text:0042C604                 mov     [esp+338h+var_324], edx
.text:0042C608                 mov     edi, offset off_468914
.text:0042C60D                 push    83h             ; uID
.text:0042C612                 lea     ecx, [esp+33Ch+var_324]
.text:0042C616                 mov     [esp+33Ch+var_4], 2
.text:0042C621                 mov     [esp+33Ch+var_328], edi
.text:0042C625                 call    ?LoadStringA@CString@@QAEHI@Z
; CString::LoadStringA(uint)
.text:0042C62A                 lea     eax, [esp+338h+var_328]
.text:0042C62E                 lea     ecx, [esp+338h+var_324]
.text:0042C632                 neg     eax
.text:0042C634                 sbb     eax, eax
.text:0042C636                 lea     esi, [ebx+0FCh]
.text:0042C63C                 and     eax, ecx
.text:0042C63E                 mov     ecx, esi
...

Пропатчим начало функции проверки, записав туда команды MOV EAX,1; RET. Сохраняем изменения, запускаем, проверяем. Информация в About стала получше, теперь там нет сообщения, что с программы отодран навесной протектор. Но в остальном ограничения остались - не показываются целиком ключи, нельзя записать найденные ключи в файл. Ищем дальше. Есть еще одна функция, где проверяется значение другой переменной - USERNAME. Тут тоже все очень наглядно:
.text:0042D7E0                 push    0FFFFFFFFh
.text:0042D7E2                 push    offset SEH_42D7E0
.text:0042D7E7                 mov     eax, large fs:0
.text:0042D7ED                 push    eax
.text:0042D7EE                 mov     large fs:0, esp
.text:0042D7F5                 push    ecx
.text:0042D7F6                 mov     eax, off_482C24
.text:0042D7FB                 mov     [esp+10h+Str1], eax
.text:0042D7FF                 lea     edx, [esp+10h+Str1]
.text:0042D803                 mov     [esp+10h+var_4], 0
.text:0042D80B                 push    edx             ; int
.text:0042D80C                 push    offset aUsername_3 ; "USERNAME"
.text:0042D811                 call    sub_42D880
.text:0042D816                 test    eax, eax
.text:0042D818                 jz      short loc_42D85E
.text:0042D81A                 mov     eax, [esp+10h+Str1]
.text:0042D81E                 push    offset aDefault ; "DEFAULT"
.text:0042D823                 push    eax             ; Str1
.text:0042D824                 call    __mbscmp
.text:0042D829                 add     esp, 8
.text:0042D82C                 test    eax, eax
.text:0042D82E                 jz      short loc_42D85E
.text:0042D830                 call    sub_42D6B0
.text:0042D835                 test    eax, eax
.text:0042D837                 jz      short loc_42D85E
.text:0042D839                 lea     ecx, [esp+10h+Str1]
.text:0042D83D                 mov     [esp+10h+var_4], 0FFFFFFFFh
.text:0042D845                 call    sub_45644C
.text:0042D84A                 mov     eax, 1
.text:0042D84F                 mov     ecx, [esp+10h+var_C]
.text:0042D853                 mov     large fs:0, ecx
.text:0042D85A                 add     esp, 10h
.text:0042D85D                 retn
.text:0042D85E ; ----------------------------------------------
.text:0042D85E loc_42D85E:
.text:0042D85E                 lea     ecx, [esp+10h+Str1]
.text:0042D862                 mov     [esp+10h+var_4], 0FFFFFFFFh
.text:0042D86A                 call    sub_45644C
.text:0042D86F                 mov     ecx, [esp+10h+var_C]
.text:0042D873                 xor     eax, eax
.text:0042D875                 mov     large fs:0, ecx
.text:0042D87C                 add     esp, 10h
.text:0042D87F                 retn

На выходе EAX=1 - программа зарегистрирована, EAX=0 - регистрации нет. С этой функцией поступаем аналогичным образом, то есть записываем в начало пару команд MOV EAX,1 и RET. Сохраняем изменения, запускаем. Вот теперь наблюдаем красоту, все ключи найдены, а список сохраняется в файл без каких-либо ограничений.


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

Вот и все, программа "зарегистрирована". В качестве дополнительного приятного бонуса в отломанном виде она становится портативной и может работать с любого съемного носителя. Это особенно удобно, когда приходится иметь дело с чужими компьютерами.

Добавлено: 19 Сентября 2013 12:29:41 Добавил: Андрей Ковальчук

Исследование защиты программы Picture Reduce


Скриншот программы Picture Reduce

Программа Picture Reduce предназначена для пакетной обработки изображений с целью изменения их размера. Вообще-то аффтар еще сам не определился с названием программы: в окне About одно название, в заголовке главного окна - другое, так что пусть будет Picture Reduce. По функционалу тоже ничего особенного, с этим прекрасно справляется множество бесплатных программ. А вот защита мне понравилась, так что подать пациента на разделочный стол.

Качаем дистрибутив, устанавливаем, запускаем. Кнопка активации в правом верхнем углу, на ввод левых регистрационных данных программа реагирует сообщением "Invalid license code!". Поищем эту строчку в исполняемом файле. Файл ничем не упакован, найдется строчка в юникоде:


Нехорошая строка найдена

Теперь посмотрим код, где это все обрабатывается:

...
.text:00413F0C                 lea     ecx, [esi+604h]
.text:00413F12                 mov     byte ptr [esp+18h], 1
; Получить значение полей имени и серийного номера
.text:00413F17                 call    ?GetWindowTextW
.text:00413F1C                 lea     ecx, [esp+4]
.text:00413F20                 push    ecx
.text:00413F21                 lea     ecx, [esi+658h]
.text:00413F27                 call    ?GetWindowTextW
.text:00413F2C                 lea     ecx, [esp+8]
; Удалить лидирующие и хвостовые пробелы
.text:00413F30                 call    ds:?Trim@
.text:00413F36                 lea     ecx, [esp+4]
.text:00413F3A                 call    ds:?Trim
.text:00413F40                 lea     ecx, [esp+4]
; Проверить длину введенного имени, оно должно быть не менее 6 символов
.text:00413F44                 call    ds:?GetLength
.text:00413F4A                 cmp     eax, 6
.text:00413F4D                 jl      short loc_413F92
.text:00413F4F                 lea     edx, [esp+4]
.text:00413F53                 push    edx
.text:00413F54                 lea     eax, [esp+0Ch]
.text:00413F58                 push    eax
.text:00413F59                 call    sub_413D60
.text:00413F5E                 push    4
; Вызвать функцию проверки серийного номера
.text:00413F60                 call    sub_40AF80
.text:00413F65                 add     esp, 0Ch
.text:00413F68                 test    eax, eax
; Если она вернула EAX=0, то серийный номер неправильный
.text:00413F6A                 jz      short loc_413F92
.text:00413F6C                 push    0
.text:00413F6E                 mov     ecx, offset dword_4237E4
.text:00413F73                 call    ds:??B?$CSimpleStringT
.text:00413F79                 push    eax
; Сообщение об успешной регистрации
.text:00413F7A                 push    offset aTheLicenseCode
; "The license code is valid, thank you fo"...
.text:00413F7F                 mov     ecx, esi
.text:00413F81                 call    ?MessageBoxW
.text:00413F86                 mov     edx, [esi]
.text:00413F88                 mov     ecx, esi
.text:00413F8A                 call    dword ptr [edx+154h]
.text:00413F90                 jmp     short loc_413FAC
.text:00413F92 ; -----------------------------------------------
.text:00413F92 loc_413F92:
.text:00413F92                 mov     ecx, offset dword_4237E4
.text:00413F97                 push    30h
.text:00413F99                 call    ds:??B?$CSimpleStringT
.text:00413F9F                 push    eax
.text:00413FA0                 mov     ecx, esi
; Сообщение о неудачной регистрации
.text:00413FA2                 push    offset aInvalidLicense
; "Invalid license code!"
.text:00413FA7                 call    ?MessageBoxW
.text:00413FAC loc_413FAC:
.text:00413FAC                 lea     ecx, [esp+4]
.text:00413FB0                 call    ds:??1?$CStringT
.text:00413FB6                 lea     ecx, [esp+8]
.text:00413FBA                 call    ds:??1?$CStringT
.text:00413FC0                 mov     ecx, [esp+0Ch]
.text:00413FC4                 pop     esi
...

Короткий путь - пропатчить функцию проверки серийного номера по адресу 40AF80, чтобы она всегда возвращала EAX=1. На этом короткий путь заканчивается и дальше можно не читать. А мы попробуем разобрать процедуру проверки серийного номера, тут как раз зарыто самое интересное в этой программе. Первую проверку валидости мы уже выяснили - длина имени должна быть не менее 6 символов. Смотрим как проверяется серийный номер.
...
.text:0040AF98                 push    esi
.text:0040AF99                 push    edi
; Получить значение таймера
.text:0040AF9A                 push    0               ; Time
.text:0040AF9C                 call    ds:_time64
.text:0040AFA2                 mov     esi, [esp+2Ch+arg_0]
.text:0040AFA6                 add     esp, 4
.text:0040AFA9                 test    esi, esi
.text:0040AFAB                 jle     short loc_40AFB2
.text:0040AFAD                 cmp     esi, 6
.text:0040AFB0                 jle     short loc_40AFB7
.text:0040AFB2 loc_40AFB2:
.text:0040AFB2                 mov     esi, 6
.text:0040AFB7 loc_40AFB7:
.text:0040AFB7                 call    ds:rand
; Получить случайное число с инициализацией генератора значением таймера
.text:0040AFBD                 cdq
.text:0040AFBE                 idiv    esi
.text:0040AFC0                 mov     ecx, offset Source ; Source
.text:0040AFC5                 test    edx, edx
; Вызвать один из вариантов проверки в зависимости от случайного числа
.text:0040AFC7                 jz      short loc_40AFD0
; Первый вариант проверки
.text:0040AFC9                 call    sub_40A6F0
.text:0040AFCE                 jmp     short loc_40AFD5
.text:0040AFD0 ; -------------------------------------------------
.text:0040AFD0 loc_40AFD0:
; Второй вариант проверки
.text:0040AFD0                 call    sub_40A790
.text:0040AFD5 loc_40AFD5:
; Если любой из вариантов вернул AL=0, то серийный номер неправильный
.text:0040AFD5                 test    al, al
.text:0040AFD7                 jz      loc_40B081
.text:0040AFDD                 mov     ecx, offset Source ; Source
; Третья проверка - обязательная
.text:0040AFE2                 call    sub_40A650
; Если проверка вернула AL=0, то серийный номер неправильный
.text:0040AFE7                 test    al, al
.text:0040AFE9                 jz      loc_40B081
; Установить заголовок зарегистрированной версии
.text:0040AFEF                 push    offset aStrfull ; "strFull"
.text:0040AFF4                 lea     ecx, [esp+2Ch+arg_0]
.text:0040AFF8                 mov     edi, 1
.text:0040AFFD                 call    ds:??0?$CStringT
...
; часть незначимого кода пропущена
...
.text:0040B06E                 mov     eax, edi
.text:0040B070                 pop     edi
.text:0040B071                 pop     esi
.text:0040B072                 mov     ecx, [esp+20h+var_C]
.text:0040B076                 mov     large fs:0, ecx
.text:0040B07D                 add     esp, 20h
.text:0040B080                 retn
.text:0040B081 ; -------------------------------------------------
.text:0040B081 loc_40B081:
; Установить триальный заголовок
.text:0040B081                 push    offset aStrtrial ; "strTrial"
.text:0040B086                 lea     ecx, [esp+2Ch+var_20]
.text:0040B08A                 xor     esi, esi
...

Основная фишка защиты в том, что серийный номер проверяется по частям в два приема, причем первая проверка выбирается случайным образом еще из двух вариантов. Итого мы имеем три проверки серийного номера, и, если хоть одна из них не пройдена, то серийный номер считается невалидным. Но ситуация осложняется тем, что серийный номер может быть "частично" правильным, то есть он может пройти одну из первых проверок, даже несколько раз подряд, но потом "подброшенная монетка" выберет другую проверку и программа снова станет незарегистрированной. На помощь дизассемблеру приходит отладчик. Ставим точки останова на все три отмеченные процедуры проверки и запускаем программу. Первая проверка:
; Первая проверка серийного номера
.text:0040A6F0                 sub     esp, 14h
.text:0040A6F3                 mov     eax, dword_4236F0
.text:0040A6F8                 push    esi
.text:0040A6F9                 mov     esi, ecx
.text:0040A6FB                 mov     [esp+18h+var_4], eax
.text:0040A6FF                 lea     eax, [esi+0Eh]
; Проверить длину серийного номера
.text:0040A702                 push    eax             ; Str
.text:0040A703                 call    ds:wcslen
.text:0040A709                 add     esp, 4
.text:0040A70C                 cmp     eax, 12h
; Если длина серийника менее 12h (десятичное 18), то он неправильный
.text:0040A70F                 jl      short loc_40A763
.text:0040A711                 lea     ecx, [esp+18h+Str]
.text:0040A715                 push    esi             ; Source
.text:0040A716                 push    ecx             ; Dest
.text:0040A717                 call    ds:wcscpy
.text:0040A71D                 add     esp, 8
.text:0040A720                 lea     edx, [esp+18h+Str]
.text:0040A724                 push    edx             ; Str

Под отладчиком видно, что в начале первой функции проверки от серийного номера отделяются первые 6 символов, а затем проверяется длина оставшейся части, она должна быть не менее 18 символов. Получается, что длина правильного серийного номера как минимум 24 символа (сумма 6+18). Хорошо, вводим серийный номер, состоящий из 24 уникальных символов и повторяем регистрацию. Смотрим дальше:
.text:0040A725                 mov     ecx, esi
; Преобразовать первые 6 символов серийного номера
.text:0040A727                 call    sub_40A550
.text:0040A72C                 lea     eax, [esp+18h+Str]

Проверка длины успешно пройдена, далее по адресу 40A550 выполняется преобразование первых 6 символов. Вот алгоритм преобразования, обозначим его как "Преобразование №1":
; Преобразование №1
.text:0040A570 loc_40A570:
.text:0040A570                 xor     eax, eax
; Получить следующий символ
.text:0040A572                 mov     al, [esi+ecx*2]
.text:0040A575                 add     al, 53h
.text:0040A577                 mov     ebx, 19h
.text:0040A57C                 and     eax, 0FEh
.text:0040A581                 cdq
.text:0040A582                 idiv    ebx
.text:0040A584                 add     edx, 41h
; Записать его обратно
.text:0040A587                 mov     [esi+ecx*2], dx
.text:0040A58B                 inc     ecx
.text:0040A58C                 cmp     ecx, edi
.text:0040A58E                 jl      short loc_40A570

На выходе получаем 6 символов.
.text:0040A730                 push    eax             ; Str
.text:0040A731                 mov     ecx, esi
.text:0040A733                 call    sub_40A5F0

Они сразу же преобразуются следующим алгоритмом, обозначим его как "Преобразование №2":
; Преобразование №2
.text:0040A610 loc_40A610:
.text:0040A610                 xor     eax, eax
; Получить следующий символ
.text:0040A612                 mov     ax, [esi+ecx*2]
.text:0040A616                 shl     ax, 3
.text:0040A61A                 mov     ebx, 0Ah
.text:0040A61F                 add     eax, 0C2h
.text:0040A624                 movzx   eax, ax
.text:0040A627                 shr     eax, 1
.text:0040A629                 cdq
.text:0040A62A                 idiv    ebx
.text:0040A62C                 mov     ebx, 19h
.text:0040A631                 xor     edx, 3
.text:0040A634                 shl     edx, 3
.text:0040A637                 movzx   eax, dx
.text:0040A63A                 cdq
.text:0040A63B                 idiv    ebx
.text:0040A63D                 add     edx, 41h
; Записать его обратно
.text:0040A640                 mov     [esi+ecx*2], dx
.text:0040A644                 inc     ecx
.text:0040A645                 cmp     ecx, edi
.text:0040A647                 jl      short loc_40A610

Символы, полученные после двух преобразований, в цикле сравниваются с последними шестью символами введенного серийного номера.
; Начало цикла проверки
.text:0040A738                 xor     eax, eax
.text:0040A73A                 lea     ecx, [esi+26h]
.text:0040A73D                 lea     ecx, [ecx+0]
.text:0040A740 loc_40A740:
.text:0040A740                 mov     dx, [esp+eax*2+18h+Str]
.text:0040A745                 cmp     dx, [ecx]
.text:0040A748                 jnz     short loc_40A763
.text:0040A74A                 inc     eax
.text:0040A74B                 add     ecx, 2
.text:0040A74E                 cmp     eax, 6
.text:0040A751                 jl      short loc_40A740
.text:0040A753                 mov     al, 1
.text:0040A755                 pop     esi
.text:0040A756                 mov     ecx, [esp+14h+var_4]
.text:0040A75A                 call    sub_415A41
.text:0040A75F                 add     esp, 14h
.text:0040A762                 retn
.text:0040A763 ; ------------------------------------------------------
.text:0040A763 loc_40A763:
.text:0040A763                 mov     ecx, [esp+18h+var_4]
.text:0040A767                 xor     al, al
.text:0040A769                 pop     esi
.text:0040A76A                 call    sub_415A41
.text:0040A76F                 add     esp, 14h
.text:0040A772                 retn

Отлично. Мы знаем, что последние 6 символов серийника получаются из первых 6 символов путем применения к ним сперва функций "Преобразование №1", а затем "Преобразование №2". Смотрим вторую функцию проверки.
; Вторая проверка серийного номера
.text:0040A790                 sub     esp, 18h
.text:0040A793                 mov     eax, dword_4236F0
.text:0040A798                 push    edi
.text:0040A799                 mov     edi, ecx
.text:0040A79B                 push    edi             ; Str
; Заполнить массив символами "HOTUCQFRE"
.text:0040A79C                 mov     [esp+20h+var_4], eax
.text:0040A7A0                 mov     [esp+20h+var_18], 48h
.text:0040A7A7                 mov     [esp+20h+var_16], 4Fh
.text:0040A7AE                 mov     [esp+20h+var_14], 54h
.text:0040A7B5                 mov     [esp+20h+var_12], 55h
.text:0040A7BC                 mov     [esp+20h+var_10], 43h
.text:0040A7C3                 mov     [esp+20h+var_E], 51h
.text:0040A7CA                 mov     [esp+20h+var_C], 46h
.text:0040A7D1                 mov     [esp+20h+var_A], 52h
.text:0040A7D8                 mov     [esp+20h+var_8], 45h
.text:0040A7DF                 call    ds:wcslen
.text:0040A7E5                 add     esp, 4
.text:0040A7E8                 cmp     eax, 6
.text:0040A7EB                 jge     short loc_40A7FD
.text:0040A7ED                 xor     al, al
.text:0040A7EF                 pop     edi
.text:0040A7F0                 mov     ecx, [esp+18h+var_4]
.text:0040A7F4                 call    sub_415A41
.text:0040A7F9                 add     esp, 18h
.text:0040A7FC                 retn
.text:0040A7FD ; -----------------------------------------
.text:0040A7FD loc_40A7FD:
.text:0040A7FD                 push    esi
; В цикле проверить, чтобы любой из первых 6 символов серийного номера
; был из числа символов, которые были занесены в массив
.text:0040A7FE                 xor     esi, esi
.text:0040A800                 test    eax, eax
.text:0040A802                 jle     short loc_40A833
.text:0040A804 loc_40A804:
.text:0040A804                 mov     dx, [edi+esi*2]
.text:0040A808                 xor     ecx, ecx
.text:0040A80A                 lea     ebx, [ebx+0]
.text:0040A810 loc_40A810:
.text:0040A810                 cmp     dx, [esp+ecx*2+20h+var_18]
.text:0040A815                 jz      short loc_40A82E
.text:0040A817                 inc     ecx
.text:0040A818                 cmp     ecx, 9
.text:0040A81B                 jl      short loc_40A810
.text:0040A81D                 pop     esi
.text:0040A81E                 xor     al, al
.text:0040A820                 pop     edi
.text:0040A821                 mov     ecx, [esp+18h+var_4]
.text:0040A825                 call    sub_415A41
.text:0040A82A                 add     esp, 18h
.text:0040A82D                 retn
.text:0040A82E ; -------------------------------------------
.text:0040A82E loc_40A82E:
.text:0040A82E                 inc     esi
.text:0040A82F                 cmp     esi, eax
.text:0040A831                 jl      short loc_40A804
.text:0040A833 loc_40A833:
.text:0040A833                 mov     ecx, [esp+20h+var_4]
.text:0040A837                 pop     esi
.text:0040A838                 mov     al, 1
.text:0040A83A                 pop     edi
.text:0040A83B                 call    sub_415A41
.text:0040A840                 add     esp, 18h
.text:0040A843                 retn

Под отладчиком тоже хорошо видно, что сперва заполняется массив символами "HOTUCQFRE". Затем проверяется, чтобы любой из 6 первых символов серийного номера был из этого списка. Еще одна проверка пройдена, мы знаем из каких символов может состоять начало серийника. Осталась последняя проверка:
; Третья проверка серийного номера
.text:0040A650                 sub     esp, 14h
.text:0040A653                 mov     eax, dword_4236F0
.text:0040A658                 push    esi
.text:0040A659                 mov     esi, ecx
.text:0040A65B                 push    edi
.text:0040A65C                 lea     edi, [esi+0Eh]
.text:0040A65F                 push    edi             ; Str
.text:0040A660                 mov     [esp+20h+var_4], eax
.text:0040A664                 call    ds:wcslen
.text:0040A66A                 add     esp, 4
.text:0040A66D                 cmp     eax, 6
.text:0040A670                 jl      short loc_40A6D4
.text:0040A672                 lea     eax, [esp+1Ch+Str]
.text:0040A676                 push    esi             ; Source
.text:0040A677                 push    eax             ; Dest
.text:0040A678                 call    ds:wcscpy
.text:0040A67E                 add     esp, 8
.text:0040A681                 lea     ecx, [esp+1Ch+Str]
.text:0040A685                 push    ecx             ; Str
.text:0040A686                 mov     ecx, esi
; Вызвать преобразование №3
.text:0040A688                 call    sub_40A5A0

Тут преобразование выполняется в три этапа. Добавился новый алгоритм, назовем его "Преобразование №3":
; Преобразование №3
.text:0040A5C0 loc_40A5C0:
; Получить следующий символ
.text:0040A5C0                 movzx   eax, word ptr [esi+ecx*2]
.text:0040A5C4                 xor     eax, 0Bh
.text:0040A5C7                 cdq
.text:0040A5C8                 mov     ebx, 13h
.text:0040A5CD                 idiv    ebx
.text:0040A5CF                 mov     ebx, 0Ah
.text:0040A5D4                 shl     edx, 1
.text:0040A5D6                 movzx   eax, dx
.text:0040A5D9                 cdq
.text:0040A5DA                 idiv    ebx
.text:0040A5DC                 add     edx, 30h
; Записать его обратно
.text:0040A5DF                 mov     [esi+ecx*2], dx
.text:0040A5E3                 inc     ecx
.text:0040A5E4                 cmp     ecx, edi
.text:0040A5E6                 jl      short loc_40A5C0

А дальше к полученной строке применяются две уже известным нам функции преобразования:
.text:0040A68D                 lea     edx, [esp+1Ch+Str]
.text:0040A691                 push    edx             ; Str
.text:0040A692                 mov     ecx, esi
; Вызвать преобразование №2
.text:0040A694                 call    sub_40A5F0
.text:0040A699                 lea     eax, [esp+1Ch+Str]
.text:0040A69D                 push    eax             ; Str
.text:0040A69E                 mov     ecx, esi
; Вызвать преобразование №1
.text:0040A6A0                 call    sub_40A550
.text:0040A6A5                 xor     eax, eax
.text:0040A6A7                 mov     ecx, edi
; Сравнить вторые 6 символов серийного номера с полученной строкой
.text:0040A6A9                 lea     esp, [esp+0]
.text:0040A6B0 loc_40A6B0:
.text:0040A6B0                 mov     dx, [esp+eax*2+1Ch+Str]
.text:0040A6B5                 cmp     dx, [ecx]
.text:0040A6B8                 jnz     short loc_40A6D4
.text:0040A6BA                 inc     eax
.text:0040A6BB                 add     ecx, 2
.text:0040A6BE                 cmp     eax, 6
.text:0040A6C1                 jl      short loc_40A6B0
.text:0040A6C3                 pop     edi
.text:0040A6C4                 mov     al, 1
.text:0040A6C6                 pop     esi
.text:0040A6C7                 mov     ecx, [esp+14h+var_4]
.text:0040A6CB                 call    sub_415A41
.text:0040A6D0                 add     esp, 14h
.text:0040A6D3                 retn
.text:0040A6D4 ; -----------------------------------------------
.text:0040A6D4 loc_40A6D4:
.text:0040A6D4                 mov     ecx, [esp+1Ch+var_4]
.text:0040A6D8                 pop     edi
.text:0040A6D9                 xor     al, al
.text:0040A6DB                 pop     esi
.text:0040A6DC                 call    sub_415A41
.text:0040A6E1                 add     esp, 14h
.text:0040A6E4                 retn

Тут тоже ничего сложного. Снова берутся первые 6 символов серийного номера, затем к ним применяется последовательно "Преобразование №3", "Преобразование №2", а затем "Преобразование №1". Полученная строка сраванивается со вторыми 6 символами серийного номера. Третьи 6 символов серийного номера могут быть любыми. Регистрационное имя может быть вообще произвольным, оно проверяется только на минимальную длину. Вот, к примеру, серийник, который проходит все проверки: HOTFRECECEXU111111AXQXAX.


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

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

Добавлено: 19 Сентября 2013 12:24:04 Добавил: Андрей Ковальчук

Исследование защиты программы Actual Search & Replace


Скриншот программы Actual Search & Replace

Программа Actual Search & Replace - мощная программа для поиска и замены фрагментов текста в файлах. Поиск можно осуществлять как по отдельным словам, так и по целым фразам или регулярным выражениям. В общем, полезный инструмент для тех, кому приходится часто что-то искать в куче текстовых или HTML-файлов. Программа действительно хорошая, что подтверждают многочисленные награды от различных интернет-изданий, если бы не одно "но". Бесплатно программа будет работать только в течение 30 дней, после чего потребует оплату.

Скачиваем дистрибутив, распаковываем, устанавливаем, запускаем. Сразу же получаем окно с предложением купить или ввести серийный номер:


Окно регистрации программы

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


Нехорошая строка найдена

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

...
CODE:004E746A                 push    ebp
CODE:004E746B                 push    offset loc_4E74A3
CODE:004E7470                 push    dword ptr fs:[eax]
CODE:004E7473                 mov     fs:[eax], esp
CODE:004E7476                 mov     eax, ds:off_4E9578
CODE:004E747B                 mov     eax, [eax]
CODE:004E747D                 mov     edx, [eax+2164h]
CODE:004E7483                 mov     eax, [ebp+var_10]
CODE:004E7486                 call    sub_48D3C4
CODE:004E748B                 lea     ecx, [ebp+var_8]
CODE:004E748E                 mov     edx, [ebp+var_C]
CODE:004E7491                 mov     eax, [ebp+var_10]
CODE:004E7494                 call    sub_48CBC8
CODE:004E7499                 xor     eax, eax
CODE:004E749B                 pop     edx
CODE:004E749C                 pop     ecx
CODE:004E749D                 pop     ecx
CODE:004E749E                 mov     fs:[eax], edx
CODE:004E74A1                 jmp     short loc_4E74EA
CODE:004E74A3 ; --------------------------------------------
CODE:004E74A3 loc_4E74A3:
CODE:004E74A3                 jmp     sub_403468
CODE:004E74A8 ; --------------------------------------------
; Первое сообщение о неправильном серийном номере
CODE:004E74A8                 push    0
CODE:004E74AA                 mov     cx, word_4E7638
CODE:004E74B1                 mov     dl, 1
CODE:004E74B3                 mov     eax, offset aIncorrectKey
; "Incorrect key"
CODE:004E74B8                 call    sub_45AA54
CODE:004E74BD                 mov     eax, [ebp+var_10]
CODE:004E74C0                 call    sub_48CB60
CODE:004E74C5                 mov     eax, [ebp+var_10]
CODE:004E74C8                 call    sub_402FBC
CODE:004E74CD                 mov     eax, ds:off_4E9578
CODE:004E74D2                 mov     eax, [eax]
CODE:004E74D4                 mov     byte ptr [eax+2170h], 0
CODE:004E74DB                 call    sub_4037C4
CODE:004E74E0                 jmp     loc_4E7600
CODE:004E74E5 ; ----------------------------------------------
CODE:004E74E5                 call    sub_4037C4
CODE:004E74EA loc_4E74EA:
CODE:004E74EA                 mov     eax, [ebp+var_10]
CODE:004E74ED                 call    sub_48CB60
CODE:004E74F2                 mov     eax, [ebp+var_10]
CODE:004E74F5                 call    sub_402FBC
CODE:004E74FA                 mov     edx, [ebp+var_8]
CODE:004E74FD                 mov     eax, offset aAcsearchRepl_0
; "AcSearch&Replace;"
CODE:004E7502                 call    sub_40420C
CODE:004E7507                 mov     ebx, eax
CODE:004E7509                 test    ebx, ebx
CODE:004E750B                 jz      short loc_4E751C
; Сравнить введенный серийный номер со строкой
CODE:004E750D                 mov     eax, [ebp+var_C]
CODE:004E7510                 mov     edx, offset aIkizhzl170uu_0
; "IKiZhZL170UUvtoyVN5ginTgPygyaoVTh1+lRil"...
CODE:004E7515                 call    sub_404030
; Если они не равны, то все в порядке
CODE:004E751A                 jnz     short loc_4E7574
CODE:004E751C loc_4E751C:
; Второе сообщение о неправильном серийном номере
CODE:004E751C                 xor     eax, eax
CODE:004E751E                 push    ebp
CODE:004E751F                 push    offset loc_4E7549
CODE:004E7524                 push    dword ptr fs:[eax]
CODE:004E7527                 mov     fs:[eax], esp
CODE:004E752A                 push    0
CODE:004E752C                 mov     eax, offset aIncorrectKey
; "Incorrect key"
CODE:004E7531                 mov     cx, word_4E7638
CODE:004E7538                 mov     dl, 1
CODE:004E753A                 call    sub_45AA54
CODE:004E753F                 xor     eax, eax
CODE:004E7541                 pop     edx
CODE:004E7542                 pop     ecx
...

Обратите внимание, что здесь два блока кода, в которых выводятся сообщения "Incorrect key". В первом случае проверяется ключ на корректность, и, если ключ неправильный, то взводится исключение и управление передается на адрес 004E74A8. Команды условных переходов не используются, что несколько затрудняет анализ. Посмотрим дальше. А дальше выполняется сравнение введенного серийного номера с какой-то хитрой строкой, причем, если они совпадают, то снова выдается сообщение о неправильном серийном номере. К чему бы это? Забегая вперед, скажу, что здесь используется "черный список" ключей. То есть разработчики блокируют ключи к своим программам, которые были получены незаконным путем, например, куплены по левым кредитным картам или выложены в свободный доступ.


Заблокированный регистрационный ключ

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


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

Догадка подтвердилась. Это действительно правильный серийный номер. В программе пропали все внешние признаки триальности, в окне About написано, что она зарегистрирована. Выходим из программы, запускаем снова. Оп-па! Триальное окно снова на месте. Можно повторять регистрацию сколько угодно раз, она будет сохраняться только на текущий сеанс работы с программой. Значит где-то выполняется дополнительная проверка на заблокированный ключ. Сперва поищем строку заблокированного ключа. Найдется еще одно совпадение. С ней выполняем те же самые манипуляции по замене символов, что и с первой строкой.


Изменяем заблокированный ключ в файле

Повторяем регистрацию - то же самое, то есть все плохо. Попробуем поискать какие-нибудь другие зацепки, например, по строке из заголовка программы "Unregistered". Найдется такой код:
...
CODE:004C0444                 mov     eax, ds:off_4E9578
CODE:004C0449                 mov     eax, [eax]
CODE:004C044B                 mov     eax, [eax+216Ch]
CODE:004C0451                 mov     edx, offset aDavidLeeSingle
; "David Lee - single user license"
CODE:004C0456                 call    sub_404030
CODE:004C045B                 jnz     loc_4C04F7
CODE:004C0461                 lea     edx, [ebp+var_34]
CODE:004C0464                 mov     eax, ds:off_4E9578
CODE:004C0469                 mov     eax, [eax]
CODE:004C046B                 call    sub_434894
CODE:004C0470                 mov     eax, [ebp+var_34]
CODE:004C0473                 mov     edx, offset aActualSearch_1
; "Actual Search & Replace (Unregistered)"
CODE:004C0478                 call    sub_404030
CODE:004C047D                 jz      short loc_4C04F7
CODE:004C047F                 lea     eax, [ebp+var_2C]
CODE:004C0482                 mov     edx, offset aUnregistered
; "Unregistered"
CODE:004C0487                 call    sub_403D38
CODE:004C048C                 mov     eax, [ebp+var_2C]
...

Ага, знакомая нам строчка "David Lee - single user license". Ее мы видели после регистрации. Значит на "черный список" проверяется не только ключ при вводе, но и регистрационное имя в уже работающей программе. Логично, ведь ключ можно записать через реестр, или же подобная незаконная регистрация могла сохраниться от предыдущей установки, где она еще не была заблокирована. Поищем в исполняемом файле все упоминания несчастного Дэвида и также затрем в каждом из них по несколько символов. Таких проверок будет как минимум три. Зато теперь, после стирания из файла всех упоминаний имени Дэвида Ли и его регистрационного ключа, программа спокойно регистрируется заблокированным ранее ключом и работает как ни в чем не бывало.

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

Добавлено: 19 Сентября 2013 12:18:31 Добавил: Андрей Ковальчук

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


Скриншот программы Cool ASCII

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

Загружаем дистрибутив, устанавливаем, запускаем. Программа радостно сообщает, что она не зарегистрирована и поэтому будет запускаться только 10 раз, после чего понадобится сходить в кассу. После запуска посмотрим, как программа реагирует на неправильный серийный номер.


Окно регистрации программы

Отлично, теперь мы знаем нехорошую строку: "Sorry, the registration code is not correct. Please try again." Переходим к анализу исполняемого файла. Он упакован пакером ASPack, который легко снимается вручную или при помощи автоматических распаковщиков, здесь никаких проблем быть не должно. Строка о неправильной регистрации тоже лежит в открытом виде:


Нехорошая строка найдена

Загоняем распакованный файл в дизассемблер, чтобы более подробно выяснить при каких условиях она появляется.

...
CODE:004CCD81                 mov     fs:[eax], esp
CODE:004CCD84                 lea     edx, [ebp-4]
CODE:004CCD87                 mov     eax, [ebx+314h]
CODE:004CCD8D                 call    sub_47812C
CODE:004CCD92                 mov     edx, [ebp-4]
; Проверить позицию подстроки "YI-9V" в строке серийного номера
CODE:004CCD95                 mov     eax, offset aYi9v_0 ; "YI-9V"
CODE:004CCD9A                 call    sub_404F5C
CODE:004CCD9F                 test    eax, eax
; Если подстроки нет, то EAX=-1
CODE:004CCDA1                 jle     loc_4CCE2E
CODE:004CCDA7                 mov     edx, [ebp-4]
; Проверить позицию подстроки "7" в строке серийного номера
CODE:004CCDAA                 mov     eax, offset a7_0 ; "7"
CODE:004CCDAF                 call    sub_404F5C
CODE:004CCDB4                 test    eax, eax
; Если подстроки нет, то EAX=-1
CODE:004CCDB6                 jle     short loc_4CCE2E
CODE:004CCDB8                 mov     edx, ds:off_4D0008
CODE:004CCDBE                 mov     edx, [edx]
CODE:004CCDC0                 lea     eax, [ebp-8]
; Записать введенный регистрационный номер в файл
CODE:004CCDC3                 mov     ecx, offset aDataCa_id_0 ; "\\Data\\ca.id"
CODE:004CCDC8                 call    sub_404C64
CODE:004CCDCD                 mov     eax, [ebp-8]
CODE:004CCDD0                 mov     edx, [ebp-4]
CODE:004CCDD3                 call    sub_4C7C78
CODE:004CCDD8                 mov     eax, ds:off_4D02F4
CODE:004CCDDD                 mov     eax, [eax]
CODE:004CCDDF                 add     eax, 31Ch
CODE:004CCDE4                 mov     edx, offset aThanksForYou_0
; "Thanks for your register. If you have a"...
CODE:004CCDE9                 call    sub_4049AC
CODE:004CCDEE                 mov     eax, ds:off_4D02F4
CODE:004CCDF3                 mov     eax, [eax]
CODE:004CCDF5                 mov     eax, [eax+2FCh]
CODE:004CCDFB                 xor     edx, edx
CODE:004CCDFD                 call    sub_4461CC
CODE:004CCE02                 mov     eax, ds:off_4D02F4
CODE:004CCE07                 mov     eax, [eax]
CODE:004CCE09                 mov     eax, [eax+2F8h]
CODE:004CCE0F                 mov     dl, 1
CODE:004CCE11                 call    sub_4461CC
CODE:004CCE16                 mov     eax, ds:off_4D02F4
CODE:004CCE1B                 mov     eax, [eax]
CODE:004CCE1D                 mov     edx, [eax]
CODE:004CCE1F                 call    dword ptr [edx+0ECh]
CODE:004CCE25                 mov     eax, ebx
CODE:004CCE27                 call    sub_46288C
CODE:004CCE2C                 jmp     short loc_4CCE7B
CODE:004CCE2E ; -----------------------------------------------------
CODE:004CCE2E loc_4CCE2E:
CODE:004CCE2E                 mov     eax, ds:off_4D02F4
CODE:004CCE33                 mov     eax, [eax]
CODE:004CCE35                 add     eax, 31Ch
; Сообщение о неправильном серийном номере
CODE:004CCE3A                 mov     edx, offset aSorryTheRegi_0
; "Sorry, the registration code is not cor"...
CODE:004CCE3F                 call    sub_4049AC
CODE:004CCE44                 mov     eax, ds:off_4D02F4
CODE:004CCE49                 mov     eax, [eax]
CODE:004CCE4B                 mov     eax, [eax+2FCh]
...

Под отладчиком видно, что проверяется наличие в строке серийного номера двух строк: "YI-9V" и "7", причем без точного месторасположения. Попробуем ввести серийный номер, в котором они содержатся, например, что-то вроде PCLYI-9V7. Программа благодарит за регистрацию:


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

Закрываем программу, запускаем ее снова. Опа! Триальное окно на месте, счетчик запусков продолжает тикать. Значит где-то должны быть еще какие-то проверки. Вернемся к коду регистрации, посмотрим на него повнимательнее. Мы видим, что регистрационные данные записываются в файл "\Data\ca.id", для верности можно сходить в эту папку и убедиться, что мы не ошиблись. Логично предположить, что раз туда сохраняется серийный номер, то он должен где-то читаться и проверяться. Поищем это место в дизассемблере по строке "ca.id". Немного ниже по коду находится аналогичная проверка с записью серийника в файл, видимо тупо продублирована процедура регистрации с заходом из другого места. Ее мы пропускаем. А еще ниже находятся интересные манипуляции с файлом ca.id:
...
CODE:004CD238                 call    sub_403B28
CODE:004CD23D                 mov     edx, ds:off_4D0008
CODE:004CD243                 mov     edx, [edx]
CODE:004CD245                 lea     eax, [ebp-8]
CODE:004CD248                 mov     ecx, offset aDataCa_id_1 ; "\\Data\\ca.id"
CODE:004CD24D                 call    sub_404C64
CODE:004CD252                 mov     eax, [ebp-8]
CODE:004CD255                 lea     edx, [ebp-4]
CODE:004CD258                 call    sub_4C7D14
CODE:004CD25D                 mov     edx, [ebp-4]
; Проверить позицию подстроки "A" в строке серийного номера
CODE:004CD260                 mov     eax, offset aA  ; "A"
CODE:004CD265                 call    sub_404F5C
CODE:004CD26A                 test    eax, eax
; Если подстроки нет, то EAX=-1
CODE:004CD26C                 jle     short loc_4CD290
CODE:004CD26E                 mov     edx, [ebp-4]
; Проверить позицию подстроки "9" в строке серийного номера
CODE:004CD271                 mov     eax, offset a9  ; "9"
CODE:004CD276                 call    sub_404F5C
CODE:004CD27B                 test    eax, eax
; Если подстроки нет, то EAX=-1
CODE:004CD27D                 jle     short loc_4CD290
CODE:004CD27F                 mov     edx, [ebp-4]
; Проверить позицию подстроки "CY" в строке серийного номера
CODE:004CD282                 mov     eax, offset aCy ; "CY"
CODE:004CD287                 call    sub_404F5C
CODE:004CD28C                 test    eax, eax
; Если подстроки нет, то EAX=-1
CODE:004CD28E                 jg      short loc_4CD29F
CODE:004CD290 loc_4CD290:
CODE:004CD290                 mov     eax, ds:off_4D037C
CODE:004CD295                 mov     eax, [eax]
CODE:004CD297                 mov     edx, [eax]
CODE:004CD299                 call    dword ptr [edx+0ECh]
CODE:004CD29F loc_4CD29F:
CODE:004CD29F                 xor     eax, eax
CODE:004CD2A1                 pop     edx
CODE:004CD2A2                 pop     ecx
CODE:004CD2A3                 pop     ecx
CODE:004CD2A4                 mov     fs:[eax], edx
CODE:004CD2A7                 push    offset loc_4CD2C1
CODE:004CD2AC loc_4CD2AC:
CODE:004CD2AC                 lea     eax, [ebp-8]
CODE:004CD2AF                 mov     edx, 2
CODE:004CD2B4                 call    sub_40497C
CODE:004CD2B9
...

Под отладчиком картина тоже проясняется. Из файла "\Data\ca.id" читается серийный номер, но уже выполняются другие проверки: наличие в нем строк "A", "9" и "CY", причем опять же без какого-то конкретного расположения их в строке. Дополним ими серийный номер до строки PCLYI-9V7ACY (подстрока "9" уже есть) и попробуем снова зарегистрировать программу:


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

На этот раз триальное окно не появляется, значит регистрация считается полностью валидной. Осталось посмотреть, где программа хранит свой триальный счетчик. Для этого найдем место, где формируется строка с указанием количества оставшихся запусков:
...
CODE:004CCAB4                 mov     eax, off_438EEC
CODE:004CCAB9                 call    sub_438FEC
CODE:004CCABE                 mov     esi, eax
CODE:004CCAC0                 mov     edx, 80000002h
CODE:004CCAC5                 mov     eax, esi
CODE:004CCAC7                 call    sub_43908C
CODE:004CCACC                 mov     cl, 1
; Открыть ветку реестра HKLM\SOFTWARE\Microsoft\Direcs3D\
; "Direcs3D" - это не опечатка, именно в этой ветке прячется триальный счетчик
CODE:004CCACE                 mov     edx, offset aSoftwareMicros
; "\\SOFTWARE\\Microsoft\\Direcs3D\\"
CODE:004CCAD3                 mov     eax, esi
CODE:004CCAD5                 call    sub_4390F0
CODE:004CCADA                 test    al, al
CODE:004CCADC                 jz      loc_4CCC79
CODE:004CCAE2                 mov     edx, offset aDirectdraw ; "DirectDraw"
CODE:004CCAE7                 mov     eax, esi
CODE:004CCAE9                 call    sub_43940C
CODE:004CCAEE                 test    al, al
CODE:004CCAF0                 jnz     short loc_4CCB2B
CODE:004CCAF2                 call    sub_40B358
CODE:004CCAF7                 add     esp, 0FFFFFFF8h
CODE:004CCAFA                 fstp    qword ptr [esp]
CODE:004CCAFD                 wait
CODE:004CCAFE                 lea     eax, [ebp-1Ch]
CODE:004CCB01                 call    sub_40BF6C
CODE:004CCB06                 mov     eax, [ebp-1Ch]
CODE:004CCB09                 lea     edx, [ebp-18h]
CODE:004CCB0C                 call    sub_4CC6B8
CODE:004CCB11                 lea     eax, [ebp-18h]
CODE:004CCB14                 lea     edx, [ebp-8]
CODE:004CCB17                 call    sub_4CC72C
CODE:004CCB1C                 mov     ecx, [ebp-8]
CODE:004CCB1F                 mov     edx, offset aDirectdraw ; "DirectDraw"
CODE:004CCB24                 mov     eax, esi
CODE:004CCB26                 call    sub_43928C
CODE:004CCB2B loc_4CCB2B:
CODE:004CCB2B                 mov     edx, offset aDirectinput ; "DirectInput"
CODE:004CCB30                 mov     eax, esi
CODE:004CCB32                 call    sub_43940C
CODE:004CCB37                 test    al, al
CODE:004CCB39                 jnz     short loc_4CCB67
CODE:004CCB3B                 lea     edx, [ebp-18h]
CODE:004CCB3E                 mov     eax, offset dword_4CCD08
CODE:004CCB43                 call    sub_4CC6B8
CODE:004CCB48                 lea     eax, [ebp-18h]
CODE:004CCB4B                 lea     edx, [ebp-20h]
CODE:004CCB4E                 call    sub_4CC72C
CODE:004CCB53                 mov     ecx, [ebp-20h]
CODE:004CCB56                 mov     edx, offset aDirectinput ; "DirectInput"
CODE:004CCB5B                 mov     eax, esi
CODE:004CCB5D                 call    sub_43928C
CODE:004CCB62                 jmp     loc_4CCC79
CODE:004CCB67 ; --------------------------------------------------
CODE:004CCB67 loc_4CCB67:
CODE:004CCB67                 lea     ecx, [ebp-4]
CODE:004CCB6A                 mov     edx, offset aDirectinput ; "DirectInput"
CODE:004CCB6F                 mov     eax, esi
CODE:004CCB71                 call    sub_4392B8
CODE:004CCB76                 mov     ebx, 1
CODE:004CCB7B loc_4CCB7B:
CODE:004CCB7B                 lea     edx, [ebp-28h]
CODE:004CCB7E                 mov     eax, ebx
CODE:004CCB80                 call    sub_40940C
CODE:004CCB85                 lea     eax, [ebp-28h]
CODE:004CCB88                 push    eax
CODE:004CCB89                 lea     edx, [ebp-2Ch]
CODE:004CCB8C                 mov     eax, ebx
CODE:004CCB8E                 call    sub_40940C
CODE:004CCB93                 mov     edx, [ebp-2Ch]
CODE:004CCB96                 pop     eax
CODE:004CCB97                 call    sub_404C20
CODE:004CCB9C                 mov     eax, [ebp-28h]
CODE:004CCB9F                 lea     edx, [ebp-18h]
CODE:004CCBA2                 call    sub_4CC6B8
CODE:004CCBA7                 lea     eax, [ebp-18h]
CODE:004CCBAA                 lea     edx, [ebp-24h]
CODE:004CCBAD                 call    sub_4CC72C
CODE:004CCBB2                 mov     edx, [ebp-24h]
CODE:004CCBB5                 mov     eax, [ebp-4]
CODE:004CCBB8                 call    sub_404D64
CODE:004CCBBD                 jz      short loc_4CCBC5
CODE:004CCBBF                 inc     ebx
CODE:004CCBC0                 cmp     ebx, 0Ah
CODE:004CCBC3                 jnz     short loc_4CCB7B
CODE:004CCBC5 loc_4CCBC5:
CODE:004CCBC5                 cmp     ebx, 0Ah
CODE:004CCBC8                 jnz     short loc_4CCBF6
CODE:004CCBCA                 mov     dword ptr [edi+31Ch], 1
CODE:004CCBD4                 mov     eax, [edi+304h]
CODE:004CCBDA                 xor     edx, edx
CODE:004CCBDC                 mov     ecx, [eax]
CODE:004CCBDE                 call    dword ptr [ecx+64h]
CODE:004CCBE1                 mov     eax, [edi+2FCh]
CODE:004CCBE7                 mov     edx, offset aYouHaveUsed10T
; "You have used 10 times."
CODE:004CCBEC                 call    sub_4462DC
CODE:004CCBF1                 jmp     loc_4CCC79
CODE:004CCBF6 ; -----------------------------------------------------
CODE:004CCBF6 loc_4CCBF6:
CODE:004CCBF6                 xor     eax, eax
CODE:004CCBF8                 mov     [edi+31Ch], eax
CODE:004CCBFE                 push    offset aYouHaveUsed ; "You have used "
CODE:004CCC03                 lea     edx, [ebp-34h]
CODE:004CCC06                 lea     eax, [ebx+1]
CODE:004CCC09                 call    sub_40940C
CODE:004CCC0E                 push    dword ptr [ebp-34h]
CODE:004CCC11                 push    offset aTimes_  ; " times."
CODE:004CCC16                 lea     eax, [ebp-30h]
CODE:004CCC19                 mov     edx, 3
CODE:004CCC1E                 call    sub_404CD8
CODE:004CCC23                 mov     edx, [ebp-30h]
...

Оригинальностью аффтар не страдает, поэтому счетчик количества запусков прячется в ветке HKLM\SOFTWARE\Microsoft\Direcs3D\ в ключах с именами DirectDraw и DirectInput. Если периодически удалять эту ветку из реестра, то программой можно будет пользоваться и без регистрации в течение неограниченного времени. Но мы уже разобрали алгоритм генерации серийного номера, так что можете написать рабочий кейген самостоятельно.

Добавлено: 19 Сентября 2013 12:13:06 Добавил: Андрей Ковальчук

Исследование защиты программы Emicsoft MTS Converter


Скриншот программы Emicsoft MTS Converter

После покупки видеокамеры мне потребовалась программа для конвертирования видео из формата MTS в какой-нибудь более удобоваримый. Перепробовал несколько разных решений, как бесплатных, так и шароварных, в результате остановился на Emicsoft MTS Converter. Простейшая в настройках программа, при этом на выходе она дает самый лучший результат. Как обычно, всю малину испортила необходимость ее покупки.

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


Окно регистрации программы

На ввод левых регистрационных данных программа реагирует сообщением "The registration code is error." Еще, блин, одни грамотеи. Ладно, посмотрим где это у нас находится. Главный исполняемый файл Emicsoft MTS Converter.exe занимает подозрительно мало места для такой программы - всего около 90 килобайт, и это при том, что он даже ничем не упакован. Подозрения оправдались, искомая строка в нем не находится ни в каком виде.


Файл со строкой найден

На помощь приходит Total Commander, поиском по всему каталогу с вложенными папками файл находится очень быстро. Ну вот, другое дело - dll почти на 16 мегабайт, с его именем, правда, аффтары тоже соригинальничали: EmicVideoMui_dll.dll Посмотрим, при каких условиях появляется сообщение о неправильной регистрации.

...
.text:0041666C                 lea     ecx, [ebp+var_54]
.text:0041666F                 push    ecx
.text:00416670                 lea     eax, [esi+328h]
.text:00416676                 push    eax
; Вызвать функцию проверки регистрационных данных
.text:00416677                 call    sub_41BCE4
.text:0041667C                 add     esp, 8
.text:0041667F                 mov     edx, 2
; Занести в стек результат ее работы
.text:00416684                 push    eax
.text:00416685                 lea     eax, [ebp+var_54]
.text:00416688                 dec     dword ptr [ebx+1Ch]
.text:0041668B                 call    sub_594E88
; Восстановить из стека результат ее работы
.text:00416690                 pop     ecx
.text:00416691                 test    cl, cl
; Если CL=0, то регистрационные данные неправильные, переход на
; триальную ветку алгоритма
.text:00416693                 jz      short loc_4166BA
; Выдать сообщение об успешной регистрации
.text:00416695                 push    0               ; uType
.text:00416697                 push    offset aRegistration_1
; "Registration"
.text:0041669C                 push    offset aSuccessful
; "Successful"
.text:004166A1                 mov     eax, esi
.text:004166A3                 call    @Controls@TWinControl@GetHandle$qqrv
.text:004166A8                 push    eax             ; hWnd
.text:004166A9                 call    MessageBoxA
.text:004166AE                 mov     dword ptr [esi+24Ch], 1
.text:004166B8                 jmp     short loc_416733
.text:004166BA ; --------------------------------------------------------
.text:004166BA loc_4166BA:
; Сообщение о неправильном регистрационном коде
.text:004166BA                 push    10h             ; uType
.text:004166BC                 push    offset aRegistration_2
; "Registration"
.text:004166C1                 push    offset aTheRegistratio
; "The registration code is error."
.text:004166C6                 mov     eax, esi
.text:004166C8                 call    @Controls@TWinControl@GetHandle$qqrv
.text:004166CD                 push    eax             ; hWnd
.text:004166CE                 call    MessageBoxA
...

Нас интересует функция проверки регистрационного номера по адресу 41BCE4, а точнее ее результат. Там используется какая-то криптография, да еще и загружаемая из внешнего модуля. Тратить время на разбор всей этой каши у меня нет никакого желания, так что ограничусь классическим патчем. Нам надо, чтобы функция проверки вернула ненулевое значение в регистре EAX, которое затем будет записано в стек и потом будет проверяться. Значит требуется просто записать пару команд MOV AL,1; RET в начало функции проверки по адресу 41BCE4. Сохраняем изменения, запускаем. Окно регистрации пропало, никаких ограничений нет. Теперь на сэкономленную денежку можно затариться пивом и углубиться в процесс обработки видео из отпуска.

Добавлено: 19 Сентября 2013 02:45:22 Добавил: Андрей Ковальчук

Исследование защиты программы WinCHM Pro


Скриншот программы WinCHM

WinCHM - небольшой по размерам, но сильный по возможностям редактор для создания файлов справок в формате CHM, HLP а также PDF- и DOC-файлов. Про все возможности этой замечательной программы во можете почитать на офсайте, и там же вдруг обнаружится ссылка на страницу покупки. Туда ходить не надо, обойдемся своими силами.

Первым делом качаем дистрибутив, устанавливаем, запускаем. Сразу получаем триальное окно:


Триальное окно

Окей, введем какой-нибудь левак в качестве регистрационного имени и номера. Получаем сообщение "Illegal registration code!". Начальные данные для анализа у нас есть, переходим к стадии кодокопания. Файл ничем не упакован, поэтому сразу загоним его в дизассемблер, а пока тот работает, поищем нехорошую строчку.


Нехорошая строка найдена

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

...
CODE:0051017E                 mov     eax, ebx
CODE:00510180                 call    sub_403574
CODE:00510185                 call    sub_50FB80
; Проверить, если DWORD по адресу 54B184 имеет не нулевое значение,
; то программа считается зарегистрированной
CODE:0051018A                 mov     eax, ds:off_54B184
CODE:0051018F                 cmp     dword ptr [eax], 0
CODE:00510192                 jz      short loc_5101EB
; Сообщение об удачной регистрации
CODE:00510194                 mov     ecx, ds:off_54B184
CODE:0051019A                 mov     ecx, [ecx]
CODE:0051019C                 lea     eax, [ebp+var_4]
CODE:0051019F                 mov     edx, offset aThankYouForPur
; "Thank you for purchasing our product!\r\n"...
CODE:005101A4                 call    sub_404670
CODE:005101A9                 mov     eax, ds:off_54B4E8
CODE:005101AE                 cmp     dword ptr [eax], 1
CODE:005101B1                 jnz     short loc_5101CF
CODE:005101B3                 push    [ebp+var_4]
CODE:005101B6                 push    offset dword_5102B8
CODE:005101BB                 push    offset aVersionWinchmP
; "Version: WinCHM Pro"
CODE:005101C0                 lea     eax, [ebp+var_4]
CODE:005101C3                 mov     edx, 3
CODE:005101C8                 call    sub_4046E4
CODE:005101CD                 jmp     short loc_5101F8
CODE:005101CF ; ------------------------------------------------------
CODE:005101CF loc_5101CF:
CODE:005101CF                 push    [ebp+var_4]
CODE:005101D2                 push    offset dword_5102B8
CODE:005101D7                 push    offset aVersionWinchmS
; "Version: WinCHM (Standard)"
CODE:005101DC                 lea     eax, [ebp+var_4]
CODE:005101DF                 mov     edx, 3
CODE:005101E4                 call    sub_4046E4
CODE:005101E9                 jmp     short loc_5101F8
CODE:005101EB ; ------------------------------------------------------
; Сообщение о некорректной регистрации
CODE:005101EB loc_5101EB:
CODE:005101EB                 lea     eax, [ebp+var_4]
CODE:005101EE                 mov     edx, offset aIllegalRegistr
; Указатель на строку "Illegal registration code!"
CODE:005101F3                 call    sub_4043E0
...

Тут вроде все понятно. Если DWORD по адресу 54B184 имеет нулевое значение, то программа работает в триальном режиме. Теперь надо найти в коде то место, где эта ячейка памяти инициализируется правильным ненулевым значением. Дизассемблер показывает, что ссылок на эту ячейку памяти очень много, и практически все они так или иначе связаны с проверкой ее значения. Все, кроме одного блока:
...
CODE:0050FC75                 mov     eax, [ebp+var_4]
CODE:0050FC78                 call    sub_403574
; Загрузить в регистр EDX ссылку на регистрационный код из реестра
CODE:0050FC7D                 mov     edx, ds:off_54B33C
CODE:0050FC83                 mov     edx, [edx]
CODE:0050FC85                 mov     eax, ds:off_54B5E8
; Загрузить в регистр EAX ссылку на регистрационное имя из реестра
CODE:0050FC8A                 mov     eax, [eax]
; Передать результаты в какую-то функцию проверки
CODE:0050FC8C                 call    sub_50F3B4
; Результат проверки вернулся в регистре EAX
CODE:0050FC91                 mov     ebx, eax
CODE:0050FC93                 mov     eax, ebx
; Если EAX был = 0, то выполнится следующий условный переход
CODE:0050FC95                 sub     eax, 1
CODE:0050FC98                 jb      short loc_50FCA5
; Если EAX было = 1, то будет переход на Single-user License
CODE:0050FC9A                 jz      short loc_50FCB1
; Если EAX было = 270Eh, то переход на Unlimited-user License
CODE:0050FC9C                 sub     eax, 270Eh
CODE:0050FCA1                 jz      short loc_50FCC2
; Иначе переход на NN-user License
CODE:0050FCA3                 jmp     short loc_50FCD3
CODE:0050FCA5 ; -------------------------------------------------
CODE:0050FCA5 loc_50FCA5:
CODE:0050FCA5                 mov     eax, ds:off_54B184
CODE:0050FCAA                 call    sub_404348
CODE:0050FCAF                 jmp     short loc_50FCEF
CODE:0050FCB1 ; -------------------------------------------------
CODE:0050FCB1 loc_50FCB1:
CODE:0050FCB1                 mov     eax, ds:off_54B184
CODE:0050FCB6                 mov     edx, offset aSingleUserLice
; "Single-user License"
CODE:0050FCBB                 call    sub_40439C
CODE:0050FCC0                 jmp     short loc_50FCEF
CODE:0050FCC2 ; -------------------------------------------------
CODE:0050FCC2 loc_50FCC2:
CODE:0050FCC2                 mov     eax, ds:off_54B184
CODE:0050FCC7                 mov     edx, offset aUnlimitedUserL
; "Unlimited-user License"
CODE:0050FCCC                 call    sub_40439C
CODE:0050FCD1                 jmp     short loc_50FCEF
CODE:0050FCD3 ; -------------------------------------------------
CODE:0050FCD3 loc_50FCD3:
CODE:0050FCD3                 lea     edx, [ebp+var_18]
CODE:0050FCD6                 mov     eax, ebx
CODE:0050FCD8                 call    sub_4094E8
CODE:0050FCDD                 mov     edx, [ebp+var_18]
CODE:0050FCE0                 mov     eax, ds:off_54B184
CODE:0050FCE5                 mov     ecx, offset aUserLicense
; "-user License"
CODE:0050FCEA                 call    sub_404670
CODE:0050FCEF loc_50FCEF:
CODE:0050FCEF                 mov     eax, ds:off_54B184
CODE:0050FCF4                 cmp     dword ptr [eax], 0
CODE:0050FCF7                 jnz     short loc_50FD0A
CODE:0050FCF9                 mov     eax, ds:off_54B3B4
CODE:0050FCFE                 mov     edx, offset aUnregistered
; " [Unregistered]"
CODE:0050FD03                 call    sub_40439C
CODE:0050FD08                 jmp     short loc_50FD14
...

Интересного много, но логика работы не совсем понятна. Попробуем под отладчиком поставить точку остановка на адрес 0050FC75 и пройти код в пошаговом режиме. Я прокомментировал приведенный выше код на основании данных из отладчика, обратите внимание на условные переходы по адресу 0050FC93 и дальше. Ситуация потихоньку проясняется. Получается, что регистрационные данные передаются в процедуру по адресу 50F3B4, если она вернула ненулевое значение, то программа считается зарегистрированной. Возвращаемся к дизассемблеру:
CODE:0050F3B4                 push    ebp
CODE:0050F3B5                 mov     ebp, esp
CODE:0050F3B7                 mov     ecx, 4
CODE:0050F3BC loc_50F3BC:
CODE:0050F3BC                 push    0
CODE:0050F3BE                 push    0
CODE:0050F3C0                 dec     ecx
CODE:0050F3C1                 jnz     short loc_50F3BC
CODE:0050F3C3                 push    ecx
CODE:0050F3C4                 push    ebx
CODE:0050F3C5                 mov     [ebp+var_8], edx
CODE:0050F3C8                 mov     [ebp+var_4], eax
CODE:0050F3CB                 mov     eax, [ebp+var_4]
CODE:0050F3CE                 call    sub_404814
CODE:0050F3D3                 mov     eax, [ebp+var_8]
CODE:0050F3D6                 call    sub_404814
CODE:0050F3DB                 xor     eax, eax
CODE:0050F3DD                 push    ebp
CODE:0050F3DE                 push    offset loc_50F9B9
CODE:0050F3E3                 push    dword ptr fs:[eax]
CODE:0050F3E6                 mov     fs:[eax], esp
CODE:0050F3E9                 lea     eax, [ebp+var_14]
CODE:0050F3EC                 mov     edx, [ebp+var_8]
CODE:0050F3EF                 call    sub_4043E0
CODE:0050F3F4                 mov     eax, [ebp+var_14]
CODE:0050F3F7                 call    sub_404624
CODE:0050F3FC                 mov     ebx, eax
CODE:0050F3FE                 mov     eax, ds:off_54B4E8
CODE:0050F403                 mov     dword ptr [eax], 0FFFFFFFFh
; В регистр EAX занести ссылку на серийный номер
CODE:0050F409                 mov     eax, [ebp+var_14]
; Вызвать очередную функцию проверки
CODE:0050F40C                 call    sub_50EBD0
; Результат функции возвращается в регистрах EDX и EAX
CODE:0050F411                 mov     [ebp+var_10], eax
CODE:0050F414                 mov     [ebp+var_C], edx
; Если EDX не равен 0, то перейти к следующей проверке
CODE:0050F417                 cmp     [ebp+var_C], 0
CODE:0050F41B                 jnz     short loc_50F430
; Если EAX=2B77h, то результат всей функции будет 1
CODE:0050F41D                 cmp     [ebp+var_10], 2B77h
CODE:0050F424                 jnz     short loc_50F430
CODE:0050F426                 mov     ebx, 1
CODE:0050F42B                 jmp     loc_50F991
CODE:0050F430 ; ----------------------------------------------
CODE:0050F430 loc_50F430:
; Если EDX не равен 0, то перейти к следующей проверке
CODE:0050F430                 cmp     [ebp+var_C], 0
CODE:0050F434                 jnz     short loc_50F449
; Если EAX=2B81h, то результат всей функции будет 2
CODE:0050F436                 cmp     [ebp+var_10], 2B81h
CODE:0050F43D                 jnz     short loc_50F449
CODE:0050F43F                 mov     ebx, 2
CODE:0050F444                 jmp     loc_50F991
CODE:0050F449 ; ----------------------------------------------
CODE:0050F449 loc_50F449:
; Если EDX не равен 0, то перейти к следующей проверке
CODE:0050F449                 cmp     [ebp+var_C], 0
CODE:0050F44D                 jnz     short loc_50F462
; Если EAX=2EF2h, то результат всей функции будет 3
CODE:0050F44F                 cmp     [ebp+var_10], 2EF2h
CODE:0050F456                 jnz     short loc_50F462
CODE:0050F458                 mov     ebx, 3
CODE:0050F45D                 jmp     loc_50F991
CODE:0050F462 ; ----------------------------------------------
CODE:0050F462 loc_50F462:
; Если EDX не равен 0, то перейти к следующей проверке
CODE:0050F462                 cmp     [ebp+var_C], 0
CODE:0050F466                 jnz     short loc_50F47B
; Если EAX=2FA7h, то результат всей функции будет 5
CODE:0050F468                 cmp     [ebp+var_10], 2FA7h
CODE:0050F46F                 jnz     short loc_50F47B
CODE:0050F471                 mov     ebx, 5
CODE:0050F476                 jmp     loc_50F991
CODE:0050F47B ; ----------------------------------------------
CODE:0050F47B loc_50F47B:
; Если EDX не равен 0, то перейти к следующей проверке
CODE:0050F47B                 cmp     [ebp+var_C], 0
CODE:0050F47F                 jnz     short loc_50F494
; Если EAX=2D80h, то результат всей функции будет 10
CODE:0050F481                 cmp     [ebp+var_10], 2D80h
CODE:0050F488                 jnz     short loc_50F494
CODE:0050F48A                 mov     ebx, 0Ah
CODE:0050F48F                 jmp     loc_50F991
CODE:0050F494 ; ----------------------------------------------
CODE:0050F494 loc_50F494:
; Если EDX не равен 0, то перейти к следующей проверке
CODE:0050F494                 cmp     [ebp+var_C], 0
CODE:0050F498                 jnz     short loc_50F4AD
; Если EAX=2CE2h, то результат всей функции будет 20
CODE:0050F49A                 cmp     [ebp+var_10], 2CE2h
CODE:0050F4A1                 jnz     short loc_50F4AD
CODE:0050F4A3                 mov     ebx, 14h
CODE:0050F4A8                 jmp     loc_50F991
CODE:0050F4AD ; ----------------------------------------------
CODE:0050F4AD loc_50F4AD:
; Если EDX не равен 0, то перейти к следующей проверке
CODE:0050F4AD                 cmp     [ebp+var_C], 0
CODE:0050F4B1                 jnz     short loc_50F4C6
; Если EAX=388Ah, то результат всей функции будет 50
CODE:0050F4B3                 cmp     [ebp+var_10], 388Ah
CODE:0050F4BA                 jnz     short loc_50F4C6
CODE:0050F4BC                 mov     ebx, 32h
CODE:0050F4C1                 jmp     loc_50F991
CODE:0050F4C6 ; ----------------------------------------------
CODE:0050F4C6 loc_50F4C6:
; Если EDX не равен 0, то перейти к следующей проверке
CODE:0050F4C6                 cmp     [ebp+var_C], 0
CODE:0050F4CA                 jnz     short loc_50F4DF
; Если EAX=25C1h, то результат всей функции будет 270Fh
CODE:0050F4CC                 cmp     [ebp+var_10], 25C1h
CODE:0050F4D3                 jnz     short loc_50F4DF
CODE:0050F4D5                 mov     ebx, 270Fh
CODE:0050F4DA                 jmp     loc_50F991
...

Что мы имеем? На основе серийного номера проводятся расчеты, и, в зависимости от этого результата, возвращается количество лицензий. Как видно из кода, их может быть 1, 2, 3, 5, 10, 20, 50 и Unlimited. Осталось выяснить какие же это расчеты. Идем обратно в отладчик, при этом ставим точку останова на вызов функции 50EBD0. Расчет заветного числа выполняется следующим образом:
...
; Счетчик символов
CODE:0050EC1C                 mov     ecx, 1
CODE:0050EC21 loc_50EC21:
; Указатель на серийный номер
CODE:0050EC21                 mov     eax, [ebp+var_14]
; Записать в EAX символ из серийного номера на позиции ECX
CODE:0050EC24                 movzx   eax, byte ptr [eax+ecx-1]
; Умножить его код на значение ECX
CODE:0050EC29                 imul    ecx
CODE:0050EC2B                 cdq
; Прибавить к счетчику
CODE:0050EC2C                 add     eax, [ebp+var_20]
CODE:0050EC2F                 adc     edx, [ebp+var_1C]
CODE:0050EC32                 mov     [ebp+var_20], eax
CODE:0050EC35                 mov     [ebp+var_1C], edx
; Взять следующий символ
CODE:0050EC38                 inc     ecx
CODE:0050EC39                 dec     ebx
; Достигнут конец серийника?
CODE:0050EC3A                 jnz     short loc_50EC21
...

Ну вот и настал момент истины, мы добрались до алгоритма проверки серийного номера. Регистрационное имя любое, лишь бы не пустое, длина серийника тоже произвольная. Основная проверка заключается в следующем: код символа умножается на его позицию, причем счетчик начинается с 1. Результаты всех умножений суммируются. Затем проверяется итоговая сумма, она должна быть одним из следующих значений: 2B77h - 1 лицензия, 2B81h - 2 лицензии, 2EF2h - 3 лицензии, 2FA7h - 5 лицензий, 2D80h - 10 лицензий, 2CE2h - 20 лицензий, 388Ah - 50 лицензий, 25C1h - неограниченное количество лицензий. Например, валидным серийником будет строка MTXSMZTGYKSNFWQ. Судя по варезным релизам, константы в некоторых версиях меняются, но суть алгоритма остается неизменной. Кейген под любой тип лицензий пишется на основе простейшего брутфорса, теперь вы можете сделать это самостоятельно. Очередная защита повержена, спасибо автору за программу и за интересную головоломку.

Добавлено: 18 Сентября 2013 11:41:47 Добавил: Андрей Ковальчук

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


Скриншот программы Spread32

Spread32 - крохотный редактор электронных таблиц Excel. Несмотря на более чем скромный размер, он поддерживает около 300 функций, форматирование ячеек, позволяет вставлять диаграммы и графики, векторные и растровые рисунки, формулы. При этом не мусорит в системе и абсолютно портативный. Весь кайф от программы портит надоедливое окно с напоминанием, появляющееся при запуске программы и при файловых операциях. Оно отсутствует в платной версии программы, но ее можно получить, естественно, только после оплаты. Никаких других ограничений в демонстрационной версии нет. Судя по галерее на сайте, автор часто бывает в отъезде, так что лишний раз беспокоить его платежами не будем.

С офсайта можно скачать несколько вариантов программы, они отличаются только языками интерфейса и платформами. Я буду исследовать, естественно, русскоязычный вариант под Windows. Распаковываем архив, запускаем. Вылазит наг-окно, о котором я говорил:


Наг-окно

Поищем строчку из окна в файле. Он, кстати, ничем не упакован. Для ускорения процесса сразу же отправим файл на дизассемблирование.


Нехорошая строка найдена

Строчка нашлась, она хранится в юникоде. Переходим к листингу дизассемблера. На строку сообщения ссылается один единственный указатель:

.rdata:0053ED38 off_53ED38      dd offset aThisCopyOfSpre
; DATA XREF: DialogFunc+4C
.rdata:0053ED38 ; "This copy of Spread32 is Shareware.\nFor"...

На него, в свою очередь, ссылается следующий код:
; Выбор действия: инициализация окна, создание, закрытие
.text:00445380                 mov     eax, [esp+arg_4]
.text:00445384                 sub     esp, 34h
.text:00445387                 sub     eax, 110h
.text:0044538C                 jz      short loc_4453BA
.text:0044538E                 dec     eax
.text:0044538F                 jnz     short loc_44539A
.text:00445391                 cmp     [esp+34h+arg_8], 50Ch
.text:00445398                 jz      short loc_4453A2
.text:0044539A loc_44539A:
.text:0044539A                 xor     eax, eax
.text:0044539C                 add     esp, 34h
.text:0044539F                 retn    10h
.text:004453A2 ; -----------------------------------------
.text:004453A2 loc_4453A2:
.text:004453A2                 mov     eax, [esp+34h+hDlg]
.text:004453A6                 push    0               ; nResult
.text:004453A8                 push    eax             ; hDlg
.text:004453A9                 call    ds:EndDialog
.text:004453AF                 mov     eax, 1
.text:004453B4                 add     esp, 34h
.text:004453B7                 retn    10h
.text:004453BA ; -----------------------------------------
.text:004453BA loc_4453BA:
.text:004453BA                 mov     ecx, ds:lpCaption
.text:004453C0                 push    esi
.text:004453C1                 mov     esi, [esp+38h+hDlg]
.text:004453C5                 push    ecx             ; lpWideCharStr
.text:004453C6                 push    esi             ; hWnd
.text:004453C7                 call    sub_41A2E0
; Загрузить строку и записать ее в наг-окно
.text:004453CC                 mov     edx, ds:off_53ED38
.text:004453D2                 push    edx             ; lpWideCharStr
.text:004453D3                 push    50Bh            ; nIDDlgItem
.text:004453D8                 push    esi             ; hDlg
.text:004453D9                 call    sub_445420
.text:004453DE                 add     esp, 14h
.text:004453E1                 call    near ptr off_52B600+0D3h
.text:004453E6                 push    eax
.text:004453E7                 mov     eax, ds:off_53E604
.text:004453EC                 push    eax
.text:004453ED                 call    near ptr off_52B600+0D3h
.text:004453F2                 push    eax             ; Format
.text:004453F3                 lea     ecx, [esp+44h+WideCharStr]
.text:004453F7                 push    offset a8xS8x   ; "%8X\n%s\n%8X"
.text:004453FC                 push    ecx             ; String
.text:004453FD                 call    _swprintf
.text:00445402                 lea     edx, [esp+4Ch+WideCharStr]
.text:00445406                 push    edx             ; lpWideCharStr
.text:00445407                 push    50Ch            ; nIDDlgItem
.text:0044540C                 push    esi             ; hDlg
.text:0044540D                 call    sub_445420
.text:00445412                 add     esp, 20h
.text:00445415                 xor     eax, eax
.text:00445417                 pop     esi
.text:00445418                 add     esp, 34h
.text:0044541B                 retn    10h

Что мы имеем? Процедуру обработчика наг-окна, которая выполняет три действия: инициализация окна с записью в него заголовка и строки сообщения о шароварной копии, открытие окна и его закрытие. Просто пропатчить начало функции на RET не получится, наг-окно так и будет висеть без текста, но к тому же без возможности его закрытия. Значит надо изменить обработчик, чтобы при любом действии вызывалась функция закрытия EndDialog. Для этого два условных перехода по адресам 0044538C и 0044538F забиваем NOP'ами, а третий условный переход по адресу 00445398 заменяем на безусловный JMP. Сохраняем изменения, запускаем программу. Надоедливое окно при запуске исчезло. Попробуем теперь выполнить какие-нибудь файловые операции, например, открыть файл и сохранить его под другим именем. И тоже не появляется никаких нежелательных окон. Цель достигнута. При желании можно даже сделать универсальный патч для всех будущих версий и различных языков.

Добавлено: 18 Сентября 2013 11:36:36 Добавил: Андрей Ковальчук