Немного контекста

Эта заключительная часть данной серии (ссылка на первую часть) должна быть выйти раньше, но из-за многих факторов (об этом будет в конце статьи, если кому интересно) этого не произошло. Но звёзды сошлись и результаты экспериментов собраны здесь.

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

Disclaimer: Данная заметка написана в ознакомительных целях и не является руководством к действиям. Статья в этот раз написана таким образом, что в ней описываются мои мысли и шаги, как я к этому пришёл. Мне кажется, что это интереснее и позволяет перенять логику решения подобных задач.

После первой части в течение этих 21 месяца много людей писали в личные сообщения в Tg/Вк и очень просили рассмотреть свежую версию 7.4.2 (с новым именем WinFX3Net), которая применяется для подключения к свежим версиям панелей ПС. Использовав старый подход, я думал, что сделаю быстро, однако при работе таймера приложение падало в неизвестную ошибку и приложение просто закрывалось. Я бился, бился пару дней, а потом забил на это дело. Где к марте 2025 года опять на меня вышли люди (Питеру привет!), но в отличии от остальных, у них была возможность получить на руки живой HASP ключ. Попробовали все методы, что смогли загуглить, но ничего полезного не выявил. Для поиска идей зарегистрировался на ru-board, где в ветках андеграунд очень много интересного. Форумчанам спасибо за направление, но опять мне из этого ничего не помогло. Опять на всё это я забил, мотивации никакой не было. Но вот, в недавние ноябрьские праздники и выходные я смог выделить время для работы за ПК и с новыми силами, опытом и помощью ИИ получилось со всем этим разобраться. Далее все шаги пойдут в том порядке, как я к ним приступал.

Файл лицензии WinFXNet.lic

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

В предыдущей части я указывал, как происходит процесс расшифровки файла в памяти программы. За это отвечает функция по адресу 00595d28:

Assembler
 00595D28    push        ebx
 00595D29    push        esi
 00595D2A    mov         esi,edx
 00595D2C    movzx       edx,byte ptr [esi+80]
 00595D33    mov         cl,7E
 00595D35    lea         eax,[esi+82]
 00595D3B    movzx       ebx,dl
 00595D3E    movzx       ebx,byte ptr [esi+ebx]
 00595D42    not         bl
 00595D44    sub         byte ptr [eax],bl
 00595D46    inc         edx
 00595D47    and         dl,7F
 00595D4A    inc         eax
 00595D4B    dec         cl
>00595D4D    jne         00595D3B
 00595D4F    mov         al,1
 00595D51    pop         esi
 00595D52    pop         ebx
 00595D53    ret

Это функция, которая выполняет операцию над массивом байтов:

  1. Берет начальный индекс из [esi+80]

  2. Обрабатывает 126 байтов (7Eh), начиная с esi+82

  3. Для каждого байта:

    • Берет байт из [esi + индекс]

    • Инвертирует его биты (not bl) (на это обращаем внимание)

    • Вычитает из текущего элемента массива

    • Увеличивает индекс по модулю 128 (and dl,7F)

Операция not bl (инвертирование битов) в языке C эквивалентна: ~byte = 255 - byte. Это стало поводом использовать для создаваемых программ именно C, а не другие языки. Я пытался подойти к этой задаче и через Delphi, и через Python, и даже .Net, но понял, что быстрее это сделать на C, но об этом будет отдельный пункт.

Вернемся к нашему алгоритму. По сути, шифрование и расшифрование будет отличаться только одним знаком. Посмотрим на код в C:

Функция де/шифрования
void encrypt_decrypt_data(unsigned char* data) {
    unsigned char index = data[128]; // начальный индекс из a2+128
    unsigned char* current_byte = &data[130]; // указатель на a2+130
    unsigned char counter = 126; //mov cl,7E - 7Eh это 126 байтов 

    do {
        // Получаем байт ключа из data[index]
        unsigned char key_byte = data[index];
        // Основная операция преобразования (как в ассемблере)
        unsigned char inverted_key = ~key_byte; // not bl
      
        // Для функции дешифровки вычитаем
        *current_byte -= inverted_key; // sub byte ptr [eax],bl
      
        // А для функции шифрования наоборот - прибавляем
        *current_byte += inverted_key;
      
        // Обновляем индекс (циклический 0-127)
        index = (index + 1) & 0x7F;
        // Переходим к следующему байту
        current_byte++;
        // Уменьшаем наш счётчик
        counter--;
    } while (counter);
}

Думаю, комментарии в коде всё наглядно объясняют. Накидываю небольшую обвертку для чтения файла, перекладку в память и пошёл тестировать. Так как все символы выводятся в ASCII, то спец символы я решил откидывать и на выводе заменять их точками (это было ошибкой, которая меня сбила). У меня было 3 файла с истекшими лицензиями и можно было понять структуру расшифрованных строк.

Для примера, полученный вывод уже патченного мной файла:

Полученная строка:
.......SE Fire & Security Oy....2Bizonozubr..Oleg Bezverkhiy........................F01-9999-99999.20991212...................

Итак, первыми идут 7 спецсимволов, далее имя дистрибьютора ключа (есть и от ESMI), далее идет имя компании, которое начинается с 2 (это важно), далее через два спецсимвола идет имя пользователя, через n символов идёт serial number и через один спецсимвол идёт дата действия лицензии в формате YYYYMMDD. Я попробовал на всех трех файлах - дешифратор работал отменно. У всех файлов было одинаковое при расшифровке расположение символов по дистрибьютору, двойки перед началом имени компании, номера лицензии и даты, при этом длина строки ровно 127. Структура повторяема - это уже хорошо, плюс первые 126 байт у всех одинаковые - осталось разобраться только с остальными 386 байтами.

При этом, дистрибьютор у всех одинаковый, то и первые байты расшифровываемой строки должны были совпадать, но на деле они различаются. И тут я поник - получается, если я не знаю формирования первых байт (с 127ого, от которого идёт весь алгоритм), то и генератор написать я не могу.

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

Полученный код
// Создаем рабочую копию
    unsigned char* work_data = (unsigned char*)malloc(file_size);
    memcpy(work_data, file_data, file_size);
    
    // ДЕШИФРУЕМ данные чтобы найти дату
    decrypt_data(work_data);
    
    // Ищем позицию старой даты, либо сразу прописываем число
    int date_position = find_date_position(work_data, old_date);
    
    if (date_position == -1) {
        printf("Дата '%s' не найдена в файле!\n", old_date);
        free(file_data);
        free(work_data);
        return;
    }
    
    // Заменяем дату в РАСШИФРОВАННЫХ данных
    for (int i = 0; i < strlen(new_date); i++) {
        if (date_position + i < 130 + 126) {
            work_data[date_position + i] = new_date[i];
        }
    }
    
    // ШИФРУЕМ данные обратно
    encrypt_data(work_data);
    
    // Копируем патченные данные обратно в исходный буфер
    memcpy(file_data + 130, work_data + 130, 126);
    
    // Сохраняем патченный файл
    if (write_file(output_file, file_data, file_size))
      {...}

Запускаю программу, подкидываю ей файл, получаю новый на выходе, подкидываю в WinFXNet и думаю вот он - успех. Не тут-то было - выходит ошибка, что файл битый! Полученный файл вновь кидаю самописному расшифровщику - строка получается, проблем нет.

Я накидывал разные теории, как может считаться контрольная сумма, но раз программа понимает, что файл битый, значит есть дополнительная проверка на контрольную сумму. В IDR смотрю ссылки на текст ошибки в файле FXStartUp.pas, который отвечает за форму, открывающейся при старте программы.

Нашлось 4 строки
Нашлось 4 строки

Загружаем программу в отладчик, ставлю на эти адреса брейкпоинты, подкидываю свой сломанный файл и смотрю, где сработает - это оказывается адрес 00596246:

00596246 lea  edx,[edx*8+6829D8];^'The license information file could not be found! Do you want to lo...

Данная строка находится в функции 005960D8. Смотрим на код функции с самого начала и прогоняем в отладчике по каждому пункту (или делаем это сами по тексту кода).

005960D8
//function sub_005960D8(?:?; ?:UnicodeString):?;
 005960D8    push        ebp
 005960D9    mov         ebp,esp
 005960DB    add         esp,0FFFFFFD8
 005960DE    push        ebx
 005960DF    push        esi
 005960E0    push        edi
 005960E1    xor         ecx,ecx
 005960E3    mov         dword ptr [ebp-18],ecx
 005960E6    mov         dword ptr [ebp-14],ecx
 005960E9    mov         dword ptr [ebp-10],ecx
 005960EC    mov         dword ptr [ebp-4],ecx
 005960EF    mov         dword ptr [ebp-8],edx
 005960F2    mov         esi,eax
 005960F4    xor         eax,eax
 005960F6    push        ebp
 005960F7    push        5962B8
 005960FC    push        dword ptr fs:[eax]
 005960FF    mov         dword ptr fs:[eax],esp
 00596102    xor         ebx,ebx
 00596104    mov         byte ptr [ebp-9],0
 00596108    mov         eax,[00709114];gvar_00709114:TStartUpForm
 0059610D    mov         eax,dword ptr [eax+3A0]
 00596113    mov         edx,dword ptr [eax]
 00596115    call        dword ptr [edx+40]
 00596118    test        al,al
>0059611A    je          0059627C
 00596120    lea         edx,[ebp-10]
 00596123    mov         eax,[00709114];gvar_00709114:TStartUpForm
 00596128    mov         eax,dword ptr [eax+3A0]
 0059612E    call        TOpenDialog.GetFileName
 00596133    mov         edx,dword ptr [ebp-10]
 00596136    mov         eax,dword ptr [ebp-8]
 00596139    call        @UStrAsg
 0059613E    lea         eax,[esi+410]
 00596144    push        eax
 00596145    lea         ecx,[esi+204]
 0059614B    mov         edx,dword ptr [ebp-8]
 0059614E    mov         edx,dword ptr [edx]
 00596150    mov         eax,esi
 00596152    call        00595EF4
 00596157    mov         edx,eax
 00596159    sub         edx,1
>0059615C    jb          00596167
 0059615E    sub         edx,4
>00596161    jne         00596233
 00596167    mov         eax,[00709114];gvar_00709114:TStartUpForm
 0059616C    mov         eax,dword ptr [eax+3A4]
 00596172    call        0058FCA8
 00596177    mov         eax,[0070911C];gvar_0070911C:TLicenseFile
 0059617C    fsubr       qword ptr [eax+410]
 00596182    call        @TRUNC
 00596187    mov         edi,eax
 00596189    lea         eax,[ebp-4]
 0059618C    push        eax
 0059618D    lea         eax,[ebp-14]
 00596190    mov         edx,dword ptr ds:[70911C];gvar_0070911C:TLicenseFile
 00596196    add         edx,2A6
 0059619C    mov         ecx,0
 005961A1    call        @LStrFromString
 005961A6    mov         eax,dword ptr [ebp-14]
 005961A9    mov         ecx,5962D8;', '
 005961AE    mov         edx,5962E8;#13+#10
 005961B3    call        0059137C
 005961B8    lea         eax,[ebp-18]
 005961BB    push        eax
 005961BC    mov         dword ptr [ebp-28],edi
 005961BF    mov         byte ptr [ebp-24],0
 005961C3    mov         eax,dword ptr [ebp-4]
 005961C6    mov         dword ptr [ebp-20],eax
 005961C9    mov         byte ptr [ebp-1C],0B
 005961CD    lea         edx,[ebp-28]
 005961D0    mov         eax,[00685658];^gvar_0068B320
 005961D5    movzx       eax,byte ptr [eax]
 005961D8    mov         ecx,eax
 005961DA    add         eax,eax
 005961DC    add         eax,eax
 005961DE    add         eax,eax
 005961E0    sub         eax,ecx
 005961E2    mov         eax,dword ptr [eax*8+6829EC];^'This license information file expires in %d days! Licens...
 005961E9    mov         ecx,1
 005961EE    call        Format
 005961F3    mov         eax,dword ptr [ebp-18]
 005961F6    push        0
 005961F8    push        0FF
 005961FA    push        0FF
 005961FC    push        0
 005961FE    movzx       ecx,word ptr ds:[5962EC];0x23 gvar_005962EC
 00596205    mov         dl,3
 00596207    call        MessageDlgPosHelp
 0059620C    sub         eax,4
>0059620F    je          00596223
 00596211    sub         eax,2
>00596214    je          0059621B
 00596216    dec         eax
>00596217    je          0059622B
>00596219    jmp         00596282
 0059621B    xor         ebx,ebx
 0059621D    mov         byte ptr [ebp-9],1
>00596221    jmp         00596282
 00596223    mov         bl,1
 00596225    mov         byte ptr [ebp-9],0
>00596229    jmp         00596282
 0059622B    xor         ebx,ebx
 0059622D    mov         byte ptr [ebp-9],0
>00596231    jmp         00596282
 00596233    mov         edx,dword ptr ds:[685658];^gvar_0068B320
 00596239    movzx       edx,byte ptr [edx]
 0059623C    mov         ecx,edx
 0059623E    add         edx,edx
 00596240    add         edx,edx
 00596242    add         edx,edx
 00596244    sub         edx,ecx
 00596246    lea         edx,[edx*8+6829D8];^'The license information file could not be found! Do you want to lo...
 0059624D    mov         eax,dword ptr [edx+eax*4-4]

Смотрю вызовы функций (первые 2 отвечают за открытия файла) и дохожу до адреса 00596152 - call 00595EF4. Бинго, первичный анализ функции 00595EF4 показывает хитрую структуру работы с файлом.

00595EF4
 00595EF4    push        ebp
 00595EF5    mov         ebp,esp
 00595EF7    add         esp,0FFFFFEEC
 00595EFD    push        ebx
 00595EFE    push        esi
 00595EFF    push        edi
 00595F00    xor         ebx,ebx
 00595F02    mov         dword ptr [ebp-110],ebx
 00595F08    mov         dword ptr [ebp-114],ebx
 00595F0E    mov         dword ptr [ebp-108],ebx
 00595F14    mov         dword ptr [ebp-10C],ebx
 00595F1A    mov         esi,ecx
 00595F1C    mov         dword ptr [ebp-4],edx
 00595F1F    mov         edi,eax
 00595F21    mov         eax,dword ptr [ebp-4]
 00595F24    call        @UStrAddRef
 00595F29    xor         eax,eax
 00595F2B    push        ebp
 00595F2C    push        5960C1
 00595F31    push        dword ptr fs:[eax]
 00595F34    mov         dword ptr fs:[eax],esp
 00595F37    xor         ebx,ebx
 00595F39    mov         ecx,esi
 00595F3B    mov         edx,dword ptr [ebp-4]
 00595F3E    mov         eax,edi
 00595F40    call        00595C58
 00595F45    test        al,al
>00595F47    jne         00595F53
 00595F49    mov         ebx,1
>00595F4E    jmp         00595FEE
 00595F53    mov         edx,esi
 00595F55    mov         eax,edi
 00595F57    call        00595CF4
 00595F5C    test        al,al
>00595F5E    jne         00595F6A
 00595F60    mov         ebx,2
>00595F65    jmp         00595FEE
 00595F6A    mov         edx,esi
 00595F6C    mov         eax,edi
 00595F6E    call        00595D28
 00595F73    test        al,al
>00595F75    jne         00595F7E
 00595F77    mov         ebx,2
>00595F7C    jmp         00595FEE
 00595F7E    mov         ecx,dword ptr [ebp+8]
 00595F81    mov         edx,esi
 00595F83    mov         eax,edi
 00595F85    call        00595D54
 00595F8A    test        al,al
>00595F8C    jne         00595F95
 00595F8E    mov         ebx,2
>00595F93    jmp         00595FEE
 00595F95    cmp         word ptr [esi+84],3
>00595F9D    je          00595FA6
 00595F9F    mov         ebx,3
>00595FA4    jmp         00595FEE
 00595FA6    mov         eax,[00709114];gvar_00709114:TStartUpForm
 00595FAB    mov         eax,dword ptr [eax+3A4]
 00595FB1    call        0058FCA8
 00595FB6    mov         eax,dword ptr [ebp+8]
 00595FB9    fcomp       qword ptr [eax]
 00595FBB    wait
 00595FBC    fnstsw      al
 00595FBE    sahf
>00595FBF    jbe         00595FC8
 00595FC1    mov         ebx,4
>00595FC6    jmp         00595FEE
 00595FC8    mov         eax,[00709114];gvar_00709114:TStartUpForm
 00595FCD    mov         eax,dword ptr [eax+3A4]
 00595FD3    call        0058FCA8
 00595FD8    mov         eax,dword ptr [ebp+8]
 00595FDB    fsubr       qword ptr [eax]
 00595FDD    fcomp       dword ptr ds:[5960D4];60:Single
 00595FE3    wait
 00595FE4    fnstsw      al
 00595FE6    sahf
>00595FE7    jae         00595FEE
 00595FE9    mov         ebx,5
 00595FEE    test        ebx,ebx
>00595FF0    je          00595FFB
 00595FF2    cmp         ebx,5
>00595FF5    jne         0059609B
 00595FFB    lea         eax,[ebp-10C]
 00596001    lea         edx,[esi+0A2]
 00596007    mov         ecx,0
 0059600C    call        @LStrFromString
 00596011    mov         eax,dword ptr [ebp-10C]
 00596017    lea         edx,[ebp-108]
 0059601D    call        005912B4
 00596022    mov         edx,dword ptr [ebp-108]
 00596028    lea         eax,[ebp-104]
 0059602E    mov         ecx,0FF
 00596033    call        @LStrToString
 00596038    lea         edx,[ebp-104]
 0059603E    lea         eax,[esi+0A2]
 00596044    mov         cl,32
 00596046    call        @PStrNCpy
 0059604B    lea         eax,[ebp-114]
 00596051    lea         edx,[esi+88]
 00596057    mov         ecx,0
 0059605C    call        @LStrFromString
 00596061    mov         eax,dword ptr [ebp-114]
 00596067    lea         edx,[ebp-110]
 0059606D    call        005912B4
 00596072    mov         edx,dword ptr [ebp-110]
 00596078    lea         eax,[ebp-104]
 0059607E    mov         ecx,0FF
 00596083    call        @LStrToString
 00596088    lea         edx,[ebp-104]
 0059608E    lea         eax,[esi+88]
 00596094    mov         cl,19
 00596096    call        @PStrNCpy
 0059609B    xor         eax,eax
 0059609D    pop         edx
 0059609E    pop         ecx
 0059609F    pop         ecx
 005960A0    mov         dword ptr fs:[eax],edx
 005960A3    push        5960C8
 005960A8    lea         eax,[ebp-114]
 005960AE    mov         edx,4
 005960B3    call        @LStrArrayClr
 005960B8    lea         eax,[ebp-4]
 005960BB    call        @UStrClr
 005960C0    ret
>005960C1    jmp         @HandleFinally
>005960C6    jmp         005960A8
 005960C8    mov         eax,ebx
 005960CA    pop         edi
 005960CB    pop         esi
 005960CC    pop         ebx
 005960CD    mov         esp,ebp
 005960CF    pop         ebp
 005960D0    ret         4

Функция создает стандартный стековый фрейм и выделяет место для локальных переменных. Выполняются следующие проверки:

  1. Вызов функции по адресу 00595C58 - первая проверка. Если проверка не проходит, устанавливается ebx = 1

  2. Вызов функции по адресу 00595CF4 - вторая проверка. Если не проходит, ebx = 2

  3. Вызов функции по адресу 00595D28 - третья проверка. Если не проходит, ebx = 2

  4. Вызов функции по адресу 00595D54 - четвертая проверка. Если не проходит, ebx = 2

  5. Проверка значения по смещению +84 от esi (cmp word ptr [esi+84],3). Если не равно 3, ebx = 3

  6. Ну и дальше проверки, которые нас не интересуют. Бегло можно увидеть, что в регистре ebx должен быть ноль.

Смотрю первую функцию - не то, а вот на второй функции 00595CF4 нашлось то, что я искал.

00595CF4
 00595CF4    push        ebx
 00595CF5    push        ecx
 00595CF6    mov         byte ptr [esp],0
 00595CFA    mov         cl,80
 00595CFC    lea         eax,[edx+180]
 00595D02    movzx       ebx,byte ptr [eax-180]
 00595D09    add         bl,byte ptr [eax-100]
 00595D0F    add         bl,byte ptr [eax-80]
 00595D12    cmp         bl,byte ptr [eax]
>00595D14    jne         00595D1F
 00595D16    inc         eax
 00595D17    dec         cl
>00595D19    jne         00595D02
 00595D1B    mov         byte ptr [esp],1
 00595D1F    movzx       eax,byte ptr [esp]
 00595D23    pop         edx
 00595D24    pop         ebx
 00595D25    ret

Это гениально просто - функция проверяет, соответствует ли сумма первых трех блоков данных четвертому блоку, причем каждый блок по 128 байт (512/4=128). В регистр EDX передается указатель на начало данных, а блоки данных расположены по смещениям:

  • [EDX+0x000] - блок 1

  • [EDX+0x080] - блок 2

  • [EDX+0x100] - блок 3

  • [EDX+0x180] - блок 4 (наша контрольная сумма)

В цикле (mov cl,80; Счетчик цикла = 128 (0x80)) мы перебираем каждый байт блока, суммируем и проверяем на результат в 4 блоке. На C получается что-то типа такого:

bool check_data_integrity(uint8_t* data) {
    for (int i = 0; i < 128; i++) {
        uint8_t sum = data[i] + data[i + 0x80] + data[i + 0x100];
        if (sum != data[i + 0x180]) {
              return false; //выходим из цикла
        }
    }
    return true; //в случае успеха возвращаем true
}

Прекрасно! Теперь можно дописать код и менять не только байты блока 3, но и блока 4.

Добавленные функции
    // Теперь нужно обновить контрольную сумму в блоке 4
    // Для этого копируем измененные данные в блок 3 (256-383)
    if (file_size >= 384) {
        memcpy(file_data + 256, work_data + 130, 126);
        // Остальные байты блока 3 (382-383) оставляем как были

        // Пересчитываем контрольную сумму для блока 4
        calculate_checksum(file_data);
        printf("Контрольная сумма пересчитана\n");
    }

// Функция вычисления контрольной суммы для блока 4
void calculate_checksum(unsigned char* data) {
    for (int i = 0; i < 128; i++) {
        data[i + 384] = data[i] + data[i + 128] + data[i + 256];
    }
}

Провожу тестирование - успех! Сделал тестовую дату в 2050 год - сработало идеально. А раз я уже могу менять дату, то и остальную информацию можно будет поменять. А пока небольшая заметка (даже офтоп) по поводу C.

IDE для C

Одна из причин, почему я долго не прикасался к задаче - я не нашёл нормальное решение в стиле "установи, вставь код, скомпилируй, получи exe, протестируй". На C/C++ я последний раз программировал в университете в 2012/2013 годах и тогда на ПК стоял опенсурсный DEV C++ и это дело работало под Windows 7. В текущее время проект заброшен, но есть инструкции, как завести/обновить компилятор для ПК под управлением Windows 10/11. Я делал по ответам на stackoverflow, но там такие старые советы, что у меня не завелось и я опять забил.

Логично поискать ещё что-то, но самое простое решение - Visual Studio/Code. Студия у меня стояла, дополнил ее галочками в инсталяции под язык C, поставил. Создаю новый проект, вставляю код, начинаю компилировать - ошибка, начинает ругаться на считывание файла. Начинаю гуглить - оказывается Microsoft сделала свою "безопасную" реализацию работы с файлами, синтаксис другой, но можно отключить такое поведение. Я просто хочу написать код по стандарту C99 - не надо думать за меня, мне нужно просто считать файлик, в памяти сделать изменения и сохранить всё в новый файл. Начинать мудрить, разбираться в настройках компилятора мне некогда.

И что же я всё таки выбрал? JetBrains Clion. При создания проекта указал тип C99 (это можно увидеть в папке CMakeLists.txt), вставил код - и он просто скомпилировался. Всё, ничего не делал - вот то, что мне нужно было. Я не спорю, я возможно ламер в этом деле, но мне проще было поставить на ноутбук другую IDE, чем разобраться, как настроить другие. Возможно только у меня такие проблемы, можете в комментариях написать, какие бесплатные аналогами вы пользуетесь и как быстро привязать тот же CMake "онлайн и без смс".

Затронувший вопрос по поводу других языков. На C я в памяти что хочу, то и ворочу. А вот эти преобразования двоичных данных в HEX и обратно, найти аналогии операции "обрезки" числа на High и Low частей регистра и т.д. В общем, я больше времени тратил на обдумывание, чем написать пару строк на C, поэтому используйте инструменты по назначению. А теперь вернемся к нашим лицензиям.

Патчер лицензий

Как говорил выше, формирование контрольной суммы я выяснил, расположение в строке тоже известно. Так как я так и не выяснил, как формируются 128 байт, от которого идёт расчет (в предоставленных мне файлах был одинаковый только 129 байт 03, а 128 у всех разный), поэтому первую строку с дистрибьютором я трогать не буду. Номер лицензии (Serial Number) у всех начинался на F01, поэтому все последующие цифры можно менять как душе свободно, главное сохранять маску вида F01-ХХХХ-ХХХХХ.

Чтобы сильно не думать, скармливаю старый код патчера даты DeepSeekу, пишу ему что хочу сделать, получаю результат и пробую его. Со 3его раза получилось то, что я хотел видеть. Полученный файл закидываю в WinFx, а в окне About белиберда - в License появились точки, в Serial Number после числа шла дата действия лицензии. Тут как говорится или ИИ дурак, либо я.

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

Полученные данные

HEX: E9 1A 03 00 7F 00 19 53 45 20 46 69 72 65 20 26 20 53 65 63 75 72 69 74 79 20 4F 79 00 00 00 00 32 42 69 7A 6F 6E 6F 7A 75 62 72 0D 0A 4F 6C 65 67 20 42 65 7A 76 65 72 6B 68 69 79 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0E 46 30 31 2D 39 39 39 39 2D 39 39 39 39 39 08 32 30 39 39 31 32 31 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

STRING: .......SE Fire & Security Oy....2Bizonozubr..Oleg Bezverkhiy........................F01-9999-99999.20991212...................

Когда я первый раз писал код, я ограничил символы ASCII только цифрами и буквами. Но есть же спецсимволы, а их я взял и просто откинул. Получается, дурак я) Смотрим таблицу (для примера вот) и верно - биты 0x00 являются пробелом, 0x0D является \r, 0Aявляется \n, 0E - переход на другую строку. Дополняюм код (просто в цикле при встречи данных битов заменяю на нужные символы) на вывод в строку спецсимволов, прогоняю вновь оригинальный файл и смотрю полученную строку:

TEXT: ...\0.\0.SE Fire & Security Oy\0\0\0\02Bizonozubr\r\nOleg Bezverkhiy\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x0EF01-9999-99999\b20991212\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0

Делаю правки и для шифровки строки, чтобы спецсимволы не пропадали. Ну а полученный результат вы уже видели на превью:

Итог

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

Немножко про HASP

Здесь большое спасибо участникам форума Ru-Board, где так сказали "выдали базу".

Вся работа с ключами строится на работе с API. В старые времена ключ определялся, как USB устройство, из-за чего можно было поставить логгер запросов по USB, в коде найти места вызова функций работы с ключом и попытаться расшифровать эти данные. На данный момент новые ключи пошли хитрее - они распознаются как HID устройства:

Пример подключенного ключа
Пример подключенного ключа

Из-за этого начинают придумывать различные тактики. В ревёрсе очень хорошо помогает Sentinel HASP LDK (мануал на русском есть), который может дать сигнатуры для IDA, да в целом понять, как накладывается защита на файлы. А их может быть несколько вариантов, чаще всего используют выносную библиотеку, привязанному к конкретному вендору вида "hasp_windows_xxxxx.dll", где xxxxx - численный идентификатор компании. В нашем случае это не так, у нас вся информация записана в основной exe файл.

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

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

Полученные данные
Полученные данные

Все эти данные программа получила из тела exe файла. В нем кстати находится большой AESный ключ, через которой и идёт вся работа. На картинке ниже вы можете увидеть этот зашифрованный ключ:

Здесь пытаюсь через Toolbox сделать код для подключения к ключу
Здесь пытаюсь через Toolbox сделать код для подключения к ключу

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

После того, даже если вы смогли подключиться к ключу, не факт, что вы можете считать данные из областей память RW/R/D. Если у вас это получилось, то ищете эмулятор ключа, подставляете свои данные и готово. На словах звучит просто, а на деле - тихий ужас. Очень интересно было бы посмотреть, как RTVIDTool2 извлек данные, но он накрыт Themida (кто ж свои секреты расскажет), поэтому со всем этим я поступил как обычно - забил. Но, вдохновившись успехами с файловыми лицензиями, я решил подступиться к новой версии программы - WinFX3Net версии 7.4.2.

Делаем патч (снова)

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

Перечень действий остался таким же, как из предыдущей статьи - закидываем exe в IDR, смотрим на полученные файлы кода, анализируем, предлагаем правки. Как и в предыдущей версии программы суть проверок осталась та же самая, но теперь вечно проверяющий таймер написан слегка по-другому:

00792C04
//----- (00792C04) --------------------------------------------------------
 int __fastcall TMainForm_LicTimerTimer(_BYTE a1) 
 { if ( !(unsigned __int8)((int ()(void))loc_792AD4)() )
 TCustomForm_Close(a1);
 return TMainForm_UpdateStatusbar((int)a1);
 }

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

Как я понял, что 00792AD4 нужная нам функция - она используется во всех проверках: при открытии файла, подключении к панели, печати, сохранении и т.д. Список всех функций, где она вызывается:

Где идет вызов 00792AD4
//----- (0078FEA4) --------------------------------------------------------
_DWORD *__fastcall TMainForm_FileNewClick(int a1)
{  if ( !(unsigned __int8)((int (__cdecl *)(struct _EXCEPTION_REGISTRATION_RECORD *, void *, int *))loc_792AD4)(
                           v8,
                           &unk_79007D,
                           &savedregs) )
    TCustomForm_Close((_BYTE *)a1);
}	
	
//----- (007900C4) --------------------------------------------------------
int __fastcall TMainForm_FileOpenClick(int a1)
{
  if ( !(unsigned __int8)((int (__cdecl *)(struct _EXCEPTION_REGISTRATION_RECORD *, void *))loc_792AD4)(v7, &unk_790358) )
    TCustomForm_Close((_BYTE *)a1);
}

//----- (007904D8) --------------------------------------------------------
int __fastcall TMainForm_FileSaveClick(_BYTE *a1)
{
  if ( !(unsigned __int8)((int (*)(void))loc_792AD4)() )
    TCustomForm_Close(a1);
  TMainForm_TrySaveFile((int)a1);
  TMainForm_UpdateTreeView((int)a1);
  return TMainForm_UpdateStatusbar((int)a1);
}

//----- (00790504) --------------------------------------------------------
int __fastcall TMainForm_FileSaveAsClick(_BYTE *a1)
{
  if ( !(unsigned __int8)((int (*)(void))loc_792AD4)() )
    TCustomForm_Close(a1);

  if ( (unsigned __int8)TMainForm_GetNewFileName((int)a1, (int *)off_7C4C8C) )
    TMainForm_TrySaveFile((int)a1);
  TMainForm_UpdateTreeView((int)a1);
  return TMainForm_UpdateStatusbar((int)a1);
}

//----- (00790590) --------------------------------------------------------
// positive sp value has been detected, the output may be wrong!
void __fastcall TMainForm_FilePrintClick(_BYTE *a1)
{
	if ( !(unsigned __int8)((int (*)(void))loc_792AD4)() )
    TCustomForm_Close(a1);
	v2 = sub_51A640();
}

//----- (0079070C) --------------------------------------------------------
int *__fastcall sub_79070C(int a1)
{
  int *result; // eax
  int *v3; // edi
  int v4; // eax

  if ( !(unsigned __int8)((int (*)(void))loc_792AD4)() )
    TCustomForm_Close((_BYTE *)a1);
  result = gvar_007C4C84;
}


//----- (00790778) --------------------------------------------------------
void *__fastcall sub_790778(int a1)
{
  void *result; // eax

  if ( !(unsigned __int8)((int (*)(void))loc_792AD4)() )
    TCustomForm_Close((_BYTE *)a1);
}


//----- (007907C0) --------------------------------------------------------
void *__fastcall sub_7907C0(int a1, int *a2)
{
  void *result; // eax
  int v5; // edx

  if ( !(unsigned __int8)((int (*)(void))loc_792AD4)() )
    TCustomForm_Close((_BYTE *)a1);
  result = gvar_007C41A0;
}

//----- (00790908) --------------------------------------------------------
void __fastcall TMainForm_ToolsReceiveClick(_BYTE *a1)
{
  _DWORD *v1; // esi
    v1 = gvar_007C46A8;
  if ( !(unsigned __int8)((int (*)(void))loc_792AD4)() )
    TCustomForm_Close(v10);
  *(_BYTE *)gvar_007C4420 = 1;
}

//----- (00790A44) --------------------------------------------------------
void __fastcall TMainForm_ToolsSendClick(_BYTE *a1)
{
  _DWORD *v1; // esi
    __writefsdword(0, (unsigned int)&v6);
  if ( !(unsigned __int8)((int (__stdcall *)(struct _EXCEPTION_REGISTRATION_RECORD *, void *, int *))loc_792AD4)(
                           v6,
                           &unk_790B93,
                           &savedregs) )
    TCustomForm_Close(v11);
}

//----- (00792C04) --------------------------------------------------------
int __fastcall TMainForm_LicTimerTimer(_BYTE *a1)
{
  if ( !(unsigned __int8)((int (*)(void))loc_792AD4)() )
    TCustomForm_Close(a1);
  return TMainForm_UpdateStatusbar((int)a1);
} 

Но, чтобы дойти до этого момента, нам нужно как обычно при старте формы TStartUpForm поправить все проверки на HASP ключ (но IDR его экспортировал как _Unit91.pas).

Посмотрим, что выдала IDA по основной точке входа

EntryPoint
//----- (0079DBD8) --------------------------------------------------------
// bad sp value at call has been detected, the output may be wrong!
void EntryPoint()
{
  int *v0; // ebx
  int v1; // eax
  char v2; // zf
  _DWORD v3[4]; // [esp-Ch] [ebp-28h] BYREF
  __int64 *v4; // [esp+4h] [ebp-18h] BYREF
  int v5[5]; // [esp+8h] [ebp-14h] BYREF
  int savedregs; // [esp+1Ch] [ebp+0h] BYREF

  v5[0] = 0;
  v4 = 0;
  InitExe((int)&dword_793637 + 1, (struct _EXCEPTION_REGISTRATION_RECORD *)&savedregs);
  v0 = Application;
  v3[2] = &savedregs;
  v3[1] = &unk_79DEEC;
  v3[0] = NtCurrentTeb()->NtTib.ExceptionList;
  __writefsdword(0, (unsigned int)v3);
  TApplication_Initialize();
  TCustomForm_Create((int *)VMT_66F648_TStartUpForm, 1);
  *gvar_007C42B8 = v1;
  if ( (unsigned __int8)TStartUpForm_VerifyLicensee(*gvar_007C42B8) )
  {
    ((void (__fastcall *)(_DWORD))TCustomForm_Show)(*gvar_007C42B8);
    ((void (__fastcall *)(_DWORD))TControl_Refresh)(*gvar_007C42B8);
    sub_5CAF2C(*v0, (int)&dword_79DF06 + 2);
    TApplication_CreateForm(*v0, (int)VMT_7881A0_TMainForm, gvar_007C449C[0]);
    TApplication_CreateForm(*v0, VMT_784AA8_TLicenseManagerForm, gvar_007C4A04);
    if 
	(ParamCount() > 1 && (ParamStr(2, &v4), sub_431F40((int)v4, 4, (__int64 **)v5), UStrEqual(v5[0], (int)aA_116), v2))
	
    {
      TApplication_CreateForm(*v0, VMT_7503E4_TFXCommHandler, gvar_007C46A8);
      TApplication_CreateForm(*v0, (int)VMT_7617F4_TAutoConfigFrm, gvar_007C4A9C);
      TApplication_CreateForm(*v0, (int)VMT_6AD998_TOverwriteDlg, gvar_007C4DEC);
    }
    else
    {
      TApplication_CreateForm(*v0, (int)VMT_762B7C_TSpecialSettingsFrm, gvar_007C4528);
      TApplication_CreateForm(*v0, VMT_73E4B8_TAddressReport, gvar_007C4864);
      TApplication_CreateForm(*v0, (int)VMT_739614_TSelectPanelsDlg, gvar_007C46A4);
      TApplication_CreateForm(*v0, (int)VMT_73A1AC_TSelectLoopsDlg, gvar_007C47D0);
      TApplication_CreateForm(*v0, VMT_73AD44_TSelectZonesDlg, gvar_007C4728);
      TApplication_CreateForm(*v0, VMT_763BE4_TDCRangeFrm, gvar_007C48B4);
      TApplication_CreateForm(*v0, VMT_775830_TDCErrorFrm, gvar_007C4988);
      TApplication_CreateForm(*v0, VMT_70AD0C_TFXColSelDlg, gvar_007C4DF0);
      TApplication_CreateForm(*v0, (int)VMT_6D3F44_TFXCGroupsDlg, gvar_007C47F4);
      TApplication_CreateForm(*v0, VMT_7503E4_TFXCommHandler, gvar_007C46A8);
      TApplication_CreateForm(*v0, VMT_70C3E8_TAPFillDlg, gvar_007C41CC);
      TApplication_CreateForm(*v0, VMT_75DF10_TFileImportDlg, gvar_007C4E80);
      TApplication_CreateForm(*v0, (int)VMT_75F994_TFileExportDlg, gvar_007C4AA0);
      TApplication_CreateForm(*v0, (int)VMT_7442C8_TConfigInfoDlg, gvar_007C4E00);
      TApplication_CreateForm(*v0, VMT_735138_TSelectVisibleDlg, gvar_007C4710);
      TApplication_CreateForm(*v0, (int)VMT_6AD998_TOverwriteDlg, gvar_007C4DEC);
      TApplication_CreateForm(*v0, VMT_673E14_TDbgFrm, gvar_007C41F0);
      TApplication_CreateForm(*v0, VMT_5DDEB8_TErrorFrm, gvar_007C4E54);
      TApplication_CreateForm(*v0, VMT_752010_TPreviewForm, gvar_007C4BD0);
      TApplication_CreateForm(*v0, VMT_6AE474_TMergeEsaForm, gvar_007C4EB4);
      TApplication_CreateForm(*v0, (int)VMT_6A674C_TEsaReport, gvar_007C41F8);
      TApplication_CreateForm(*v0, VMT_666FBC_TCalErrForm, gvar_007C4A10);
      TApplication_CreateForm(*v0, VMT_6D5DE4_TLoopCtrlrTypeChangeDlg, gvar_007C4624);
      TApplication_CreateForm(*v0, VMT_6D71C0_TLcToSlcConversionErrorsDlg, gvar_007C4DF8);
    }
	
    UStrClr((_DWORD *)(*v0 + 0x64));
    *(_BYTE *)(*v0 + 0x6F) = 0;
    TApplication_Run(*v0);
  }
  
  __writefsdword(0, v3[3]);
  v5[0] = (int)&dword_79DEF0 + 3;
  UStrArrayClr((int)&v4, 2);
  JUMPOUT(0x79DEF3);
}
0079DBD8 в Asm
 0079DBD8    push        ebp
 0079DBD9    mov         ebp,esp
 0079DBDB    add         esp,0FFFFFFE8
 0079DBDE    push        ebx
 0079DBDF    xor         eax,eax
 0079DBE1    mov         dword ptr [ebp-14],eax
 0079DBE4    mov         dword ptr [ebp-18],eax
 0079DBE7    mov         eax,793638
 0079DBEC    call        @InitExe
 0079DBF1    mov         ebx,dword ptr ds:[7C4A18];^Application:TApplication
 0079DBF7    xor         eax,eax
 0079DBF9    push        ebp
 0079DBFA    push        79DEEC
 0079DBFF    push        dword ptr fs:[eax]
 0079DC02    mov         dword ptr fs:[eax],esp
 0079DC05    mov         eax,dword ptr [ebx]
 0079DC07    call        TApplication.Initialize
 0079DC0C    mov         ecx,dword ptr [ebx]
 0079DC0E    mov         dl,1
 0079DC10    mov         eax,[0066F648];TStartUpForm
 0079DC15    call        TCustomForm.Create;TStartUpForm.Create
 0079DC1A    mov         edx,dword ptr ds:[7C42B8];^gvar_0082862C:TStartUpForm
 0079DC20    mov         dword ptr [edx],eax
 0079DC22    mov         eax,[007C42B8];^gvar_0082862C:TStartUpForm
 0079DC27    mov         eax,dword ptr [eax]
 0079DC29    call        TStartUpForm.VerifyLicensee //0067006C
 0079DC2E    test        al,al
>0079DC30    je          0079DED1
 0079DC36    mov         eax,[007C42B8];^gvar_0082862C:TStartUpForm
 0079DC3B    mov         eax,dword ptr [eax]
 0079DC3D    call        TCustomForm.Show

Видим интересную функцию TStartUpForm_VerifyLicensee по адресу 0067006C - тут уже прям имя говорит само за себя) Следующая за ней команда TEST AL,AL проверяет, равен ли регистр AL нулю, если равен, то флаг ZF будет включен, ну а уже по нему будем решать, перейти ли нам на 0079DED1.

Смотрим на большой листинг данной функции, но нам нужно выделить всего пару моментов, о них ниже.

function TStartUpForm.VerifyLicensee
//0067006C function TStartUpForm.VerifyLicensee:Boolean;
 0067006C    push        ebp
 0067006D    mov         ebp,esp
 0067006F    mov         ecx,8D
 00670074    push        0
 00670076    push        0
 00670078    dec         ecx
>00670079    jne         00670074
 0067007B    push        ecx
 0067007C    push        ebx
 0067007D    push        esi
 0067007E    push        edi
 0067007F    mov         edi,eax
 00670081    xor         eax,eax
 00670083    push        ebp
 00670084    push        6706F6
 00670089    push        dword ptr fs:[eax]
 0067008C    mov         dword ptr fs:[eax],esp
 0067008F    mov         byte ptr [ebp-25],0
 00670093    mov         ebx,2
 00670098    mov         eax,dword ptr [edi+3DC];TStartUpForm.LicenseKey:TNewLicenseKey
 0067009E    call        TNewLicenseKey.Verify //0066A690
 006700A3    mov         esi,eax
 006700A5    cmp         esi,7
>006700A8    jne         006700D5

 006700AA    push        0
 006700AC    mov         eax,[007C42C4];^gvar_007CA830
 006700B1    movzx       eax,byte ptr [eax]
 006700B4    imul        eax,eax,7
>006700B7    jno         006700BE
 006700B9    call        @IntOver
 006700BE    mov         eax,dword ptr [eax*8+7C10C4];^'USB License key could not be found! Insert the license k...
 006700C5    movzx       ecx,word ptr ds:[670708];0x28 gvar_00670708
 006700CC    mov         dl,1
 006700CE    call        MessageDlg
 006700D3    mov         ebx,eax
 006700D5    test        esi,esi
>006700D7    je          006700DE
 006700D9    cmp         ebx,2
>006700DC    jne         00670093
 006700DE    mov         eax,esi
 006700E0    cmp         eax,29
>006700E3    jg          00670110
>006700E5    je          00670126
 006700E7    sub         eax,1
>006700EA    jb          00670228
 006700F0    sub         eax,6
>006700F3    je          00670228
 006700F9    sub         eax,5
>006700FC    je          006701AD
 00670102    sub         eax,0D
>00670105    je          006701AD
>0067010B    jmp         006701D8
 00670110    sub         eax,1F41
>00670115    je          00670154
 00670117    dec         eax
>00670118    je          00670182
 0067011A    dec         eax
>0067011B    je          00670228
>00670121    jmp         006701D8
 00670126    push        0
 00670128    mov         eax,[007C42C4];^gvar_007CA830
 0067012D    movzx       eax,byte ptr [eax]
 00670130    imul        eax,eax,7
>00670133    jno         0067013A
 00670135    call        @IntOver
 0067013A    mov         eax,dword ptr [eax*8+7C10E0];^'The USB License key has expired'
 00670141    movzx       ecx,word ptr ds:[67070C];0x4 gvar_0067070C
 00670148    mov         dl,1
 0067014A    call        MessageDlg
>0067014F    jmp         00670228
 00670154    push        0
 00670156    mov         eax,[007C42C4];^gvar_007CA830
 0067015B    movzx       eax,byte ptr [eax]
 0067015E    imul        eax,eax,7
>00670161    jno         00670168
 00670163    call        @IntOver
 00670168    mov         eax,dword ptr [eax*8+7C10D0];^'USB License key is invalid'
 0067016F    movzx       ecx,word ptr ds:[67070C];0x4 gvar_0067070C
 00670176    mov         dl,1
 00670178    call        MessageDlg
>0067017D    jmp         00670228
 00670182    push        0
 00670184    mov         eax,[007C42C4];^gvar_007CA830
 00670189    movzx       eax,byte ptr [eax]
 0067018C    imul        eax,eax,7
>0067018F    jno         00670196
 00670191    call        @IntOver
 00670196    mov         eax,dword ptr [eax*8+7C10E0];^'The USB License key has expired'
 0067019D    movzx       ecx,word ptr ds:[67070C];0x4 gvar_0067070C
 006701A4    mov         dl,1
 006701A6    call        MessageDlg
>006701AB    jmp         00670228
 006701AD    push        0
 006701AF    mov         eax,[007C42C4];^gvar_007CA830
 006701B4    movzx       eax,byte ptr [eax]
 006701B7    imul        eax,eax,7
>006701BA    jno         006701C1
 006701BC    call        @IntOver
 006701C1    mov         eax,dword ptr [eax*8+7C10D8];^'USB License key clock failure'
 006701C8    movzx       ecx,word ptr ds:[67070C];0x4 gvar_0067070C
 006701CF    mov         dl,1
 006701D1    call        MessageDlg
>006701D6    jmp         00670228
 006701D8    push        0
 006701DA    lea         eax,[ebp-444]
 006701E0    push        eax
 006701E1    mov         dword ptr [ebp-44C],esi
 006701E7    mov         byte ptr [ebp-448],0
 006701EE    lea         edx,[ebp-44C]
 006701F4    mov         eax,[007C42C4];^gvar_007CA830
 006701F9    movzx       eax,byte ptr [eax]
 006701FC    imul        eax,eax,7
>006701FF    jno         00670206
 00670201    call        @IntOver
 00670206    mov         eax,dword ptr [eax*8+7C10CC];^'USB License key error: %d'
 0067020D    xor         ecx,ecx
 0067020F    call        Format
 00670214    mov         eax,dword ptr [ebp-444]
 0067021A    movzx       ecx,word ptr ds:[67070C];0x4 gvar_0067070C
 00670221    mov         dl,1
 00670223    call        MessageDlg
 00670228    test        esi,esi
>0067022A    je          00670235
 0067022C    mov         byte ptr [ebp-25],0
>00670230    jmp         006706B5
 00670235    mov         eax,dword ptr [edi+3DC];TStartUpForm.LicenseKey:TNewLicenseKey
 0067023B    call        00669630
 00670240    fstp        qword ptr [ebp-44C]
 00670246    wait
 00670247    mov         eax,dword ptr [edi+3DC];TStartUpForm.LicenseKey:TNewLicenseKey
 0067024D    call        006695D4
 00670252    fsubr       qword ptr [ebp-44C]
 00670258    fcomp       dword ptr ds:[670710];60:Single
 0067025E    wait
 0067025F    fnstsw      al
 00670261    sahf
>00670262    jae         006702F2
 00670268    push        0
 0067026A    lea         eax,[ebp-450]
 00670270    push        eax
 00670271    mov         eax,dword ptr [edi+3DC];TStartUpForm.LicenseKey:TNewLicenseKey
 00670277    call        00669630
 0067027C    fstp        qword ptr [ebp-458]
 00670282    wait
 00670283    mov         eax,dword ptr [edi+3DC];TStartUpForm.LicenseKey:TNewLicenseKey
 00670289    call        006695D4
 0067028E    fsubr       qword ptr [ebp-458]
 00670294    call        @TRUNC
 00670299    mov         dword ptr [ebp-458],eax
 0067029F    mov         dword ptr [ebp-454],edx
 006702A5    lea         eax,[ebp-458]
 006702AB    mov         dword ptr [ebp-44C],eax
 006702B1    mov         byte ptr [ebp-448],10
 006702B8    lea         edx,[ebp-44C]
 006702BE    mov         eax,[007C42C4];^gvar_007CA830
 006702C3    movzx       eax,byte ptr [eax]
 006702C6    imul        eax,eax,7
>006702C9    jno         006702D0
 006702CB    call        @IntOver
 006702D0    mov         eax,dword ptr [eax*8+7C10C8];^'The USB License key for this software expires in %d days!...
 006702D7    xor         ecx,ecx
 006702D9    call        Format
 006702DE    mov         eax,dword ptr [ebp-450]
 006702E4    movzx       ecx,word ptr ds:[67070C];0x4 gvar_0067070C
 006702EB    xor         edx,edx
 006702ED    call        MessageDlg
 006702F2    lea         eax,[ebp-236]
 006702F8    push        eax
 006702F9    push        0
 006702FB    push        0
 006702FD    push        0
 006702FF    push        0
 00670301    call        shell32.SHGetFolderPathW
 00670306    lea         eax,[ebp-45C]
 0067030C    lea         edx,[ebp-236]
 00670312    call        @UStrFromPWChar
 00670317    push        dword ptr [ebp-45C]
 0067031D    push        670720;'\'
 00670322    push        670730;'winfxnet[1].lic'
 00670327    lea         eax,[ebp-4]
 0067032A    mov         edx,3
 0067032F    call        @UStrCatN
 00670334    mov         dl,1
 00670336    mov         eax,dword ptr [ebp-4]
 00670339    call        0041EA18
 0067033E    test        al,al
>00670340    je          0067039E
 00670342    mov         dl,1
 00670344    mov         eax,[0066AC0C];TLicenseFile
 00670349    call        TObject.Create;TLicenseFile.Create
 0067034E    mov         ebx,eax
 00670350    mov         eax,dword ptr [edi+3DC];TStartUpForm.LicenseKey:TNewLicenseKey
 00670356    call        006695D4
 0067035B    add         esp,0FFFFFFF8
 0067035E    fstp        qword ptr [esp]
 00670361    wait
 00670362    mov         edx,dword ptr [ebp-4]
 00670365    mov         eax,ebx
 00670367    call        TLicenseFile.VerifyLicFile
 0067036C    mov         esi,eax
 0067036E    test        esi,esi
>00670370    je          00670377
 00670372    cmp         esi,5
>00670375    jne         00670397
 00670377    mov         eax,[00828630];gvar_00828630:TLicenseFile
 0067037C    call        TObject.Free
 00670381    mov         dword ptr ds:[828630],ebx;gvar_00828630:TLicenseFile
 00670387    mov         eax,edi
 00670389    call        0066FD94
 0067038E    mov         byte ptr [ebp-25],1
>00670392    jmp         006706B5
 00670397    mov         eax,ebx
 00670399    call        TObject.Free
 0067039E    lea         eax,[ebp-440]
 006703A4    push        eax
 006703A5    push        0
 006703A7    push        0
 006703A9    push        1C
 006703AB    push        0
 006703AD    call        shell32.SHGetFolderPathW
 006703B2    lea         eax,[ebp-460]
 006703B8    lea         edx,[ebp-440]
 006703BE    mov         ecx,105
 006703C3    call        @UStrFromWArray
 006703C8    mov         edx,dword ptr [ebp-460]
 006703CE    lea         eax,[ebp-8]
 006703D1    mov         ecx,67075C;'\Esmi\WinFXNet\'
 006703D6    call        @UStrCat3
 006703DB    lea         eax,[ebp-0C]
 006703DE    mov         ecx,670788;'winfxnet.lic'
 006703E3    mov         edx,dword ptr [ebp-8]
 006703E6    call        @UStrCat3
 006703EB    lea         eax,[ebp-464]
 006703F1    lea         edx,[ebp-440]
 006703F7    mov         ecx,105
 006703FC    call        @UStrFromWArray
 00670401    mov         edx,dword ptr [ebp-464]
 00670407    lea         eax,[ebp-10]
 0067040A    mov         ecx,6707B0;'\Pelco\WinFXNet\'
 0067040F    call        @UStrCat3
 00670414    lea         eax,[ebp-14]
 00670417    mov         ecx,670788;'winfxnet.lic'
 0067041C    mov         edx,dword ptr [ebp-10]
 0067041F    call        @UStrCat3
 00670424    lea         eax,[ebp-468]
 0067042A    lea         edx,[ebp-440]
 00670430    mov         ecx,105
 00670435    call        @UStrFromWArray
 0067043A    mov         edx,dword ptr [ebp-468]
 00670440    lea         eax,[ebp-18]
 00670443    mov         ecx,6707E0;'\Schneider Electric\WinFXNet\'
 00670448    call        @UStrCat3
 0067044D    lea         eax,[ebp-1C]
 00670450    mov         ecx,670788;'winfxnet.lic'
 00670455    mov         edx,dword ptr [ebp-18]
 00670458    call        @UStrCat3
 0067045D    mov         dl,1
 0067045F    mov         eax,dword ptr [ebp-1C]
 00670462    call        0041EA18
 00670467    test        al,al
>00670469    je          00670478
 0067046B    lea         eax,[ebp-20]
 0067046E    mov         edx,dword ptr [ebp-1C]
 00670471    call        @UStrLAsg
>00670476    jmp         0067049E
 00670478    mov         dl,1
 0067047A    mov         eax,dword ptr [ebp-14]
 0067047D    call        0041EA18
 00670482    test        al,al
>00670484    je          00670493
 00670486    lea         eax,[ebp-20]
 00670489    mov         edx,dword ptr [ebp-14]
 0067048C    call        @UStrLAsg
>00670491    jmp         0067049E
 00670493    lea         eax,[ebp-20]
 00670496    mov         edx,dword ptr [ebp-0C]
 00670499    call        @UStrLAsg
 0067049E    xor         ebx,ebx
 006704A0    mov         eax,dword ptr [edi+3DC];TStartUpForm.LicenseKey:TNewLicenseKey
 006704A6    call        006695D4
 006704AB    add         esp,0FFFFFFF8
 006704AE    fstp        qword ptr [esp]
 006704B1    wait
 006704B2    mov         edx,dword ptr [ebp-20]
 006704B5    mov         eax,[00828630];gvar_00828630:TLicenseFile
 006704BA    call        TLicenseFile.VerifyLicFile
 006704BF    mov         esi,eax
 006704C1    mov         eax,esi
 006704C3    sub         eax,1
>006704C6    jb          006704D8
 006704C8    sub         eax,4
>006704CB    jb          0067052C
>006704CD    je          006705BC
>006704D3    jmp         00670697
 006704D8    xor         ebx,ebx
 006704DA    mov         eax,dword ptr [ebp-20]
 006704DD    mov         edx,dword ptr [ebp-14]
 006704E0    call        @UStrEqual
>006704E5    je          006704F8
 006704E7    mov         eax,dword ptr [ebp-20]
 006704EA    mov         edx,dword ptr [ebp-0C]
 006704ED    call        @UStrEqual
>006704F2    jne         00670697
 006704F8    mov         dl,1
 006704FA    mov         eax,dword ptr [ebp-18]
 006704FD    call        0041EAB0
 00670502    test        al,al
>00670504    jne         0067050E
 00670506    mov         eax,dword ptr [ebp-18]
 00670509    call        0041EB90
 0067050E    push        0
 00670510    mov         eax,dword ptr [ebp-1C]
 00670513    call        @UStrToPWChar
 00670518    push        eax
 00670519    mov         eax,dword ptr [ebp-20]
 0067051C    call        @UStrToPWChar
 00670521    push        eax
 00670522    call        kernel32.CopyFileW
>00670527    jmp         00670697
 0067052C    push        0
 0067052E    dec         esi
 0067052F    cmp         esi,4
>00670532    jbe         00670539
 00670534    call        @BoundErr
 00670539    inc         esi
 0067053A    mov         eax,[007C42C4];^gvar_007CA830
 0067053F    movzx       eax,byte ptr [eax]
 00670542    imul        eax,eax,7
>00670545    jno         0067054C
 00670547    call        @IntOver
 0067054C    lea         eax,[eax*8+7C10AC];^'The license information file could not be found! Do you want to lo...
 00670553    mov         eax,dword ptr [eax+esi*4-4]
 00670557    movzx       ecx,word ptr ds:[67081C];0x3 gvar_0067081C
 0067055E    mov         dl,1
 00670560    call        MessageDlg
 00670565    cmp         eax,6
>00670568    jne         006705B5
 0067056A    lea         edx,[ebp-24]
 0067056D    mov         eax,edi
 0067056F    call        0066FE34
 00670574    test        al,al
>00670576    je          006705AE
 00670578    mov         dl,1
 0067057A    mov         eax,dword ptr [ebp-18]
 0067057D    call        0041EAB0
 00670582    test        al,al
>00670584    jne         0067058E
 00670586    mov         eax,dword ptr [ebp-18]
 00670589    call        0041EB90
 0067058E    push        0
 00670590    mov         eax,dword ptr [ebp-1C]
 00670593    call        @UStrToPWChar
 00670598    push        eax
 00670599    mov         eax,dword ptr [ebp-24]
 0067059C    call        @UStrToPWChar
 006705A1    push        eax
 006705A2    call        kernel32.CopyFileW
 006705A7    mov         bl,1
>006705A9    jmp         00670697
 006705AE    xor         ebx,ebx
>006705B0    jmp         00670697
 006705B5    xor         ebx,ebx
>006705B7    jmp         00670697
 006705BC    mov         eax,dword ptr [edi+3DC];TStartUpForm.LicenseKey:TNewLicenseKey
 006705C2    call        006695D4
 006705C7    mov         eax,[00828630];gvar_00828630:TLicenseFile
 006705CC    fsubr       qword ptr [eax+208]
 006705D2    call        @TRUNC
 006705D7    push        eax
 006705D8    sar         eax,1F
 006705DB    cmp         eax,edx
 006705DD    pop         eax
>006705DE    je          006705E5
 006705E0    call        @BoundErr
 006705E5    mov         dword ptr [ebp-2C],eax
 006705E8    push        0
 006705EA    lea         eax,[ebp-46C]
 006705F0    push        eax
 006705F1    dec         esi
 006705F2    cmp         esi,4
>006705F5    jbe         006705FC
 006705F7    call        @BoundErr
 006705FC    inc         esi
 006705FD    mov         eax,[007C42C4];^gvar_007CA830
 00670602    movzx       eax,byte ptr [eax]
 00670605    imul        eax,eax,7
>00670608    jno         0067060F
 0067060A    call        @IntOver
 0067060F    lea         eax,[eax*8+7C10AC];^'The license information file could not be found! Do you want to lo...
 00670616    mov         eax,dword ptr [eax+esi*4-4]
 0067061A    mov         edx,dword ptr [ebp-2C]
 0067061D    mov         dword ptr [ebp-44C],edx
 00670623    mov         byte ptr [ebp-448],0
 0067062A    lea         edx,[ebp-44C]
 00670630    xor         ecx,ecx
 00670632    call        Format
 00670637    mov         eax,dword ptr [ebp-46C]
 0067063D    movzx       ecx,word ptr ds:[67081C];0x3 gvar_0067081C
 00670644    xor         edx,edx
 00670646    call        MessageDlg
 0067064B    cmp         eax,6
>0067064E    jne         00670695
 00670650    lea         edx,[ebp-24]
 00670653    mov         eax,edi
 00670655    call        0066FE34
 0067065A    test        al,al
>0067065C    je          00670691
 0067065E    mov         dl,1
 00670660    mov         eax,dword ptr [ebp-18]
 00670663    call        0041EAB0
 00670668    test        al,al
>0067066A    jne         00670674
 0067066C    mov         eax,dword ptr [ebp-18]
 0067066F    call        0041EB90
 00670674    push        0
 00670676    mov         eax,dword ptr [ebp-1C]
 00670679    call        @UStrToPWChar
 0067067E    push        eax
 0067067F    mov         eax,dword ptr [ebp-24]
 00670682    call        @UStrToPWChar
 00670687    push        eax
 00670688    call        kernel32.CopyFileW
 0067068D    mov         bl,1
>0067068F    jmp         00670697
 00670691    xor         ebx,ebx
>00670693    jmp         00670697
 00670695    xor         ebx,ebx
 00670697    test        bl,bl
>00670699    jne         0067045D
 0067069F    cmp         esi,5
>006706A2    jne         006706A6
 006706A4    xor         esi,esi
 006706A6    test        esi,esi
>006706A8    jne         006706B5
 006706AA    mov         eax,edi
 006706AC    call        0066FD94
 006706B1    mov         byte ptr [ebp-25],1
 006706B5    xor         eax,eax
 006706B7    pop         edx
 006706B8    pop         ecx
 006706B9    pop         ecx
 006706BA    mov         dword ptr fs:[eax],edx
 006706BD    push        6706FD
 006706C2    lea         eax,[ebp-46C]
 006706C8    mov         edx,5
 006706CD    call        @UStrArrayClr
 006706D2    lea         eax,[ebp-450]
 006706D8    call        @UStrClr
 006706DD    lea         eax,[ebp-444]
 006706E3    call        @UStrClr
 006706E8    lea         eax,[ebp-24]
 006706EB    mov         edx,9
 006706F0    call        @UStrArrayClr
 006706F5    ret
>006706F6    jmp         @HandleFinally
>006706FB    jmp         006706C2
 006706FD    movzx       eax,byte ptr [ebp-25]
 00670701    pop         edi
 00670702    pop         esi
 00670703    pop         ebx
 00670704    mov         esp,ebp
 00670706    pop         ebp
 00670707    ret

В коде очень много проверок, но нас интересует в самом начале следующий вызов:

 0067009E    call        TNewLicenseKey.Verify //0066A690

Что ж, идем дальше смотреть, что там в 0066A690

0066A690
//0066A690 function TNewLicenseKey.Verify:Cardinal;

 0066A690    push        ebp
 0066A691    mov         ebp,esp
 0066A693    add         esp,0FFFFFFDC
 0066A696    mov         dword ptr [ebp-4],eax
 0066A699    mov         eax,dword ptr [ebp-4]
 
 0066A69C    call        0066975C
 0066A6A1    mov         dword ptr [ebp-0C],eax
 0066A6A4    cmp         dword ptr [ebp-0C],0
>0066A6A8    je          0066A6B5

 0066A6AA    mov         eax,dword ptr [ebp-0C]
 0066A6AD    mov         dword ptr [ebp-8],eax
>0066A6B0    jmp         0066A9E2
 0066A6B5    mov         edx,1
 0066A6BA    mov         eax,dword ptr [ebp-4]
 
 0066A6BD    call        006696E0
 0066A6C2    mov         dword ptr [ebp-0C],eax
 0066A6C5    cmp         dword ptr [ebp-0C],0
>0066A6C9    je          0066A6D6

 0066A6CB    mov         eax,dword ptr [ebp-0C]
 0066A6CE    mov         dword ptr [ebp-8],eax
>0066A6D1    jmp         0066A9E2
 0066A6D6    xor         edx,edx
 0066A6D8    push        ebp
 0066A6D9    push        66A9DB
 0066A6DE    push        dword ptr fs:[edx]
 0066A6E1    mov         dword ptr fs:[edx],esp
 0066A6E4    mov         eax,dword ptr [ebp-4]
 
 0066A6E7    call        0066977C
 0066A6EC    mov         dword ptr [ebp-0C],eax
 0066A6EF    cmp         dword ptr [ebp-0C],0
>0066A6F3    je          0066A705

 0066A6F5    mov         eax,dword ptr [ebp-0C]
 0066A6F8    mov         dword ptr [ebp-8],eax
 0066A6FB    call        @TryFinallyExit
>0066A700    jmp         0066A9E2
 0066A705    mov         eax,dword ptr [ebp-4]
 
 0066A708    call        00669A04
 0066A70D    mov         dword ptr [ebp-0C],eax
 0066A710    cmp         dword ptr [ebp-0C],0
>0066A714    je          0066A726

 0066A716    mov         eax,dword ptr [ebp-0C]
 0066A719    mov         dword ptr [ebp-8],eax
 0066A71C    call        @TryFinallyExit
>0066A721    jmp         0066A9E2
 0066A726    mov         eax,dword ptr [ebp-4]
 
 0066A729    call        00669AA0
 0066A72E    mov         dword ptr [ebp-0C],eax
 0066A731    cmp         dword ptr [ebp-0C],0
>0066A735    je          0066A747

 0066A737    mov         eax,dword ptr [ebp-0C]
 0066A73A    mov         dword ptr [ebp-8],eax
 0066A73D    call        @TryFinallyExit
>0066A742    jmp         0066A9E2
 0066A747    mov         eax,dword ptr [ebp-4]
 0066A74A    call        0066A090
 0066A74F    mov         eax,dword ptr [ebp-4]
 
 0066A752    call        0066A580
 0066A757    mov         dword ptr [ebp-0C],eax
 0066A75A    cmp         dword ptr [ebp-0C],0
>0066A75E    je          0066A770

 0066A760    mov         eax,dword ptr [ebp-0C]
 0066A763    mov         dword ptr [ebp-8],eax
 0066A766    call        @TryFinallyExit
>0066A76B    jmp         0066A9E2
 0066A770    mov         eax,dword ptr [ebp-4]
 0066A773    cmp         word ptr [eax+30],1;TNewLicenseKey.FKeyData:TLicenseKeyData
 
>0066A778    jbe         0066A78B

 0066A77A    mov         dword ptr [ebp-8],3D
 0066A781    call        @TryFinallyExit
>0066A786    jmp         0066A9E2
 0066A78B    mov         eax,dword ptr [ebp-4]
 0066A78E    movzx       eax,byte ptr [eax+40]
 0066A792    sub         al,1
 
>0066A794    jb          0066A7BC

>0066A796    je          0066A79A
>0066A798    jmp         0066A7AB
 0066A79A    mov         dword ptr [ebp-8],1F41
 0066A7A1    call        @TryFinallyExit
>0066A7A6    jmp         0066A9E2
 0066A7AB    mov         dword ptr [ebp-8],3D
 0066A7B2    call        @TryFinallyExit
>0066A7B7    jmp         0066A9E2
 0066A7BC    mov         eax,dword ptr [ebp-4]
 0066A7BF    mov         edx,dword ptr [ebp-4]
 0066A7C2    fld         qword ptr [eax+10];TNewLicenseKey.FKeyDate:TDateTime
 0066A7C5    fcomp       qword ptr [edx+18];TNewLicenseKey.FExpDate:TDateTime
 0066A7C8    wait
 0066A7C9    fnstsw      al
 0066A7CB    sahf
 
>0066A7CC    jbe         0066A7DF

 0066A7CE    mov         dword ptr [ebp-8],1F42
 0066A7D5    call        @TryFinallyExit
>0066A7DA    jmp         0066A9E2
 0066A7DF    mov         cx,1
 0066A7E3    mov         dx,1
 0066A7E7    mov         ax,7DA
 0066A7EB    call        00420F84
 0066A7F0    mov         eax,dword ptr [ebp-4]
 0066A7F3    fcomp       qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A7F6    wait
 0066A7F7    fnstsw      al
 0066A7F9    sahf
 
>0066A7FA    jae         0066A8A9

 0066A800    call        00421148
 0066A805    fstp        qword ptr [ebp-18]
 0066A808    wait
 0066A809    mov         eax,dword ptr [ebp-4]
 0066A80C    fld         qword ptr [ebp-18]
 0066A80F    fcomp       qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A812    wait
 0066A813    fnstsw      al
 0066A815    sahf
 
>0066A816    jbe         0066A838

 0066A818    mov         eax,dword ptr [ebp-4]
 0066A81B    mov         byte ptr [eax+40],1
 0066A81F    mov         eax,dword ptr [ebp-4]
 0066A822    call        0066A600
 0066A827    mov         dword ptr [ebp-8],1F42
 0066A82E    call        @TryFinallyExit
>0066A833    jmp         0066A9E2
 0066A838    fld         tbyte ptr ds:[66A9EC];0,0833333333333333:Extended
 0066A83E    fadd        qword ptr [ebp-18]
 0066A841    mov         eax,dword ptr [ebp-4]
 0066A844    fcomp       qword ptr [eax+20];TNewLicenseKey.FRecDate:TDateTime
 0066A847    wait
 0066A848    fnstsw      al
 0066A84A    sahf
>0066A84B    jae         0066A892
 0066A84D    mov         eax,dword ptr [ebp-4]
 0066A850    fld         qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A853    fsub        qword ptr [ebp-18]
 0066A856    fdiv        dword ptr ds:[66A9F8];10:Single
 0066A85C    call        @TRUNC
 0066A861    mov         dword ptr [ebp-24],eax
 0066A864    mov         dword ptr [ebp-20],edx
 0066A867    fild        qword ptr [ebp-24]
 0066A86A    mov         eax,dword ptr [ebp-4]
 0066A86D    fsubr       qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A870    fld1
 0066A872    fsubp       st(1),st
 0066A874    mov         eax,dword ptr [ebp-4]
 0066A877    fstp        qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A87A    wait
 0066A87B    mov         eax,dword ptr [ebp-4]
 0066A87E    call        0066A600
 0066A883    xor         eax,eax
 0066A885    mov         dword ptr [ebp-8],eax
 0066A888    call        @TryFinallyExit
>0066A88D    jmp         0066A9E2
 0066A892    mov         eax,dword ptr [ebp-4]
 0066A895    call        0066A600
 0066A89A    xor         eax,eax
 0066A89C    mov         dword ptr [ebp-8],eax
 0066A89F    call        @TryFinallyExit
>0066A8A4    jmp         0066A9E2
 0066A8A9    mov         byte ptr [ebp-19],1
 0066A8AD    call        00421148
 0066A8B2    fstp        qword ptr [ebp-18]
 0066A8B5    wait
 0066A8B6    fld         qword ptr [ebp-18]
 0066A8B9    fsub        dword ptr ds:[66A9FC];3:Single
 0066A8BF    mov         eax,dword ptr [ebp-4]
 0066A8C2    fcomp       qword ptr [eax+10];TNewLicenseKey.FKeyDate:TDateTime
 0066A8C5    wait
 0066A8C6    fnstsw      al
 0066A8C8    sahf
>0066A8C9    jae         0066A8FA
 0066A8CB    fld         qword ptr [ebp-18]
 0066A8CE    fadd        dword ptr ds:[66A9FC];3:Single
 0066A8D4    mov         eax,dword ptr [ebp-4]
 0066A8D7    fcomp       qword ptr [eax+10];TNewLicenseKey.FKeyDate:TDateTime
 0066A8DA    wait
 0066A8DB    fnstsw      al
 0066A8DD    sahf
>0066A8DE    jbe         0066A8FA
 0066A8E0    mov         cx,1
 0066A8E4    mov         dx,1
 0066A8E8    mov         ax,7DE
 0066A8EC    call        00420F84
 0066A8F1    fcomp       qword ptr [ebp-18]
 0066A8F4    wait
 0066A8F5    fnstsw      al
 0066A8F7    sahf
>0066A8F8    jb          0066A8FE
 0066A8FA    xor         eax,eax
>0066A8FC    jmp         0066A900
 0066A8FE    mov         al,1
 0066A900    test        al,al
>0066A902    jne         0066A9B5
 0066A908    xor         ecx,ecx
 0066A90A    mov         dl,1
 0066A90C    mov         eax,[00666FBC];TCalErrForm
 0066A911    call        TCustomForm.Create;TCalErrForm.Create
 0066A916    mov         edx,dword ptr ds:[7C4A10];^gvar_00828628:TCalErrForm
 0066A91C    mov         dword ptr [edx],eax
 0066A91E    xor         eax,eax
 0066A920    push        ebp
 0066A921    push        66A9AE
 0066A926    push        dword ptr fs:[eax]
 0066A929    mov         dword ptr fs:[eax],esp
 0066A92C    mov         eax,[007C4A10];^gvar_00828628:TCalErrForm
 0066A931    mov         eax,dword ptr [eax]
 0066A933    mov         edx,dword ptr [eax]
 0066A935    call        dword ptr [edx+13C]
 0066A93B    dec         eax
>0066A93C    je          0066A94C
 0066A93E    dec         eax
>0066A93F    je          0066A981
 0066A941    sub         eax,2
>0066A944    jne         0066A994
 0066A946    mov         byte ptr [ebp-19],0
>0066A94A    jmp         0066A994
 0066A94C    call        00421148
 0066A951    fstp        qword ptr [ebp-18]
 0066A954    wait
 0066A955    fld         qword ptr [ebp-18]
 0066A958    fadd        dword ptr ds:[66AA00];14:Single
 0066A95E    mov         eax,dword ptr [ebp-4]
 0066A961    fstp        qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A964    wait
 0066A965    mov         eax,dword ptr [ebp-4]
 0066A968    call        0066A600
 0066A96D    mov         dword ptr [ebp-0C],eax
 0066A970    mov         eax,dword ptr [ebp-4]
 0066A973    mov         edx,dword ptr [ebp-18]
 0066A976    mov         dword ptr [eax+10],edx;TNewLicenseKey.FKeyDate:TDateTime
 0066A979    mov         edx,dword ptr [ebp-14]
 0066A97C    mov         dword ptr [eax+14],edx;TNewLicenseKey.?f14:dword
>0066A97F    jmp         0066A994
 0066A981    mov         dword ptr [ebp-8],1F43
 0066A988    call        @TryFinallyExit
 0066A98D    call        @TryFinallyExit
>0066A992    jmp         0066A9E2
 0066A994    xor         eax,eax
 0066A996    pop         edx
 0066A997    pop         ecx
 0066A998    pop         ecx
 0066A999    mov         dword ptr fs:[eax],edx
 0066A99C    push        66A9B5
 0066A9A1    mov         eax,[007C4A10];^gvar_00828628:TCalErrForm
 0066A9A6    mov         eax,dword ptr [eax]
 0066A9A8    call        TObject.Free
 0066A9AD    ret
>0066A9AE    jmp         @HandleFinally
>0066A9B3    jmp         0066A9A1
 0066A9B5    cmp         byte ptr [ebp-19],0
>0066A9B9    je          0066A8A9
 0066A9BF    mov         eax,dword ptr [ebp-0C]
 0066A9C2    mov         dword ptr [ebp-8],eax
 0066A9C5    xor         eax,eax
 0066A9C7    pop         edx
 0066A9C8    pop         ecx
 0066A9C9    pop         ecx
 0066A9CA    mov         dword ptr fs:[eax],edx
 0066A9CD    push        66A9E2
 0066A9D2    mov         eax,dword ptr [ebp-4]
 0066A9D5    call        00669744
 0066A9DA    ret
>0066A9DB    jmp         @HandleFinally
>0066A9E0    jmp         0066A9D2
 0066A9E2    mov         eax,dword ptr [ebp-8]
 0066A9E5    mov         esp,ebp
 0066A9E7    pop         ebp
 0066A9E8    ret

И так, логика простая. Видим следующий паттерн:

 0066A69C    call        0066975C
 0066A6A1    mov         dword ptr [ebp-0C],eax
 0066A6A4    cmp         dword ptr [ebp-0C],0
>0066A6A8    je          0066A6B5

После вызова функции в регистр EAX перекладывается возвращаемое значение данной функции (код ошибки). CMP сравнивает результат с нулём, JE совершит переход если результат будет равен 0. Логика: если ошибки нет, то продолжаем проверку, иначе - обрабатываем ошибку по коду ошибки (простите за тавтологию). Вызовы функций мы оставляем на месте, а вот перекладку в регистр мы заменим. Нам необходимо, чтобы в eax попал 0 - проще всего это сделать через xor eax, eax плюс к этому добавится один NOP - в размеры команды побайтово мы попадаем. После сравнения с нулем нам нужно обязательно перейти по следующему адресу, поэтому JE мы меняем на JMP. Логика я думаю понятна, делаем аналогично после вызова следующих функций:

Функции
//TNewLicenseKey.InitKey
 0066A69C    call        0066975C
 0066A6A1    mov         dword ptr [ebp-0C],eax
 0066A6A4    cmp         dword ptr [ebp-0C],0
>0066A6A8    je          0066A6B5

//TNewLicenseKey.CheckKeyPresent
 0066A6BD    call        006696E0
 0066A6C2    mov         dword ptr [ebp-0C],eax
 0066A6C5    cmp         dword ptr [ebp-0C],0
>0066A6C9    je          0066A6D6

//TNewLicenseKey.Authenticate
 0066A6E7    call        0066977C
 0066A6EC    mov         dword ptr [ebp-0C],eax
 0066A6EF    cmp         dword ptr [ebp-0C],0
>0066A6F3    je          0066A705

//TNewLicenseKey.ReadKeyData
 0066A708    call        00669A04
 0066A70D    mov         dword ptr [ebp-0C],eax
 0066A710    cmp         dword ptr [ebp-0C],0
>0066A714    je          0066A726

// TNewLicenseKey.VerifyKeySignature
 0066A729    call        00669AA0
 0066A72E    mov         dword ptr [ebp-0C],eax
 0066A731    cmp         dword ptr [ebp-0C],0
>0066A735    je          0066A747

 
 0066A752    call        0066A580
 0066A757    mov         dword ptr [ebp-0C],eax
 0066A75A    cmp         dword ptr [ebp-0C],0
>0066A75E    je          0066A770

После идут проверки махинаций с ключом. Идем по коду, смотрим переходы и в случае чего меняем эту последовательность.

Проверка структуры данных ключа. Здесь поступаем аналогично - вместо JBE мы меняем на JMP.

0066A773    cmp         word ptr [eax+30],1  ; Проверка версии данных
0066A778    jbe         0066A78B
0066A77A    mov         dword ptr [ebp-8],3D  ; Ошибка: неверная версия

Проверка статуса ключа (адрес 0066A78B). Здесь поступаем аналогично - вместо JB мы меняем на JMP, тогда следующие проверки мы откинем.

0066A78B    mov         eax,dword ptr [ebp-4]
0066A78E    movzx       eax,byte ptr [eax+40]  ; FKeyStatus
0066A792    sub         al,1
0066A794    jb          0066A7BC    ; Статус 0 - продолжить
0066A796    je          0066A79A    ; Статус 1 - ошибка

Смотрим, что находится по адресу 0066A7BC. Здесь идет проверка сроков - даты активации с датой истечения ключа. Делаем переход по адресу обязательным (JBE на JMP):

 0066A7BC    mov         eax,dword ptr [ebp-4]
 0066A7BF    mov         edx,dword ptr [ebp-4]
 0066A7C2    fld         qword ptr [eax+10];TNewLicenseKey.FKeyDate:TDateTime
 0066A7C5    fcomp       qword ptr [edx+18];TNewLicenseKey.FExpDate:TDateTime
 0066A7C8    wait
 0066A7C9    fnstsw      al
 0066A7CB    sahf
 0066A7CC    jbe         0066A7DF

Далее идет сверка локального времени с неким FGrcDate:TDateTime. А вот тут мы JAE просто убираем - заменяем всю инструкцию на NOP.

 0066A7DF    mov         cx,1
 0066A7E3    mov         dx,1
 0066A7E7    mov         ax,7DA
 0066A7EB    call        00420F84 ; GetLocalTime
 0066A7F0    mov         eax,dword ptr [ebp-4]
 0066A7F3    fcomp       qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A7F6    wait
 0066A7F7    fnstsw      al
 0066A7F9    sahf
 0066A7FA    jae         0066A8A9

Идем дальше по коду, так как прыжка на адрес не было. Тут повторная проверка на ситуацию до окончания периода - я так понял, это защита от быстрого изменения времени между вызовами. Делаем аналогично - JBE на JMP:

 0066A800    call        00421148
 0066A805    fstp        qword ptr [ebp-18]
 0066A808    wait
 0066A809    mov         eax,dword ptr [ebp-4]
 0066A80C    fld         qword ptr [ebp-18]
 0066A80F    fcomp       qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A812    wait
 0066A813    fnstsw      al
 0066A815    sahf
>0066A816    jbe         0066A838

Ну и оставшийся код проверяет:

//Если осталось меньше 2 часов, то начинает постепенно ограничивать функциональность. 
 0066A838    fld         tbyte ptr ds:[66A9EC];0,0833333333333333 (2 часа)
 0066A83E    fadd        qword ptr [ebp-18] ; Текущее время + 2 часа
 0066A841    mov         eax,dword ptr [ebp-4]
 0066A844    fcomp       qword ptr [eax+20];TNewLicenseKey.FRecDate:TDateTime происходит
 0066A847    wait
 0066A848    fnstsw      al
 0066A84A    sahf
>0066A84B    jae         0066A892 ; Если >= FRecDate

//Алгоритм корректировки часов
 0066A84D    mov         eax,dword ptr [ebp-4]
 0066A850    fld         qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A853    fsub        qword ptr [ebp-18]; - Текущее время
 0066A856    fdiv        dword ptr ds:[66A9F8]; Делим на 10
 0066A85C    call        @TRUNC
 0066A861    mov         dword ptr [ebp-24],eax; Загружаем результат
 0066A864    mov         dword ptr [ebp-20],edx
 0066A867    fild        qword ptr [ebp-24]
 0066A86A    mov         eax,dword ptr [ebp-4]
 0066A86D    fsubr       qword ptr [eax+28];TNewLicenseKey.FGrcDate:TDateTime
 0066A870    fld1
 0066A872    fsubp       st(1),st ; Вычитаем 1 день
 0066A874    mov         eax,dword ptr [ebp-4]

 0066A877    fstp        qword ptr [eax+28]; Сохраняем новую FGrcDate:TDateTime
 0066A87A    wait
 0066A87B    mov         eax,dword ptr [ebp-4]
 0066A87E    call        0066A600
 0066A883    xor         eax,eax
 0066A885    mov         dword ptr [ebp-8],eax
 0066A888    call        @TryFinallyExit
>0066A88D    jmp         0066A9E2

И по адресу 0066A9E2 происходит выход из функции

 0066A9E2    mov         eax,dword ptr [ebp-8]
 0066A9E5    mov         esp,ebp
 0066A9E7    pop         ebp
 0066A9E8    ret

Так, от ключа отделались, осталось изменить проверку 00792AD4.

00792AD4
//function sub_00792AD4
 00792AD4    push        ebp
 00792AD5    mov         ebp,esp
 00792AD7    add         esp,0FFFFFFF0
 00792ADA    push        ebx
 00792ADB    push        esi
 00792ADC    push        edi
 00792ADD    xor         eax,eax
 00792ADF    mov         dword ptr [ebp-8],eax
 00792AE2    xor         eax,eax
 00792AE4    push        ebp
 00792AE5    push        792BEB
 00792AEA    push        dword ptr fs:[eax]
 00792AED    mov         dword ptr fs:[eax],esp
 00792AF0    mov         eax,[007C4420];^gvar_007CA838
 00792AF5    cmp         byte ptr [eax],0
>00792AF8    je          00792B01
 00792AFA    mov         bl,1
>00792AFC    jmp         00792BD5
 00792B01    xor         ebx,ebx
 00792B03    xor         esi,esi
 00792B05    lea         eax,[ebp-4]
 00792B08    push        eax
 00792B09    mov         eax,[007C4158];^gvar_0078AB68
 00792B0E    push        eax
 00792B0F    push        1
 00792B11    call        005DE4AB
 00792B16    mov         edi,eax
 00792B18    test        edi,edi
>00792B1A    je          00792B32
 00792B1C    lea         eax,[ebp-4]
 00792B1F    push        eax
 00792B20    mov         eax,[007C4158];^gvar_0078AB68
 00792B25    push        eax
 00792B26    push        0FFFF4800
 00792B2B    call        005DE4AB
 00792B30    mov         edi,eax
 00792B32    mov         eax,edi
 00792B34    sub         eax,1
>00792B37    jb          00792B40
 00792B39    sub         eax,6
>00792B3C    je          00792B44
>00792B3E    jmp         00792B74
 00792B40    mov         bl,1
>00792B42    jmp         00792BBA
 00792B44    push        0
 00792B46    mov         eax,[007C42C4];^gvar_007CA830
 00792B4B    movzx       eax,byte ptr [eax]
 00792B4E    imul        eax,eax,7
>00792B51    jno         00792B58
 00792B53    call        @IntOver
 00792B58    mov         edx,dword ptr ds:[7C43E8];^gvar_007C10AC
 00792B5E    mov         eax,dword ptr [edx+eax*8+18]
 00792B62    movzx       ecx,word ptr ds:[792BFC];0x28 gvar_00792BFC
 00792B69    mov         dl,1
 00792B6B    call        MessageDlg
 00792B70    mov         esi,eax
>00792B72    jmp         00792BBA
 00792B74    push        0
 00792B76    lea         eax,[ebp-8]
 00792B79    push        eax
 00792B7A    mov         eax,[007C42C4];^gvar_007CA830
 00792B7F    movzx       eax,byte ptr [eax]
 00792B82    imul        eax,eax,7
>00792B85    jno         00792B8C
 00792B87    call        @IntOver
 00792B8C    mov         edx,dword ptr ds:[7C43E8];^gvar_007C10AC
 00792B92    mov         eax,dword ptr [edx+eax*8+20]
 00792B96    mov         dword ptr [ebp-10],edi
 00792B99    mov         byte ptr [ebp-0C],0
 00792B9D    lea         edx,[ebp-10]
 00792BA0    xor         ecx,ecx
 00792BA2    call        Format
 00792BA7    mov         eax,dword ptr [ebp-8]
 00792BAA    movzx       ecx,word ptr ds:[792C00];0x4 gvar_00792C00
 00792BB1    mov         dl,1
 00792BB3    call        MessageDlg
 00792BB8    mov         esi,eax
 00792BBA    test        edi,edi
>00792BBC    je          00792BCC
 00792BBE    cmp         esi,2
>00792BC1    je          00792BCC
 00792BC3    cmp         esi,1
>00792BC6    jne         00792B05
 00792BCC    mov         eax,dword ptr [ebp-4]
 00792BCF    push        eax
 00792BD0    call        005DE5BB
 00792BD5    xor         eax,eax
 00792BD7    pop         edx
 00792BD8    pop         ecx
 00792BD9    pop         ecx
 00792BDA    mov         dword ptr fs:[eax],edx
 00792BDD    push        792BF2
 00792BE2    lea         eax,[ebp-8]
 00792BE5    call        @UStrClr
 00792BEA    ret
>00792BEB    jmp         @HandleFinally
>00792BF0    jmp         00792BE2
 00792BF2    mov         eax,ebx
 00792BF4    pop         edi
 00792BF5    pop         esi
 00792BF6    pop         ebx
 00792BF7    mov         esp,ebp
 00792BF9    pop         ebp
 00792BFA    ret

Спасибо разработчикам, что они сэкономили мне время.

 00792AF0    mov         eax,[007C4420];^gvar_007CA838
 00792AF5    cmp         byte ptr [eax],0
 00792AF8    je          00792B01
 00792AFA    mov         bl,1
 00792AFC    jmp         00792BD5  ; Пропуск проверки если флаг установлен

Здесь мы проверяем глобальный флаг, который, указывает на уже пройденную проверку. Если флаг установлен - сразу возвращается True, поэтому JE мы NOPим, чтобы случайно не перейти и дальше код нас выведет на 00792BD5 и мы выйдем из функции с установленным в регистре единичкой. А дальше нам и не интересно - функция всегда будет возвращать, что проверка пройдена. Идеально.

Итог

В заключении оставлю ссылки на:

https://github.com/OlegBezverhii/WinFXNet-lic-patcher/ - три программы для работы с файлами лицензий.

https://github.com/OlegBezverhii/WinFX3Net-SchEl/blob/main/patch/patch.1337 - ссылка на патч для x32dbg

Надеюсь вам было интересно и познавательно, как обычно в личку/комментарии принимаю предложения и замечания.

P.S.

Основная часть статьи закончилась, хочу пару строк оставить как автор, чтобы сразу ответить на вопросы, которые задаются или возникают после прочтения.

Почему долго выходила статья? Основным ограничением у меня выступает работа - я работаю не в прямом смысле ITшником, у компании удаленки нету, командировки присутствуют, а самое главное - ненормированный рабочий день. Вообще как бы по ТК вроде он у нас с 9 до 18:30, но я могу по пальцам руки пересчитать, когда я выходил из дверей здания в это время. Потом так же повлияло смена семейного положения - жене надо уделять время, а когда ты только в 20 часов уходишь с работы, то уже и за ПК сидеть не хочется, да и времени нету. Все свои статьи здесь я написал только когда у меня были отпуска. Плюс мотивации разбираться никакой не было, кроме как ради спортивного интереса, что смогу помочь и сам чему-то научиться. Это к вопросу, почему на Хабре мало крутых статей - люди становятся взрослее, хобби меняются, меняют возможно сферу работы и место проживания - им просто не до этого. Дополнительно к этому читая здесь статьи о прохождениях собеседований, когда человека даже с опытом выступлений на конференциях не берут на работу, то наличие статей на Хабре уже не является каким-то плюсом в глазах работодателя - как будущего так и текущего. Мне кажется, что всего лишь пару человек с работы видели мои статьи здесь, остальные даже не знают.

Почему статья именно на Хабре, а не в другом месте? Статьи с Хабра хорошо находится в поиске - это лучше, чем личный блог, в который приходят пару колек) После первой части много людей написало в ВК и TG, всем ответил как смог. Сейчас кто-то закрыл прием личных сообщений, но надеюсь им статья попадется на глаза, а дальше сами думаю разберутся. Хотя я так долго писал, что был человек, который уволился с места работы и ему уже не актуально было - это жизнь, так бывает) Реально писали со всей страны, потому что у многих такая проблема. А статья простая, на что-то прям крутое для того же журнала Хакер не тянет.

А зачем про это рассказывать, производитель же Хабр тоже читает и поменяет защиту? Да пожалуйста - мы всё равно купить ничего не можем из-за санкций, а курс на импортозамещение идёт полным ходом. Опять же за эти почти два года репозитории никто не снёс, а кому надо и сам может всё сделать своими руками.

Почему молодежь не хочет разбираться, копаться с ночи на пролёт с дебаггером? А из него возникает другой вопрос - а для чего? Сейчас кучу программ есть на любой вкус - хочешь бесплатные, хочешь платные. Многим реально проще заплатить за лицензию, главное чтобы её продали (а не как в нашем случае). Плюс всё уходит в веб и знания для веба котируются выше, чем знание Asm. А в конторах, занимающихся ИБ и так народу хватает, поэтому получаешь навыки, которые пригодятся возможно раз в жизни. Сейчас активное развитие reverse engineering идёт только у создателей читов и у противоборствующих им взломщиков этих читов. Это моё мнение, возможно я ошибаюсь.

Ну и от меня уже личная просьба - не пишите мне пожалуйста с предложениями по взлому прошивок/программ и т.д. - у меня нет свободного времени на это. Есть ресурсы, где вам за деньги всё что хотят сделают: вам и гарантия результата будет и по времени быстро сделают - не зря же люди за это деньги берут.

На этом всё. Следующая статья будет в следующем году (но это не точно) - опишу, как я с коллегой делаем небольшой приборчик для работы с приборами по Modbus. Похожие решения я видел в интернете, но они закрытые и платные, а нам нужен небольшой функционал - надеюсь, ребятам из сферы АСУТП это тоже поможет. Поэтому ждите)

Комментарии (4)


  1. Mingun
    09.11.2025 12:39

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

    Тут что-то непонятное написано -- jmp это же и есть безусловный переход, если вы не него и меняли, то что вам еще советовали?

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


    1. Bizonozubr Автор
      09.11.2025 12:39

      если вы не его и меняли, то что вам еще советовали?

      Видимо коряво сформулировал я мысль. Там, где был, например, jz я менял на jze - так я делал в первой статье (менял проверку ноль/единица). А сейчас да, я все переходы заменил на jmpы - безусловно переходить всегда. Я переформулирую, но уже завтра с утра)


  1. Ilya_JOATMON
    09.11.2025 12:39

    Часто имеет смысл просто найти точки в основной программе, которые общаются с защитой. (Если конечно она не накрыта "конвертом"). Может оказаться что там просто проверка lic ok? yes/no. Тогда можно запатчить это место, а код защиты отключить(выкинуть) нахрен.


  1. vdudouyt
    09.11.2025 12:39

    А не было ли мысли пойти по другому пути - а именно, вместо того, чтобы отучать WinFXNet от жадности (а вместе с ней - и склонности к соблюдению санкций) отреверсить протокол и релизнуть опенсорсную софтину (в т.ч. и под альтернативные ОС)?

    Казалось бы, так и волки сыты, и Shneider Electric не "ограблен" на 164 EUR, или сколько там.