Исследование защиты программы AXE Advanced Hex Editor


Скриншот программы 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
; и так далее, там такого чудо-кода еще много
...

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


Строка сообщения о модифицированном файле

Спасибо автору за хороший шестнадцатеричный редактор и за интересную защиту. Когда надоедает щелкать однотипные защиты, такие компьютерные ребусы приходятся как нельзя кстати.
Добавлено: 13 Сентября 2013 04:33:52 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...