Предыстория
В сфере АСУ ТП инженерам по работе приходится не только писать ПО, но и использовать готовое ПО от производителей оборудования. В связи с санкциями, многие поставщики покинули РФ, а оборудование и ПО необходимо продолжать эксплуатировать дальше.
В данной статье будет расмотрена 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..., выбираем скрипт и ждем применения:
IDA
В Ghidra выполним скрипты из набора Dhrake:
Ghidra
Делаем всё, как написано на гитхабе - 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();
}
Ghidra
void EntryPoint(void)
{
undefined *puVar1;
char cVar2;
undefined4 uVar3;
int iVar4;
undefined4 unaff_EBX;
undefined4 *in_FS_OFFSET;
bool bVar5;
undefined4 uStack_30;
undefined *puStack_2c;
undefined *puStack_28;
undefined4 local_20;
undefined4 local_1c;
wchar_t *local_18 [5];
local_18[0] = (wchar_t *)0x0;
local_1c = 0;
local_20 = 0;
puStack_28 = (undefined *)0x65fed4;
@InitExe(&DAT_00658424);
puVar1 = Application;
puStack_2c = &LAB_00660198;
uStack_30 = *in_FS_OFFSET;
*in_FS_OFFSET = &uStack_30;
puStack_28 = &stack0xfffffffc;
TApplication.Initialize(*(undefined4 *)puVar1);
uVar3 = TCustomForm.Create(&LAB_00595778,1,*(undefined4 *)puVar1);
*(undefined4 *)gvar_00685650 = uVar3;
cVar2 = FUN_005966b8(*(undefined4 *)gvar_00685650);
if (cVar2 == '\0') goto LAB_00660175;
TCustomForm.Show(*(undefined4 *)gvar_00685650);
TControl.Refresh(*(undefined4 *)gvar_00685650);
TApplication.SetTitle(*(undefined4 *)puVar1,&DAT_006601b4);
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_00616150,gvar_00685784);
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_00601a94,gvar_006857E8);
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005e12a0,gvar_00685A24);
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005dfa58,gvar_006858D8);
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005e027c,gvar_006859A0);
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005e0aa8,gvar_00685928);
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_006038c0,gvar_00685A5C);
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_00604510,gvar_00685AEC);
iVar4 = ParamCount();
if (iVar4 < 2) {
LAB_00660052:
uStack_30 = 0x660065;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005dc8b0,gvar_00685DC4);
uStack_30 = 0x660078;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005d07e0,gvar_006859C0);
uStack_30 = 0x66008b;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005ec000,gvar_006858DC);
uStack_30 = 0x66009e;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_00627940,gvar_006855B8);
uStack_30 = 0x6600b1;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005fd944,gvar_00685E28);
uStack_30 = 0x6600c4;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005fefd4,gvar_00685B94);
uStack_30 = 0x6600d7;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005e7820,gvar_00685DDC);
uStack_30 = 0x6600ea;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005dee5c,gvar_00685918);
uStack_30 = 0x6600fd;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005ba430,gvar_00685DC8);
uStack_30 = 0x660110;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_0059ab9c,gvar_006855D0);
uStack_30 = 0x660123;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005b3c7c,gvar_00685E08);
uStack_30 = 0x660136;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005f2420,gvar_00685C48);
uStack_30 = 0x660149;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005babf0,gvar_00685E50);
uStack_30 = 0x66015c;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005b3ef4,gvar_006855D4);
}
else {
ParamStr(2,&local_20);
@WStrFromUStr(&local_1c,local_20);
uStack_30 = 0x660005;
LeftStr(local_1c,4,local_18);
uStack_30 = 0x660012;
bVar5 = @WStrEqual(local_18[0],L"auto");
if (!bVar5) goto LAB_00660052;
uStack_30 = 0x660027;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005ec000,gvar_006858DC);
uStack_30 = 0x66003a;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_006009cc,gvar_00685B90);
uStack_30 = 0x66004d;
TApplication.CreateForm(*(undefined4 *)puVar1,&LAB_005ba430,gvar_00685DC8);
}
uStack_30 = 0x660168;
@UStrAsg(*(int *)puVar1 + 0x50,0);
*(undefined *)(*(int *)puVar1 + 0x5b) = 0;
uStack_30 = 0x660175;
TApplication.Run(*(undefined4 *)puVar1);
LAB_00660175:
*in_FS_OFFSET = puStack_2c;
puStack_28 = (undefined *)0x66018a;
@UStrClr(&local_20,puStack_2c,unaff_EBX);
puStack_28 = (undefined *)0x660197;
@WStrArrayClr(&local_1c,2);
return;
}
Тут кому как нравится (мне кажется нагляднее Гидра выдала), но суть у всех одинаковая - смотрим на выполнение функции FUN_005966b8.
IDA
void __fastcall sub_5966B8(int a1)
{
int v1; // esi
int v2; // ebx
double v3; // st7
double v4; // st7
char v5; // bl
int v6; // esi
char v7; // zf
const WCHAR *v8; // eax
const WCHAR *v9; // eax
double v10; // st7
int v11; // ecx
int v12; // ecx
int v13; // ecx
int v14; // ecx
const WCHAR *v15; // [esp-14h] [ebp-390h]
const WCHAR *v16; // [esp-14h] [ebp-390h]
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; // [esp-Ch] [ebp-388h] BYREF
void *v18; // [esp-8h] [ebp-384h]
int *v19; // [esp-4h] [ebp-380h]
int v20; // [esp+Ch] [ebp-370h] BYREF
int v21[2]; // [esp+10h] [ebp-36Ch] BYREF
int v22; // [esp+18h] [ebp-364h] BYREF
char v23[256]; // [esp+1Ch] [ebp-360h] BYREF
int v24; // [esp+11Ch] [ebp-260h] BYREF
int v25; // [esp+120h] [ebp-25Ch] BYREF
int v26; // [esp+124h] [ebp-258h] BYREF
int v27; // [esp+128h] [ebp-254h] BYREF
__int64 v28; // [esp+12Ch] [ebp-250h] BYREF
double v29; // [esp+134h] [ebp-248h]
int v30; // [esp+13Ch] [ebp-240h] BYREF
double v31; // [esp+140h] [ebp-23Ch]
int v32; // [esp+148h] [ebp-234h] BYREF
WCHAR pszPath[261]; // [esp+14Ch] [ebp-230h] BYREF
char v34; // [esp+357h] [ebp-25h]
int v35; // [esp+358h] [ebp-24h]
int v36; // [esp+35Ch] [ebp-20h] BYREF
int v37; // [esp+360h] [ebp-1Ch] BYREF
int v38; // [esp+364h] [ebp-18h] BYREF
int v39; // [esp+368h] [ebp-14h] BYREF
int v40; // [esp+36Ch] [ebp-10h] BYREF
int v41; // [esp+370h] [ebp-Ch] BYREF
int v42; // [esp+374h] [ebp-8h] BYREF
int v43; // [esp+378h] [ebp-4h] BYREF
int savedregs; // [esp+37Ch] [ebp+0h] BYREF
v35 = a1;
v19 = &savedregs;
v18 = &loc_596DE0;
ExceptionList = NtCurrentTeb()->NtTib.ExceptionList;
__writefsdword(0, (unsigned int)&ExceptionList);
v34 = 0;
do
{
v1 = 2;
v2 = sub_590CDC(*(_DWORD *)(v35 + 932));
if ( v2 == 7 )
v1 = MessageDlgPosHelp(0, -1, -1, 0);
}
while ( v2 && v1 != 2 );
if ( v2 > 41 )
{
if ( v2 != 8001 && v2 != 8002 )
{
if ( v2 != 8003 )
{
LABEL_20:
LODWORD(v31) = v2;
BYTE4(v31) = 0;
Format(&v32);
MessageDlgPosHelp(0, -1, -1, 0);
}
LABEL_21:
if ( v2 )
{
v34 = 0;
goto LABEL_59;
}
v31 = sub_58FD08(*(_DWORD *)(v35 + 932));
v3 = ((double (__fastcall *)(_DWORD))loc_58FCA8)(*(_DWORD *)(v35 + 932));
if ( v31 - v3 < 60.0 )
{
v29 = sub_58FD08(*(_DWORD *)(v35 + 932));
v4 = ((double (__fastcall *)(_DWORD))loc_58FCA8)(*(_DWORD *)(v35 + 932));
v28 = TRUNC(v29 - v4);
LODWORD(v31) = &v28;
BYTE4(v31) = 16;
Format(&v30);
MessageDlgPosHelp(0, -1, -1, 0);
}
shell32_SHGetSpecialFolderPathW(0, pszPath, 28, 0);
UStrFromWArray(&v27, pszPath, 261);
UStrCat3(&v43, v27, &off_596E0C);
UStrCat3(&v42, v43, &loc_596E38);
UStrFromWArray(&v26, pszPath, 261);
UStrCat3(&v41, v26, &off_596E60);
UStrCat3(&v40, v41, &loc_596E38);
UStrFromWArray(&v25, pszPath, 261);
UStrCat3(&v39, v25, aSchneider);
UStrCat3(&v38, v39, &loc_596E38);
while ( 1 )
{
if ( (unsigned __int8)FileExists(v38) )
{
UStrLAsg(&v37, v38);
}
else if ( (unsigned __int8)FileExists(v40) )
{
UStrLAsg(&v37, v40);
}
else
{
UStrLAsg(&v37, v42);
}
v5 = 0;
v6 = sub_595EF4(LODWORD(dbl_70911C) + 1032);
if ( !v6 )
{
v5 = 0;
UStrEqual(v37, v40);
if ( v7 || (UStrEqual(v37, v42), v7) )
{
if ( !(unsigned __int8)DirectoryExists(v39) )
ForceDirectories(v39);
v15 = (const WCHAR *)UStrToPWChar(v38);
v8 = (const WCHAR *)UStrToPWChar(v37);
kernel32_CopyFileW(v8, v15, 0);
}
goto LABEL_54;
}
if ( (unsigned int)(v6 - 1) < 4 )
{
if ( MessageDlgPosHelp(0, -1, -1, 0) != 6 )
{
v5 = 0;
goto LABEL_54;
}
if ( !(unsigned __int8)sub_5960D8(LODWORD(dbl_70911C), &v36) )
{
v5 = 0;
goto LABEL_54;
}
if ( !(unsigned __int8)DirectoryExists(v39) )
ForceDirectories(v39);
}
else
{
if ( v6 != 5 )
goto LABEL_54;
v10 = ((double (__fastcall *)(_DWORD))loc_58FCA8)(*(_DWORD *)(v35 + 932));
LODWORD(v31) = TRUNC(*(double *)(LODWORD(dbl_70911C) + 1032) - v10);
BYTE4(v31) = 0;
Format(&v24);
if ( MessageDlgPosHelp(0, -1, -1, 0) != 6 )
{
v5 = 0;
goto LABEL_54;
}
if ( !(unsigned __int8)sub_5960D8(LODWORD(dbl_70911C), &v36) )
{
v5 = 0;
goto LABEL_54;
}
if ( !(unsigned __int8)DirectoryExists(v39) )
ForceDirectories(v39);
}
v16 = (const WCHAR *)UStrToPWChar(v38);
v9 = (const WCHAR *)UStrToPWChar(v36);
kernel32_CopyFileW(v9, v16, 0);
v5 = 1;
LABEL_54:
if ( !v5 )
{
if ( v6 == 5 )
v6 = 0;
if ( !v6 )
{
LStrFromString(ExceptionList);
sub_5912B4(v21[1], &v22);
LStrToString(v23, v22, 255);
LOBYTE(v11) = 50;
PStrNCpy(LODWORD(dbl_70911C) + 166, v23, v11);
LStrFromString(v18);
sub_5912B4(v20, v21);
LStrToString(v23, v21[0], 255);
LOBYTE(v12) = 25;
PStrNCpy(LODWORD(dbl_70911C) + 140, v23, v12);
*(_BYTE *)gvar_0068564C = (*(_BYTE *)(LODWORD(dbl_70911C) + 138) & 1) != 0;
*(_BYTE *)gvar_00685670 = (*(_BYTE *)(LODWORD(dbl_70911C) + 138) & 2) != 0;
*(_BYTE *)gvar_00685A88 = (*(_BYTE *)(LODWORD(dbl_70911C) + 138) & 4) != 0;
*(_BYTE *)gvar_00685DE8 = (*(_BYTE *)(LODWORD(dbl_70911C) + 138) & 8) != 0;
*(_BYTE *)gvar_00685800 = (*(_BYTE *)(LODWORD(dbl_70911C) + 138) & 0x10) != 0;
*(_BYTE *)gvar_00685DFC = (*(_BYTE *)(LODWORD(dbl_70911C) + 138) & 0x20) != 0;
*(_BYTE *)gvar_00685964 = (*(_BYTE *)(LODWORD(dbl_70911C) + 138) & 0x40) != 0;
*(_BYTE *)gvar_00685C5C = *(char *)(LODWORD(dbl_70911C) + 138) < 0;
v34 = 1;
}
LABEL_59:
__writefsdword(0, (unsigned int)ExceptionList);
v19 = (int *)&loc_596DE7;
LStrArrayClr(&v20, 4);
UStrArrayClr(&v24, 4, v13);
UStrClr(v19);
UStrClr(v19);
UStrArrayClr(&v36, 8, v14);
JUMPOUT(0x596DF1);
}
}
}
}
else if ( v2 != 41 )
{
if ( !v2 || v2 == 7 )
goto LABEL_21;
if ( v2 != 12 && v2 != 25 )
goto LABEL_20;
}
MessageDlgPosHelp(0, -1, -1, 0);
goto LABEL_21;
}
Ghidra
void 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,0x28);
}
} 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"\\Esmi\\WinFXNet\\");
@UStrCat3(&local_c,local_8,L"winfxnet.lic");
@UStrFromWArray(&local_25c,local_234,0x105);
@UStrCat3((wchar_t **)&stack0xfffffff0,local_25c,L"\\Pelco\\WinFXNet\\");
@UStrCat3((wchar_t **)&stack0xffffffec,unaff_EBX,L"winfxnet.lic");
@UStrFromWArray(&local_260,local_234,0x105);
@UStrCat3((wchar_t **)&stack0xffffffe8,local_260,L"\\Schneider Electric\\WinFXNet\\");
@UStrCat3(&local_1c,unaff_EDI,L"winfxnet.lic");
do {
cVar3 = FileExists(local_1c);
if (cVar3 == '\0') {
cVar3 = FileExists(unaff_ESI);
if (cVar3 == '\0') {
iVar8 = 0x596a79;
@UStrLAsg(&local_20,local_c);
}
else {
iVar8 = 0x596a6c;
@UStrLAsg(&local_20,unaff_ESI);
}
}
else {
iVar8 = 0x596a53;
@UStrLAsg(&local_20,local_1c);
}
bVar1 = false;
local_24 = DAT_0070911c + 0x408;
iVar5 = FUN_00595ef4(DAT_0070911c,local_20,DAT_0070911c + 4);
if (iVar5 == 0) {
bVar1 = false;
bVar9 = true;
@UStrEqual(local_20,unaff_ESI);
if ((bVar9) || (@UStrEqual(local_20,local_c), bVar9)) {
cVar3 = DirectoryExists(unaff_EDI);
if (cVar3 == '\0') {
ForceDirectories(unaff_EDI);
}
BVar10 = 0;
pWVar6 = (LPCWSTR)@UStrToPWChar(local_1c);
pWVar7 = (LPCWSTR)@UStrToPWChar(local_20);
uStackY_38 = 0x596af8;
kernel32.CopyFileW(pWVar7,pWVar6,BVar10);
}
}
else if (iVar5 - 1U < 4) {
uStackY_38 = 0;
uStackY_3c = 0x596b30;
iVar8 = MessageDlgPosHelp(*(undefined4 *)
((uint)(byte)*gvar_00685658 * 0x38 + 0x6829d4 + iVar5 * 4),1,3) ;
if (iVar8 == 6) {
cVar3 = FUN_005960d8(DAT_0070911c,&local_24);
if (cVar3 == '\0') {
bVar1 = false;
}
else {
cVar3 = DirectoryExists(unaff_EDI);
if (cVar3 == '\0') {
ForceDirectories(unaff_EDI);
}
BVar10 = 0;
pWVar6 = (LPCWSTR)@UStrToPWChar(local_1c);
pWVar7 = (LPCWSTR)@UStrToPWChar(local_24);
uStackY_38 = 0x596b70;
kernel32.CopyFileW(pWVar7,pWVar6,BVar10);
bVar1 = true;
}
}
else {
bVar1 = false;
}
}
else if (iVar5 == 5) {
FUN_0058fca8(*(undefined4 *)(iVar8 + 0x3a4));
uVar4 = @TRUNC();
local_240._0_5_ = (uint5)uVar4;
Format((&PTR_u_The_license_information_file_for_006829e8)[(uint)(byte)*gvar_00685658 * 0x e],
&local_240,0);
uStackY_38 = 0;
uStackY_3c = 0x596bfc;
iVar8 = MessageDlgPosHelp(local_264,0,3);
if (iVar8 == 6) {
cVar3 = FUN_005960d8(DAT_0070911c,&local_24);
if (cVar3 == '\0') {
bVar1 = false;
}
else {
cVar3 = DirectoryExists(unaff_EDI);
if (cVar3 == '\0') {
ForceDirectories(unaff_EDI);
}
BVar10 = 0;
pWVar6 = (LPCWSTR)@UStrToPWChar(local_1c);
pWVar7 = (LPCWSTR)@UStrToPWChar(local_24);
uStackY_38 = 0x596c3c;
kernel32.CopyFileW(pWVar7,pWVar6,BVar10);
bVar1 = true;
}
}
else {
bVar1 = false;
}
}
} while (bVar1);
if (iVar5 == 5) {
iVar5 = 0;
}
if (iVar5 == 0) {
@LStrFromString(&local_36c,DAT_0070911c + 0xa6,0);
local_24 = 0x596c86;
FUN_005912b4(local_36c,&local_368);
local_24 = 0x596c9c;
@LStrToString(local_364,local_368,0xff);
local_24 = 0x596cb0;
@PStrNCpy(DAT_0070911c + 0xa6,local_364,0x32);
local_24 = 0x596cc8;
@LStrFromString(&local_374,DAT_0070911c + 0x8c,0);
local_24 = 0x596cd9;
FUN_005912b4(local_374,&local_370);
local_24 = 0x596cef;
@LStrToString(local_364,local_370,0xff);
local_24 = 0x596d03;
@PStrNCpy(DAT_0070911c + 0x8c,local_364,0x19);
*gvar_0068564C = (*(byte *)(DAT_0070911c + 0x8a) & 1) != 0;
*gvar_00685670 = (*(byte *)(DAT_0070911c + 0x8a) & 2) != 0;
*gvar_00685A88 = (*(byte *)(DAT_0070911c + 0x8a) & 4) != 0;
*gvar_00685DE8 = (*(byte *)(DAT_0070911c + 0x8a) & 8) != 0;
*gvar_00685800 = (*(byte *)(DAT_0070911c + 0x8a) & 0x10) != 0;
*gvar_00685DFC = (*(byte *)(DAT_0070911c + 0x8a) & 0x20) != 0;
*gvar_00685964 = (*(byte *)(DAT_0070911c + 0x8a) & 0x40) != 0;
*gvar_00685C5C = (*(byte *)(DAT_0070911c + 0x8a) & 0x80) != 0;
}
}
pwVar2 = local_1c;
*in_FS_OFFSET = local_24;
local_1c = (wchar_t *)&LAB_00596de7;
local_20 = (undefined *)0x596dac;
@LStrArrayClr(&local_374,4,pwVar2);
local_20 = (undefined *)0x596dbc;
@UStrArrayClr(&local_264,4);
local_20 = (undefined *)0x596dc7;
@UStrClr(&local_244);
local_20 = (undefined *)0x596dd2;
@UStrClr(&local_238);
local_20 = (undefined *)0x596ddf;
@UStrArrayClr(&local_24,8);
return;
}
Нам на глаза падает вызов функции FUN_00590cdc, которая взависимости от возвращаемого значения позволит нам запустить программу дальше или нет. Исходя из условия цикла, если мы закоментируем вызов данной функции и изменим условие на проверку равенства, то всё должно пройти дальше. Пробуем - открываем x32dbg, загружаем exeшник, переходим по адресу 005966f2 (этот адрес мы получили из той же гидры):
x32dbg
Вызов данной функции я заменяю на mov eax, 0x0
и смотрю, что и этого выходит:
x32dbg
Отлично! Мы дошли до выбора файла лицензии уже ПО. Файл действующей лицензии у нас есть (в отличии от HASP ключа), поэтому я указываю его и он появится в открытой папке на видео (\AppData\Local\Schneider Electric\WinFXNet).
Продолжим дальше вычищать программу от скрытых проверок ключа. Вернёмся к IDR. Откроем основную форму и посмотрим на элементы:
В глаза бросается хитрое название таймера - LicTimer, который имеет вызов функции call 0061EFB8
. Посмотрим, что там внутри такого:
Ghidra
void FUN_0061efb8(void)
{
undefined *puVar1;
int iVar2;
int iVar3;
undefined4 *in_FS_OFFSET;
undefined4 uStack_2c;
undefined *puStack_28;
undefined *puStack_24;
int local_14;
undefined local_10;
undefined4 local_c [2];
puStack_24 = &stack0xfffffffc;
local_c[0] = 0;
puStack_28 = &LAB_0061f0de;
uStack_2c = *in_FS_OFFSET;
*in_FS_OFFSET = &uStack_2c;
if (*gvar_00685734 == '\0') {
iVar3 = 0;
puStack_24 = &stack0xfffffffc;
do {
iVar2 = FUN_005054a9();
if (iVar2 != 0) {
iVar2 = FUN_005054a9();
}
if (iVar2 != 0) {
if (iVar2 == 7) {
iVar3 = MessageDlgPosHelp(*(undefined4 *)
(PTR_PTR_u_The_license_information_file_cou_0068570c +
(uint)(byte)*gvar_00685658 * 0x38 + 0x18),1,0x28,0,0xffffffff ,
0xffffffff,0);
}
else {
local_10 = 0;
local_14 = iVar2;
Format(*(undefined4 *)
(PTR_PTR_u_The_license_information_file_cou_0068570c +
(uint)(byte)*gvar_00685658 * 0x38 + 0x20),&local_14,0,local_c);
iVar3 = MessageDlgPosHelp(local_c[0],1,4,0,0xffffffff,0xffffffff,0);
}
}
} while (((iVar2 != 0) && (iVar3 != 2)) && (iVar3 != 1));
FUN_005055e9();
}
puVar1 = puStack_24;
*in_FS_OFFSET = uStack_2c;
puStack_24 = &LAB_0061f0e5;
puStack_28 = (undefined *)0x61f0dd;
@UStrClr(local_c,uStack_2c,puVar1);
return;
}
Думаю, уже сразу видно, что идет вновь проверка на лицензию. В IDR можно экспортировать весь проект через File -> Save Delphi Project, что мы и сделаем. Откроем поиск по файлам в Notepad++ и поищем все вхождения данной функции:
Продолжаем дальше в x32dbg - переходим по адресам, nop'им вызов функции и для процедуры открытия меняем переход с jne на je (иначе там по условию закроется приложение).
На этом всё - рабочий вариант программы без требования HASP ключа у нас есть. Осталось только разобраться с генерацией лицензии на программу, на что влиияет серийный номер, как программа понимает, какие функции по этой лицензии доступны. Это оставлю на вторую часть, если доделаю (или дадут доделать), то напишу. Но а пока (в виде бонуса) для понимания генерации лицензии посмотрим, как идет расшифровка файла лицензии внутри программы.
Файл лицензии
Первым делом посмотрим через HEX редактор сам файл:
HEX lic
Файл размером 512 байт, из которых первые 127 - это нормальный текст, остальное - непонятный набор байт. Помните, текстом выше, я говорил про функцию OpenDialog - вот на неё можно поставить брейкпоинт, но я сделал хитрее. Прогоняем первый раз цикл с открыванием файла, выйдет окно, что лицензия доступна ещё N дней и предложит продолжить, либо открыть другой файл. В это время мы в x32dbg смотрим вызов стека и смотрим по строкам, где у нас считался файл в память. По этому адресу в дампе ставим брейкпоинт на чтение/запись и возвращаемся к программе. Вновь выбираем файл, жмем открыть и попадаем на наш брейкпоинт. Функция, отвечающая за расшифровку файла из памяти, является FUN_00595d28.
Assambler
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
Ghidra
undefined4 FUN_00595d28(undefined4 param_1,int param_2)
{
char *pcVar1;
char cVar2;
byte bVar3;
bVar3 = *(byte *)(param_2 + 0x80);
cVar2 = '~';
pcVar1 = (char *)(param_2 + 0x82);
do {
*pcVar1 = *pcVar1 - ~*(byte *)(param_2 + (uint)bVar3);
bVar3 = bVar3 + 1 & 0x7f;
pcVar1 = pcVar1 + 1;
cVar2 = cVar2 + -1;
} while (cVar2 != '\0');
return CONCAT31((int3)((uint)pcVar1 >> 8),1);
}
IDA
char __fastcall sub_595D28(int a1, int a2)
{
int v3; // edx
char v4; // cl
_BYTE *v5; // eax
v3 = *(unsigned __int8 *)(a2 + 128);
v4 = 126;
v5 = (_BYTE *)(a2 + 130);
do
{
*v5 += *(_BYTE *)(a2 + (unsigned __int8)v3++) + 1;
LOBYTE(v3) = v3 & 0x7F;
++v5;
--v4;
}
while ( v4 );
return 1;
}
Чтобы понять, что происходит, лучше просто в пошаговой отладке посмотреть, что происходит с регистрами и областью памяти, где находится загруженный файл лицензии:
x32dbg
Смотрим за значением регистра EDX - начинается с 0000060, доходит до 7E, обнуляется (после and dl,7F
) и дальше до 5D и происходит выход из цикла. То есть первые 127 байт в файле лицензии не что иное, как кодовое слово для кодирования, просто счет начинается со смещения [esi+80]
, доходит до конца фразы, возвращается с самого начала и до предпоследнего символа, откуда начинался счет. Гениально и просто. Осталось только провернуть фарш назад и получится генератор лицензии.
Итог
Очень везёт, что программа ничем не запакована, средств антиотладки почти нет (я увидел только один момент). В целом, если дальше разобраться со структурой HASP, то можно сделать эмулятор ключа, но для этого физически нужно иметь ключ на первом этапе хотя бы. Ну а про лицензию для ПО и остальное нужно разбираться дальше, как описал выше алгоритм расшифровки несложный, надо только перенести его на простой язык программирования.