Предыстория
В сфере АСУ ТП инженерам по работе приходится не только писать ПО, но и использовать готовое ПО от производителей оборудования. В связи с санкциями, многие поставщики покинули РФ, а оборудование и ПО необходимо продолжать эксплуатировать дальше.
В данной статье будет расмотрена WinFXNet — программа производителя Schneider Electric (ESMI) для конфигурирования станций пожарной сигнализации серии Esmi FX. К сожалению, из-за санкций, ключ USB Esmi FX FFS00393016 приобрести нельзя, а он, в свою очередь, имеет встроенный таймер, который настроен на 4-летний период. У многих данный ключ по времени уже закончился, плюс скоро закончится и лицензия на само ПО (файл формата lic). Поэтому достаем дизассемблеры и посмотрим, можно ли обойти данную защиту.
Disclaimer: Данная заметка написана в ознакомительных целях и не является руководством к действиям. Хотя, понимая всю безвыходность данной ситуации, как временное решение имеет право на жизнь, но решать только вам. Статья написана как туториал, поэтому постараюсь детально описать все шаги поиска нужных мест в программе.
Приступаем
В первой части данной статьи быстро пробежимся по процессу, как я искал функции, мешающие запуску программы.
Для иследования нам понадобятся следующие утилиты:
- Detect It Easy (DiE) (github) — детектор типа файлов.
- Ghidra (github) либо IDA/Binary Ninja — дизассемблер на ваше усмотрение.
- x64dbg (github) либо любой любимый вами дебаггер.
- Interactive Delphi Reconstructor (IDR) (github) — интерактивный декомпилятор под Delphi.
- Dhrake (github) — комплект скриптов для Ghidr’ы, заточенные под Delphi после IDR.
- Сама программа — в интернете установщика нигде нет, поэтому залил на github — надеюсь файл проживет какое-то время.
Начинаем с определения, на чем же написана программа (из тегов и списка утилит я думаю вы уже догадались):
Прекрасно, старая версия Delphi 2010 года — значит IDR свободно расшифрует формы. Так же DiE нам подсказывает, что используется защита с NetHASP/Hardlock dongle.
Скачиваем IDR, распаковываем архивы с бинарными штампами и анализируем наш exe файл. Ждем, когда пройдет анализ и выбираем в пункте меню «Tools -> IDC generator»:
Полученный файл будет иметь следующую структуру:
Данный файл необходимо чутка подредактировать — IDR генерирует длинные строки через разделитель строк, из-за чего IDA и другие скрипты будут ругаться. Поэтому полученные пробелы и переходы строк необходимо убрать и превратить всё в одну строку:
Затем применим сгенеренный IDC-скрипт: в IDA Pro жмем File → Script File…, выбираем скрипт и ждем применения:
В Ghidra выполним скрипты из набора Dhrake:
Делаем всё, как написано на гитхабе — DhrakeInit.java выполняем, указываем, где находится наш idc файл, ждем выполнение работы скрипта.
Отлично, теперь дизассемблер понимает наши функции и руками ничего не приходится искать и править. Иногда бывает, что функции имеют неверные границы, но это можете поправить руками самостоятельно.
Вернемся к IDR, который кстати указал для нас стартовую функцию с незамысловатым название EntryPoint. IDR может попытаться воссоздать нам исходный код данной функции, через нажатие клавишы Src:
Программа скорее всего ругнется, но примерное описание ассемблерных функций переведёт в код на Delphi:
Итак, при старте программы открывается форма TStartUpForm — отличное название формы, прям передаёт суть своего назначения. В IDR открываем вкладку Forms, находим нашу форму и смотрим что к чему:
А пока вернемся к коду выше и посмотрим, что нам сгенерировали дизассамблеры для функции EntryPoint:
IDA
void __noreturn EntryPoint()
{
int *v0; // ebx
char v1; // zf
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; // [esp-Ch] [ebp-2Ch] BYREF
void *v3; // [esp-8h] [ebp-28h]
int *v4; // [esp-4h] [ebp-24h]
int v5; // [esp+4h] [ebp-1Ch]
int v6; // [esp+8h] [ebp-18h]
int v7[5]; // [esp+Ch] [ebp-14h] BYREF
int savedregs; // [esp+20h] [ebp+0h] BYREF
v7[0] = 0;
v6 = 0;
v5 = 0;
InitExe();
v0 = Application[0];
v4 = &savedregs;
v3 = &loc_660198;
ExceptionList = NtCurrentTeb()->NtTib.ExceptionList;
__writefsdword(0, (unsigned int)&ExceptionList);
TApplication_Initialize(ExceptionList);
TCustomForm_Create(*v0, 1);
*gvar_00685650 = (int)&loc_595777 + 1;
if ( (unsigned __int8)((int (__stdcall *)(struct _EXCEPTION_REGISTRATION_RECORD *))sub_5966B8)(ExceptionList) )
{
TCustomForm_Show();
TControl_Refresh();
TApplication_SetTitle();
TApplication_CreateForm(gvar_00685784, VMT_6160F8_TMainForm);
TApplication_CreateForm(gvar_006857E8, VMT_601A3C_TSpecialSettingsFrm);
TApplication_CreateForm(gvar_00685A24, VMT_5E1248_TAddressReport);
TApplication_CreateForm(gvar_006858D8, VMT_5DFA00_TSelectPanelsDlg);
TApplication_CreateForm(gvar_006859A0, VMT_5E0224_TSelectLoopsDlg);
TApplication_CreateForm(gvar_00685928, VMT_5E0A50_TSelectZonesDlg);
TApplication_CreateForm(gvar_00685A5C, VMT_603868_TDCRangeFrm);
TApplication_CreateForm(gvar_00685AEC, VMT_6044B8_TDCErrorFrm);
if ( ParamCount() > 1 && (ParamStr(), WStrFromUStr(v3), LeftStr(v7, 4), WStrEqual(), v1) )
{
TApplication_CreateForm(gvar_006858DC, VMT_5EBFA8_TFXCommHandler);
TApplication_CreateForm(gvar_00685B90, VMT_600974_TAutoConfigFrm);
TApplication_CreateForm(gvar_00685DC8, &off_5BA430);
}
else
{
TApplication_CreateForm(gvar_00685DC4, VMT_5DC858_TFXColSelDlg);
TApplication_CreateForm(gvar_006859C0, VMT_5D0788_TFXCGroupsDlg);
TApplication_CreateForm(gvar_006858DC, VMT_5EBFA8_TFXCommHandler);
TApplication_CreateForm(gvar_006855B8, VMT_6278E8_TAPFillDlg);
TApplication_CreateForm(gvar_00685E28, VMT_5FD8EC_TFileImportDlg);
TApplication_CreateForm(gvar_00685B94, VMT_5FEF7C_TFileExportDlg);
TApplication_CreateForm(gvar_00685DDC, VMT_5E77C8_TConfigInfoDlg);
TApplication_CreateForm(gvar_00685918, VMT_5DEE04_TSelectVisibleDlg);
TApplication_CreateForm(gvar_00685DC8, &off_5BA430);
TApplication_CreateForm(gvar_006855D0, &cls_FXDbgForm_TDbgFrm);
TApplication_CreateForm(gvar_00685E08, (char *)&loc_5B3C7B + 1);
TApplication_CreateForm(gvar_00685C48, VMT_5F23C8_TPreviewForm);
TApplication_CreateForm(gvar_00685E50, &cls_FXEsaFileMerge_TMergeEsaForm);
TApplication_CreateForm(gvar_006855D4, &cls_FXEsaFileMergeReport_TEsaReport);
}
UStrAsg();
*(_BYTE *)(*v0 + 91) = 0;
TApplication_Run();
}
__writefsdword(0, (unsigned int)v4);
UStrClr(&loc_66019F);
WStrArrayClr();
Halt0();
}
Ghidravoid FUN_005966b8(int param_1)
{
bool bVar1;
wchar_t *pwVar2;
char cVar3;
uint uVar4;
int iVar5;
LPCWSTR pWVar6;
LPCWSTR pWVar7;
int iVar8;
wchar_t *unaff_EBX;
undefined4 unaff_ESI;
wchar_t *unaff_EDI;
int *in_FS_OFFSET;
bool bVar9;
float10 in_ST0;
float10 in_ST1;
float10 in_ST2;
undefined4 param_11;
undefined4 local_374;
undefined4 local_370;
undefined4 local_36c;
undefined4 local_368;
undefined local_364 [256];
undefined4 local_264;
wchar_t *local_260;
wchar_t *local_25c;
wchar_t *local_258;
undefined4 local_254 [2];
double local_24c;
undefined4 local_244;
undefined8 local_240;
undefined4 local_238;
WCHAR local_234 [252];
undefined4 uStackY_3c;
undefined4 uStackY_38;
BOOL BVar10;
undefined4 *puVar11;
int local_24;
undefined *local_20;
wchar_t *local_1c;
wchar_t *local_c;
wchar_t *local_8;
local_1c = (wchar_t *)&stack0xfffffffc;
iVar8 = 0x6e;
do {
local_8 = (wchar_t *)0x0;
iVar8 = iVar8 + -1;
} while (iVar8 != 0);
local_20 = &LAB_00596de0;
local_24 = *in_FS_OFFSET;
*in_FS_OFFSET = (int)&local_24;
do {
iVar8 = 2;
puVar11 = (undefined4 *)(param_1 + 0x3a4);
param_1 = 0x5966f7;
uVar4 = FUN_00590cdc(*puVar11);
if (uVar4 == 0x70) {
param_1 = 0;
uStackY_38 = 0x59672d;
iVar8 = MessageDlgPosHelp((&PTR_u_USB_License_key_could_not_be_fou_006829f0)
[(uint)(byte)*gvar_00685658 * 0xe],1,0×28);
}
} while ((uVar4 != 0) && (iVar8 != 2));
if ((int)uVar4 < 0x2a) {
if (uVar4 == 0x29) {
param_1 = 0;
uStackY_38 = 0x5967af;
MessageDlgPosHelp((&PTR_u_The_USB_License_key_has_expired_00682a0c)
[(uint)(byte)*gvar_00685658 * 0xe],1,4);
goto LAB_005968a3;
}
if ((uVar4 == 0) || (uVar4 == 7)) goto LAB_005968a3;
if ((uVar4 == 0xc) || (uVar4 == 0x19)) {
param_1 = 0;
uStackY_38 = 0x59684b;
MessageDlgPosHelp((&PTR_u_USB_License_key_clock_failure_00682a04)
[(uint)(byte)*gvar_00685658 * 0xe],1,4);
goto LAB_005968a3;
}
}
else {
if (uVar4 == 0x1f41) {
param_1 = 0;
uStackY_38 = 0x5967e3;
MessageDlgPosHelp((&PTR_u_USB_License_key_is_invalid_006829fc)
[(uint)(byte)*gvar_00685658 * 0xe],1,4);
goto LAB_005968a3;
}
if (uVar4 == 0x1f42) {
param_1 = 0;
uStackY_38 = 0x596817;
MessageDlgPosHelp((&PTR_u_The_USB_License_key_has_expired_00682a0c)
[(uint)(byte)*gvar_00685658 * 0xe],1,4);
goto LAB_005968a3;
}
if (uVar4 == 0x1f43) goto LAB_005968a3;
}
local_240._0_5_ = (uint5)uVar4;
Format((&PTR_u_USB_License_key_error:_%d_006829f8)[(uint)(byte)*gvar_00685658 * 0xe],&local_240 ,0)
;
param_1 = 0;
uStackY_38 = 0x5968a3;
MessageDlgPosHelp(local_238,1,4);
LAB_005968a3:
if (uVar4 == 0) {
iVar8 = 0x5968be;
FUN_0058fd08(*(undefined4 *)(param_1 + 0x3a4));
local_240 = (double)in_ST0;
FUN_0058fca8(*(undefined4 *)(iVar8 + 0x3a4));
if ((float10)local_240 — in_ST1 < (float10)60.0) {
puVar11 = &local_244;
FUN_0058fd08(param_11);
local_24c = (double)in_ST2;
FUN_0058fca8(puVar11[0xe9]);
local_254[0] = @TRUNC();
local_240._0_5_ = CONCAT14(0x10,local_254);
Format((&PTR_u_The_USB_License_key_for_this_sof_006829f4)[(uint)(byte)*gvar_00685658 * 0xe] ,
&local_240,0);
uStackY_38 = 0x59697f;
MessageDlgPosHelp(local_244,0,4);
}
uStackY_38 = 0x596991;
shell32.SHGetSpecialFolderPathW((HWND)0x0,local_234,0x1c,0);
@UStrFromWArray(&local_258,local_234,0x105);
@UStrCat3(&local_8,local_258,L»EsmiWinFXNet»);
@UStrCat3(&local_c,local_8,L»winfxnet.lic»);
@UStrFromWArray(&local_25c,local_234,0x105);
@UStrCat3((wchar_t **)&stack0xfffffff0,local_25c,L»PelcoWinFXNet»);
@UStrCat3((wchar_t **)&stack0xffffffec,unaff_EBX,L»winfxnet.lic»);
@UStrFromWArray(&local_260,local_234,0x105);
@UStrCat3((wchar_t **)&stack0xffffffe8,local_260,L»Schneider ElectricWinFXNet»);
@UStrCat3(&local_1c,unaff_EDI,L»winfxnet.lic»);
do {
cVar3 = FileExists(local_1c);
if (cVar3 == ‘