Online Activation Process

I tested the registration process using code “test”. The application appears to use the device’s UUID as part of the activation:

Device ID: 6C074FFE-0939-5A8A-BC0F-4994FCA543E0-5986

The application makes the following HTTP request:

curl 'http://reg.code-industry.net/mpe-5/pr/index-5.php?1=1&2=test&3=6C074FFE-0939-5A8A-BC0F-4994FCA543E0-5986&4=6d61634f532031352e33202d2031352e33' \
-H 'Host: reg.code-industry.net' \
-H 'Connection: Keep-Alive' \
-H 'Accept-Language: en-US,*' \
-H 'User-Agent: Mozilla/5.0'

Which returns:

not found

I confirmed the device UUID is retrieved using:

ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/ { print $3; }'

"6C074FFE-0939-5A8A-BC0F-4994FCA543E0"

The “5986” suffix indicates the software version (5.9.86).

According to Apple documentation, the hardware UUID persists across OS upgrades, reinstalls, and multi-boot installations, making it a reliable identifier for license enforcement.

Registration URL Generation

All online activation is handled by the RegCrypt::GetRegistrationURL function, which:

  1. Checks subscription status:
  • If active: uses URL http://reg.code-industry.net/mpe-5/pr/sub-5.php
  • Otherwise: uses URL http://reg.code-industry.net/mpe-5/pr/index-5.php
  1. Modifies the URL based on flags:
  • If a4’s first bit is set: appends “?1=1&2=”
  • Otherwise: appends “?1=2&2=”
  • Then appends the user identifier, “&3=”, and additional parameters (a2 and a3)
  1. Handles subscriptions differently:
  • For active subscriptions: adds more parameters including timestamp and encoded values
  • Otherwise: simply appends identifiers directly

This function is referenced by:

  • RegDialog::pb_deactive
  • RegDialog::pb_reg
  • MainWindow::ValidateLicense
  • MainReg::Deactivate

Offline Activation

The function sub_1002E4108 only initializes UI elements of the registration dialog (setting text for labels, buttons, and group boxes). It doesn’t handle button clicks or user interactions. This function is called by sub_1002E13F4.

License Validation Analysis

MainWindow::ValidateLicense

void __fastcall MainWindow::ValidateLicense(MainWindow *this, const QString *a2)
{
  // variables declarations removed for clarity

  if ( *(_DWORD *)(*((_QWORD *)this + 138) + 4LL) && !*((_BYTE *)this + 98) )
  {
    *((_BYTE *)this + 98) = 1;  // Set license flag
    
    if ( *(_DWORD *)(*((_QWORD *)this + 139) + 4LL) )
    {
      // License server validation path
      MainReg::ReadReg((MainReg *)&MainReg::GetInstance(void)::instance, &v32);
      
      // [Memory cleanup code removed]
      
      MainReg::ActivateFromLicenseServer(
        (MainReg *)&MainReg::GetInstance(void)::instance,
        (const QString *)this + 138,
        (const QString *)this + 143,
        (const QString *)this + 139,
        (const QString *)this + 140);
        
      MainReg::ReadReg((MainReg *)&MainReg::GetInstance(void)::instance, v28);
      *((_BYTE *)this + 97) = *((_DWORD *)v28[0].d + 1) != 0;  // Set validity flag
      
      // [Memory cleanup code removed]
      
      QDocTab::SetRegProgram(*((QDocTab **)this + 18), *((_BYTE *)this + 97));
      
      if ( *((_BYTE *)this + 97) )  // If license is valid
      {
        // Update window title and handle concurrent licensing
        // [Window title update code removed]
      }
      else if ( byte_101D16CC1 )
      {
        // Show license server connection failure
        QMetaObject::tr(&v29, (QMetaObject *)&MainWindow::staticMetaObject, 
                        "Failed to connect to License server", 0LL, -1);
        // [Message box code retained]
      }
    }
    else
    {
      // Local validation path
      QString::toLatin1_helper(v28, (QString *)this + 138, a2);
      IsConcurrent = RegCrypt::IsConcurrent((RegCrypt *)v28, v8);
      
      // [Memory cleanup code removed]
      
      if ( (IsConcurrent & 1) != 0 )
        return;
      
      // Generate registration URL and validate via network
      // [Network request setup code retained]
      v21 = QNetworkAccessManager::get(v19, (const QNetworkRequest *)&v29);
      QObject::connect(&v23, v21, "2finished()", this, "1Check_for_Validation_Finished()", 0LL);
      
      // [Cleanup code removed]
    }
  }
}

This function checks if the application is licensed:

  1. If already licensed (flag at this+98 is set), returns immediately
  2. Otherwise, sets the license flag (this+98 = 1)
  3. Reads stored license key using MainReg::ReadReg
  4. Contacts licensing server via MainReg::ActivateFromLicenseServer
  5. Sets validity flag (this+97) based on response
  6. If valid:
    • Updates window title
    • Handles concurrent licensing if enabled
  7. If invalid:
    • Checks if concurrent using RegCrypt::IsConcurrent
    • Generates registration URL
    • Validates via network request
    • Displays failure message if needed

The key conditional check is:

if ( *(_DWORD *)(*((_QWORD *)this + 138) + 4LL) && !*((_BYTE *)this + 98) )

Where !*((_BYTE *)this + 98) evaluates if the license flag is 0 (false).

assembly.png

Looking at the ARM64 assembly:

SUB   SP, SP, #0xB0        ; Allocate stack space
STP   X22, X21, [SP,#0xA0+var_20] ; Save registers
STP   X20, X19, [SP,#0xA0+var_10] ; Save registers
STP   X29, X30, [SP,#0xA0+var_s0] ; Save frame pointer & return address
ADD   X29, SP, #0xA0       ; Set frame pointer

LDR   X8, [X0,#0x450]      ; Load pointer from [this + 0x450]
LDR   W8, [X8,#4]          ; Load DWORD from offset 4
CBZ   W8, loc_1002E97C4    ; If 0, skip validation

MOV   X19, X0              ; Store this pointer in X19
LDRB  W8, [X0,#0x62]       ; Load byte at [this + 0x62] (license flag)
CBZ   W8, loc_1002E97D8    ; If zero, continue validation

loc_1002E97D8:
ADD   X20, X19, #0x450     ; X20 = (this + 0x450)
MOV   W8, #1               ; W8 = 1
STRB  W8, [X19,#0x62]      ; Store 1 at (this + 0x62) (set license flag)

I attempted to patch this by changing:

88 00 00 34 F3 03 00 AA 08 88 41 39 C8 00 00 34
to 
00 00 00 14 F3 03 00 AA 08 88 41 39 C8 00 00 34

However, this caused the app to fail with:

00:38:30 23.02.25  Registration:  Error: REG06
00:38:30 23.02.25  Main:  Main window initilization finished

MainWindow::VerifyLicense

Simply calls MainWindow::ValidateLicense.

MainWindow::ReadReg

__int64 __fastcall MainWindow::ReadReg(MainWindow *this)
{
  // Reset license flag
  *((_BYTE *)this + 100) = 0;
  
  // Clear any existing registration data
  // [Memory cleanup code removed]
  
  // Read license information from settings
  GeneralSettings::GeneralSettings((GeneralSettings *)v67);
  MainVersion = GeneralSettings::GetMainVersion((GeneralSettings *)v67);
  
  // Get registration code
  GeneralSettings::GetRegistrationCode((GeneralSettings *)v67, v4, v5);
  QString::trimmed_helper(&v66, &v65, v6);
  *((QString *)this + 138) = v66;
  
  // [Memory cleanup code removed]
  
  // Get license server IP
  GeneralSettings::GetLicenseServerIp((__int64 *)&v66, (GeneralSettings *)v67, v10);
  *((QString *)this + 139) = v66;
  
  // Get license server port
  LicenseServerPort = (QString *)GeneralSettings::GetLicenseServerPort((GeneralSettings *)v67);
  QString::number(&v66, LicenseServerPort, 10, v15);
  *((QString *)this + 140) = v66;
  
  // Check if license is concurrent
  if ( !*((_DWORD *)*v13 + 1) )
    goto LABEL_47;
    
  QString::toLatin1_helper(&v66, (QString *)this + 138, v16);
  IsConcurrent = RegCrypt::IsConcurrent((RegCrypt *)&v66, v18);
  
  // [Memory cleanup code removed]
  
  if ( IsConcurrent )
  {
    // Remove activation code if license is concurrent
    QPDFSettings::Remove((QPDFSettings *)v67, v22);
    // [Memory cleanup code removed]
  }
  else
  {
LABEL_47:
    // Get activation code for non-concurrent license
    GeneralSettings::GetActivationCode((__int64 *)&v65, (GeneralSettings *)v67);
    QString::trimmed_helper(&v66, &v65, v24);
    *((QString *)this + 141) = v66;
  }
  
  // Load machine ID
  LoadMachineID(&v66);
  *((QString *)this + 143) = v66;
  
  // [Machine ID handling code removed]
  
  // Version check logic
  if ( MainVersion <= 5729 && MainVersion )
  {
    // Handle old version
    // [Old version code removed]
    return 0LL;
  }
  
  // Validation check
  if ( *(_DWORD *)(*((_QWORD *)this + 139) + 4LL) || *(int *)(*((_QWORD *)this + 141) + 4LL) > 50 )
  {
    if ( *((int *)*v13 + 1) < 18 || *(int *)(*((_QWORD *)this + 141) + 4LL) < 40 )
      return 0LL;
      
    // Perform license validation
    RegCrypt::CheckActivation(&v58, v41, v42, v40, &v65, 0LL);
    
    // [Memory cleanup code removed]
    
    return 1LL;  // This is the key return value for license validation success
  }
  
  // License validation failed path
  QPDFSettings::Remove((QPDFSettings *)v67, v34);
  
  // Log error
  Logger::LogMessage(v47, v50, v51);
  
  // [Memory cleanup code removed]
  
  return 0LL;  // Return failure
}

This function:

  1. Resets license flag: *((_BYTE *)this + 100) = 0
  2. Reads stored license code via GeneralSettings::GetRegistrationCode
GeneralSettings::GetRegistrationCode((__int64 *)&v73, (GeneralSettings *)v75, v4);
QString::trimmed_helper(&v74, &v73, v5);
*((QString *)this + 138) = v74;
  1. Reads license server IP and port
  2. Checks if license is concurrent
  3. Removes activation code if license is concurrent
  4. Retrieves and validates activation code
  5. Loads machine ID
  6. Performs validation using RegCrypt::CheckActivation
  7. If validation fails, removes stored registration and activation codes

I attempted to patch (the second return 1LL;): assembly3.png

TBZ  W20, #0, loc_100078328
  
74 00 00 36 20 00 80 52 6E 00 00 14 E8 00 80 52
to
1F 20 03 D5   ; NOP (No Operation)

MainWindow::on_actionRegister_triggered

This function handles the “Register” button click:

  1. Opens registration dialog
  2. Updates program’s registration state
  3. Triggers license validation
void __fastcall MainWindow::on_actionRegister_triggered(MainWindow *this)
{
  // Initial setup and flags
  if ( (*(_BYTE *)(*((_QWORD *)this + 5) + 10LL) & 1) != 0 )
    *((_BYTE *)this + 101) = 1;
    
  // Create and show registration dialog
  v2 = (RegDialog *)operator new(0xE8uLL);
  RegDialog::RegDialog(v2, this);
  RegDialog::SetRegProgram(v2, *((_BYTE *)this + 97), (const QString *)this + 143);
  (*(void (__fastcall **)(RegDialog *))(*(_QWORD *)v2 + 424LL))(v2);
  
  // Get registration status from dialog
  v14 = 0;
  RegProgram = RegDialog::getRegProgram(v2, &v14);
  *((_BYTE *)this + 97) = RegProgram;  // Set validity flag
  *((_BYTE *)this + 98) = RegProgram;  // Set license flag
  
  // Update window title
  QString::operator=((char *)this + 1088, &qword_101D78C40);
  
  // Handle unregistered state
  if ( *((_BYTE *)this + 97) || *((_BYTE *)this + 391) )
    goto LABEL_17;
    
  // [Unregistered title update code removed]
  
LABEL_17:
  // Clean up dialog
  (*(void (__fastcall **)(RegDialog *))(*(_QWORD *)v2 + 32LL))(v2);
  
  // Update window title and document tabs
  MainWindow::SetWindowTitle(this, v8);
  QDocTab::SetRegProgram(*((QDocTab **)this + 18), *((_BYTE *)this + 97));
  
  // Validate license if newly registered
  if ( *((_BYTE *)this + 97) )
  {
    if ( v14 )
    {
      *((_BYTE *)this + 98) = 0;  // Reset license flag
      MainWindow::ReadReg(this);  // Read registration info
      MainWindow::ValidateLicense(this, v10);  // Validate license
    }
  }
}

Key code section to patch:

RegProgram = RegDialog::getRegProgram(v2, &v14);
*((_BYTE *)this + 97) = RegProgram;
*((_BYTE *)this + 98) = RegProgram;

assembly2.png

I modified:

1B 86 09 94   ; BL RegDialog::getRegProgram
60 86 01 39   ; STRB W0, [X19,#0x61]
60 8A 01 39   ; STRB W0, [X19,#0x62]
75 02 11 91   ; ADD X21, X19, #0x440
to
20 00 80 52   ; MOV W0, #1
60 86 01 39   ; STRB W0, [X19,#0x61] (Store 1 in this + 0x61)
60 8A 01 39   ; STRB W0, [X19,#0x62] (Store 1 in this + 0x62)
75 02 11 91   ; ADD X21, X19, #0x440 (Keep existing instruction)

MainReg::ReadReg

atomic_uint *__usercall MainReg::ReadReg@<X0>(MainReg *this@<X0>, _QWORD *a2@<X8>)
{
  // [Variables and initial setup removed for clarity]

  // Get registration and activation information
  GeneralSettings::GeneralSettings((GeneralSettings *)v33);
  GeneralSettings::GetRegistrationCode((GeneralSettings *)v33, v5, v6);
  // [Memory management code removed]
  GeneralSettings::GetActivationCode((__int64 *)&v34, (GeneralSettings *)v33);
  // [Memory management code removed]
  
  // Load machine ID for validation
  LoadMachineID(&v31[4]);
  *(_DWORD *)v31 = -1;
  
  // Check if registration/activation codes are valid length
  if ( d[1] >= 18 && v10[1] >= 19 )
  {
    // Prepare parameters for validation
    v30.d = d;  // Registration code
    v29 = *(atomic_uint **)&v31[4];  // Machine ID
    v28 = v10;  // Activation code
    
    // Validate the registration
    RegCrypt::CheckActivation(&v30, v14, v15, v13, (const QString *)v31, 0LL);
    
    // [Memory cleanup code removed]
    
    // Store result and return
    *a2 = *(_QWORD *)&v31[4];
    *(_QWORD *)&v31[4] = &QArrayData::shared_null;
  }
  else
  {
    // Log error if codes don't meet length requirements
    v19 = (Logger *)*((_QWORD *)this + 9);
    v27 = (atomic_uint *)QString::fromAscii_helper((QString *)"Registration", (const char *)0xC, v12);
    QString::number(&v34, (QString *)*(unsigned int *)v31, 10, v20);
    QString::fromUtf8_helper(&v26, (QString *)"Error: REG0", (const char *)0xB, v21);
    QString::append(&v26, &v34);
    Logger::LogMessage(v19, v22, v23);
    
    // [Memory cleanup code removed]
    
    *a2 = &QArrayData::shared_null;
  }
  
  // [Final memory cleanup removed]
  
  return result;
}

This function:

  1. Retrieves registration and activation codes
  • Calls GeneralSettings::GetRegistrationCode
  • Calls GeneralSettings::GetActivationCode
  • Calls LoadMachineID
  1. Validates registration using RegCrypt::CheckActivation
  2. Handles validation results

Potentially patchable section:

RegCrypt::CheckActivation(&v30, v14, v15, v13, (const QString *)v31, 0LL);
    v16 = v28;
    if ( *v28 != -1 )
    {
      if ( *v28 )
      {
        if ( atomic_fetch_add(v28, 0xFFFFFFFF) != 1 )
          goto LABEL_29;
        v16 = v28;
      }
      QArrayData::deallocate(v16, 2LL, 8LL);
    }

assembly4.png

BL   __ZN8RegCrypt15CheckActivationE7QStringS0_S0_RKS0_Rib ; RegCrypt::CheckActivation
TBZ  W0, #0, loc_100392FF0

20 05 00 36 E0 0F 40 F9 08 00 40 B9 1F 05 00 31

Could patch with:

E0 0F 40 F9   ; MOV W0, #1

or

1F 20 03 D5   ; NOP (No Operation)

Additional Functions to analyse later

  • TrialMessage::Setup: Sets up trial message UI with watermark and “Register” button
  • MainWindow::MainWindow: Initializes main window
  • MainReg::RegFinished: Handles registration completion
  • RegCrypt::CheckActivation: Core validation function

Additional Patch

I didn’t like the Incorrect registration code. even if the random code was working so I changed :

00496E636F727265637420726567697374726174696F6E20636F64652E00
to
00637261636B65642062792061717A73203A290000000000000000000000

Working for now

Activation check bypassed :)

Edit: 28-06-2025

Simplified Activation Bypass

After further analysis, I discovered a more elegant approach to bypass the activation check entirely by targeting the MainWindow::ReadReg function, which serves as the central validation point for all registration logic.

Understanding the Registration Flow

The registration validation follows this critical path:

  1. MainWindow::on_actionRegister_triggered → handles registration button click
  2. MainWindow::ReadReg → validates stored license information
  3. RegCrypt::CheckActivation → performs cryptographic validation

Rather than patching multiple validation points, we can intercept the flow at MainWindow::ReadReg, which returns:

  • 1 for valid registration
  • 0 for invalid/missing registration

Function Analysis: MainWindow::ReadReg

This function performs comprehensive license validation:

__int64 __fastcall MainWindow::ReadReg(MainWindow *this)
{
  // Reset license validation flag
  *((_BYTE *)this + 100) = 0;
  
  // Load registration settings
  GeneralSettings::GeneralSettings((GeneralSettings *)v75);
  MainVersion = GeneralSettings::GetMainVersion((GeneralSettings *)v75);
  
  // Retrieve stored codes
  GeneralSettings::GetRegistrationCode((__int64 *)&v73, (GeneralSettings *)v75, v4);
  GeneralSettings::GetLicenseServerIp((__int64 *)&v74, (GeneralSettings *)v75, v9);
  GeneralSettings::GetActivationCode((__int64 *)&v73, (GeneralSettings *)v75);
  
  // Check for concurrent licensing
  if ( !*(_DWORD *)(*(_QWORD *)v12 + 4LL) )
    goto LABEL_47;
    
  IsConcurrent = RegCrypt::IsConcurrent((RegCrypt *)&v74, v17);
  
  // Core validation logic
  if ( *(int *)(*(_QWORD *)v12 + 4LL) >= 18 && *(int *)(*((_QWORD *)this + 139) + 4LL) >= 40 )
  {
    // Prepare validation parameters
    v66 = *(atomic_uint **)v12;        // Registration code
    v65 = (atomic_uint *)*((_QWORD *)this + 141);  // Machine ID
    v64 = (atomic_uint *)*((_QWORD *)this + 139);  // Activation code
    
    // Perform cryptographic validation
    v43 = RegCrypt::CheckActivation((RegCrypt *)&v66, v40, v41, v39, &v73, 0, v29);
    
    if ( (v43 & 1) != 0 )
      return 1;  // ← TARGET: Force return 1 here
  }
  
  // Cleanup and error handling
  LODWORD(v73.d) = 7;
  QPDFSettings::Remove((QPDFSettings *)v75, v48);  // Remove invalid codes
  
  return 0;  // Registration failed
}

The Elegant Solution

Instead of patching multiple validation points, we can simply force MainWindow::ReadReg to always return 1 by modifying the function prologue:

ARM64 Binary Patch: ida1.png

Original prologue:
FF 03 03 D1    ; SUB SP, SP, #0xC0
F8 5F 08 A9    ; STP X24, X23, [SP,#0x80]
F6 57 09 A9    ; STP X22, X21, [SP,#0x90]

Patched to:
20 00 80 52    ; MOV W0, #1
C0 03 5F D6    ; RET
F6 57 09 A9    ; STP X22, X21, [SP,#0x90] (unchanged)

Intel x64 Binary Patch: The challenge with Universal Binaries is that both ARM64 and Intel x64 architectures are present. The Intel binary has stripped symbols, making function identification more complex.

Locating the Intel Function

To find the equivalent function in the Intel binary:

  1. Follow the call chain: MainWindow::on_actionRegister_triggered calls MainWindow::ReadReg xrefs.png
  2. Use cross-references: The registration button handler is called from a switch statement (case 272) xrefs2.png xrefs3.png
  3. Pattern matching: Compare disassembly structure between ARM64 and Intel versions

The Intel equivalent function sub_1000A7B00 can be identified by:

  • Similar call patterns to settings functions
  • Identical string references
  • Matching control flow structure Function comparison between ARM64 and Intel binaries

We then obtain the Hex 55 48 89 E5 41 57 41 56 41 55 41 54 53 48 81 EC 88 00 00 00 49 89 FC C6 47 64 00 and patch it the same way!

Original prologue:
55                ; PUSH RBP
48 89 E5          ; MOV RBP, RSP
41 57             ; PUSH R15
41 56             ; PUSH R14

Patched to:
B8 01 00 00 00    ; MOV EAX, 1
C3                ; RET
41 56             ; PUSH R14 (unchanged)

This approach is more robust than the previous multi-point patching strategy because:

  • Single point of failure elimination
  • Cleaner code execution path
  • No risk of partial validation bypass
  • Works regardless of registration method (online/offline)

That’s it :p