Как стать автором
Обновить

«Отучаем» WinFXNet от жадности (часть 1)

Уровень сложностиПростой
Время на прочтение18 мин
Количество просмотров12K

Предыстория

В сфере АСУ ТП инженерам по работе приходится не только писать ПО, но и использовать готовое ПО от производителей оборудования. В связи с санкциями, многие поставщики покинули РФ, а оборудование и ПО необходимо продолжать эксплуатировать дальше.

В данной статье будет расмотрена 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
Delphi 2010

Прекрасно, старая версия Delphi 2010 года - значит IDR свободно расшифрует формы. Так же DiE нам подсказывает, что используется защита с NetHASP/Hardlock dongle.

Скачиваем IDR, распаковываем архивы с бинарными штампами и анализируем наш exe файл. Ждем, когда пройдет анализ и выбираем в пункте меню "Tools -> IDC generator":

IDC generator
IDC generator

Полученный файл будет иметь следующую структуру:

Сгенерированный idc файл
Сгенерированный idc файл

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

Примеры строк в idc, где нужно подправить
Примеры строк в idc, где нужно подправить

Затем применим сгенеренный IDC-скрипт: в IDA Pro жмем File → Script File..., выбираем скрипт и ждем применения:

IDA
Либо через View -> Recent scripts выбираем уже запущенные ранее скрипты
Либо через View -> Recent scripts выбираем уже запущенные ранее скрипты
Не забываем у IDA в меню Options → Compiler указать компилятор Delphi (по умолчанию стоит C).
Не забываем у IDA в меню Options → Compiler указать компилятор Delphi (по умолчанию стоит C).

В Ghidra выполним скрипты из набора Dhrake:

Ghidra
Перед запуском надо положить в правильное место. Места можно посмотреть через Script Directories
Перед запуском надо положить в правильное место. Места можно посмотреть через Script Directories
Я положу в USER_HOME/ghidra_scripts
Я положу в USER_HOME/ghidra_scripts

Делаем всё, как написано на гитхабе - DhrakeInit.java выполняем, указываем, где находится наш idc файл, ждем выполнение работы скрипта.

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

Вернемся к IDR, который кстати указал для нас стартовую функцию с незамысловатым название EntryPoint. IDR может попытаться воссоздать нам исходный код данной функции, через нажатие клавишы Src:

Программа скорее всего ругнется, но примерное описание ассемблерных функций переведёт в код на Delphi:

Выглядит читаемо
Выглядит читаемо

Итак, при старте программы открывается форма TStartUpForm - отличное название формы, прям передаёт суть своего назначения. В IDR открываем вкладку Forms, находим нашу форму и смотрим что к чему:

Нам будет нужна функция открытия диалогово окна OpenDialog, но это чуть позже.
Нам будет нужна функция открытия диалогово окна OpenDialog, но это чуть позже.

А пока вернемся к коду выше и посмотрим, что нам сгенерировали дизассамблеры для функции 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
Щелкаем правой кнопкой мыши в поле CPU, выбираем Перейти -> Выражение
Щелкаем правой кнопкой мыши в поле CPU, выбираем Перейти -> Выражение
Вбиваем наш адрес и жмем окей. По F2 выставляем точку останова, но на адрес выше.
Вбиваем наш адрес и жмем окей. По F2 выставляем точку останова, но на адрес выше.

Вызов данной функции я заменяю на mov eax, 0x0 и смотрю, что и этого выходит:

x32dbg
Правой кнопкой по адресу выбираем Ассемблировать
Правой кнопкой по адресу выбираем Ассемблировать

Отлично! Мы дошли до выбора файла лицензии уже ПО. Файл действующей лицензии у нас есть (в отличии от HASP ключа), поэтому я указываю его и он появится в открытой папке на видео (\AppData\Local\Schneider Electric\WinFXNet).

Продолжим дальше вычищать программу от скрытых проверок ключа. Вернёмся к IDR. Откроем основную форму и посмотрим на элементы:

TMainForm
TMainForm

В глаза бросается хитрое название таймера - 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++ и поищем все вхождения данной функции:

10 раз используется данная функция в обработках различных кнопок на форме TMainForm
10 раз используется данная функция в обработках различных кнопок на форме TMainForm

Продолжаем дальше в 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, то можно сделать эмулятор ключа, но для этого физически нужно иметь ключ на первом этапе хотя бы. Ну а про лицензию для ПО и остальное нужно разбираться дальше, как описал выше алгоритм расшифровки несложный, надо только перенести его на простой язык программирования.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Выпускать вторую часть?
95.14% Да235
4.45% Нет11
0.4% На данном этапе функционала программы хватает1
Проголосовали 247 пользователей. Воздержались 17 пользователей.
Теги:
Хабы:
Всего голосов 87: ↑82 и ↓5+90
Комментарии28

Публикации

Истории

Работа

Ближайшие события