Часть 1: Обход защиты
Однажды меня попросили прочитать файлы с расширением .xcm программы холтер-мониторинга и вывести из них кардиограмму на график. Всё бы ничего, но формат файлов оказался кастомным и не подходил под стандарты ни одним байтом. Без оригинальной программы разобраться в том, как их читать, было невозможно.
Я попросил прислать мне саму программу, но мне ответили, что без аппаратного ключа она не работает. Ключ при этом находится в Бразилии, и прислать его не могут, так как он нужен медикам для работы. «Присылайте так, разберусь», — сказал я. Была мысль глянуть, что там да как статически, а если получится — заставить её работать без ключа и смотреть уже в динамике.
Программа, к слову, называется CardioSmart фирмы Cardios и выглядит так:

Она написана на C++ с использованием MFC и представляет собой MDI-приложение (может работать с несколькими документами одновременно).
Первые находки
Ладно, довольно ли��ики, переходим к самому интересному. Когда я заглянул в каталог приложения, то увидел кучу DLL-библиотек с интересными названиями. Это явно указывало на то, что код писался с использованием принципов «чистой архитектуры», с разделением ответственности даже на бинарном уровне! Всё как завещал дядюшка Боб. То есть это были не просто библиотеки, а четко выделенные модули программы.
Также в директории я обнаружил файлы с расширением .cmm. По сути, это те же самые DLL-библиотеки, но с измененным расширением — видимо, чтобы скрыть их истинную природу от посторонних глаз. Они тоже являются полноценными модулями программы.
Диалог License status
При запуске программы появляется диалог ввода серийного номера. Нужно поискать по модулям, где он хранится, найти, откуда он вызывается. Кто-то скажет: «У тебя же наверняка есть модули с названиями license.dll, LicenseManager.dll и т.д.». Только вот подобных названий нет, поэтому придется искать. Бегло глянув ресурсы с помощью PE Explorer (программка старая, но выручает иногда), я обнаружил, что искомый диалог находится в AppsBase.dll. Зная ID диалога (24080 в десятичном виде или 5E10h в шестнадцатеричном), я загрузил AppsBase.dll в IDA Pro. Поиск по константе вывел на место вызова:
Конструктор CdlgLicenseStatus
CdlgLicenseStatus *__thiscall CdlgLicenseStatus::CdlgLicenseStatus( CdlgLicenseStatus *this, struct ChlpLicenseManager *a2, struct CWnd *a3) { CdlgLicenseStatus *result; // eax CDialog::CDialog(this, 0x5E10u, a3); *(_DWORD *)this = &CdlgLicenseStatus::`vftable'; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>((char *)this + 168); ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>((char *)this + 172); ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>((char *)this + 180); ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>((char *)this + 184); *((_DWORD *)this + 48) = a2; *(_QWORD *)((char *)this + 196) = 0i64; *((_DWORD *)this + 51) = 0; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=( (char *)this + 168, &word_58A186F8); ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=( (char *)this + 172, &word_58A186F8); ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=( (char *)this + 180, &word_58A186F8); result = this; *((_BYTE *)this + 188) = 0; *((_DWORD *)this + 44) = 0; return result; }
Это конструктор класса CdlgLicenseStatus. Сам класс и является тем самым диалогом активации. Интерес вызвал обработчик нажатия кнопки "Activate" — стандартный для таких случаев метод OnOK:
void __thiscall CdlgLicenseStatus::OnOK(const wchar_t **this) { if ( CWnd::UpdateData((CWnd *)this, 1) ) { CdlgLicenseStatus::WriteLicense(this); CdlgLicenseStatus::RefreshLicense((ChlpLicenseManager **)this); CDialog::EndDialog((CDialog *)this, 1); } }
Метод WriteLicense, судя по всему, записывает лицензию в донгл (аппаратный ключ). Реализовано это через вызов метода WriteSerialNumberToReg, который, судя по названию, пишет серийный номер в реестр.
void __thiscall CdlgLicenseStatus::WriteLicense(const wchar_t **this) { if ( *((_DWORD *)this[45] - 3) ) { CtypCfgConnEDongle::WriteSerialNumberToReg(L"License", L"Fallback URL", this[45]); } else if ( this[44] ) { CtypCfgConnEDongle::WriteSerialNumberToReg(L"License", L"Fallback URL", this[42]); if ( !*((_DWORD *)this[42] - 3) ) ChlpCustomerSubscription::ClearSubscription(); } CtypCfgConnEDongle::WriteSerialNumberToReg(L"License", L"Profile06", &word_58A186F8); }
Модуль MfcEx.dll
Класс CtypCfgConnEDongle находится в модуле MfcEx.dll. Что ж посмотрим, что же это за метод такой WriteSerialNumberToReg.
Метод WriteSerialNumberToReg:
int __cdecl CtypCfgConnEDongle::WriteSerialNumberToReg(const wchar_t *a1, const wchar_t *a2, wchar_t *a3) { struct AFX_MODULE_STATE *ModuleState; // eax int v4; // esi int v6; // [esp+0h] [ebp-F0h] BYREF char v7[8]; // [esp+D4h] [ebp-1Ch] BYREF int v8[2]; // [esp+DCh] [ebp-14h] BYREF int v9; // [esp+ECh] [ebp-4h] v8[1] = (int)&v6; v9 = 0; sub_10614C00(1); LOBYTE(v9) = 1; sub_10614D60((int)v8, a3); LOBYTE(v9) = 2; ModuleState = AfxGetModuleState(); v4 = (*(int (__thiscall **)(_DWORD, const wchar_t *, const wchar_t *, int))(**((_DWORD **)ModuleState + 1) + 144))( *((_DWORD *)ModuleState + 1), a1, a2, v8[0]); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v8); sub_10614C20(v7); return v4; }
В модуле MfcEx.dll нет вызовов RegWrite и подобных. Если продолжать отлаживать это в лоб, можно просто утонуть в абстракциях и потратить кучу време��и впустую. К счастью, есть программа ProcessMonitor, и она показала, что серийный номер пишется вовсе не в реестр, а в ini файл C:\ProgramData\Cardios\CardioMg\CardioMg.ini. А еще что ведутся интересные логи CardioMg.0.log и Dongle.00.0.log.
Логи CardioMg.0.log и Dongle.00.0.log
CardioMg.0.log:
2026-02-05 16:10:38 <MAIN:INFO > {ModulesBase.dll::CappMainBase::PreInitInstance @408}: _______________________START__________________________ 2026-02-05 16:10:38 <MAIN:INFO > {ModulesBase.dll::CappMainBase::PreInitInstance @409}: InitInstance: version 7.5.4.0 2026-02-05 16:10:39 <MAIN:WARN > {CardioLoop.cmm::DllMain @39}: License not found for CardioLoop Module. 2026-02-05 16:10:39 <MAIN:ERROR> {MultiCom.dll::CappMain::LoadModules @867}: The 'CardioLoop.cmm' library, located on the CardioManager® folder could not be loaded. Windows returned error code: 1114. 2026-02-05 16:10:40 <MAIN:WARN > {CardioNetMod.cmm::DllMain @45}: License not found for CardioNet Module. 2026-02-05 16:10:40 <MAIN:ERROR> {MultiCom.dll::CappMain::LoadModules @867}: The 'CardioNetMod.cmm' library, located on the CardioManager® folder could not be loaded. Windows returned error code: 1114. 2026-02-05 16:10:40 <MAIN:WARN > {Event.cmm::DllMain @43}: License not found for Event Module. 2026-02-05 16:10:40 <MAIN:ERROR> {MultiCom.dll::CappMain::LoadModules @867}: The 'Event.cmm' library, located on the CardioManager® folder could not be loaded. Windows returned error code: 1114. 2026-02-05 16:10:41 <MAIN:WARN > {Monitor.cmm::DllMain @42}: License not found for Monitor Module. 2026-02-05 16:10:41 <MAIN:ERROR> {MultiCom.dll::CappMain::LoadModules @867}: The 'Monitor.cmm' library, located on the CardioManager® folder could not be loaded. Windows returned error code: 1114. 2026-02-05 16:10:46 <MAIN:WARN > {ModulesBase.dll::CappMainBase::CheckLicenseState @849}: InitInstance: No license found 2026-02-05 16:10:46 <MAIN:INFO > {AppsBase.dll::CappLicensed::UserLoginLdap @74}: ldap_init succeeded 2026-02-05 16:10:46 <MAIN:INFO > {AppsBase.dll::CappLicensed::UserLoginLdap @81}: ldap_set_option succeeded - version set to 3 2026-02-05 16:10:46 <MAIN:INFO > {AppsBase.dll::CappLicensed::UserLoginLdap @106}: ldap_connect failed with 0x51. 2026-02-05 16:10:46 <MAIN:WARN > {MultiCom.dll::CappMain::AuthenticateUser @1835}: Skipping user authentication: No Ldap connection or users data 2026-02-05 16:10:46 <MAIN:INFO > {ModulesBase.dll::CappMainBase::CheckSwUpdate @897}: Checking software update 2026-02-05 16:10:46 <MAIN:INFO > {MultiCom.dll::CappMain::InitInstance @671}: Application CardioManager® started
Dongle.00.0.log:
10240609 F:\CSLibs\MfcEx\devDongleWeb.cpp 176: Dongle ID matches! 10241171 F:\CSLibs\MfcEx\devDongleWeb.cpp 587: Session ID: '77Kfc46acb4b4q1ab95d9cad022c7fea' failed with error code 1 Description: ""10241171 F:\CSLibs\MfcEx\hlpDongle.cpp 235: Dumping buf Buffer eJwzMFAwGEWjaBSNolFEX+Tmhg+ZmoFIAOmn8fA= 10241171 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 182: m_nLicenseState = licNotFound 10241171 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 207: m_nLicenseState = licNotFound (0x9DC8) 10241171 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 360: License state after ChlpLicenseManager::Read() is licNotFound 10241171 F:\CSLibs\MfcEx\hlpDongle.cpp 123: Failed to init loaded dongle 10241453 F:\CSLibs\MfcEx\devDongleWeb.cpp 176: Dongle ID matches! 10241453 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 508: ChlpLicenseManager::IsLicenseValid() 10241468 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 508: ChlpLicenseManager::IsLicenseValid() 10241468 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 764: ChlpLicenseManager::GetComponentLicenseLevel(CardioSmart Base) 10241468 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 508: ChlpLicenseManager::IsLicenseValid() 10396562 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 265: ChlpLicenseManager::Read() 10396562 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 162: ChlpLicenseManager::ReadDongle(int nAddr) 10397812 F:\CSLibs\MfcEx\devDongleWeb.cpp 587: Session ID: '93Qf6c8b41734k1e894edd27b5ege8ff' failed with error code 1 Description: ""10397812 F:\CSLibs\MfcEx\hlpDongle.cpp 235: Dumping buf Buffer eJwzMFAwGEWjaBSNolFEX+Tmhg+ZmoFIAOmn8fA= 10397812 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 182: m_nLicenseState = licNotFound 10397812 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 207: m_nLicenseState = licNotFound (0x9DC8) 10397812 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 360: License state after ChlpLicenseManager::Read() is licNotFound 10397812 F:\CSLibs\MfcEx\hlpDongle.cpp 123: Failed to init loaded dongle 10398078 F:\CSLibs\MfcEx\devDongleWeb.cpp 176: Dongle ID matches! 10398078 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 508: ChlpLicenseManager::IsLicenseValid() 10398078 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 508: ChlpLicenseManager::IsLicenseValid() 10398078 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 764: ChlpLicenseManager::GetComponentLicenseLevel(CardioSmart Base) 10398078 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 508: ChlpLicenseManager::IsLicenseValid() 10406015 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 508: ChlpLicenseManager::IsLicenseValid() 10406015 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 764: ChlpLicenseManager::GetComponentLicenseLevel(CardioSmart Base) 10406015 F:\CSLibs\MfcEx\hlpLicenseManager.cpp 508: ChlpLicenseManager::IsLicenseValid() 10406937 F:\CSLibs\MfcEx\devDongleWeb.cpp 230: Freeing Dongle 10406937 F:\CSLibs\MfcEx\devDongleWeb.cpp 230: Freeing Dongle
Ну это, блин, просто праздник какой-то — шикарный подгон от разработчиков. Остается просто пропатчить метод ChlpLicenseManager::IsLicenseValid так, чтобы он всегда возвращал true.
Логи четко указали на цель: основная проверка лицензии завязана на метод IsLicenseValid. Посмотрим, как он выглядит в коде модуля MfcEx.dll:
Метод ChlpLicenseManager::IsLicenseValid
.text:106183F0 ; bool __thiscall ChlpLicenseManager::IsLicenseValid(ChlpLicenseManager *this) .text:106183F0 public ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ .text:106183F0 ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ proc near .text:106183F0 ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+2A↑p .text:106183F0 ; DATA XREF: .rdata:off_1063C078↓o .text:106183F0 .text:106183F0 pvtime = qword ptr -40h .text:106183F0 var_34 = byte ptr -34h .text:106183F0 var_28 = byte ptr -28h .text:106183F0 var_1C = dword ptr -1Ch .text:106183F0 var_18 = byte ptr -18h .text:106183F0 var_10 = byte ptr -10h .text:106183F0 var_C = dword ptr -0Ch .text:106183F0 var_4 = dword ptr -4 .text:106183F0 arg_4 = dword ptr 0Ch .text:106183F0 .text:106183F0 ; FUNCTION CHUNK AT .text:10627EB2 SIZE 00000006 BYTES .text:106183F0 ; FUNCTION CHUNK AT .text:1062A810 SIZE 0000002C BYTES .text:106183F0 ; FUNCTION CHUNK AT .text:1062A841 SIZE 0000001D BYTES .text:106183F0 .text:106183F0 ; __unwind { // ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ_SEH .text:106183F0 push ebp .text:106183F1 mov ebp, esp .text:106183F3 push 0FFFFFFFFh .text:106183F5 push offset ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ_SEH .text:106183FA mov eax, large fs:0 .text:10618400 push eax .text:10618401 sub esp, 34h .text:10618404 push ebx .text:10618405 push esi .text:10618406 push edi .text:10618407 mov eax, ___security_cookie .text:1061840C xor eax, ebp .text:1061840E push eax .text:1061840F lea eax, [ebp+var_C] .text:10618412 mov large fs:0, eax .text:10618418 mov edi, ecx .text:1061841A cmp dword ptr [edi+20h], 0 .text:1061841E mov ebx, ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10618424 jz short loc_1061847C .text:10618426 cmp dword_1064B060, 0 .text:1061842D jz short loc_1061847C .text:1061842F cmp dword_1064CE48, 2710h .text:10618439 jg short loc_1061847C .text:1061843B lea ecx, [ebp+var_10] .text:1061843E call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:10618444 push 1FCh .text:10618449 lea eax, [ebp+var_10] .text:1061844C ; try { .text:1061844C mov [ebp+var_4], 0 .text:10618453 push offset aFCslibsMfcexHl_15 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:10618458 push eax .text:10618459 call ebx ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:1061845B add esp, 0Ch .text:1061845E lea eax, [ebp+var_10] .text:10618461 mov ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:10618466 push eax .text:10618467 call ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:1061846C lea ecx, [ebp+var_10] .text:1061846C ; } // starts at 1061844C .text:1061846F mov [ebp+var_4], 0FFFFFFFFh .text:10618476 call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:1061847C .text:1061847C loc_1061847C: ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+34↑j .text:1061847C ; ChlpLicenseManager::IsLicenseValid(void)+3D↑j ... .text:1061847C movzx eax, word ptr [edi+20h] .text:10618480 lea ecx, [ebp+var_28] ; this .text:10618483 push 1 ; int .text:10618485 push edi ; struct CSyncObject * .text:10618486 mov [ebp+var_1C], eax .text:10618489 call ??0CSingleLock@@QAE@PAVCSyncObject@@H@Z ; CSingleLock::CSingleLock(CSyncObject *,int) .text:1061848E mov esi, ds:GetTickCount .text:10618494 ; try { .text:10618494 mov [ebp+var_4], 1 .text:1061849B call esi ; GetTickCount .text:1061849D sub eax, [edi+2Ch] .text:106184A0 cmp eax, 0EA60h .text:106184A5 jbe loc_10618608 .text:106184AB call esi ; GetTickCount .text:106184AD mov [edi+2Ch], eax .text:106184B0 cmp dword_1064B060, 0 .text:106184B7 jz short loc_10618500 .text:106184B9 cmp dword_1064CE48, 2710h .text:106184C3 jg short loc_10618500 .text:106184C5 lea ecx, [ebp+var_10] .text:106184C8 call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:106184CE push 20Ah .text:106184D3 lea eax, [ebp+var_10] .text:106184D3 ; } // starts at 10618494 .text:106184D6 ; try { .text:106184D6 mov byte ptr [ebp+var_4], 2 .text:106184DA push offset aFCslibsMfcexHl_16 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:106184DF push eax .text:106184E0 call ebx ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:106184E2 add esp, 0Ch .text:106184E5 lea eax, [ebp+var_10] .text:106184E8 mov ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:106184ED push eax .text:106184EE call ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:106184F3 lea ecx, [ebp+var_10] .text:106184F3 ; } // starts at 106184D6 .text:106184F6 ; try { .text:106184F6 mov byte ptr [ebp+var_4], 1 .text:106184FA call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:10618500 .text:10618500 loc_10618500: ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+C7↑j .text:10618500 ; ChlpLicenseManager::IsLicenseValid(void)+D3↑j .text:10618500 mov ecx, [edi+20h] .text:10618503 test ecx, ecx .text:10618505 jnz loc_10618608 .text:1061850B mov ecx, dword_1064B044 .text:10618511 test ecx, ecx .text:10618513 jz short loc_10618523 .text:10618515 mov eax, [ecx] .text:10618517 mov eax, [eax+18h] .text:1061851A call eax .text:1061851C mov ecx, [edi+20h] .text:1061851F test al, al .text:10618521 jnz short loc_1061852F .text:10618523 .text:10618523 loc_10618523: ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+123↑j .text:10618523 mov dword ptr [edi+20h], 3 .text:1061852A jmp loc_10618608 .text:1061852F ; --------------------------------------------------------------------------- .text:1061852F .text:1061852F loc_1061852F: ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+131↑j .text:1061852F test ecx, ecx .text:10618531 jnz loc_10618608 .text:10618537 mov eax, [edi+1Ch] .text:1061853A mov ecx, 0FFFFh .text:1061853F cmp [eax+1D4h], cx .text:10618546 jz loc_10618608 .text:1061854C mov ecx, edi ; this .text:1061854E call ?IsExecTimeValid@ChlpLicenseManager@@QAE_NXZ ; ChlpLicenseManager::IsExecTimeValid(void) .text:10618553 test al, al .text:10618555 jz loc_10618601 .text:1061855B cmp byte ptr [edi+7ACh], 0 .text:10618562 jz short loc_106185B3 .text:10618564 cmp byte ptr [edi+7AFh], 0 .text:1061856B jz short loc_1061859F .text:1061856D lea eax, [edi+7A8h] .text:10618573 push eax .text:10618574 lea ecx, [ebp+var_10] .text:10618577 call ds:??0?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAE@ABV01@@Z ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:1061857D mov ecx, eax .text:1061857D ; } // starts at 106184F6 .text:1061857F ; try { .text:1061857F mov byte ptr [ebp+var_4], 3 .text:10618583 call ds:?Trim@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAEAAV12@XZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(void) .text:10618589 lea ecx, [ebp+var_10] .text:10618589 ; } // starts at 1061857F .text:1061858C ; try { .text:1061858C mov byte ptr [ebp+var_4], 1 .text:10618590 mov eax, [eax] .text:10618592 mov esi, [eax-0Ch] .text:10618595 call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:1061859B test esi, esi .text:1061859D jz short loc_106185B3 .text:1061859F .text:1061859F loc_1061859F: ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+17B↑j .text:1061859F mov ecx, edi ; this .text:106185A1 call ?CheckAndTryToExtendLicense@ChlpLicenseManager@@QAE_NXZ ; ChlpLicenseManager::CheckAndTryToExtendLicense(void) .text:106185A6 test al, al .text:106185A8 jnz short loc_10618608 .text:106185AA mov dword ptr [edi+20h], 3 .text:106185B1 jmp short loc_10618608 .text:106185B3 ; --------------------------------------------------------------------------- .text:106185B3 .text:106185B3 loc_106185B3: ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+172↑j .text:106185B3 ; ChlpLicenseManager::IsLicenseValid(void)+1AD↑j .text:106185B3 cmp dword ptr [edi+20h], 0 .text:106185B7 jnz short loc_10618608 .text:106185B9 mov ecx, [edi+1Ch] .text:106185BC lea eax, [ebp+var_18] .text:106185BF push eax .text:106185C0 call ?GetLastExecTime@CtypLicenseManager@@QBE?AVCOleDateTime@ATL@@XZ ; CtypLicenseManager::GetLastExecTime(void) .text:106185C5 lea eax, [ebp+var_18] .text:106185C8 push eax .text:106185C9 lea eax, [ebp+var_34] .text:106185CC push eax .text:106185CD lea eax, [ebp+pvtime] .text:106185D0 push eax ; pvtime .text:106185D1 call sub_10618060 .text:106185D6 mov ecx, eax .text:106185D8 call sub_10617660 .text:106185DD mov ecx, eax .text:106185DF call sub_10618080 .text:106185E4 fstp qword ptr [ebp-14h] .text:106185E7 movsd xmm0, qword ptr [ebp-14h] .text:106185EC comisd xmm0, ds:qword_106319D8 .text:106185F4 jb short loc_10618608 .text:106185F6 push 1 ; bool .text:106185F8 mov ecx, edi ; this .text:106185FA call ?Save@ChlpLicenseManager@@QAEX_N@Z ; ChlpLicenseManager::Save(bool) .text:106185FF jmp short loc_10618608 .text:10618601 ; --------------------------------------------------------------------------- .text:10618601 .text:10618601 loc_10618601: ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+165↑j .text:10618601 mov dword ptr [edi+20h], 2 .text:10618608 .text:10618608 loc_10618608: ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+B5↑j .text:10618608 ; ChlpLicenseManager::IsLicenseValid(void)+115↑j ... .text:10618608 mov eax, [ebp+var_1C] .text:1061860B movzx eax, ax .text:1061860E mov byte ptr [edi+30h], 1 .text:10618612 cmp [edi+20h], eax .text:10618615 jz short loc_1061866D .text:10618617 cmp dword_1064B060, 0 .text:1061861E jz short loc_1061866D .text:10618620 cmp dword_1064CE48, 2710h .text:1061862A jg short loc_1061866D .text:1061862C lea ecx, [ebp+var_10] .text:1061862F call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:10618635 mov eax, [edi+20h] .text:10618635 ; } // starts at 1061858C .text:10618638 ; try { .text:10618638 mov byte ptr [ebp+var_4], 4 .text:1061863C push off_1064B2F0[eax*4] ; "licValid" .text:10618643 lea eax, [ebp+var_10] .text:10618646 push 232h .text:1061864B push offset aFCslibsMfcexHl_17 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:10618650 push eax .text:10618651 call ebx ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10618653 add esp, 10h .text:10618656 lea eax, [ebp+var_10] .text:10618659 mov ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:1061865E push eax .text:1061865F call ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:10618664 lea ecx, [ebp+var_10] .text:10618667 call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:1061866D .text:1061866D loc_1061866D: ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+225↑j .text:1061866D ; ChlpLicenseManager::IsLicenseValid(void)+22E↑j ... .text:1061866D cmp dword ptr [edi+20h], 0 .text:10618671 setz bl .text:10618674 lea ecx, [ebp+var_28] ; this .text:10618674 ; } // starts at 10618638 .text:10618677 ; try { .text:10618677 mov [ebp+var_4], 5 .text:1061867E call ?Unlock@CSingleLock@@QAEHXZ ; CSingleLock::Unlock(void) .text:10618683 mov al, bl ; Меняем на mov al, 1 .text:10618685 mov ecx, [ebp+var_C] .text:10618688 mov large fs:0, ecx .text:1061868F pop ecx .text:10618690 pop edi .text:10618691 pop esi .text:10618692 pop ebx .text:10618693 mov esp, ebp .text:10618695 pop ebp .text:10618696 retn .text:10618696 ; } // starts at 10618677 .text:10618696 ; } // starts at 106183F0 .text:10618696 ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ endp
Инструкцию mov al, bl по адресу 10618683 заменил на mov al, 1
Таким образом какая бы лицензия не была всегда метод ChlpLicenseManager::IsLicenseValid вернет true.
Но нельзя просто так взять и пропатчить одну функцию — это было бы слишком просто, а на практике так не бывает. Метод-то возвращает успех, но из-за отсутствия физического донгла программа падает. Отладка показала: краш происходит в методе ChlpLicenseManager::GetComponentLicenseLevel
Метод ChlpLicenseManager::GetComponentLicenseLevel
.text:10617AE0 ; __unwind { // ?GetComponentLicenseLevel@ChlpLicenseManager@@QAEHW4enumComponentLM@@H@Z_SEH .text:10617AE0 push ebp .text:10617AE1 mov ebp, esp .text:10617AE3 push 0FFFFFFFFh .text:10617AE5 push offset ?GetComponentLicenseLevel@ChlpLicenseManager@@QAEHW4enumComponentLM@@H@Z_SEH .text:10617AEA mov eax, large fs:0 .text:10617AF0 push eax .text:10617AF1 sub esp, 20h .text:10617AF4 push ebx .text:10617AF5 push esi .text:10617AF6 push edi .text:10617AF7 mov eax, ___security_cookie .text:10617AFC xor eax, ebp .text:10617AFE push eax .text:10617AFF lea eax, [ebp+var_C] .text:10617B02 mov large fs:0, eax .text:10617B08 mov esi, ecx .text:10617B0A call ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ ; ChlpLicenseManager::IsLicenseValid(void) .text:10617B0F test al, 1 .text:10617B11 jnz short loc_10617B85 ; меняем на jz short loc_10617B85 .text:10617B13 cmp dword_1064B060, 0 .text:10617B1A jz short loc_10617B6F .text:10617B1C cmp dword_1064CE48, 2710h .text:10617B26 jg short loc_10617B6F .text:10617B28 lea ecx, [ebp+arg_4] .text:10617B2B call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:10617B31 push [ebp+ArgList] ; ArgList .text:10617B34 ; try { .text:10617B34 mov [ebp+var_4], 0 .text:10617B3B call sub_10619780 .text:10617B40 push eax .text:10617B41 push 2FCh .text:10617B46 lea eax, [ebp+arg_4] .text:10617B49 push offset aFCslibsMfcexHl_11 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:10617B4E push eax .text:10617B4F call ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10617B55 add esp, 14h .text:10617B58 lea eax, [ebp+arg_4] .text:10617B5B mov ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:10617B60 push eax .text:10617B61 call ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:10617B66 lea ecx, [ebp+arg_4] .text:10617B69 call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:10617B6F .text:10617B6F loc_10617B6F: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+3A↑j .text:10617B6F ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+46↑j .text:10617B6F xor eax, eax .text:10617B71 mov ecx, [ebp+var_C] .text:10617B74 mov large fs:0, ecx .text:10617B7B pop ecx .text:10617B7C pop edi .text:10617B7D pop esi .text:10617B7E pop ebx .text:10617B7F mov esp, ebp .text:10617B81 pop ebp .text:10617B82 retn 8 .text:10617B85 ; --------------------------------------------------------------------------- .text:10617B85 .text:10617B85 loc_10617B85: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+31↑j .text:10617B85 push 1 ; int .text:10617B87 push esi ; struct CSyncObject * .text:10617B88 lea ecx, [ebp+var_2C] ; this .text:10617B8B call ??0CSingleLock@@QAE@PAVCSyncObject@@H@Z ; CSingleLock::CSingleLock(CSyncObject *,int) .text:10617B90 mov eax, [esi+1Ch] .text:10617B93 lea ecx, [ebp+var_D] .text:10617B96 mov ebx, [ebp+ArgList] .text:10617B96 ; } // starts at 10617B34 .text:10617B99 ; try { .text:10617B99 mov [ebp+var_4], 1 .text:10617BA0 movzx eax, byte ptr [eax+ebx] ; Падает вот здесь .text:10617BA4 push eax .text:10617BA5 call ??4CtypComponentLicense@@QAEAAV0@E@Z ; CtypComponentLicense::operator=(uchar) .text:10617BAA mov al, [ebp+var_D] .text:10617BAD mov edi, [ebp+arg_4] .text:10617BB0 movzx ecx, al .text:10617BB3 and ecx, 0Fh .text:10617BB6 cmp ecx, edi .text:10617BB8 jge loc_10617C66 .text:10617BBE mov eax, dword_1064B060 .text:10617BC3 mov esi, ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10617BC9 test eax, eax .text:10617BCB jz loc_10617D26 .text:10617BD1 cmp dword_1064CE48, 2710h .text:10617BDB jg short loc_10617C20 .text:10617BDD lea ecx, [ebp+ArgList] .text:10617BE0 call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:10617BE6 push ebx ; ArgList .text:10617BE6 ; } // starts at 10617B99 .text:10617BE7 ; try { .text:10617BE7 mov byte ptr [ebp+var_4], 2 .text:10617BEB call sub_10619780 .text:10617BF0 push eax .text:10617BF1 push 309h .text:10617BF6 lea eax, [ebp+ArgList] .text:10617BF9 push offset aFCslibsMfcexHl_11 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:10617BFE push eax .text:10617BFF call esi ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10617C01 add esp, 14h .text:10617C04 lea eax, [ebp+ArgList] .text:10617C07 mov ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:10617C0C push eax .text:10617C0D call ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:10617C12 lea ecx, [ebp+ArgList] .text:10617C15 call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:10617C1B mov eax, dword_1064B060 .text:10617C20 .text:10617C20 loc_10617C20: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+FB↑j .text:10617C20 test eax, eax .text:10617C22 jz loc_10617D26 .text:10617C28 cmp dword_1064CE48, 2710h .text:10617C32 jg loc_10617D26 .text:10617C38 lea ecx, [ebp+ArgList] .text:10617C3B call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:10617C41 movzx eax, [ebp+var_D] .text:10617C45 push edi .text:10617C46 and eax, 0Fh .text:10617C46 ; } // starts at 10617BE7 .text:10617C49 ; try { .text:10617C49 mov byte ptr [ebp+var_4], 3 .text:10617C4D push eax .text:10617C4E push 30Bh .text:10617C53 lea eax, [ebp+ArgList] .text:10617C56 push offset aFCslibsMfcexHl_12 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:10617C5B push eax .text:10617C5C call esi ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10617C5E add esp, 14h .text:10617C61 jmp loc_10617D0F .text:10617C66 ; --------------------------------------------------------------------------- .text:10617C66 .text:10617C66 loc_10617C66: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+D8↑j .text:10617C66 test al, 10h .text:10617C68 jz loc_10617D30 .text:10617C6E mov ecx, esi ; this .text:10617C70 call ?HasLicenseExpired@ChlpLicenseManager@@QBE_NXZ ; ChlpLicenseManager::HasLicenseExpired(void) .text:10617C75 test al, al .text:10617C77 jz loc_10617D2D .text:10617C7D mov eax, dword_1064B060 .text:10617C82 mov esi, ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10617C88 test eax, eax .text:10617C8A jz loc_10617D26 .text:10617C90 cmp dword_1064CE48, 2710h .text:10617C9A jg short loc_10617CDF .text:10617C9C lea ecx, [ebp+ArgList] .text:10617C9F call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:10617CA5 push ebx ; ArgList .text:10617CA5 ; } // starts at 10617C49 .text:10617CA6 ; try { .text:10617CA6 mov byte ptr [ebp+var_4], 5 .text:10617CAA call sub_10619780 .text:10617CAF push eax .text:10617CB0 push 310h .text:10617CB5 lea eax, [ebp+ArgList] .text:10617CB8 push offset aFCslibsMfcexHl_11 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:10617CBD push eax .text:10617CBE call esi ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10617CC0 add esp, 14h .text:10617CC3 lea eax, [ebp+ArgList] .text:10617CC6 mov ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:10617CCB push eax .text:10617CCC call ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:10617CD1 lea ecx, [ebp+ArgList] .text:10617CD4 call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:10617CDA mov eax, dword_1064B060 .text:10617CDF .text:10617CDF loc_10617CDF: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+1BA↑j .text:10617CDF test eax, eax .text:10617CE1 jz short loc_10617D26 .text:10617CE3 cmp dword_1064CE48, 2710h .text:10617CED jg short loc_10617D26 .text:10617CEF lea ecx, [ebp+ArgList] .text:10617CF2 call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:10617CF8 push 311h .text:10617CFD lea eax, [ebp+ArgList] .text:10617CFD ; } // starts at 10617CA6 .text:10617D00 ; try { .text:10617D00 mov byte ptr [ebp+var_4], 6 .text:10617D04 push offset aFCslibsMfcexHl_13 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:10617D09 push eax .text:10617D0A call esi ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10617D0C add esp, 0Ch .text:10617D0F .text:10617D0F loc_10617D0F: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+181↑j .text:10617D0F lea eax, [ebp+ArgList] .text:10617D12 mov ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:10617D17 push eax .text:10617D18 call ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:10617D1D lea ecx, [ebp+ArgList] .text:10617D20 call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:10617D26 .text:10617D26 loc_10617D26: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+EB↑j .text:10617D26 ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+142↑j ... .text:10617D26 xor ebx, ebx .text:10617D28 jmp loc_10617E8E .text:10617D2D ; --------------------------------------------------------------------------- .text:10617D2D .text:10617D2D loc_10617D2D: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+197↑j .text:10617D2D mov al, [ebp+var_D] .text:10617D30 .text:10617D30 loc_10617D30: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+188↑j .text:10617D30 mov edi, [ebp+ArgList] .text:10617D33 movzx ebx, al .text:10617D36 shr ebx, 5 .text:10617D39 inc ebx .text:10617D3A lea eax, [edi+0Dh] .text:10617D3D mov [ebp+var_1C], ebx .text:10617D40 lea eax, [esi+eax*4] .text:10617D43 mov [ebp+var_20], eax .text:10617D46 cmp [eax], ebx .text:10617D48 jz loc_10617E8E .text:10617D4E mov eax, dword_1064B060 .text:10617D53 test eax, eax .text:10617D55 jz loc_10617E89 .text:10617D5B cmp dword_1064CE48, 2710h .text:10617D65 jg short loc_10617DAE .text:10617D67 lea ecx, [ebp+arg_4] .text:10617D6A call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:10617D70 push edi ; ArgList .text:10617D70 ; } // starts at 10617D00 .text:10617D71 ; try { .text:10617D71 mov byte ptr [ebp+var_4], 8 .text:10617D75 call sub_10619780 .text:10617D7A push eax .text:10617D7B push 319h .text:10617D80 lea eax, [ebp+arg_4] .text:10617D83 push offset aFCslibsMfcexHl_11 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:10617D88 push eax .text:10617D89 call ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10617D8F add esp, 14h .text:10617D92 lea eax, [ebp+arg_4] .text:10617D95 mov ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:10617D9A push eax .text:10617D9B call ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:10617DA0 lea ecx, [ebp+arg_4] .text:10617DA3 call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:10617DA9 mov eax, dword_1064B060 .text:10617DAE .text:10617DAE loc_10617DAE: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+285↑j .text:10617DAE test eax, eax .text:10617DB0 jz loc_10617E89 .text:10617DB6 cmp dword_1064CE48, 2710h .text:10617DC0 jg loc_10617E89 .text:10617DC6 lea ecx, [ebp+var_18] .text:10617DC9 call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:10617DCF movzx edi, [ebp+var_D] .text:10617DD3 lea ecx, [ebp+arg_4+3] ; this .text:10617DD6 xor esi, esi .text:10617DD6 ; } // starts at 10617D71 .text:10617DD8 ; try { .text:10617DD8 mov byte ptr [ebp+var_4], 9 .text:10617DDC shr edi, 5 .text:10617DDF mov [ebp+var_14], esi .text:10617DE2 call ?GetSize@CarrComponents@@QBEHXZ ; CarrComponents::GetSize(void) .text:10617DE7 test eax, eax .text:10617DE9 jle short loc_10617E41 .text:10617DEB mov ebx, offset ?m_arrComponents@CarrComponents@@1QBUCtypDongleComponents@@B ; CtypDongleComponents const * const CarrComponents::m_arrComponents .text:10617DF0 .text:10617DF0 loc_10617DF0: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+35C↓j .text:10617DF0 mov eax, [ebp+ArgList] .text:10617DF3 cmp [ebx], eax .text:10617DF5 jnz short loc_10617E2B .text:10617DF7 mov ecx, [ebx+10h] .text:10617DFA xor eax, eax .text:10617DFC mov esi, offset Buffer .text:10617E01 cmp [ecx], ax .text:10617E04 jz short loc_10617E28 .text:10617E06 .text:10617E06 loc_10617E06: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+346↓j .text:10617E06 cmp eax, edi .text:10617E08 jg short loc_10617E28 .text:10617E0A movzx edx, word ptr [ecx] .text:10617E0D cmp edx, 0Ah .text:10617E10 jnz short loc_10617E15 .text:10617E12 inc eax .text:10617E13 jmp short loc_10617E1F .text:10617E15 ; --------------------------------------------------------------------------- .text:10617E15 .text:10617E15 loc_10617E15: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+330↑j .text:10617E15 cmp eax, edi .text:10617E17 jnz short loc_10617E1F .text:10617E19 mov [esi], dx .text:10617E1C add esi, 2 .text:10617E1F .text:10617E1F loc_10617E1F: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+333↑j .text:10617E1F ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+337↑j .text:10617E1F add ecx, 2 .text:10617E22 cmp word ptr [ecx], 0 .text:10617E26 jnz short loc_10617E06 .text:10617E28 .text:10617E28 loc_10617E28: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+324↑j .text:10617E28 ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+328↑j .text:10617E28 mov esi, [ebp+var_14] .text:10617E2B .text:10617E2B loc_10617E2B: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+315↑j .text:10617E2B inc esi .text:10617E2C lea ecx, [ebp+arg_4+3] ; this .text:10617E2F mov [ebp+var_14], esi .text:10617E32 add ebx, 14h .text:10617E35 call ?GetSize@CarrComponents@@QBEHXZ ; CarrComponents::GetSize(void) .text:10617E3A cmp esi, eax .text:10617E3C jl short loc_10617DF0 .text:10617E3E mov ebx, [ebp+var_1C] .text:10617E41 .text:10617E41 loc_10617E41: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+309↑j .text:10617E41 lea eax, [edi+1] .text:10617E44 push eax ; ArgList .text:10617E45 push offset aD ; "%d" .text:10617E4A push 18h ; BufferCount .text:10617E4C push offset Buffer ; Buffer .text:10617E51 call sub_106197F0 .text:10617E56 push offset Buffer .text:10617E5B push 31Ah .text:10617E60 lea eax, [ebp+var_18] .text:10617E63 push offset aFCslibsMfcexHl_14 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"... .text:10617E68 push eax .text:10617E69 call ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:10617E6F add esp, 20h .text:10617E72 lea eax, [ebp+var_18] .text:10617E75 mov ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:10617E7A push eax .text:10617E7B call ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:10617E80 lea ecx, [ebp+var_18] .text:10617E83 call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:10617E89 .text:10617E89 loc_10617E89: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+275↑j .text:10617E89 ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+2D0↑j ... .text:10617E89 mov eax, [ebp+var_20] .text:10617E8C mov [eax], ebx .text:10617E8E .text:10617E8E loc_10617E8E: ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+248↑j .text:10617E8E ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+268↑j .text:10617E8E lea ecx, [ebp+var_2C] ; this .text:10617E8E ; } // starts at 10617DD8 .text:10617E91 ; try { .text:10617E91 mov [ebp+var_4], 0Ah .text:10617E98 call ?Unlock@CSingleLock@@QAEHXZ ; CSingleLock::Unlock(void) .text:10617E9D mov eax, ebx .text:10617E9F mov ecx, [ebp+var_C] .text:10617EA2 mov large fs:0, ecx .text:10617EA9 pop ecx .text:10617EAA pop edi .text:10617EAB pop esi .text:10617EAC pop ebx .text:10617EAD mov esp, ebp .text:10617EAF pop ebp .text:10617EB0 retn 8 .text:10617EB0 ; } // starts at 10617E91 .text:10617EB0 ; } // starts at 10617AE0 .text:10617EB0 ?GetComponentLicenseLevel@ChlpLicenseManager@@QAEHW4enumComponentLM@@H@Z endp
Патч получился такой:
Инструкцию jnz short loc_10617B85 по адресу 10617B11 заменил на jz short loc_10617B85.
Инверсия условия позволила проскочить участок с неинициализированными данными, и программа перестала падать. В диалоге активации я ввел произвольный серийный номер. После нажатия "Activate" приложение попросило перезапуск.
После рестарта диалог исчез, и открылось главное окно. Я откатил патч, и программа продолжила запускаться штатно.
Импорт .xcm файла и снова проверка лицензии
Казалось бы победа! Но рано радоваться: при попытке импортировать файл с результатами холтера вылезло новое сообщение:

Понятно где-то есть еще одна проверка лицензии. Я глянул лог CardioMg.0.log и увидел там:
2026-02-11 16:45:43 <MAIN:WARN > {MultiCom.dll::CappMain::ImportExam @1763}: Exception occurred [CxcpCardioMg]: This operation is not supported in this Demo version or license is over. Contact our Sales Department for more information on this product @ (55)(11)3883-3010. Exception intercepted! Rethrowing.
Отлично! Исключение обрабатывается в методе CappMain::ImportExam из модуля MultiCom.dll. По стеку видно что исключение было брошено из метода CdtMultiDocTemplate::ThrowIfNotExample:
Address Module Function 0000000076E40D72 KERNELBASE.dll kernelbase_RaiseException+62 00000000753B4971 VCRUNTIME140.dll vcruntime140__CxxThrowException+61 000000001111B905 MultiCom.dll public: virtual void __thiscall CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+0xE5 00000000111058A4 MultiCom.dll public: void __thiscall CappMain::ImportExam(class CStringArray const &)+0x564
Методы CdtMultiDocTemplate::ThrowIfNotExample и CdtCardioMg::AssertForExample
Метод CdtMultiDocTemplate::ThrowIfNotExample:
; void __thiscall CdtMultiDocTemplate::ThrowIfNotExample(CdtMultiDocTemplate *this, wchar_t *) .text:1111B820 public ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z .text:1111B820 ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z proc near .text:1111B820 ; DATA XREF: .rdata:1113B698↓o .text:1111B820 ; .rdata:off_11146E98↓o .text:1111B820 .text:1111B820 pExceptionObject= dword ptr -10h .text:1111B820 var_C = dword ptr -0Ch .text:1111B820 var_4 = dword ptr -4 .text:1111B820 arg_0 = dword ptr 8 .text:1111B820 arg_4 = dword ptr 0Ch .text:1111B820 .text:1111B820 ; FUNCTION CHUNK AT .text:11132890 SIZE 00000013 BYTES .text:1111B820 ; FUNCTION CHUNK AT .text:111328A8 SIZE 0000001D BYTES .text:1111B820 .text:1111B820 ; __unwind { // ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z_SEH .text:1111B820 push ebp .text:1111B821 mov ebp, esp .text:1111B823 push 0FFFFFFFFh .text:1111B825 push offset ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z_SEH .text:1111B82A mov eax, large fs:0 .text:1111B830 push eax .text:1111B831 push ecx .text:1111B832 push esi .text:1111B833 mov eax, ___security_cookie .text:1111B838 xor eax, ebp .text:1111B83A push eax .text:1111B83B lea eax, [ebp+var_C] .text:1111B83E mov large fs:0, eax .text:1111B844 mov esi, ecx .text:1111B846 mov eax, [esi] .text:1111B848 push [ebp+arg_0] .text:1111B84B mov eax, [eax+0A4h] .text:1111B851 call eax ; Вызов CdtCardioMg::AssertForExample из модуля ModulesBase.dll .text:1111B853 test al, al .text:1111B855 jnz short loc_1111B8B5 .text:1111B857 cmp dword ptr [esi+0B0h], 1 .text:1111B85E jnz short loc_1111B8C7 .text:1111B860 call sub_1111B920 .text:1111B865 mov ecx, ds:?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:1111B86B call ds:?CanLog@ChlpDongle@@QAE_NXZ ; ChlpDongle::CanLog(void) .text:1111B871 test al, al .text:1111B873 jz short loc_1111B8B5 .text:1111B875 lea ecx, [ebp+arg_0] .text:1111B878 call ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void) .text:1111B87E push 0A2h .text:1111B883 lea eax, [ebp+arg_0] .text:1111B886 ; try { .text:1111B886 mov [ebp+var_4], 1 .text:1111B88D push offset aFCardiomgModul ; "F:\\CardioMg\\ModulesBase\\dtCardioMg.h"... .text:1111B892 push eax .text:1111B893 call ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) .text:1111B899 mov ecx, ds:?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice .text:1111B89F lea eax, [ebp+arg_0] .text:1111B8A2 add esp, 0Ch .text:1111B8A5 push eax .text:1111B8A6 call ds:?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &) .text:1111B8AC lea ecx, [ebp+arg_0] .text:1111B8AF call ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void) .text:1111B8B5 .text:1111B8B5 loc_1111B8B5: ; CODE XREF: CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+35↑j .text:1111B8B5 ; CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+53↑j .text:1111B8B5 mov ecx, [ebp+var_C] .text:1111B8B8 mov large fs:0, ecx .text:1111B8BF pop ecx .text:1111B8C0 pop esi .text:1111B8C1 mov esp, ebp .text:1111B8C3 pop ebp .text:1111B8C4 retn 4 .text:1111B8C7 ; --------------------------------------------------------------------------- .text:1111B8C7 .text:1111B8C7 loc_1111B8C7: ; CODE XREF: CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+3E↑j .text:1111B8C7 push 10h .text:1111B8C9 call sub_11102090 .text:1111B8CE mov esi, eax .text:1111B8D0 mov [ebp+arg_0], esi .text:1111B8D0 ; } // starts at 1111B886 .text:1111B8D3 ; try { .text:1111B8D3 mov [ebp+var_4], 0 .text:1111B8DA test esi, esi .text:1111B8DC jz short loc_1111B8F0 .text:1111B8DE push 0Eh .text:1111B8E0 mov ecx, esi .text:1111B8E2 call ds:??0CxcpCardioMg@@QAE@H@Z ; CxcpCardioMg::CxcpCardioMg(int) .text:1111B8E8 mov dword ptr [esi], offset ??_7CxcpCardioMg@@6B@ ; const CxcpCardioMg::`vftable' .text:1111B8EE jmp short loc_1111B8F2 .text:1111B8F0 ; --------------------------------------------------------------------------- .text:1111B8F0 .text:1111B8F0 loc_1111B8F0: ; CODE XREF: CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+BC↑j .text:1111B8F0 xor esi, esi .text:1111B8F2 .text:1111B8F2 loc_1111B8F2: ; CODE XREF: CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+CE↑j .text:1111B8F2 push offset __TI4PAVCxcpCardioMg@@ ; pThrowInfo .text:1111B8F7 lea eax, [ebp+pExceptionObject] .text:1111B8F7 ; } // starts at 1111B8D3 .text:1111B8FA mov [ebp+var_4], 0FFFFFFFFh .text:1111B901 push eax ; pExceptionObject .text:1111B902 mov [ebp+pExceptionObject], esi .text:1111B905 call _CxxThrowException .text:1111B905 ; } // starts at 1111B820 .text:1111B905 ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z endp
Метод CdtCardioMg::AssertForExample:
.text:00CC8A40 ; bool __thiscall CdtCardioMg::AssertForExample(CdtCardioMg *__hidden this, const wchar_t *) .text:00CC8A40 public ?AssertForExample@CdtCardioMg@@UAE_NPB_W@Z .text:00CC8A40 ?AssertForExample@CdtCardioMg@@UAE_NPB_W@Z proc near .text:00CC8A40 ; DATA XREF: .rdata:00CD0BD4↓o .text:00CC8A40 ; .rdata:off_CD4228↓o .text:00CC8A40 .text:00CC8A40 arg_0 = dword ptr 8 .text:00CC8A40 .text:00CC8A40 push ebp .text:00CC8A41 mov ebp, esp .text:00CC8A43 mov eax, [ebp+arg_0] .text:00CC8A46 cmp word ptr [eax], 58h ; 'X' .text:00CC8A4A jnz short loc_CC8A66 .text:00CC8A4C add eax, 6 .text:00CC8A4F push eax ; String .text:00CC8A50 call ds:_wtoi .text:00CC8A56 add esp, 4 .text:00CC8A59 cmp eax, 26DEh .text:00CC8A5E jl short loc_CC8A66 .text:00CC8A60 mov al, 1 .text:00CC8A62 pop ebp .text:00CC8A63 retn 4 .text:00CC8A66 ; --------------------------------------------------------------------------- .text:00CC8A66 .text:00CC8A66 loc_CC8A66: ; CODE XREF: CdtCardioMg::AssertForExample(wchar_t const *)+A↑j .text:00CC8A66 ; CdtCardioMg::AssertForExample(wchar_t const *)+1E↑j .text:00CC8A66 xor al, al ; Меняем на mov al, 1 .text:00CC8A68 pop ebp .text:00CC8A69 retn 4
Метод CdtMultiDocTemplate::ThrowIfNotExample вызывает метод CdtCardioMg::AssertForExampleи при результате равным true бросает исключение. Я пропатчил метод CdtCardioMg::AssertForExample так: заменил инструкцию xor al, al по адресу 00CC8A66на mov al, 1
С помощью этого патча импорт наконец-то завершился успешно!
Открытие импортированного документа или снова хождение по мукам
Но опять рано радоваться: при попытке открыть импортированный документ появилось еще одно сообщение:

Я снова заглянул в лог CardioMg.0.log и увидел там:
2026-02-11 17:53:33 <MAIN:WARN > {MultiCom.dll::CfrmMain::OnFileOpen @352}: Exception occurred [CxcpDongle]: Operation not permitted. Check the license validity or a fail with the dongle hardware. Trying to recover license.
Ага, значит исключение обрабатывается в методе CfrmMain::OnFileOpen модуля MultiCom.dll. Гдянул стек вызовов:
Address Module Function 0000000076E40D72 KERNELBASE.dll kernelbase_RaiseException+62 00000000753B4971 VCRUNTIME140.dll vcruntime140__CxxThrowException+61 000000001241CC45 Holter.cmm holter_?OnOpenDocument@CdocHolter@@UAEHPB_W@Z+5E5 000000005964D545 mfc140u.dll mfc140u_11979+125 000000005964D411 mfc140u.dll mfc140u_11978+21 000000001111B5CF MultiCom.dll public: virtual class CDocument * __thiscall CdtMultiDocTemplate::OpenDocumentFile(wchar_t const *,int)+0xCF 0000000011107B69 MultiCom.dll public: class CDocument * __thiscall CappMain::OpenDocTemplate(unsigned int,wchar_t const *)+0x39 000000001111E856 MultiCom.dll protected: void __thiscall CfrmMain::OnFileOpen(void)+0x106
Судя по стеку исключение выбрасывается в методе CdocHolter::OnOpenDocument из модуля Holter.cmm
Метод CdocHolter::OnOpenDocument
int __thiscall CdocHolter::OnOpenDocument(CdocHolter *this, const wchar_t *a2) { int App; // eax int TotalOpenedMasterDocs; // esi struct AFX_MODULE_STATE *ModuleState; // eax int v6; // edx ChlpDongle *v7; // ecx int v8; // edx int v9; // eax int v10; // ecx ChlpLicenseManager *v11; // esi int DeviceName; // eax char v13; // bl int v14; // eax int Schema; // eax HCURSOR CursorW; // eax int v17; // eax CdtHolter *v18; // esi const wchar_t *v19; // eax struct CWinThread *Thread; // ecx int v21; // eax CdbCamX *v22; // esi int v23; // ebx int PreviousMdbVersion; // eax unsigned __int8 v25; // bl char v26; // al int v27; // ecx const wchar_t *v29; // eax int v30; // eax const wchar_t *v31; // eax wchar_t *v32; // eax wchar_t *v33; // esi wchar_t *v34; // eax wchar_t *v35; // esi int v36; // [esp+0h] [ebp-108h] BYREF int pExceptionObject[2]; // [esp+D8h] [ebp-30h] BYREF char v38[4]; // [esp+E0h] [ebp-28h] BYREF wchar_t *v39; // [esp+E4h] [ebp-24h] BYREF int v40; // [esp+E8h] [ebp-20h] char v41[4]; // [esp+F0h] [ebp-18h] BYREF int v42[2]; // [esp+F4h] [ebp-14h] BYREF int v43; // [esp+104h] [ebp-4h] v42[1] = (int)&v36; pExceptionObject[1] = (int)this; v40 = 0; App = CappMain::GetApp(); if ( ChlpLicenseManager::GetComponentLicenseLevel(App + 332, 8, 1) <= 0 ) { if ( ChlpDongle::CanLog(g_DongleDevice) ) { ((void (__thiscall *)(const wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&a2); v43 = 0; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format( &a2, L"F:\\CardioSmart\\Holter\\docHolter.cpp%6d: Platform level is unlicensed/expired.\n", 1310); ChlpDongle::LogString(g_DongleDevice, &a2); v43 = -1; ((void (__thiscall *)(const wchar_t **))CMFCRibbonInfo::XID::~XID)(&a2); } v34 = (wchar_t *)sub_1240D1B0(0xCu); v35 = v34; a2 = v34; v43 = 1; if ( v34 ) { CxcpDongle::CxcpDongle((CxcpDongle *)v34, 0); *(_DWORD *)v35 = &CxcpDongle::`vftable'; } else { v35 = 0; } v43 = -1; v39 = v35; CxxThrowException(&v39, (_ThrowInfo *)&_TI4PAVCxcpDongle__); } TotalOpenedMasterDocs = CdocBase::GetTotalOpenedMasterDocs( this, (const struct CRuntimeClass *)&CdocHolter::classCdocHolter); ModuleState = AfxGetModuleState(); if ( TotalOpenedMasterDocs > (*(int (__thiscall **)(_DWORD, const wchar_t *, const wchar_t *, int))(**((_DWORD **)ModuleState + 1) + 132))( *((_DWORD *)ModuleState + 1), L"Holter", L"MaxOpenExams", 7) && AfxMessageBox(0x844u, 0x104u, 0xFFFFFFFF) != 6 || !CdocBase::OnOpenDocument(this, a2) ) { return 0; } CUIntArray::SetAtGrow( (CdocHolter *)((char *)this + 2560), *((_DWORD *)this + 642), (CdocHolter *)((char *)this + 2584) != 0 ? (unsigned int)this + 5304 : 0); v6 = *((_DWORD *)this + 270); if ( v6 ) CUIntArray::SetAtGrow( (CdocHolter *)((char *)this + 2560), *((_DWORD *)this + 642), *(_DWORD *)(v6 + 844) != 0 ? *(_DWORD *)(v6 + 844) + 360 : 0); v7 = (ChlpDongle *)g_DongleDevice; v8 = *((_DWORD *)&g_DongleDevice + 1); if ( v8 ) { (*(void (__thiscall **)(_DWORD))(*(_DWORD *)v8 + 28))(*((_DWORD *)&g_DongleDevice + 1)); v7 = (ChlpDongle *)g_DongleDevice; } if ( ChlpDongle::CanLog(v7) ) { ((void (__thiscall *)(const wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&a2); v43 = 2; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format( &a2, L"F:\\CardioSmart\\Holter\\docHolter.h%6d: Dongle is present: let's test validity.\n", 755); ChlpDongle::LogString(g_DongleDevice, &a2); v43 = -1; ((void (__thiscall *)(const wchar_t **))CMFCRibbonInfo::XID::~XID)(&a2); } v9 = CappMain::GetApp(); v10 = *(_DWORD *)(v9 + 360); if ( !v10 || !*(_DWORD *)(v10 + 474) ) goto LABEL_47; v11 = (ChlpLicenseManager *)(v9 + 332); DeviceName = ChlpLicenseManager::GetDeviceName(v9 + 332, v41); v13 = 1; v43 = 3; v40 = 1; if ( ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CompareNoCase( DeviceName, L"Compact-500") && (v14 = ChlpLicenseManager::GetDeviceName(v11, v38), v13 = 3, v43 = 4, v40 = 3, ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CompareNoCase(v14, L"NeoKey")) || (Schema = ChlpDongle::GetSchema(g_DongleDevice), HIBYTE(a2) = 1, Schema >= 1) ) { HIBYTE(a2) = 0; } if ( (v13 & 2) != 0 ) { v13 &= ~2u; ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v38); } v43 = -1; if ( (v13 & 1) != 0 ) ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v41); if ( HIBYTE(a2) || !ChlpLicenseManager::IsLicenseValid(v11) ) { LABEL_47: v32 = (wchar_t *)sub_1240D1B0(0xCu); v33 = v32; a2 = v32; v43 = 5; if ( v32 ) { CxcpDongle::CxcpDongle((CxcpDongle *)v32, 0); *(_DWORD *)v33 = &CxcpDongle::`vftable'; } else { v33 = 0; } v43 = -1; pExceptionObject[0] = (int)v33; CxxThrowException(pExceptionObject, (_ThrowInfo *)&_TI4PAVCxcpDongle__); } CursorW = LoadCursorW(0, (LPCWSTR)0x7F02); v42[0] = (int)SetCursor(CursorW); v17 = *(_DWORD *)this; v18 = (CdtHolter *)*((_DWORD *)this + 10); v43 = 7; v19 = (const wchar_t *)(*(int (__thiscall **)(CdocHolter *))(v17 + 304))(this); CdtHolter::AddExamInstance(v18, v19, this); CdocHolter::ConfigureWorkDBFolder(this); CdocHolter::InitExamParams(this, v41); if ( *((_BYTE *)this + 1141) ) { Thread = AfxGetThread(); if ( Thread ) v21 = (*(int (__thiscall **)(struct CWinThread *))(*(_DWORD *)Thread + 124))(Thread); else v21 = 0; v22 = (CdocHolter *)((char *)this + 1168); v23 = *(_DWORD *)CdbCamX::CheckOpen((char *)this + 1168, &a2, v21); } else { v23 = 4; v22 = (CdocHolter *)((char *)this + 1168); } a2 = (const wchar_t *)v23; if ( (v23 & 0x20) != 0 ) { PreviousMdbVersion = CdbCamX::GetPreviousMdbVersion(v22); CdtHolter::AdjustPluginsSettings( *((CdtHolter **)this + 10), (CdocHolter *)((char *)this + 5312), PreviousMdbVersion, 19); v25 = v23 & 0xDF; } else { v25 = (unsigned __int8)a2; } v26 = v25 | v41[0]; if ( ((v25 | v41[0]) & 1) != 0 ) { LABEL_39: ChlpWaitCursor::~ChlpWaitCursor((ChlpWaitCursor *)v42); return 0; } if ( *((_BYTE *)this + 1141) && (v26 & 4) != 0 ) { LOBYTE(v43) = 8; CdocHolter::CallCAMEx(this, 0); v27 = *((_DWORD *)this + 538); if ( (!v27 || ChlpPath::Exists((ChlpPath *)(v27 + 88))) && (*(_BYTE *)CdbCamX::CheckCamMdbVersion(v22, &a2, 0, 0) & 0x1F) != 0 && AfxMessageBox(0x79Fu, 0x31u, 0xFFFFFFFF) != 1 ) { goto LABEL_39; } v43 = 7; } else if ( (v26 & 8) != 0 ) { v29 = (const wchar_t *)(*(int (__thiscall **)(CdocHolter *))(*(_DWORD *)this + 304))(this); CdtHolter::DeleteFilesFromExam(v29, 1); *((_BYTE *)this + 1133) = 1; v30 = sub_124142C0(); PostMessageW(*(HWND *)(v30 + 32), 0x111u, 0x8833u, 0); } else if ( (v26 & 0x10) != 0 ) { v31 = (const wchar_t *)(*(int (__thiscall **)(CdocHolter *))(*(_DWORD *)this + 304))(this); CdtHolter::DeleteFilesFromExam(v31, 1); } CdocHolter::Init24hsGraphics(this); CdocHolter::AttachFilesToDoc(this); ChlpWaitCursor::~ChlpWaitCursor((ChlpWaitCursor *)v42); return 1; }
Я пропатчил метод ChlpLicenseManager::GetComponentLicenseLevel чтобы он возращал 1 если второй аргумент равен 8
Почему именно так? Потому что, если тупо возвращать 1 для всех вызовов этого метода, программа решит, что активирована лицензия для ветеринаров. Интерфейс перестроится, и все кнопки для работы с человеческой ЭКГ станут недоступны.
Патч в итоге получился такой:
.text:10617BAD mov edi, 8 .text:10617BB2 jmp short loc_10617BB6 ; Прыгаем через мусор .text:10617BB2 ; --------------------------------------------------------------------------- .text:10617BB4 db 0E1h .text:10617BB5 db 0Fh .text:10617BB6 ; --------------------------------------------------------------------------- .text:10617BB6 .text:10617BB6 loc_10617BB6: .text:10617BB6 cmp ebx, edi .text:10617BB8 jz loc_10617C66
И наконец все заработало без аппаратного ключа и лицензии!
Таким макаром мне удалось отучить программу от ключа и теперь я могу преступить непосредственно реверс-инжинирингу и уже анализировать код в динамике.
Часть 2: Реверс-инжиниринг
При импорте выбранного файла вызывается уже упомянутый мною в прошлой части статьи метод CappMain::ImportExam из модуля MultiCom.dll.
Метод CappMain::ImportExam
void __thiscall CappMain::ImportExam(CappMain *this, const struct CStringArray *a2) { int ComponentLicenseLevel; // esi int v3; // ebx int v4; // eax int v5; // eax int v6; // ebx int v7; // eax int v8; // esi int DocTemplateByAnalysisMode; // ecx CengSignalFilesMinimal *v10; // eax char v11; // al wchar_t **v12; // ecx int v13; // eax wchar_t **v14; // ecx int v15; // ecx CengSignalFilesMinimal *v16; // eax int v17; // eax struct CdtCardioMg *v18; // esi CengSignalFilesMinimal *v19; // eax CMapStringToPtr *v20; // ecx unsigned int v21; // ebx int i; // esi __int16 v23; // si int v24; // esi int v25; // edi int v26; // eax unsigned int j; // edx __int16 v28; // bx __int16 ExamCount; // di __int16 v30; // di int StringRepresentation; // [esp-10h] [ebp-808h] int v32; // [esp+0h] [ebp-7F8h] int Extension; // [esp+8h] [ebp-7F0h] int v34; // [esp+8h] [ebp-7F0h] int v35; // [esp+8h] [ebp-7F0h] int v36; // [esp+20h] [ebp-7D8h] BYREF char v37[520]; // [esp+30h] [ebp-7C8h] BYREF char v38[64]; // [esp+238h] [ebp-5C0h] BYREF int v39[50]; // [esp+278h] [ebp-580h] BYREF int v40; // [esp+340h] [ebp-4B8h] __int16 v41[56]; // [esp+3BCh] [ebp-43Ch] BYREF char v42[20]; // [esp+430h] [ebp-3C8h] BYREF int v43[2]; // [esp+444h] [ebp-3B4h] BYREF int v44[2]; // [esp+44Ch] [ebp-3ACh] BYREF int v45[2]; // [esp+46Ch] [ebp-38Ch] BYREF void **v46; // [esp+474h] [ebp-384h] BYREF int v47; // [esp+478h] [ebp-380h] unsigned int v48; // [esp+47Ch] [ebp-37Ch] __int128 v49; // [esp+480h] [ebp-378h] char v50[4]; // [esp+490h] [ebp-368h] BYREF char v51[4]; // [esp+494h] [ebp-364h] BYREF int *v52; // [esp+498h] [ebp-360h] CappMain *v53; // [esp+4A0h] [ebp-358h] char v54[4]; // [esp+4A4h] [ebp-354h] BYREF wchar_t *v55; // [esp+4A8h] [ebp-350h] char v56[4]; // [esp+4ACh] [ebp-34Ch] BYREF char v57[4]; // [esp+4B0h] [ebp-348h] BYREF int v58; // [esp+4BCh] [ebp-33Ch] const struct CStringArray *v59; // [esp+4C0h] [ebp-338h] char v60; // [esp+4C4h] [ebp-334h] BYREF wchar_t *v61; // [esp+4C8h] [ebp-330h] BYREF wchar_t *v62; // [esp+4CCh] [ebp-32Ch] BYREF int v63; // [esp+4D0h] [ebp-328h] wchar_t *v64; // [esp+4D4h] [ebp-324h] BYREF CappMain *v65; // [esp+4D8h] [ebp-320h] int v66; // [esp+4DCh] [ebp-31Ch] int v67; // [esp+4E0h] [ebp-318h] wchar_t *v68; // [esp+4E4h] [ebp-314h] BYREF wchar_t *v69[2]; // [esp+4E8h] [ebp-310h] BYREF wchar_t *v70; // [esp+4F0h] [ebp-308h] BYREF char v71[12]; // [esp+4F4h] [ebp-304h] BYREF int v72; // [esp+500h] [ebp-2F8h] const wchar_t *v73; // [esp+75Ch] [ebp-9Ch] __int16 v74; // [esp+760h] [ebp-98h] int v75; // [esp+770h] [ebp-88h] char v76[16]; // [esp+7D4h] [ebp-24h] BYREF int *v77; // [esp+7E8h] [ebp-10h] int v78; // [esp+7F4h] [ebp-4h] v77 = &v36; v65 = this; v59 = a2; v52 = (int *)((char *)a2 + 8); v67 = *((_DWORD *)a2 + 2); v53 = this; v69[1] = 0; ComponentLicenseLevel = ChlpLicenseManager::GetComponentLicenseLevel((char *)this + 332, 404, 1); v3 = 1; CdlgWait::CdlgWait((CdlgWait *)v37, 0); v46 = &CMap<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>,wchar_t const *,short,short>::`vftable'; v47 = 0; v48 = 17; v49 = xmmword_11137850; v78 = 1; if ( ComponentLicenseLevel != 1 ) { if ( ComponentLicenseLevel == 2 ) { v3 = 2; } else if ( ComponentLicenseLevel == 3 ) { v3 = 12; } } v4 = *(_DWORD *)v65; v66 = v3; v37[516] = 0; v5 = (*(int (__thiscall **)(CappMain *, const wchar_t *, const wchar_t *, _DWORD))(v4 + 132))( v65, L"Export Settings", L"Export Import Cores", 0); if ( v5 > 0 ) { if ( v5 < v3 ) v3 = v5; v66 = v3; } CdbControl::RefreshIfIll(0, 0); ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(v69); v6 = 0; LOBYTE(v78) = 2; LOBYTE(v63) = 1; while ( 1 ) { v58 = v6; if ( v6 >= v67 ) break; if ( v6 < 0 || v6 >= *v52 ) goto LABEL_76; ChlpPath::ChlpPath((ChlpPath *)&v70, *(const wchar_t **)(*((_DWORD *)v59 + 1) + 4 * v6)); LOBYTE(v78) = 4; Extension = ChlpPath::GetExtension(&v70, v51); LOBYTE(v78) = 5; v7 = sub_11120810(Extension); LOBYTE(v78) = 4; v8 = v7; ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v51); switch ( v8 ) { case 3: DocTemplateByAnalysisMode = CappMainBase::GetDocTemplateByAnalysisMode(v65, 4); if ( DocTemplateByAnalysisMode ) { v10 = (CengSignalFilesMinimal *)(*(int (__thiscall **)(int))(*(_DWORD *)DocTemplateByAnalysisMode + 180))(DocTemplateByAnalysisMode); if ( v10 ) CengSignalFilesMinimal::TestSlotAvail(v10, 1); sub_111200E0(&v70); LOBYTE(v78) = 6; v44[0] = (int)v38; v44[1] = 1000 * (v6 + 1) / v67; v11 = sub_11120820(v44, 3, v63); v12 = (wchar_t **)&v60; goto LABEL_19; } ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&v64); LOBYTE(v78) = 7; v34 = *(_DWORD *)CarcDiskHeaderAbs::GetAnalysisModeName(v50, 4); LOBYTE(v78) = 8; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(&v64, 23735, v70, v34); LOBYTE(v78) = 7; ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v50); v13 = AfxMessageBox(v64, 0x4031u, 0x5CB7u); v14 = &v64; if ( v13 != 1 ) goto LABEL_23; LABEL_22: ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(v14); ChlpPath::~ChlpPath((ChlpPath *)&v70); LOBYTE(v78) = 2; ++v6; break; case 4: v15 = CappMainBase::GetDocTemplateByAnalysisMode(v65, 4); if ( !v15 ) { ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&v62); LOBYTE(v78) = 10; v35 = *(_DWORD *)CarcDiskHeaderAbs::GetAnalysisModeName(v57, 4); LOBYTE(v78) = 11; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(&v62, 23735, v70, v35); LOBYTE(v78) = 10; ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v57); v17 = AfxMessageBox(v62, 0x4031u, 0x5CB7u); v14 = &v62; if ( v17 != 1 ) { LABEL_23: ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(v14); LABEL_51: ChlpPath::~ChlpPath((ChlpPath *)&v70); LOBYTE(v78) = 2; goto LABEL_52; } goto LABEL_22; } v16 = (CengSignalFilesMinimal *)(*(int (__thiscall **)(int))(*(_DWORD *)v15 + 180))(v15); if ( v16 ) CengSignalFilesMinimal::TestSlotAvail(v16, 1); sub_111200E0(&v70); LOBYTE(v78) = 9; v43[0] = (int)v38; v43[1] = 1000 * (v6 + 1) / v67; v11 = sub_11120820(v43, 4, v63); v12 = &v68; LABEL_19: if ( v11 ) { LOBYTE(v63) = 0; ChlpPath::~ChlpPath((ChlpPath *)v12); goto LABEL_70; } ChlpPath::~ChlpPath((ChlpPath *)v12); ChlpPath::~ChlpPath((ChlpPath *)&v70); LOBYTE(v78) = 2; ++v6; break; case 1: CFile::CFile((CFile *)v42, v70, 0x8020u); LOBYTE(v78) = 12; ChlpImportExam::ChlpImportExam((ChlpImportExam *)v71, (struct CFile *)v42, v66); LOBYTE(v78) = 13; v18 = (struct CdtCardioMg *)CappMainBase::GetDocTemplateByAnalysisMode(v65, v75); if ( v18 ) { v19 = (CengSignalFilesMinimal *)(*(int (__thiscall **)(struct CdtCardioMg *))(*(_DWORD *)v18 + 180))(v18); if ( v19 ) CengSignalFilesMinimal::TestSlotAvail(v19, 1); v45[0] = (int)v38; v45[1] = 1000 * (v6 + 1) / v67; if ( ChlpImportExam::Read((ChlpImportExam *)v71, v18, (const struct ChlpNewNestedJob *)v45) ) { if ( !*((_DWORD *)v73 - 3) ) { AfxMessageBox(L"No ExamSetUUID", 0, 0); goto LABEL_39; } CsUuid::CsUuid((CsUuid *)v76, v73); StringRepresentation = CsUuid::GetStringRepresentation(v76, v56, 0, 1); LOBYTE(v78) = 14; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=( v69, StringRepresentation); LOBYTE(v78) = 13; ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v56); (*(void (__thiscall **)(struct CdtCardioMg *, int))(*(_DWORD *)v18 + 216))(v18, v72); v55 = v69[0]; v21 = CMapStringToPtr::HashKey(v20, v69[0]); if ( v47 ) { for ( i = *(_DWORD *)(v47 + 4 * (v21 % v48)); i; i = *(_DWORD *)(i + 8) ) { if ( *(_DWORD *)(i + 12) == v21 && !ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Compare(i, v55) ) { goto LABEL_47; } } } v23 = v74; *(_WORD *)sub_11102240(v69[0]) = v23; } LABEL_47: ChlpImportExam::~ChlpImportExam((ChlpImportExam *)v71); CFile::~CFile((CFile *)v42); LABEL_70: v78 = 2; ChlpPath::~ChlpPath((ChlpPath *)&v70); v6 = v58 + 1; } else { ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&v61); LOBYTE(v78) = 15; v32 = *(_DWORD *)CarcDiskHeaderAbs::GetAnalysisModeName(v54, v75); LOBYTE(v78) = 16; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(&v61, 23735, v70, v32); LOBYTE(v78) = 15; ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v54); if ( AfxMessageBox(v61, 0x4031u, 0x5CB7u) != 1 ) { ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(&v61); ChlpImportExam::~ChlpImportExam((ChlpImportExam *)v71); CFile::~CFile((CFile *)v42); goto LABEL_51; } ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(&v61); LABEL_39: ChlpImportExam::~ChlpImportExam((ChlpImportExam *)v71); CFile::~CFile((CFile *)v42); ChlpPath::~ChlpPath((ChlpPath *)&v70); LOBYTE(v78) = 2; ++v6; } break; default: goto LABEL_70; } } LABEL_52: CtbExams::CtbExams((CtbExams *)v39, 0); LOBYTE(v78) = 28; v24 = -((_DWORD)v49 != 0); if ( (_DWORD)v49 ) { while ( v47 ) { v25 = v24; if ( !v24 ) break; if ( v24 == -1 ) { v26 = 0; if ( v48 ) { while ( 1 ) { v25 = *(_DWORD *)(v47 + 4 * v26); if ( v25 ) break; if ( ++v26 >= v48 ) goto LABEL_76; } } } v24 = *(_DWORD *)(v25 + 8); if ( !v24 ) { for ( j = *(_DWORD *)(v25 + 12) % v48 + 1; j < v48; ++j ) { v24 = *(_DWORD *)(v47 + 4 * j); if ( v24 ) break; } } ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v69, v25); v28 = *(_WORD *)(v25 + 4); CtbExams::SetFilterForScanExamSet((CtbExams *)v39, v69[0], 0); CsetBase::Open((CsetBase *)v39, 0); ExamCount = CtbExams::GetExamCount((CtbExams *)v39); if ( v28 <= 0 || v41[0] == -1 ) { v30 = 0; while ( !v40 ) { CsetBase::Edit((CsetBase *)v39); v41[0] = v30++; CRecordset::SetFieldNull((CRecordset *)v39, v41, 0); CsetBase::Update((CsetBase *)v39); (*(void (__thiscall **)(int *, int, int))(v39[0] + 28))(v39, 1, 1); } CRecordset::Close((CRecordset *)v39); CtbExamsSets::UpdateExamCount(v69[0], v30); } else { if ( ExamCount > 0 && ExamCount != v28 ) { ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&v68); LOBYTE(v78) = 29; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(&v68, 23729, v28, ExamCount); AfxMessageBox(v68, 0, 0); LOBYTE(v78) = 28; ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(&v68); } CRecordset::Close((CRecordset *)v39); } if ( !v24 ) goto LABEL_75; } LABEL_76: AfxThrowInvalidArgException(); } LABEL_75: CtbExams::~CtbExams((CtbExams *)v39); ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(v69); LOBYTE(v78) = 30; v46 = &CMap<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>,wchar_t const *,short,short>::`vftable'; sub_11107EC0(&v46); CdlgWait::~CdlgWait((CdlgWait *)v37); }
Код CappMain::ImportExam проясняет общую логику: программа не просто открывает .xcm, она выполняет полноценный импорт данных в СУБД. Судя по вызовам классов CsetBase и методам UpdateExamCount, файл парсится, и его содержимое раскладывается по таблицам базы данных.
К слову, программа при установке предлагает выбор пользователю из нескольких СУБД. Я выбрал MySQL.
Хотя программа и импортирует данные в СУБД лезть в базу у меня не было нужды. Моя задача — выяснить, что откуда и как читается из файла, а не то, как оно потом записывается в таблицы.
Основную логику импорта выполняет класс ChlpImportExam из модуля ModulesBase.dll.
Конструктор ChlpImportExam
ChlpImportExam *__thiscall ChlpImportExam::ChlpImportExam(ChlpImportExam *this, struct CFile *a2, void *a3) { CArchive *v4; // eax CArchive *v5; // eax _DWORD v7[8]; // [esp+0h] [ebp-2Ch] BYREF int v8; // [esp+28h] [ebp-4h] v7[7] = v7; v7[6] = this; CarcDiskHeaderFD::CarcDiskHeaderFD(this, 0); v8 = 0; *(_DWORD *)this = &ChlpImportExam::`vftable'; CtypVariable::CtypVariable((ChlpImportExam *)((char *)this + 672)); LOBYTE(v8) = 1; *((_DWORD *)this + 182) = a3; v4 = (CArchive *)operator new(0x48u); LOBYTE(v8) = 2; if ( v4 ) v5 = CArchive::CArchive(v4, a2, 1u, 4096, 0); else v5 = 0; *((_DWORD *)this + 1) = v5; LOBYTE(v8) = 3; CarcDiskHeaderAbs::Read(this); return this; }
Как видно из листинга, файл .xcm представляет собой архив, созданный стандартным классом MFC CArchive. Это означает, что данные внутри организованы не как простой поток байтов, а как иерархия сериализованных объектов.
Судя по вызову CarcDiskHeaderAbs::Read, первым делом из архива вычитывается заголовок файла. Вероятно, CarcDiskHeaderFD является наследником CarcDiskHeaderAbs. К слову, оба этих класса находятся в модуле Serializer.dll.
Конструктор CarcDiskHeaderFD:
CarcDiskHeaderFD *__thiscall CarcDiskHeaderFD::CarcDiskHeaderFD( CarcDiskHeaderFD *this, int (__stdcall ***a2)(int, int)) { CarcDiskHeaderAbs::CarcDiskHeaderAbs(this, a2); *(_DWORD *)this = &CarcDiskHeaderFD::`vftable'; ChlpPath::ChlpPath((CarcDiskHeaderFD *)((char *)this + 668)); return this; }
Судя по коду, выходит, что CarcDiskHeaderFD действительно является наследником CarcDiskHeaderAbs
Метод CarcDiskHeaderAbs::Read
int __thiscall CarcDiskHeaderAbs::Read(CarcDiskHeaderAbs *this) { int v2; // esi int v3; // ecx unsigned int v4; // edx int v5; // ecx bool v6; // zf unsigned int v7; // edx char v8; // al CarcDiskHeaderAbs *v9; // ecx const wchar_t *v11; // eax const wchar_t *v12; // eax v2 = *((_DWORD *)this + 1); if ( (*(_BYTE *)(v2 + 24) & 1) == 0 ) { v11 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20); AfxThrowArchiveException(4, v11); } v3 = *(_DWORD *)(v2 + 40); v4 = *(_DWORD *)(v2 + 44); if ( v3 + 1 > v4 ) CArchive::FillBuffer((CArchive *)v2, v3 - v4 + 1); *((_BYTE *)this + 8) = **(_BYTE **)(v2 + 40); v5 = *(_DWORD *)(v2 + 40) + 1; v6 = (*(_BYTE *)(v2 + 24) & 1) == 0; *(_DWORD *)(v2 + 40) = v5; if ( v6 ) { v12 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20); AfxThrowArchiveException(4, v12); } v7 = *(_DWORD *)(v2 + 44); if ( v5 + 1 > v7 ) CArchive::FillBuffer((CArchive *)v2, v5 - v7 + 1); *((_BYTE *)this + 9) = *(_BYTE *)(*(_DWORD *)(v2 + 40))++; v8 = *((_BYTE *)this + 8); if ( v8 == 48 ) { v9 = this; if ( (unsigned __int16)(((*((unsigned __int8 *)this + 9) << 8) | 0x30) - 12592) <= 0xA00u ) return CarcDiskHeaderAbs::ReadMAPA(this); } else { v9 = this; if ( v8 == 49 ) return CarcDiskHeaderAbs::ReadHolter(this); } return (*(int (__thiscall **)(CarcDiskHeaderAbs *, int))(*(_DWORD *)this + 40))(v9, 7); }
Тут читается тип данных: либо это MAPA (тип 48) — суточное давление, либо Holter (тип 49). Суточное давление, понятное дело, мне не нужно — меня интересует только метод CarcDiskHeaderAbs::ReadHolter.
Метод CarcDiskHeaderAbs::ReadHolter
int __thiscall CarcDiskHeaderAbs::ReadHolter(CarcDiskHeaderAbs *this) { int v2; // esi int v3; // ecx unsigned int v4; // edx int v5; // ecx bool v6; // zf unsigned int v7; // edx char *v8; // eax char v9; // cl int v11; // ecx unsigned int v12; // edx unsigned __int16 *v13; // eax int v14; // ecx int v15; // ecx unsigned int v16; // edx unsigned __int16 *v17; // eax int v18; // ecx int v19; // ecx unsigned int v20; // edx bool v21; // al int v22; // ecx unsigned int v23; // edx const wchar_t *v24; // eax const wchar_t *v25; // eax const wchar_t *v26; // eax const wchar_t *v27; // eax const wchar_t *v28; // eax const wchar_t *v29; // eax int v30; // [esp+Ch] [ebp-10h] BYREF int v31; // [esp+18h] [ebp-4h] v2 = *((_DWORD *)this + 1); if ( (*(_BYTE *)(v2 + 24) & 1) == 0 ) { v24 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20); AfxThrowArchiveException(4, v24); } v3 = *(_DWORD *)(v2 + 40); v4 = *(_DWORD *)(v2 + 44); if ( v3 + 1 > v4 ) CArchive::FillBuffer((CArchive *)v2, v3 - v4 + 1); *((_BYTE *)this + 612) = **(_BYTE **)(v2 + 40); v5 = *(_DWORD *)(v2 + 40) + 1; v6 = (*(_BYTE *)(v2 + 24) & 1) == 0; *(_DWORD *)(v2 + 40) = v5; if ( v6 ) { v25 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20); AfxThrowArchiveException(4, v25); } v7 = *(_DWORD *)(v2 + 44); if ( v5 + 1 > v7 ) CArchive::FillBuffer((CArchive *)v2, v5 - v7 + 1); v8 = *(char **)(v2 + 40); v9 = *v8; *(_DWORD *)(v2 + 40) = v8 + 1; if ( v9 != 26 ) return (*(int (__thiscall **)(CarcDiskHeaderAbs *, int))(*(_DWORD *)this + 40))(this, 5); ReadString((CArchive *)v2, (int)this + 624); // Номер исследования ReadString((CArchive *)v2, (int)this + 12); // ID исследования ReadString((CArchive *)v2, (int)this + 20); // Имя и фамилия врача-аналитика ReadString((CArchive *)v2, (int)this + 24); // Имя пациента ReadString((CArchive *)v2, (int)this + 28); // Фамилия пациента CarcDiskHeaderAbs::ReadFix(this, (CarcDiskHeaderAbs *)((char *)this + 32)); // Читает дату и время исследования ReadString((CArchive *)v2, (int)this + 44); // Имя и фамилия лечащего врача ReadString((CArchive *)v2, (int)this + 48); // Офис установки холтера ReadString((CArchive *)v2, (int)this + 632); // Название холтера if ( (*(_BYTE *)(v2 + 24) & 1) == 0 ) { v26 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20); AfxThrowArchiveException(4, v26); } v11 = *(_DWORD *)(v2 + 40); v12 = *(_DWORD *)(v2 + 44); if ( v11 + 2 > v12 ) CArchive::FillBuffer((CArchive *)v2, v11 - v12 + 2); v13 = *(unsigned __int16 **)(v2 + 40); v14 = *v13; *(_DWORD *)(v2 + 40) = v13 + 1; *((_DWORD *)this + 159) = v14; if ( (*(_BYTE *)(v2 + 24) & 1) == 0 ) { v27 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20); AfxThrowArchiveException(4, v27); } v15 = *(_DWORD *)(v2 + 40); v16 = *(_DWORD *)(v2 + 44); if ( v15 + 2 > v16 ) CArchive::FillBuffer((CArchive *)v2, v15 - v16 + 2); v17 = *(unsigned __int16 **)(v2 + 40); v18 = *v17; *(_DWORD *)(v2 + 40) = v17 + 1; *((_DWORD *)this + 160) = v18; if ( v18 == 4 || v18 == 7 ) *((_DWORD *)this + 160) = 1; if ( (*(_BYTE *)(v2 + 24) & 1) == 0 ) { v28 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20); AfxThrowArchiveException(4, v28); } v19 = *(_DWORD *)(v2 + 40); v20 = *(_DWORD *)(v2 + 44); if ( v19 + 2 > v20 ) CArchive::FillBuffer((CArchive *)v2, v19 - v20 + 2); *((_WORD *)this + 322) = **(_WORD **)(v2 + 40); *(_DWORD *)(v2 + 40) += 2; if ( *((_BYTE *)this + 8) == 48 ) v21 = ((unsigned __int16)(*((unsigned __int8 *)this + 9) << 8) | 0x30u) > 0x3730; else v21 = CarcDiskHeaderAbs::GetExportVersion(this) >= 3; if ( v21 ) { ReadString((CArchive *)v2, (int)this + 616); // Контрольная сумма if ( (*(_BYTE *)(v2 + 24) & 1) == 0 ) { v29 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20); AfxThrowArchiveException(4, v29); } v22 = *(_DWORD *)(v2 + 40); v23 = *(_DWORD *)(v2 + 44); if ( v22 + 2 > v23 ) CArchive::FillBuffer((CArchive *)v2, v22 - v23 + 2); *((_WORD *)this + 314) = **(_WORD **)(v2 + 40); *(_DWORD *)(v2 + 40) += 2; if ( *((_WORD *)this + 314) == 32484 ) *((_WORD *)this + 314) = -1; if ( CarcDiskHeaderAbs::GetExportVersion(this) >= 4 ) { ReadString((CArchive *)v2, (int)this + 52); // Страховой полис if ( CarcDiskHeaderAbs::GetExportVersion(this) >= 5 ) { ReadString((CArchive *)v2, (int)this + 16); // JSON с информацией о холтере (сжатый в zlib и закодированный в base64) ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v30); v31 = 0; ReadCString((CArchive *)v2, (int)&v30); CmtypTrackingInfo::SetEncodedString((CarcDiskHeaderAbs *)((char *)this + 56), (const char *)v30); v31 = -1; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v30); } } } CarcDiskHeaderAbs::CheckData(this); return 1; }
Судя по этому коду, из заголовка xcm-файла читаются:
Номер исследования
ID исследования
Имя и фамилия врача-аналитика
Имя пациента
Фамилия пациента
Дата и время исследования
Имя и фамилия лечащего врача
Офис установки холтера
Название холтера
В зависимости от версии также читаются:
Контрольная сумма
Страховой полис
JSON с информацией о холтере (сжатый в zlib и закодированный в base64)
Заголовок xcm-файла
00000000 31 35 30 1A FF FE FF 07 48 00 20 00 34 00 36 00 33 00 31 00 32 00 09 31 51 33 2D 33 150 яюя H 4 6 3 1 2 1Q3-3 00000028 35 38 39 30 FF FE FF 01 2E 00 FF FE FF 13 41 00 6C 00 63 00 65 00 75 00 20 00 4C 00 5890яюя . яюя A l c e u L 00000056 75 00 69 00 7A 00 20 00 64 00 61 00 20 00 53 00 69 00 6C 00 76 00 61 00 FF FE FF 07 u i z d a S i l v a яюя 00000084 4D 00 61 00 72 00 74 00 69 00 6E 00 73 00 E9 07 06 00 11 00 09 00 21 00 00 00 FF FE M a r t i n s й ! яю 00000112 FF 1F 44 00 72 00 2E 00 20 00 52 00 6F 00 64 00 72 00 69 00 67 00 6F 00 20 00 56 00 я D r . R o d r i g o V 00000140 61 00 6C 00 65 00 6E 00 74 00 65 00 20 00 52 00 61 00 6D 00 6F 00 73 00 20 00 52 00 a l e n t e R a m o s R 00000168 6F 00 63 00 68 00 61 00 FF FE FF 07 43 00 6C 00 69 00 6E 00 69 00 63 00 61 00 FF FE o c h a яюя C l i n i c a яю 00000196 FF 12 48 00 6F 00 6C 00 74 00 65 00 72 00 20 00 64 00 65 00 20 00 33 00 20 00 63 00 я H o l t e r d e 3 c 00000224 61 00 6E 00 61 00 69 00 73 00 04 00 08 00 05 00 20 37 46 31 45 44 33 36 36 34 44 30 a n a i s 7F1ED3664D0 00000252 38 34 38 43 35 39 34 33 38 36 44 39 34 45 36 41 33 44 33 39 32 00 00 FF FE FF 00 00 848C594386D94E6A3D392 яюя 00000280 FF 34 01 65 4A 78 46 6A 38 31 71 77 6C 41 51 68 66 64 39 43 70 6C 31 54 4F 39 66 6F я4 eJxFj81qwlAQhfd9Cpl1TO9fo 00000308 73 6E 4F 4A 6F 6F 42 63 53 50 59 39 61 41 44 48 63 67 66 6B 32 75 74 6C 62 35 37 6B snOJooBcSPY9aADHcgfk2utlb57k 00000336 78 74 6F 34 53 37 75 4F 64 39 68 35 73 77 54 4E 69 33 57 6A 32 2B 53 71 6F 54 63 72 xto4S7uOd9h5swTNi3Wj2+SqoTcr 00000364 56 31 71 72 46 6E 70 43 44 5A 39 58 37 57 44 78 37 71 65 41 45 42 77 6A 74 6A 51 4B V1qrFnpCDZ9X7WDx7qeAEBwjtjQK 00000392 41 71 55 4B 33 64 48 38 6F 75 69 5A 6D 72 39 44 4D 38 6B 49 30 74 6A 75 37 61 78 69 AqUK3dH8ouiZmr9DM8kI0tju7axi 00000420 59 30 5A 33 54 66 30 56 63 76 2B 6A 44 58 6B 4B 73 6A 77 31 53 5A 78 45 5A 54 30 79 Y0Z3Tf0Vcv+jDXkKsjw1SZxEZT0y 00000448 52 66 61 66 76 55 73 36 4C 6C 72 35 79 32 7A 65 79 4C 68 4B 51 6D 4A 79 6B 7A 32 71 RfafvUs6Llr5y2zeyLhKQmJykz2q 00000476 70 33 54 66 2B 78 64 32 4E 4E 57 70 4A 4D 68 54 4E 32 78 4E 48 63 55 4B 74 46 50 35 p3Tf+xd2NNWpJMhTN2xNHcUKtFP5 00000504 59 7A 53 64 71 6E 56 55 72 6D 46 55 6E 6C 34 38 42 2B 61 53 2B 70 59 32 39 48 63 33 YzSdqnVUrmFUnl48B+aS+pY29Hc3 00000532 38 4E 74 4F 6C 75 70 53 52 37 34 41 76 6B 54 74 4E 56 6A 78 5A 2B 67 44 39 79 77 4C 8NtOlupSR74AvkTtNVjxZ+gD9ywL 00000560 2F 45 42 65 5A 6F 6B 4E 6F 6E 67 39 48 48 7A 31 77 4A 76 41 30 32 5A 6C 31 39 34 78 /EBeZokNong9HHz1wJvA02Zl194x 00000588 6C 75 7A luz
Первый байт данных определяет тип данных: MAPA (0x30) или Holter (0x31). Следующие два байта задают мажорную и минорную версии xcm-файла соответственно, а далее идут поля, указанные выше.
Затем идут данные врача-аналитика, которые читаются методом CarctAnalyst::Load
Метод CarctAnalyst::Load
void __thiscall CarctAnalyst::Load(CarctAnalyst *this, unsigned int a2) { CarcDiskHeaderAbs *v3; // esi int v4; // edi int v5; // eax char *v6; // ecx int v7; // ecx unsigned int v8; // edx unsigned __int8 *v9; // eax unsigned __int8 *v10; // edx unsigned int v11; // eax unsigned int v12; // ecx unsigned int v13; // esi unsigned __int16 v14; // ax int v15; // eax unsigned int v16; // eax int v17; // eax int v18; // eax int v19; // eax int v20; // eax int v21; // eax int v22; // eax int v23; // eax int v24; // eax int v25; // eax int v26; // eax int v27; // eax int v28; // eax int v29; // eax int v30; // eax int v31; // eax int v32; // eax int v33; // eax int v34; // eax int v35; // eax int v36; // eax int v37; // eax int v38; // eax int v39; // eax int v40; // eax int v41; // eax int v42; // eax int v43; // eax int v44; // eax int v45; // eax int v46; // eax char *v47; // ecx int v48; // eax int v49; // eax int v50; // eax int v51; // eax int v52; // eax int v53; // eax int v54; // eax int v55; // eax int v56; // eax int v57; // eax int v58; // eax int v59; // eax int v60; // eax int v61; // eax const wchar_t *v62; // eax const wchar_t *v63; // eax int v64; // eax int v65; // eax const wchar_t *v66; // eax const wchar_t *v67; // eax int v68; // [esp-8h] [ebp-38h] int v69; // [esp-8h] [ebp-38h] int v70; // [esp-8h] [ebp-38h] int v71; // [esp-8h] [ebp-38h] int v72; // [esp-8h] [ebp-38h] int v73; // [esp-8h] [ebp-38h] int v74; // [esp-8h] [ebp-38h] int v75; // [esp-8h] [ebp-38h] int v76; // [esp-8h] [ebp-38h] int v77; // [esp-8h] [ebp-38h] int v78; // [esp-8h] [ebp-38h] int v79; // [esp-8h] [ebp-38h] int v80; // [esp-8h] [ebp-38h] int v81; // [esp-8h] [ebp-38h] int v82; // [esp-4h] [ebp-34h] int v83; // [esp-4h] [ebp-34h] int v84; // [esp-4h] [ebp-34h] int v85; // [esp-4h] [ebp-34h] int v86; // [esp-4h] [ebp-34h] int v87; // [esp-4h] [ebp-34h] int v88; // [esp-4h] [ebp-34h] int v89; // [esp-4h] [ebp-34h] int v90; // [esp-4h] [ebp-34h] int v91; // [esp-4h] [ebp-34h] int v92; // [esp-4h] [ebp-34h] int v93; // [esp-4h] [ebp-34h] int v94; // [esp-4h] [ebp-34h] int v95; // [esp-4h] [ebp-34h] int v96; // [esp-4h] [ebp-34h] char v97[4]; // [esp+10h] [ebp-20h] BYREF char v98[4]; // [esp+14h] [ebp-1Ch] BYREF char v99[4]; // [esp+18h] [ebp-18h] BYREF int v100; // [esp+1Ch] [ebp-14h] int v101; // [esp+20h] [ebp-10h] BYREF int v102; // [esp+2Ch] [ebp-4h] ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v101); v3 = (CarcDiskHeaderAbs *)a2; v102 = 0; v4 = *(_DWORD *)(a2 + 4); if ( *(_BYTE *)(a2 + 8) != 48 ) { v5 = *(_DWORD *)(a2 + 20); v6 = (char *)(a2 + 20); a2 = 48; if ( !*(_DWORD *)(v5 - 12) ) ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v6, L"???"); if ( CarcDiskHeaderAbs::IsNewerVersion(v3) ) { v60 = sub_5C622FB0((_DWORD *)v4); v61 = (*(int (__thiscall **)(int, unsigned int *))(*(_DWORD *)v60 + 32))(v60, &a2); LOBYTE(v102) = 1; v62 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v61); AfxThrowArchiveException(7, v62); } if ( CarcDiskHeaderAbs::GetExportVersion(v3) > 1 ) { if ( (*(_BYTE *)(v4 + 24) & 1) == 0 ) { v63 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v4 + 20); AfxThrowArchiveException(4, v63); } v7 = *(_DWORD *)(v4 + 40); v8 = *(_DWORD *)(v4 + 44); if ( v7 + 1 > v8 ) CArchive::FillBuffer((CArchive *)v4, v7 - v8 + 1); v9 = *(unsigned __int8 **)(v4 + 40); v10 = v9 + 1; v11 = *v9; *(_DWORD *)(v4 + 40) = v10; a2 = v11; if ( v11 > 0x35 ) { v64 = sub_5C622FB0((_DWORD *)v4); v65 = (*(int (__thiscall **)(int, unsigned int *))(*(_DWORD *)v64 + 32))(v64, &a2); LOBYTE(v102) = 2; v66 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v65); AfxThrowArchiveException(7, v66); } if ( v11 - 50 <= 2 ) { if ( (*(_BYTE *)(v4 + 24) & 1) == 0 ) { v67 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v4 + 20); AfxThrowArchiveException(4, v67); } v12 = *(_DWORD *)(v4 + 44); if ( (unsigned int)(v10 + 4) > v12 ) CArchive::FillBuffer((CArchive *)v4, (unsigned int)&v10[-v12 + 4]); *(_DWORD *)(v4 + 40) += 4; } } ReadString((CArchive *)v4, (int)this + 8); // Имя врача-аналитика if ( !*(_DWORD *)(*((_DWORD *)this + 2) - 12) ) CmtypAnalyst::SetDefaultAnalystName(this, 0); ReadString((CArchive *)v4, (int)this + 12); // ID врача-аналитика ReadString((CArchive *)v4, (int)this + 16); // Тип ID врача-аналитика ReadString((CArchive *)v4, (int)this + 20); // Офис врача-аналитика ReadString((CArchive *)v4, (int)this + 24); // Адрес врача-а��алитика ReadString((CArchive *)v4, (int)this + 28); // Уровень компетенции врача-аналитика ReadString((CArchive *)v4, (int)this + 32); // Почтовый индекс врача-аналитика ReadString((CArchive *)v4, (int)this + 36); // Город врача-аналитика ReadString((CArchive *)v4, (int)this + 40); // Штат врача-аналитика v13 = a2; if ( a2 < 0x34 ) { ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=( (char *)this + 44, &word_5C659A40); ReadString((CArchive *)v4, (int)this + 48); } else { ReadString((CArchive *)v4, (int)this + 44); // Страна врача-аналитика ReadString((CArchive *)v4, (int)this + 48); if ( v13 >= 0x35 ) goto LABEL_22; } ReadString((CArchive *)v4, (int)&v101); LABEL_22: if ( v13 < 0x33 ) { ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=( (char *)this + 52, &word_5C659A40); ReadString((CArchive *)v4, (int)this + 60); if ( v13 < 0x31 ) ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=( (char *)this + 56, &word_5C659A40); else ReadString((CArchive *)v4, (int)this + 56); } else { ReadString((CArchive *)v4, (int)this + 52); ReadString((CArchive *)v4, (int)this + 60); ReadString((CArchive *)v4, (int)this + 56); } goto LABEL_39; } v14 = (*(unsigned __int8 *)(a2 + 9) << 8) | 0x30; v100 = v14; if ( v14 != 12592 ) { ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&a2); v15 = *(_DWORD *)this; LOBYTE(v102) = 3; (*(void (__thiscall **)(CarctAnalyst *))(v15 + 24))(this); ReadString((CArchive *)v4, (int)&a2); v16 = a2; if ( !*(_DWORD *)(a2 - 12) ) { ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(&a2, L"-"); v16 = a2; } v17 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v98, v16); LOBYTE(v102) = 4; v18 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v17, v99, 50); v19 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v18, v68, v82); LOBYTE(v102) = 5; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 8, v19); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); ReadString((CArchive *)v4, (int)&a2); v20 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, a2); LOBYTE(v102) = 6; v21 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v20, v98, 12); v22 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v21, v69, v83); LOBYTE(v102) = 7; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 12, v22); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); ReadString((CArchive *)v4, (int)&a2); v23 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, a2); LOBYTE(v102) = 8; v24 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v23, v98, 12); v25 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v24, v70, v84); LOBYTE(v102) = 9; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 16, v25); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); ReadString((CArchive *)v4, (int)&a2); v26 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, a2); LOBYTE(v102) = 10; v27 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v26, v98, 50); v28 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v27, v71, v85); LOBYTE(v102) = 11; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 20, v28); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); ReadString((CArchive *)v4, (int)&a2); v29 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, a2); LOBYTE(v102) = 12; v30 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v29, v98, 50); v31 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v30, v72, v86); LOBYTE(v102) = 13; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 24, v31); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); ReadString((CArchive *)v4, (int)&a2); v32 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, a2); LOBYTE(v102) = 14; v33 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v32, v98, 50); v34 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v33, v73, v87); LOBYTE(v102) = 15; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 28, v34); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); ReadString((CArchive *)v4, (int)&a2); v35 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, a2); LOBYTE(v102) = 16; v36 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v35, v98, 10); v37 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v36, v74, v88); LOBYTE(v102) = 17; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 32, v37); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); ReadString((CArchive *)v4, (int)&a2); v38 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, a2); LOBYTE(v102) = 18; v39 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v38, v98, 30); v40 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v39, v75, v89); LOBYTE(v102) = 19; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 36, v40); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); if ( (unsigned __int16)v100 <= 0x3530u ) { ReadString((CArchive *)v4, (int)&a2); v92 = *(_DWORD *)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(&a2, v97, 3); LOBYTE(v102) = 22; v48 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, v92); LOBYTE(v102) = 23; v49 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v48, v98, 30); v50 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v49, v78, v93); LOBYTE(v102) = 24; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 40, v50); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); v47 = v97; } else { ReadString((CArchive *)v4, (int)&a2); v41 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, a2); LOBYTE(v102) = 20; v42 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v41, v98, 30); v43 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v42, v76, v90); LOBYTE(v102) = 21; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 40, v43); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99); ReadString((CArchive *)v4, (int)&a2); v44 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v99, a2); LOBYTE(v102) = 25; v45 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v44, v98, 30); v46 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v45, v77, v91); LOBYTE(v102) = 26; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 44, v46); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); v47 = v99; } LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v47); ReadString((CArchive *)v4, (int)&a2); v51 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v98, a2); LOBYTE(v102) = 27; v52 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v51, v97, 18); v53 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v52, v79, v94); LOBYTE(v102) = 28; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 48, v53); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v97); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); if ( *((_WORD *)v3 + 4) < 0x3B30u ) ReadString((CArchive *)v4, (int)&v101); if ( (unsigned __int16)v100 <= 0x3530u ) { ReadString((CArchive *)v4, (int)&a2); ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 60, a2); } else { ReadString((CArchive *)v4, (int)&a2); v54 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v98, a2); LOBYTE(v102) = 29; v55 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v54, v97, 18); v56 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v55, v80, v95); LOBYTE(v102) = 30; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 52, v56); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v97); LOBYTE(v102) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); ReadString((CArchive *)v4, (int)&a2); ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 60, a2); ReadString((CArchive *)v4, (int)&a2); v57 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( v98, a2); LOBYTE(v102) = 31; v58 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v57, v97, 40); v59 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v58, v81, v96); LOBYTE(v102) = 32; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 56, v59); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v97); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98); } CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&a2); } LABEL_39: CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v101); }
Судя по коду, читаются следующие данные врача-аналитика:
Имя
ID
Тип ID
Офис
Адрес
Уровень компетенции
Почтовый индекс
Город
Штат
Затем методом CarctPatient::Load читаются данные пациента.
CarctPatient::Load
void __thiscall CarctPatient::Load(CarctPatient *this, struct CarcDiskHeaderAbs *a2) { CarctPatient *v2; // esi int v3; // edi int v4; // ecx unsigned int v5; // edx unsigned __int8 *v6; // eax unsigned __int8 *v7; // edx unsigned int v8; // eax unsigned int v9; // ecx WORD v10; // ax WORD v11; // si int v12; // edi int v13; // eax __int16 v14; // ax WORD v15; // ax int v16; // esi unsigned int v17; // eax bool v18; // cc int v19; // ecx unsigned __int16 v20; // cx WORD v21; // ax WORD v22; // di WORD v23; // ax WORD v24; // si int v25; // eax CarcDiskHeaderAbs *v26; // edx int v27; // ecx unsigned int v28; // edx unsigned __int16 *v29; // eax CarcDiskHeaderAbs *v30; // ecx CarcDiskHeaderAbs *v31; // edi int v32; // eax int v33; // eax int v34; // eax int v35; // eax int v36; // eax int v37; // eax int v38; // eax int v39; // eax int v40; // eax int v41; // eax int v42; // eax int v43; // eax const wchar_t *v44; // eax const wchar_t *v45; // eax int v46; // eax int v47; // eax const wchar_t *v48; // eax const wchar_t *v49; // eax const wchar_t *v50; // eax int v51; // [esp-8h] [ebp-B8h] int v52; // [esp-8h] [ebp-B8h] int v53; // [esp-8h] [ebp-B8h] const wchar_t *v54; // [esp-4h] [ebp-B4h] const wchar_t *v55; // [esp-4h] [ebp-B4h] const wchar_t *v56; // [esp-4h] [ebp-B4h] const wchar_t *v57; // [esp-4h] [ebp-B4h] const wchar_t *v58; // [esp-4h] [ebp-B4h] const wchar_t *v59; // [esp-4h] [ebp-B4h] const wchar_t *v60; // [esp-4h] [ebp-B4h] const wchar_t *v61; // [esp-4h] [ebp-B4h] const wchar_t *v62; // [esp-4h] [ebp-B4h] const wchar_t *v63; // [esp-4h] [ebp-B4h] const wchar_t *v64; // [esp-4h] [ebp-B4h] const wchar_t *v65; // [esp-4h] [ebp-B4h] int v66; // [esp-4h] [ebp-B4h] int v67; // [esp-4h] [ebp-B4h] int v68; // [esp-4h] [ebp-B4h] int v69; // [esp+Ch] [ebp-A4h] WORD v70; // [esp+10h] [ebp-A0h] WORD v71; // [esp+10h] [ebp-A0h] WORD v72; // [esp+14h] [ebp-9Ch] WORD v73; // [esp+14h] [ebp-9Ch] int v74; // [esp+18h] [ebp-98h] BYREF DOUBLE pvtime; // [esp+1Ch] [ebp-94h] BYREF int v76; // [esp+24h] [ebp-8Ch] int v77; // [esp+28h] [ebp-88h] int v78; // [esp+2Ch] [ebp-84h] BYREF int v79; // [esp+30h] [ebp-80h] BYREF char v80[4]; // [esp+34h] [ebp-7Ch] BYREF __time64_t Time; // [esp+38h] [ebp-78h] BYREF int v82; // [esp+40h] [ebp-70h] BYREF CarcDiskHeaderAbs *v83; // [esp+44h] [ebp-6Ch] BYREF struct tm v84; // [esp+48h] [ebp-68h] struct tm Tm; // [esp+6Ch] [ebp-44h] BYREF struct _SYSTEMTIME SystemTime; // [esp+90h] [ebp-20h] BYREF int v87; // [esp+ACh] [ebp-4h] v2 = this; v78 = (int)this; v83 = a2; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v74); v87 = 0; v3 = *((_DWORD *)v83 + 1); v69 = v3; if ( *((_BYTE *)v83 + 8) != 48 ) { v82 = 48; if ( CarcDiskHeaderAbs::IsNewerVersion(v83) ) { v42 = sub_5C622FB0((_DWORD *)v3); v43 = (*(int (__thiscall **)(int, char *))(*(_DWORD *)v42 + 32))(v42, (char *)&Time + 4); LOBYTE(v87) = 1; v44 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v43); AfxThrowArchiveException(7, v44); } if ( CarcDiskHeaderAbs::GetExportVersion(v83) > 1 ) { if ( (*(_BYTE *)(v3 + 24) & 1) == 0 ) { v45 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v3 + 20); AfxThrowArchiveException(4, v45); } v4 = *(_DWORD *)(v3 + 40); v5 = *(_DWORD *)(v3 + 44); if ( v4 + 1 > v5 ) CArchive::FillBuffer((CArchive *)v3, v4 - v5 + 1); v6 = *(unsigned __int8 **)(v3 + 40); v7 = v6 + 1; v8 = *v6; *(_DWORD *)(v3 + 40) = v7; v82 = v8; if ( v8 > 0x35 ) { v46 = sub_5C622FB0((_DWORD *)v3); v47 = (*(int (__thiscall **)(int, char *))(*(_DWORD *)v46 + 32))(v46, (char *)&Time + 4); LOBYTE(v87) = 2; v48 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v47); AfxThrowArchiveException(7, v48); } if ( v8 - 50 <= 2 ) { if ( (*(_BYTE *)(v3 + 24) & 1) == 0 ) { v49 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v3 + 20); AfxThrowArchiveException(4, v49); } v9 = *(_DWORD *)(v3 + 44); if ( (unsigned int)(v7 + 4) > v9 ) CArchive::FillBuffer((CArchive *)v3, (unsigned int)&v7[-v9 + 4]); *(_DWORD *)(v3 + 40) += 4; } } ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=( (char *)v2 + 8, *((_DWORD *)v83 + 6)); ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=( (char *)v2 + 12, *((_DWORD *)v83 + 7)); if ( (unsigned int)v82 < 0x34 ) { sub_5C6210F0((CArchive *)v3, (int)&v83); if ( (_WORD)v83 != 32484 ) { if ( v82 == 49 ) { Time = time64(0); pvtime = 0.0; v76 = 0; if ( localtime64_s(&Tm, &Time) ) goto LABEL_23; v84 = Tm; SystemTime.wYear = LOWORD(Tm.tm_year) + 1900; SystemTime.wMonth = LOWORD(Tm.tm_mon) + 1; SystemTime.wDayOfWeek = Tm.tm_wday; SystemTime.wDay = Tm.tm_mday; SystemTime.wHour = Tm.tm_hour; SystemTime.wMinute = Tm.tm_min; SystemTime.wSecond = Tm.tm_sec; SystemTime.wMilliseconds = 0; v13 = sub_5C622D60(&pvtime, &SystemTime); v76 = 0; if ( !v13 ) LABEL_23: v76 = 1; SystemTime = 0i64; v14 = sub_5C642CC0(&pvtime); v15 = v14 - (_WORD)v83; } else { v15 = (_WORD)v83 + 1900; SystemTime = 0i64; } SystemTime.wYear = v15; SystemTime.wMonth = 1; *(_DWORD *)&SystemTime.wDay = 1; *(_DWORD *)&SystemTime.wMinute = 0; *((_DWORD *)v2 + 7) = sub_5C622D60((DOUBLE *)((char *)v2 + 20), &SystemTime) == 0; } } else { ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v83); LOBYTE(v87) = 3; if ( (unsigned int)v82 >= 0x35 ) ReadString((CArchive *)v3, (int)v2 + 16); ReadString((CArchive *)v3, (int)&v83); // Дата рождения пациента if ( *((_DWORD *)v83 - 3) == 14 ) { v54 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left( &v83, v80, 4); LOBYTE(v87) = 4; v72 = wtol(v54); LOBYTE(v87) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80); v55 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid( &v83, v80, 4, 2); LOBYTE(v87) = 5; v70 = wtol(v55); LOBYTE(v87) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80); v56 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid( &v83, v80, 6, 2); LOBYTE(v87) = 6; v77 = wtol(v56); LOBYTE(v87) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80); v57 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid( &v83, v80, 8, 2); LOBYTE(v87) = 7; v79 = wtol(v57); LOBYTE(v87) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80); v58 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid( &v83, v80, 10, 2); LOBYTE(v87) = 8; HIDWORD(Time) = wtol(v58); LOBYTE(v87) = 3; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80); v59 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Right( &v83, v80, 2); LOBYTE(v87) = 9; v10 = wtol(v59); LOBYTE(v87) = 3; v11 = v10; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80); v12 = v78; SystemTime = 0i64; SystemTime.wYear = v72; SystemTime.wMonth = v70; SystemTime.wDay = v77; SystemTime.wHour = v79; SystemTime.wMinute = WORD2(Time); SystemTime.wSecond = v11; *(_DWORD *)(v12 + 28) = sub_5C622D60((DOUBLE *)(v78 + 20), &SystemTime) == 0; v3 = v69; } LOBYTE(v87) = 0; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v83); } v16 = v78; if ( *(_DWORD *)(v78 + 28) == 1 ) *(_DWORD *)(v78 + 28) = 2; ReadString((CArchive *)v3, v16 + 36); // Адрес пациента ReadString((CArchive *)v3, v16 + 40); // Жалоба пациента ReadString((CArchive *)v3, v16 + 52); // Почтовый индекс пациента ReadString((CArchive *)v3, v16 + 44); // Город пациента ReadString((CArchive *)v3, v16 + 48); // Штат пациента if ( (unsigned int)v82 < 0x34 ) { ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v16 + 56, &word_5C659A40); ReadString((CArchive *)v3, v16 + 60); } else { ReadString((CArchive *)v3, v16 + 56); // Страна пациента ReadString((CArchive *)v3, v16 + 60); // Телефон пациента v17 = v82; if ( (unsigned int)v82 > 0x34 ) goto LABEL_34; } ReadString((CArchive *)v3, (int)&v74); v17 = v82; LABEL_34: if ( v17 < 0x33 ) ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v16 + 64, &word_5C659A40); else ReadString((CArchive *)v3, v16 + 64); // Мобильный телефон пациента ReadString((CArchive *)v3, v16 + 32); // Пол пациента ReadString((CArchive *)v3, v16 + 76); // Рост пациента ReadString((CArchive *)v3, v16 + 72); // Вес пациента sub_5C6210F0((CArchive *)v3, (int)&v78); v18 = (unsigned int)v82 <= 0x30; *(_DWORD *)(v16 + 80) = (unsigned __int16)v78; if ( v18 ) { ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v16 + 68, &word_5C659A40); v19 = v16 + 84; } else { ReadString((CArchive *)v3, v16 + 68); v19 = v16 + 84; if ( (unsigned int)v82 >= 0x34 ) { ReadString((CArchive *)v3, v16 + 84); goto LABEL_64; } } ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v19, &word_5C659A40); goto LABEL_64; } ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v82); LOBYTE(v87) = 10; pvtime = 0.0; v76 = 2; v20 = *((_WORD *)v83 + 4); if ( v20 > 0x3530u ) { if ( v20 >= 0x3B30u ) ReadString((CArchive *)v3, (int)v2 + 16); ReadString((CArchive *)v3, (int)&v82); if ( *(_DWORD *)(v82 - 12) != 14 ) goto LABEL_50; v60 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left( &v82, (char *)&Time + 4, 4); LOBYTE(v87) = 11; v79 = wtol(v60); LOBYTE(v87) = 10; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4)); v61 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid( &v82, (char *)&Time + 4, 4, 2); LOBYTE(v87) = 12; v77 = wtol(v61); LOBYTE(v87) = 10; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4)); v62 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid( &v82, (char *)&Time + 4, 6, 2); LOBYTE(v87) = 13; v71 = wtol(v62); LOBYTE(v87) = 10; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4)); v63 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid( &v82, (char *)&Time + 4, 8, 2); LOBYTE(v87) = 14; v73 = wtol(v63); LOBYTE(v87) = 10; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4)); v64 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid( &v82, (char *)&Time + 4, 10, 2); LOBYTE(v87) = 15; v21 = wtol(v64); LOBYTE(v87) = 10; v22 = v21; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4)); v65 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Right( &v82, (char *)&Time + 4, 2); LOBYTE(v87) = 16; v23 = wtol(v65); LOBYTE(v87) = 10; v24 = v23; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4)); SystemTime = 0i64; SystemTime.wYear = v79; SystemTime.wMonth = v77; SystemTime.wDay = v71; SystemTime.wHour = v73; SystemTime.wMinute = v22; SystemTime.wSecond = v24; v25 = sub_5C622D60(&pvtime, &SystemTime); v3 = v69; v2 = (CarctPatient *)v78; goto LABEL_49; } sub_5C6210F0((CArchive *)v3, (int)&v78); if ( (_WORD)v78 != 32484 ) { SystemTime = 0i64; SystemTime.wYear = v78 + 1900; SystemTime.wMonth = 1; *(_DWORD *)&SystemTime.wDay = 1; *(_DWORD *)&SystemTime.wMinute = 0; v25 = sub_5C622D60(&pvtime, &SystemTime); LABEL_49: v76 = v25 == 0; } LABEL_50: ReadString((CArchive *)v3, (int)v2 + 36); ReadString((CArchive *)v3, (int)v2 + 40); ReadString((CArchive *)v3, (int)v2 + 52); ReadString((CArchive *)v3, (int)v2 + 44); ReadString((CArchive *)v3, (int)v2 + 48); if ( *((_WORD *)v83 + 4) > 0x3530u ) ReadString((CArchive *)v3, (int)v2 + 56); ReadString((CArchive *)v3, (int)v2 + 60); v26 = v83; if ( *((_WORD *)v83 + 4) < 0x3B30u ) { ReadString((CArchive *)v3, (int)&v74); v26 = v83; } if ( *((_WORD *)v26 + 4) > 0x3530u ) ReadString((CArchive *)v3, (int)v2 + 64); ReadString((CArchive *)v3, (int)v2 + 32); ReadString((CArchive *)v3, (int)v2 + 76); ReadString((CArchive *)v3, (int)v2 + 72); if ( (*(_BYTE *)(v3 + 24) & 1) == 0 ) { v50 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v3 + 20); AfxThrowArchiveException(4, v50); } v27 = *(_DWORD *)(v3 + 40); v28 = *(_DWORD *)(v3 + 44); if ( v27 + 2 > v28 ) CArchive::FillBuffer((CArchive *)v3, v27 - v28 + 2); v29 = *(unsigned __int16 **)(v3 + 40); v77 = *v29; v30 = v83; *(_DWORD *)(v3 + 40) = v29 + 1; if ( *((_WORD *)v30 + 4) > 0x3530u ) { ReadString((CArchive *)v3, (int)v2 + 68); ReadString((CArchive *)v3, (int)v2 + 84); } v31 = v83; v32 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( &v79, *((_DWORD *)v83 + 6)); LOBYTE(v87) = 17; v33 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v32, (char *)&Time + 4, 50); v34 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v33, v51, v66); LOBYTE(v87) = 18; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)v2 + 8, v34); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4)); LOBYTE(v87) = 10; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v79); v35 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( &v79, *((_DWORD *)v31 + 7)); LOBYTE(v87) = 19; v36 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v35, (char *)&Time + 4, 50); v37 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v36, v52, v67); LOBYTE(v87) = 20; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)v2 + 12, v37); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4)); LOBYTE(v87) = 10; CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v79); v38 = v76; *(DOUBLE *)((char *)v2 + 20) = pvtime; *((_DWORD *)v2 + 7) = v38; *((_DWORD *)v2 + 20) = (_WORD)v77 != 0; if ( !*(_DWORD *)(*((_DWORD *)v2 + 21) - 12) ) { v39 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>( &v79, *((_DWORD *)v2 + 21)); LOBYTE(v87) = 21; v40 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v39, (char *)&Time + 4, 50); v41 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v40, v53, v68); LOBYTE(v87) = 22; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)v2 + 84, v41); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4)); CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v79); } CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v82); LABEL_64: CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v74); }
Метод читает:
Дату рождения пациента
Адрес
Жалобу
Почтовый индекс
Город
Штат
В зависимости от версии также читаются:
Страна
Телефон
Мобильный телефон
Пол
Рост
Вес
Поскольку статья и так получилась перегруженной деталями, я не буду утомлять вас дальнейшим разбором реверс-инжиниринга. Перейдем сразу к итогу.
В результате получился вот такой код на Python, который читает XCM-файл и строит графики ЭКГ:
XCM_Reader
import bz2 import struct import numpy as np from matplotlib import pyplot as plt from matplotlib.widgets import Button import sys from datetime import datetime, timedelta import base64 import zlib current_index = 0 sample_rate = 200 def find(haystack, start_offset, needle): if start_offset >= len(haystack): return -1 if not needle: return start_offset pos = haystack.find(needle, start_offset) return pos if pos != -1 else -1 class Reader: cur_pos = 0 raw_ecg = bytearray() def __init__(self, buf): self.buf = buf def seek(self, offset): self.cur_pos += offset def read_byte(self): value = self.buf[self.cur_pos] self.cur_pos += 1 return value def read_short(self): value = struct.unpack('<H', self.buf[self.cur_pos:self.cur_pos + 2])[0] self.cur_pos += 2 return value def read_int(self): value = struct.unpack('<I', self.buf[self.cur_pos:self.cur_pos + 4])[0] self.cur_pos += 4 return value def read_long(self): value = struct.unpack('<Q', self.buf[self.cur_pos:self.cur_pos + 8])[0] self.cur_pos += 8 return value def read_string_length(self): char_size = 1 bLength = self.read_byte() if bLength < 0xff: return (char_size, bLength) wLength = self.read_short() if wLength == 0xfffe: char_size = 2 bLength = self.read_byte() if bLength < 0xff: return (char_size, bLength) wLength = self.read_short() if wLength < 0xffff: return (char_size, wLength) dwLength = self.read_int() if dwLength < 0xffffffff: return (char_size, dwLength) qwLength = self.read_long() return (char_size, qwLength) def read_string(self): string_length = self.read_string_length() char_size = string_length[0] len = string_length[1] * char_size s = self.buf[self.cur_pos:self.cur_pos + len] self.cur_pos += len if char_size == 1: return s.decode("windows-1252") if char_size == 2: return s.decode("utf-16") def decompress(self, data): try: return bz2.decompress(data) except Exception: return data def decompress_data(self): bz_size = self.read_int() raw_data = bytearray() while True: compressed_size = self.read_int() decompressed_data = self.decompress(self.buf[self.cur_pos:self.cur_pos + compressed_size]) raw_data.extend(decompressed_data) self.cur_pos += compressed_size bz_size = self.read_int() if bz_size == 0: break return raw_data def read_block(self): print(self.read_string()) print("Size: {}".format(self.read_int())) block_size = self.read_int() print("Block size: {}".format(block_size)) block_type = self.read_byte() if block_type > 51: raise ValueError('Incorrect xcm!') if (block_type == 51) and (block_size <= 265365): return self.decompress_data() return bytearray() class DiaryReader(Reader): BASE_DATE = datetime(1899, 12, 30) def read_count(self): count = self.read_short() if count != 0xFFFF: return count return self.read_int() def read_double(self): value = struct.unpack('<d', self.buf[self.cur_pos:self.cur_pos + 8])[0] self.cur_pos += 8 return value def read_datetime(self): try: time = self.read_double() days = int(time) fractional_seconds = (time - days) * 86400 return self.BASE_DATE + timedelta(days=days, seconds=fractional_seconds) except: return self.BASE_DATE def read_diary(self): print("Diary:") self.seek(2) print("ID: {}".format(self.read_string())) print("Exam number: {}".format(self.read_string())) print("Patient Name: {}".format(self.read_string())) self.seek(12) count = self.read_count() print("Activites count: {}".format(count)) self.seek(4) start_dt = self.read_datetime() print("Exam Start Date: " + start_dt.strftime("%Y-%m-%d %H:%M:%S")) self.seek(12) end_dt = self.BASE_DATE for i in range(count): if (start_dt != self.BASE_DATE) and (end_dt == self.BASE_DATE): print("Activity {}: {} {}".format(i + 1, self.read_string(), start_dt.strftime("%H:%M"))) elif (start_dt != self.BASE_DATE) and (end_dt != self.BASE_DATE): print("Activity {}: {} {} - {}".format(i + 1, self.read_string(), start_dt.strftime("%H:%M"), end_dt.strftime("%H:%M"))) else: print("Activity {}: {}".format(i + 1, self.read_string())) self.seek(12) print("Symptom: {}".format(self.read_string())) self.seek(4) start_dt = self.read_datetime() self.seek(4) end_dt = self.read_datetime() class XCMReader(Reader): meta_data = list() attachment_data = list() def read_date(self): year = self.read_short() month = self.read_short() day = self.read_short() hour = self.read_short() minute = self.read_short() second = self.read_short() dt = datetime(year, month, day, hour, minute, second) print("Date: " + dt.strftime("%Y-%m-%d %H:%M:%S")) def get_export_version(self): if self.xcm_type > 49: return 0x3FFF if self.xcm_type != 49: return (self.xcm_type == 48) - 1 if self.xcm_major_version > 55: return 0x3FFF match self.xcm_major_version: case 55: if self.xcm_minor_version <= 48: if self.xcm_minor_version == 48: return 7 else: return -1 return 0x3FFF case 54: if self.xcm_minor_version == 49: return 6 else: return 8 * (self.xcm_minor_version == 50) - 1 case 53: if self.xcm_minor_version == 48: return 5 else: if self.xcm_minor_version == 49: return 6 return -1 case 52: return 4 case 51: return 3 case 50: return 2 case _: return 2 * (self.xcm_major_version == 49) - 1 def is_newer_version(self): if self.xcm_type <= 49: if self.xcm_type != 49: return False if self.xcm_major_version <= 55: if self.xcm_major_version == 55: return self.xcm_minor_version > 48 return False return True def read_holter_header(self): if self.read_byte() != 26: return print("Holter:") print("Exam number: " + self.read_string()) print("ID: " + self.read_string()) print("Analyst: " + self.read_string()) print("Patient Name: " + self.read_string()) print("Patient Last Name: " + self.read_string()) self.read_date() print("Requester: " + self.read_string()) print("Office: " + self.read_string()) print("Holter: " + self.read_string()) self.seek(6) if self.get_export_version() >= 3: print("Check sum: " + self.read_string()) self.seek(2) if self.get_export_version() >= 4: print("Health plan: " + self.read_string()) if self.get_export_version() >= 5: print(self.read_string()) base64_str = self.read_string() json_str = zlib.decompress(base64.b64decode(base64_str)).decode() print("Holter JSON: " + json_str) def read_analyst(self): if self.xcm_type != 48: if not self.is_newer_version() and self.get_export_version() > 1: analyst_type = self.read_byte() if analyst_type > 53: raise ValueError('Incorrect xcm!') if analyst_type - 50 <= 2: self.seek(4) print("Analyst:") print("Analyst name: " + self.read_string()) print("Analyst id: " + self.read_string()) print("Analyst type of id: " + self.read_string()) print("Analyst office: " + self.read_string()) print("Analyst addres: " + self.read_string()) print("Analyst compL:" + self.read_string()) print("Analyst ZIP code: " + self.read_string()) print("Analyst city: " + self.read_string()) print("Analyst state: " + self.read_string()) if analyst_type < 52: print(self.read_string()) else: print("Analyst contry: " + self.read_string()) print(self.read_string()) if analyst_type < 53: print(self.read_string()) if analyst_type < 51: print(self.read_string()) if analyst_type >= 49: print(self.read_string()) else: print(self.read_string()) print(self.read_string()) print(self.read_string()) def read_patient(self): if self.xcm_type != 48: if not self.is_newer_version() and self.get_export_version() > 1: patient_type = self.read_byte() if patient_type > 53: raise ValueError('Incorrect xcm!') if patient_type - 50 <= 2: self.seek(4) if patient_type < 52: raise ValueError('Not supported!') else: print("Patient:") if patient_type >= 53: print(self.read_string()) date = datetime.strptime(self.read_string(), "%Y%m%d%H%M%S").strftime("%Y-%m-%d %H:%M:%S") print("Patient date of birth: " + date) print("Patient addres: " + self.read_string()) print("Patient compL:" + self.read_string()) print("Patient ZIP code: " + self.read_string()) print("Patient city: " + self.read_string()) print("Patient state: " + self.read_string()) if patient_type < 52: print(self.read_string()) else: print("Patient contry: " + self.read_string()) print("Patient phone: " + self.read_string()) if patient_type < 53: print(self.read_string()) if patient_type > 51: print("Patient mobile phone: " + self.read_string()) print("Patient sex: " + self.read_string()) print("Patient height: " + self.read_string()) print("Patient weight: " + self.read_string()) if patient_type < 53: self.seek(2) if patient_type > 48: print(self.read_string()) if patient_type <= 52: print("Patient health plan: " + self.read_string()) if patient_type == 53: print(self.read_string()) print(self.read_string()) print("Patient health plan: " + self.read_string()) self.seek(6) def read_doctor(self): if self.xcm_type != 48: if not self.is_newer_version() and self.get_export_version() > 1: doctor_type = self.read_byte() if doctor_type == 255: self.seek(3) doctor_type = self.read_byte() if doctor_type > 53: raise ValueError('Incorrect xcm!') if doctor_type - 50 <= 2: self.seek(4) print("Doctor:") print(self.read_string()) print("Doctor name: " + self.read_string()) print("Doctor office: " + self.read_string()) print(self.read_string()) print(self.read_string()) print(self.read_string()) print(self.read_string()) print(self.read_string()) if doctor_type < 52: print(self.read_string()) print(self.read_string()) if doctor_type == 53: print(self.read_string()) print(self.read_string()) if doctor_type < 53: print(self.read_string()) if doctor_type < 51: print(self.read_string()) if doctor_type < 49: return else: print(self.read_string()) print(self.read_string()) print(self.read_string()) if doctor_type == 52: print(self.read_string()) self.seek(4) if doctor_type <= 52: self.seek(12) def read_device_config(self): if self.xcm_type != 48: if not self.is_newer_version() and self.get_export_version() > 1: device_config_type = self.read_byte() if device_config_type > 49: raise ValueError('Incorrect xcm!') self.seek(4) print("Device config:") print(self.read_string()) print(self.read_string()) print(self.read_string()) def read_channel(self): if self.xcm_type != 48: if not self.is_newer_version() and self.get_export_version() > 1: channel_type = self.read_byte() if channel_type > 49: raise ValueError('Incorrect xcm!') print("Channel:") print(self.read_short()) print(self.read_short()) print(self.read_short()) def read_channels(self, channels): print("Channeles: {}".format(channels)) for _ in range(channels): self.read_channel() def read_signal(self): if self.xcm_type != 48: if not self.is_newer_version() and self.get_export_version() > 1: print("Signal:") self.seek(1) signal_type = self.read_byte() if signal_type > 53: raise ValueError('Incorrect xcm!') print("ADC bits: {}".format(self.read_short())) if signal_type > 50: print("Format ID: {}".format(self.read_short())) print(self.read_int()) channels = self.read_short() print(channels) print(self.read_string()) print(self.read_int()) print("Number of samples: {}".format(self.read_int())) print("Sample rate: {}".format(self.read_short())) self.read_date() print(self.read_short()) print(self.read_short()) if (self.xcm_major_version > 52) and (self.xcm_major_version < 54) and (self.xcm_major_version != 55): self.seek(8) elif self.xcm_major_version == 55: self.seek(18) self.read_channels(channels) def read_exam(self): self.cur_pos = find(self.buf, self.cur_pos, bytearray(b'\xff\xfe\xff')) print("Exam:") self.raw_ecg = self.read_block() def read_meta_data(self): def is_need_read(): result = self.read_byte() if result == 0: self.seek(1) result = self.read_byte() if result > 1: self.seek(-2) return self.read_byte() if result == 0xFE: self.seek(-3) return self.read_byte() return result print("Data:") while is_need_read(): self.meta_data.append(self.read_block()) if self.read_byte() == 255: self.seek(-1) self.meta_data.append(self.read_block()) else: self.seek(-1) def read_attachments(self): print("Attachments:") type = self.read_byte() if type > 51: raise ValueError('Incorrect xcm!') self.read_date() self.read_date() self.seek(3) print(self.read_string()) print(self.read_string()) print(self.read_string()) print(self.read_string()) if (self.xcm_major_version == 55) or (self.xcm_major_version == 52): self.attachment_data.append(self.read_block()) if self.read_string() == '{^¨ATTACHS~`}': val = self.read_short() for _ in range(val): attachment_name = self.read_string() print("Attachment name: {}".format(attachment_name)) self.seek(4) block = self.read_block() if attachment_name.find(".ped") != -1: diary_reader = DiaryReader(block) diary_reader.read_diary() self.attachment_data.append(block) def read_xcm(self): self.xcm_type = self.read_byte() self.xcm_major_version = self.read_byte() self.xcm_minor_version = self.read_byte() if self.xcm_type == 49: self.read_holter_header() self.read_analyst() self.read_patient() self.read_doctor() self.read_device_config() self.read_signal() self.read_exam() self.read_meta_data() self.read_attachments() def save_meta_data(self): with open('ecg.bin', "wb") as ecg_file: ecg_file.write(self.raw_ecg) meta_ind = 0 for data in self.meta_data: with open('meta_data_{}.bin'.format(meta_ind), "wb") as meta_data_file: meta_data_file.write(data) meta_ind += 1 attachment_ind = 0 for data in self.attachment_data: with open('attachment_{}.bin'.format(attachment_ind), "wb") as attachment_file: attachment_file.write(data) attachment_ind += 1 def show_ecg(raw_ecg, adc_range = 5, adc_zero = 128, initial_val = 256): raw_ecg = np.frombuffer(raw_ecg, dtype=np.uint8) channels = 3 labels = ["I", "II", "III"] ch_start_offset = 262144 num_samples = len(raw_ecg) - (ch_start_offset * channels) x = np.zeros((channels, num_samples), dtype=np.float16) for ch in range(channels): start = ch * ch_start_offset x[ch] = (raw_ecg[start : start + num_samples].astype(np.float16) - adc_zero) * (adc_range / initial_val) sample_len = x.shape[1] display_range = 2000 current_index = 0 fig, ax = plt.subplots(figsize=(20, 10), nrows=channels, ncols=1) fig.subplots_adjust(hspace=0.15, left=0.03, right=0.98, top=0.95, bottom=0.1) fig.suptitle("Channels") plt.xlabel("Time (s)") lines = [] tick_positions = np.linspace(current_index, current_index + display_range, 10) tick_labels = np.arange(current_index // sample_rate, (current_index + display_range) // sample_rate) for i, a in enumerate(ax): line, = a.plot(range(current_index, (current_index + display_range)), x[i][current_index:current_index + display_range]) a.set_ylabel(labels[i], rotation=0) a.set_xticks(tick_positions) a.set_xticklabels(tick_labels) a.grid(True, alpha=0.2) lines.append(line) def update_plot(forward=True): nonlocal current_index step = display_range if forward else -display_range current_index += step if current_index < 0: current_index = 0 elif current_index + display_range > sample_len: current_index = sample_len - display_range tick_labels = np.arange(current_index // sample_rate, (current_index + display_range) // sample_rate) for i, line in enumerate(lines): line.set_ydata(x[i][current_index:current_index + display_range]) line.axes.set_xticklabels(tick_labels) plt.draw() ax_next_button = plt.axes([0.81, 0.01, 0.1, 0.05]) ax_prev_button = plt.axes([0.7, 0.01, 0.1, 0.05]) next_button = Button(ax_next_button, 'Next') prev_button = Button(ax_prev_button, 'Previous') next_button.on_clicked(lambda event: update_plot(forward=True)) prev_button.on_clicked(lambda event: update_plot(forward=False)) plt.show() def main(): if len(sys.argv) != 2: print("Usage: python XCM_Reader.py <xcm file name>") return filename = sys.argv[1] try: with open(filename, "rb") as file: buf = file.read() reader = XCMReader(buf) reader.read_xcm() reader.save_meta_data() show_ecg(reader.raw_ecg) except FileNotFoundError: print("File not found.") except Exception as e: print(f"An error occurred: {e}") if __name__ == "__main__": main()
Пояснение к коду
Класс Reader
Это базовый класс для парсинга бинарных данных. Он представляет собой реализацию логики класса CArchive из библиотеки MFC на Python. В коде используется специфическая структура чтения строк и длин, характерная для сериализации объектов в Visual C++. Данные внутри XCM-файла упакованы блоками с помощью bz2, которые метод decompress_data собирает в единый массив байтов. Это позволяет извлекать как сигналы ЭКГ, так и другие вложенные метаданные для последующей обработки.
Класс DiaryReader
Предназначен для парсинга дневника пациента: событий, зафиксированных во время холтеровского мониторирования (жалоб, активности). Время в файле хранится в формате double. Это соответствует стандарту COleDateTime в MFC, где целая часть — количество дней, прошедших с полуночи 30 декабря 1899 года, а дробная — часть суток.
Класс XCMReader
Непосредственно сам парсер. Формат XCM чувствителен к версиям, поэтому логика чтения постоянно ветвится. При чтении метаданных в методе read_holter_header извлекается основная информация о записи: номер обследования, ID, ФИО врача-аналитика и пациента. Работа начинается с проверки байта 26 (0x1A) — контрольного маркера начала заголовка. Дальнейшее ветвление напрямую зависит от версии экспорта: если она выше 3-й, читается контрольная сумма, а начиная с 4-й — добавляется информация о страховом плане. В 5-й версии разработчики внедрили «матрешку», где читается строка Base64, которая после декодирования и декомпрессии через zlib превращается в JSON с полным набором данных по холтеру.
При чтении данных врача-аналитика в методе read_analyst выполняется разбор полей, структура которых менялась от версии к версии. Номера типов (байт analyst_type) в диапазоне 50–53 диктуют, нужно ли делать смещение seek(4) и какие поля появятся в потоке дальше. В коде встречается много вызовов read_string(), результат которых просто выводится в консоль без присвоения переменным. Это неидентифицированные поля, которые в имеющихся файлах оказались пустыми, поэтому их назначение определить невозможно. Все методы до read_exam работают аналогично read_doctor — в них сохраняется та же логика с номерами типов, ветвлением по версиям и неидентифицированными полями.
В методе read_exam для поиска сигнатуры используется небольшая хитрость. В некоторых файлах между метаданными и сигналом встречаются вставки, из-за которых статический указатель промахивается мимо начала данных. Чтобы обойти проблему, я добавил поиск маркера \xff\xfe\xff — это гарантирует точное позиционирование на начало блока ЭКГ. После успешного нахождения сигнатуры вызывается read_block(), который вытягивает сырой сигнал для последующей декомпрессии.
Декомпрессия и отрисовка графиков (show_ecg)
Это финальный этап, где сырые данные превращаются в понятный вид. Внутри XCM-файла данные ЭКГ упакованы блоками, и метод decompress_data собирает их в единый массив байтов, выполняя распаковку bz2 (в старых версиях блоки могут быть не сжаты, тогда они читаются как есть).
Полученные данные из АЦП, которые обычно представляют собой значения от 0 до 255, необходимо центрировать и масштабировать. Для этого используется формула: (байт - adc_zero) * (диапазон / начальное значение), что превращает безликие цифры в реальное напряжение в микровольтах.
Важный нюанс заключается в разделении по каналам: в этом формате данные не перемешаны посемплово, а идут огромными кусками друг за другом. Код просто нарезает общий массив на три равные части (отведения I, II и III), используя фиксированное смещение ch_start_offset. Чтобы с результатом было удобно работать, я реализовал интерактивный интерфейс: графики можно листать кнопками «Next» и «Previous». Это позволяет комфортно просматривать многочасовую запись холтера, не теряя при этом нормальный масштаб.
Итог
Этот скрипт — результат объединения всех выводов, полученных при анализе ассемблерного и декомпилированного кода. Он умеет автоматически определять версии, распаковывать bz2-блоки и корректно интерпретировать специфические форматы дат. В результате проделанной работы появился готовый инструмент на Python, позволяющий читать и анализировать XCM-файлы из программы холтер-мониторинга.
