Немного контекста
Эта заключительная часть данной серии (ссылка на первую часть) должна быть выйти раньше, но из-за многих факторов (об этом будет в конце статьи, если кому интересно) этого не произошло. Но звёзды сошлись и результаты экспериментов собраны здесь.
В данной статье поясню, как я разбирался в работе файловой лицензии, как новая версия программы не поддалась мне с первого раза (поэтому в этот раз патч сделан по иной схеме, но лучше с моей точки зрения), а так же поговорим о экспериментах с живым 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Это функция, которая выполняет операцию над массивом байтов:
Берет начальный индекс из
[esi+80]Обрабатывает 126 байтов (7Eh), начиная с
esi+82-
Для каждого байта:
Берет байт из
[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, который отвечает за форму, открывающейся при старте программы.

Загружаем программу в отладчик, ставлю на эти адреса брейкпоинты, подкидываю свой сломанный файл и смотрю, где сработает - это оказывается адрес 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Функция создает стандартный стековый фрейм и выделяет место для локальных переменных. Выполняются следующие проверки:
Вызов функции по адресу 00595C58 - первая проверка. Если проверка не проходит, устанавливается
ebx = 1Вызов функции по адресу 00595CF4 - вторая проверка. Если не проходит,
ebx = 2Вызов функции по адресу 00595D28 - третья проверка. Если не проходит,
ebx = 2Вызов функции по адресу 00595D54 - четвертая проверка. Если не проходит,
ebx = 2Проверка значения по смещению +84 от esi (cmp word ptr [esi+84],3). Если не равно 3,
ebx = 3Ну и дальше проверки, которые нас не интересуют. Бегло можно увидеть, что в регистре 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ный ключ, через которой и идёт вся работа. На картинке ниже вы можете увидеть этот зашифрованный ключ:
Теперь, когда у вас есть пароли, можно попытаться подключиться через 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. Похожие решения я видел в интернете, но они закрытые и платные, а нам нужен небольшой функционал - надеюсь, ребятам из сферы АСУТП это тоже поможет. Поэтому ждите)
Mingun
Тут что-то непонятное написано -- jmp это же и есть безусловный переход, если вы не него и меняли, то что вам еще советовали?
А вообще, интересно, как пишутся такие защиты. На хабре есть куча статей, как компиляторы умным образом понимают, что всякие проверки можно из кода выкинуть, поскольку раньше эта или более обширная проверка уже делалась. Но вот как сделать прямо противоположное -- оставить дублирующуюся проверку, желательно побольше во всяких разных местах и не прям одинаковые, чтобы искать было сложно, никто не пишет. Да, можно выключить оптимизации, но ведь этим вы и взломщикам работу упростите, верно?