
Скриншот программы 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.

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