Немного контекста
Эта заключительная часть данной серии (ссылка на первую часть) должна быть выйти раньше, но из-за многих факторов (об этом будет в конце статьи, если кому интересно) этого не произошло. Но звёзды сошлись и результаты экспериментов собраны здесь.
В данной статье поясню, как я разбирался в работе файловой лицензии, как новая версия программы не поддалась мне с первого раза (поэтому в этот раз патч сделан по иной схеме, но лучше с моей точки зрения), а так же поговорим о экспериментах с живым 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 тут не прокатит. Взглянув другим взглядом на всё это, я решил сделать всё по другому - теперь мы не будем убирать вызов функции проверки, а будем патчить саму функцию, чтобы на выходе ничего не ломалось. Плюс на форуме верно подметили, что в предыдущей статье я менял переход по условиям (проверка по нулю/единице, для примера jz я менял на jze) , хотя для логики лучше железобетонно переходить на нужный адрес, то есть использовать безусловный 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. Похожие решения я видел в интернете, но они закрытые и платные, а нам нужен небольшой функционал - надеюсь, ребятам из сферы АСУТП это тоже поможет. Поэтому ждите)
