
Скриншот программы AXE Advanced Hex Editor
AXE Advanced Hex Editor - продвинутый шестнадцатеричный редактор с множеством функций, поддержкой скриптового языка, разбором структур, возможностью работать с файлами неограниченного размера и еще целой кучей всяких приятных плюшек. Разработка программы уже давно прекращена, офсайт тоже не отвечает, оплатить лицензию негде (если вы вдруг решили это сделать). Значит мы можем с чистой совестью поковыряться в его бинарных потрошках.
Скачиваем
дистрибутив, устанавливаем. Главный исполняемый файл ничем не защищен и не упакован, отправляем его в дизассемблер. А пока посмотрим на систему регистрации и триальности. На ввод неправильных регистрационных данных программа реагирует вот таким безграмотным сообщением:

Сообщение о неправильной регистрации
Попробуем найти эту строчку в исполняемом файле. Ничего не найдено. Ни в ASCII, ни в юникоде, никак. Ладно, план "А" не сработал, переходим к плану "Б". Посмотрим, что у нас находится в ресурсах:

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

ID элемента диалогового окна
Тут логика простая. Если есть диалоговые окна и известны индексы их элементов, то в исполняемом файле должны быть видны какие-нибудь манипуляции с ними. Конкретно нас интересует установка регистрационного имени в окно "О программе". Для триальной версии это будет строка "Trial", для зарегистрированной, очевидно, должно быть имя пользователя, адрес email или что-то подобное. Индекс этого элемента диалогового окна - 1120 в десятичной или 460h в шестнадцатеричной системе счисления. Поищем в дизассемблере участок кода, где используется это значение и где неподалеку есть функции установки текста в диалоговом окне. Второе вхождение по строке "460h" наводит нас на такой код:
.text:00431794 push edi
.text:00431795 mov byte ptr [esp+0D0h+var_4], 6
.text:0043179D push ebp
.text:0043179E call ?AfxGetApp@@YGPAVCWinApp@@XZ
.text:004317A3 mov ecx, eax
; Вызвать функцию проверки
.text:004317A5 call sub_42F620
; Если она вернула EAX=0, то переход
.text:004317AA cmp eax, ebx
.text:004317AC push edi ; int
.text:004317AD jz short loc_431812
; Иначе действия с элементами диалогового окна
.text:004317AF lea ecx, [esp+0CCh+lpString]
.text:004317B3 push offset aD_1 ; "%d"
.text:004317B8 push ecx ; int
.text:004317B9 call sub_4B0144
.text:004317BE mov edx, [esp+0D4h+lpString]
.text:004317C2 add esp, 0Ch
.text:004317C5 mov ecx, esi
.text:004317C7 push edx ; lpString
; ID элемента диалогового окна "Trial Days"
.text:004317C8 push 45Eh ; nIDDlgItem
.text:004317CD call sub_4B7BD2
.text:004317D2 mov ecx, eax
.text:004317D4 call sub_4B7D49
.text:004317D9 mov eax, [esp+0C8h+var_B0]
.text:004317DD lea ecx, [esp+0C8h+lpString]
.text:004317E1 push eax ; int
.text:004317E2 push offset aS ; "%s"
.text:004317E7 push ecx ; int
.text:004317E8 call sub_4B0144
.text:004317ED mov edx, [esp+0D4h+lpString]
.text:004317F1 add esp, 0Ch
.text:004317F4 mov ecx, esi
.text:004317F6 push edx ; lpString
; ID элемента диалогового окна "Name"
.text:004317F7 push 460h ; nIDDlgItem
; В этой функции вызывается GetDlgItem
.text:004317FC call sub_4B7BD2
.text:00431801 mov ecx, eax
; В этой функции вызывается SetWindowTextA
.text:00431803 call sub_4B7D49
.text:00431808 lea eax, [esp+0C8h+var_88]
.text:0043180C push eax ; int
.text:0043180D jmp loc_4318DF
.text:00431812 ; -------------------------------------------------
; Триальная ветка алгоритма
.text:00431812 loc_431812:
.text:00431812 lea ecx, [esp+0CCh+Format]
.text:00431816 lea edx, [esp+0CCh+lpString]
.text:0043181A push ecx ; Format
.text:0043181B push edx ; int
.text:0043181C call sub_4B0144
.text:00431821 mov eax, [esp+0D4h+lpString]
.text:00431825 add esp, 0Ch
.text:00431828 mov ecx, esi
.text:0043182A push eax ; lpString
.text:0043182B push 45Eh ; nIDDlgItem
.text:00431830 call sub_4B7BD2
.text:00431835 mov ecx, eax
.text:00431837 call sub_4B7D49
.text:0043183C mov ecx, [esp+0C8h+var_B0]
.text:00431840 lea edx, [esp+0C8h+Format]
.text:00431844 push ecx ; int
.text:00431845 lea eax, [esp+0CCh+lpString]
.text:00431849 push edx ; Format
.text:0043184A push eax ; int
.text:0043184B call sub_4B0144
.text:00431850 mov ecx, [esp+0D4h+lpString]
.text:00431854 add esp, 0Ch
.text:00431857 push ecx ; lpString
.text:00431858 push 460h ; nIDDlgItem
.text:0043185D mov ecx, esi
.text:0043185F call sub_4B7BD2
.text:00431864 mov ecx, eax
.text:00431866 call sub_4B7D49
Вот теперь нам попалась реальная зацепка. По адресу 004317A5 вызывается функция проверки, а по ее результатам в диалоговом окне "О программе" выставляются те или иные значения. Чтобы программа считала себя зарегистрированной, функция проверки должна вернуть в EAX значение отличное от нуля. Переходим к самой функции проверки. Параллельно с анализом в дизассемблере, ее лучше пройти под отладчиком, так будет гораздо нагляднее.
.text:0042F620 mov eax, large fs:0
.text:0042F626 push 0FFFFFFFFh
.text:0042F628 push offset unknown_libname_309
.text:0042F62D push eax
.text:0042F62E mov large fs:0, esp
.text:0042F635 push ebx
.text:0042F636 push esi
.text:0042F637 push edi
.text:0042F638 mov eax, [esp+18h+arg_8]
.text:0042F63C mov [esp+18h+var_4], 0
.text:0042F644 mov ecx, [eax-8]
; Регистрационное имя не должно быть пустым
.text:0042F647 test ecx, ecx
.text:0042F649 jz loc_42F70E
.text:0042F64F mov esi, [esp+18h+arg_4]
; Регистрационный код не должен быть пустым
.text:0042F653 test esi, esi
.text:0042F655 jz loc_42F70E
; Проверка регистрационного кода на значение "10021"
.text:0042F65B cmp esi, 2725h
.text:0042F661 jnz short loc_42F68D
; Если он равен этому значению, то вернуть EAX = 1, то есть "зарегистрировано"
.text:0042F663 lea ecx, [esp+18h+arg_8]
.text:0042F667 mov [esp+18h+var_4], 0FFFFFFFFh
.text:0042F66F call sub_4B3861
.text:0042F674 mov eax, 1
.text:0042F679 mov ecx, [esp+18h+var_C]
.text:0042F67D mov large fs:0, ecx
.text:0042F684 pop edi
.text:0042F685 pop esi
.text:0042F686 pop ebx
.text:0042F687 add esp, 0Ch
.text:0042F68A retn 0Ch
Это первая часть проверки. Во-первых мы узнаем, что серийный номер преобразуется в числовое значение. Дальше здесь проверяется, чтобы имя и введенный серийный номер не были пустыми, а после этого введенный серийник сравнивается со значением "10021", и если совпадает, то программа считает себя зарегистрированной. То есть серийник "10021" является универсальным для любого регистрационного имени. После всего ранее увиденного это мне больше напоминает ситуацию когда "ворота поставили, а забор забыли". Тем не менее, программа действительно принимает этот регистрационный код.

Программа успешно зарегистрирована
Что-то меня тут все-таки смущает. Посмотрим лучше что там находится дальше. Эта ветка алгоритма активируется, когда введено регистрационное имя и код, который не является универсальным.
.text:0042F68D loc_42F68D:
; Проверить длину введенного регистрационного имени
.text:0042F68D cmp ecx, 4
.text:0042F690 jge short loc_42F6A4
; Если она меньше 4, то дописать в конец к имени строку "abcd"
.text:0042F692 push offset aAbcd ; "abcd"
.text:0042F697 lea ecx, [esp+1Ch+arg_8]
.text:0042F69B call sub_4B3C3D
.text:0042F6A0 mov eax, [esp+18h+arg_8]
.text:0042F6A4 loc_42F6A4:
; Длина регистрационного имени
.text:0042F6A4 mov ecx, [eax-8]
; BL = третий символ регистрационного имени
.text:0042F6A7 mov bl, [eax+2]
; DL = первый символ регистрационного имени
.text:0042F6AA mov dl, [eax]
; EAX = последний символ регистрационного имени
.text:0042F6AC movsx eax, byte ptr [ecx+eax-1]
; EDI = третий символ регистрационного имени
.text:0042F6B1 movsx edi, bl
; EDX = первый символ регистрационного имени
.text:0042F6B4 movsx edx, dl
.text:0042F6B7 add eax, edi
.text:0042F6B9 add eax, edx
.text:0042F6BB imul eax, ecx
; Сложить три символа и перемножить их на длину строки
.text:0042F6BE and eax, 8FFFFFFFh
.text:0042F6C3 jns short loc_42F6CC
; Для отрицательных чисел дополнительная обработка
.text:0042F6C5 dec eax
.text:0042F6C6 or eax, 0F0000000h
.text:0042F6CB inc eax
.text:0042F6CC loc_42F6CC:
; Введенный серийный номер больше 10000000h?
.text:0042F6CC cmp esi, 10000000h
.text:0042F6D2 jbe short loc_42F6D9
; Да, добавить к проверочному значению 10000000h
.text:0042F6D4 add eax, 10000000h
.text:0042F6D9 loc_42F6D9:
; ПоXORить проверочное значение с константой 39A13D7h
.text:0042F6D9 xor eax, 39A13D7h
.text:0042F6DE xor ecx, ecx
; Сравнить введенный серийный номер и проверочное значение
.text:0042F6E0 cmp esi, eax
.text:0042F6E2 mov [esp+18h+var_4], 0FFFFFFFFh
.text:0042F6EA setz cl
.text:0042F6ED mov esi, ecx
.text:0042F6EF lea ecx, [esp+18h+arg_8]
.text:0042F6F3 call sub_4B3861
.text:0042F6F8 mov eax, esi
.text:0042F6FA mov ecx, [esp+18h+var_C]
.text:0042F6FE mov large fs:0, ecx
.text:0042F705 pop edi
.text:0042F706 pop esi
.text:0042F707 pop ebx
.text:0042F708 add esp, 0Ch
.text:0042F70B retn 0Ch
Вот теперь это выглядит гораздо правдоподобнее. Мы выяснили формулу вычисления правильного регистрационного кода, ее без труда можно оформить в кейген или сниффер. Так, например, для имени ManHunter / PCL регистрационный код будет 60429527.

Программа успешно зарегистрирована
Если посмотреть по перекрестным ссылкам и другие места, откуда вызывается функция проверки серийного номера, то можно разгадать загадку, почему не сработал план "А". После неудачной проверки вот таким мудреным образом расшифровывается и складывается по отдельным символам строка сообщения "This isnot a valid registration code". Неудивительно, что среди этой кодо-вермишели автор пропустил пробел между двумя словами.
.text:0043F5C8 call sub_42F620
.text:0043F5CD cmp eax, ebx
.text:0043F5CF jz loc_43F8FB
.text:0043F5D5 mov al, byte_514B64
.text:0043F5DA mov cl, byte_514B68
.text:0043F5E0 mov dl, byte_514B6C
.text:0043F5E6 xor al, 0AAh
.text:0043F5E8 xor cl, 55h
.text:0043F5EB mov [esp+24h], al
.text:0043F5EF mov al, byte_514B70
.text:0043F5F4 mov [esp+25h], cl
.text:0043F5F8 mov cl, byte_514B74
.text:0043F5FE xor dl, 66h
.text:0043F601 xor al, 0CCh
.text:0043F603 xor cl, 77h
.text:0043F606 mov [esp+26h], dl
.text:0043F60A mov dl, byte_514B78
.text:0043F610 mov [esp+27h], al
.text:0043F614 mov al, byte_514B7C
.text:0043F619 mov [esp+28h], cl
.text:0043F61D mov cl, byte_514B80
.text:0043F623 xor dl, 55h
.text:0043F626 xor al, 65h
.text:0043F628 xor cl, 0ACh
.text:0043F62B mov [esp+29h], dl
.text:0043F62F mov dl, byte_514B84
.text:0043F635 mov [esp+2Ah], al
.text:0043F639 mov al, byte_514B88
.text:0043F63E mov [esp+2Bh], cl
.text:0043F642 mov cl, byte_514B90
.text:0043F648 xor dl, 35h
.text:0043F64B xor al, 71h
.text:0043F64D xor cl, 0AAh
.text:0043F650 mov [esp+2Ch], dl
.text:0043F654 mov dl, byte_514B94
.text:0043F65A mov [esp+2Dh], al
.text:0043F65E mov al, byte_514B98
.text:0043F663 mov [esp+30h], cl
.text:0043F667 mov cl, byte_514B9C
.text:0043F66D xor dl, 55h
; и так далее, там такого чудо-кода еще много
...
Кстати, для желающих просто пропатчить функцию проверки регистрации, у автора заготовлена еще вот такая подлянка. Да, программа после патча регистрируется любыми данными, но вот условия срабатывания этой проверки я не нашел. По всей видимости, ожидать ее можно или при каком-нибудь определенном действии, или при неудачном схождении звезд на небе. В любом случае, проверка целостности файла предусмотрена.

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