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