Исследование защиты программы 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 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...