MobaXterm24.2 分析

DirWangK發表於2024-09-22

MobaXterm

目錄
  • MobaXterm
    • 0、啟動視窗 TForm1
    • 1、TForm1_FormCreate
      • decrypt_9FDA48
        • 1)xxBase64Decode_9FD80C
        • 2)DecryptBytes_9FD9DC
    • 2、許可結構
      • 1) Type
      • 2) version_info_3A8
      • 3) user_limit
      • 4) Version
      • 5) unuse
      • 6)NoGames
      • 7)NoPlugins
      • 解析函式parse_9FEB5C
    • other
      • sub_A03F80
      • TFormAbout_FormCreate
  • py

MobaXterm_Personal_24.2.exe

delphi 程式 分析首選IDR,生成IDC指令碼後結合 IDA PRO分析

注意:IDA 不能只看F5的反編譯結果,如果未修正函式呼叫方式、棧引數時,結果往往不正確;需要看彙編確定引數。

0、啟動視窗 TForm1

image-20240921120353791

1、TForm1_FormCreate

decrypt_9FDA48

當指定 “-chklic keyfilepath" 時,會驗證檔案keyfilepath

int __usercall decrypt_9FDA48@<eax>(char **a1@<ecx>, int key@<edx>, int a3@<eax>)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  a3a = 0;
  v7 = &savedregs;
  v6[1] = (unsigned int)&loc_9FDA90;
  v6[0] = (unsigned int)NtCurrentTeb()->NtTib.ExceptionList;
  __writefsdword(0, (unsigned int)v6);
  xxBase64Decode_9FD940(a3, (int)a1, (unsigned int)&a3a);
  DecryptBytes_9FD9DC((char *)a3a, key, a1);
  __writefsdword(0, v6[0]);
  v7 = (int *)&loc_9FDA97;
  return LStrClr();
}

1)xxBase64Decode_9FD80C

相較標準base64,位元組序解析不同

int __usercall xxBase64Decode_9FD80C@<eax>(unsigned __int8 *a1@<eax>, int *a2@<edx>)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  // 相較標準base64,位元組序解析不同
  v4 = LStrLen((int)a1) - 2;
  if ( v4 )
  {
    v6 = v4 - 1;
    if ( v6 )
    {
      result = v6 - 1;
      if ( !result )
      {
        LStrSetLength(
          a2,
          3,
          v5,
          ((unsigned __int8)index_table_BA26F4[a1[3]] << 0x12)
        + ((unsigned __int8)index_table_BA26F4[a1[2]] << 0xC)
        + ((unsigned __int8)index_table_BA26F4[a1[1]] << 6)
        + (unsigned __int8)index_table_BA26F4[*a1]);
        v12 = LStrLen(*a2);
        v13 = UniqueStringA(v12);
        return Move(v16, v13);
      }
    }
    else
    {
      LStrSetLength(
        a2,
        2,
        v5,
        ((unsigned __int8)index_table_BA26F4[a1[2]] << 0xC)
      + ((unsigned __int8)index_table_BA26F4[a1[1]] << 6)
      + (unsigned __int8)index_table_BA26F4[*a1]);
      v10 = LStrLen(*a2);
      v11 = UniqueStringA(v10);
      return Move(v15, v11);
    }
  }
  else
  {
    LStrSetLength(
      a2,
      1,
      v5,
      ((unsigned __int8)index_table_BA26F4[a1[1]] << 6) + (unsigned __int8)index_table_BA26F4[*a1]);
    v8 = LStrLen(*a2);
    v9 = UniqueStringA(v8);
    return Move(v14, v9);
  }
  return result;
}


2)DecryptBytes_9FD9DC

位元組xor

int __usercall DecryptBytes_9FD9DC@<eax>(char *a1@<eax>, int reuse_key_or_index@<edx>, char **d_str@<ecx>)
{
  __int16 key; // si
  int result; // eax
  char *v7; // [esp+0h] [ebp-18h]
  __int16 v8; // [esp+4h] [ebp-14h]

  v7 = a1;
  LStrAsg((volatile __int32 *)d_str, (__int32)a1);
  key = reuse_key_or_index;
  result = LStrLen((int)*d_str);
  if ( !(_WORD)result )
    return result;
  v8 = result;
  LOWORD(reuse_key_or_index) = 1;
  do
  {
    *(_BYTE *)(UniqueStringA(v7) + (unsigned __int16)reuse_key_or_index - 1) = HIBYTE(key) ^ (*d_str)[(unsigned __int16)reuse_key_or_index - 1];
    result = (unsigned __int8)v7[(unsigned __int16)reuse_key_or_index - 1];
    key = result & key | 0x482D;
    ++reuse_key_or_index;
    --v8;
  }
  while ( v8 );
  return result;
}

2、許可結構

經過解密後,許可資訊使用‘#’分隔為7部分

    lic_format='{0}#{1}#{2}#{3}#{4}#{5}#{6}#'.format(
        Type,
        '{}|{}{}'.format(UserName, MajorVersion, MinorVersion),
        Count,# number
        '{}3{}6{}'.format(MajorVersion,MinorVersion,MinorVersion),
        number,#unuse
        NoGames,
        NoPlugins,
    )

1) Type

結合TFormAbout_FormCreate 分析得到

Professional Edition    1
unknow? trial version   2
Educational Edition		3
Personal Edition		4

2) version_info_3A8

'{}|{}{}'.format(UserName, MajorVersion, MinorVersion),

  LStrLAsg(&version, "24.2.0.5220");
  LStrAsg(&v671->ver_3_46_F84, (__int32)"3.46");
  LStrAsg(&v671->ver_3_02_F88, (__int32)"2.02");
  str_find_AA7528(version, 1, '.', &v638);      // 24
  v427 = v638;
  v426 = ".";
  str_find_AA7528(version, 2, '.', &v637);      // 2
  v424 = v637;
  // LStrCatN
  // eax,edx  (棧不定引數,從左到右入棧)
  LStrCatN((char **)&v671->version_info_3A8, 3);// 24.2
                                                // 這裡ida 沒有識別對不定引數(因為前面有些函式沒有調整,導致這裡沒能給出不定引數)

3) user_limit

在TForm1_FormCreate 和TFormAbout_FormCreate引用

4) Version

'{}3{}6{}'.format(MajorVersion,MinorVersion,MinorVersion),

5) unuse

未使用

6)NoGames

可在sub_A03F80 中看到相關應用

7)NoPlugins

可在sub_A03F80 中看到相關應用

解析函式parse_9FEB5C

int __usercall parse_9FEB5C@<eax>(__int32 a1@<eax>, __int32 a2@<ecx>)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  _InterlockedExchange(&v60, a2);
  v60 = a1;
  LStrAddRef(v24, v25, v26);
  v23 = &savedregs;
  v22 = &loc_9FF193;
  ExceptionList = NtCurrentTeb()->NtTib.ExceptionList;
  __writefsdword(0, (unsigned int)&ExceptionList);
  LStrClr();
  v20[2] = &savedregs;
  v20[1] = &loc_9FF15E;
  v20[0] = NtCurrentTeb()->NtTib.ExceptionList;
  __writefsdword(0, (unsigned int)v20);
  Trim(v60, (int)&temp);
  v3 = 0x19;
  LOBYTE(v3) = gvar_009FF1A4;
  v19 = v3;
  v18 = &v51;
  StringReplace(temp, (int)"=", 0);
  LStrLAsg(&temp, v51);
  decrypt_9FDA48((char **)&v50, 0x787, temp);
  LStrLAsg(&temp, v50);
  if ( temp )
  {
    v17 = &savedregs;
    v16 = &loc_9FF0AF;
    v15 = NtCurrentTeb()->NtTib.ExceptionList;
    __writefsdword(0, (unsigned int)&v15);
    // license 使用'#' 分隔為7部分
    str_find_AA7528(temp, 1, '#', &v49);
    Trim(v49, (int)&type);                      // 第一部分:許可型別 1,3,4
    str_find_AA7528(temp, 2, '#', &v48);
    Trim(v48, (int)&lic_info);                  // UserName|MajorVersion.MinorVersion
    str_find_AA7528(temp, 3, '#', &v47);
    Trim(v47, (int)&count);                     // user_limit
    str_find_AA7528(temp, 4, '#', &v46);
    Trim(v46, (int)&iMinorVersion);             // {MajorVersion}3{MinorVersion}6{MinorVersion}
    str_find_AA7528(temp, 5, '#', &v45);
    Trim(v45, (int)unknow);                     // unuse
    str_find_AA7528(temp, 6, '#', &v44);
    Trim(v44, (int)&NoGames);
    str_find_AA7528(temp, 7, '#', &v43);
    Trim(v43, (int)&NoPlugins);
    v4 = StrToInt(type);
    // license 許可型別
    TForm1_BCEC40->license_type_F8C = v4;
    if ( !v4 )
      goto LABEL_21;
    license_type_F8C = TForm1_BCEC40->license_type_F8C;
    if ( license_type_F8C == 1 || license_type_F8C == 3 || license_type_F8C == 4 )
    {
      // 24.2
      str_find_AA7528(TForm1_BCEC40->version_info_3A8, 1, '.', &v41);// 24
      // push    offset a3_18    ; "3"
      str_find_AA7528(TForm1_BCEC40->version_info_3A8, 2, '.', &v40);// 2
      //   push    offset a6_19    ; "6"
      str_find_AA7528(TForm1_BCEC40->version_info_3A8, 2, '.', &v39);// 2
      LStrCatN(&v42, 5, v39);                   // 版本號,x.y  {MajorVersion}3{MinorVersion}6{MinorVersion}
                                                // {x}3{y}6{y}
                                                // 243262
      LStrCmp((int)iMinorVersion, (int *)v42);
      if ( v6 )                                 // zf
      {
        // -->相等時
        str_find_AA7528(lic_info, 2, '|', &v32);
        str_find_AA7528(TForm1_BCEC40->version_info_3A8, 1, '.', &v31);// 24
        str_find_AA7528(TForm1_BCEC40->version_info_3A8, 2, '.', &v30);// 2
        LStrCat(v11, v30);
        LStrCmp((int)&v31, (int *)v31);
        if ( v6 )                               // zf
        {
          // 相等時
          str_find_AA7528(lic_info, 1, '|', v29);
          StringReplace(v29[0], (int)"\\", 0);
          LStrAsg(&TForm1_BCEC40->username_F9C, v29[1]);
          utf8_dec_AB6800((char *)TForm1_BCEC40->username_F9C, &v28);
          LStrAsg(&TForm1_BCEC40->username_F9C, v28);
          StrToInt(count);
          IntToStr(v8, (int)&v27);
          LStrAsg(&TForm1_BCEC40->user_limit_FA0, v27);
          TForm1_BCEC40->NoGames_FA4 = 1;
          TForm1_BCEC40->NoPlugins_FA5 = 1;
          LStrCmp(NoGames, (int *)"1");
          if ( !v6 )
            TForm1_BCEC40->NoGames_FA4 = 0;
          LStrCmp(NoPlugins, (int *)"1");
          if ( !v6 )
            TForm1_BCEC40->NoPlugins_FA5 = 0;
        }
        else
        {
          TForm1_BCEC40->license_type_F8C = 0;
        }
      }
      else
      {
        TForm1_BCEC40->license_type_F8C = 0;
        if ( (unsigned __int8)*iMinorVersion >= '1'
          && (unsigned __int8)*iMinorVersion <= *(_BYTE *)TForm1_BCEC40->version_info_3A8
          && (unsigned __int8)iMinorVersion[1] >= '0'
          && (unsigned __int8)iMinorVersion[1] <= '9'
          && (unsigned __int8)iMinorVersion[3] >= '0'
          && (unsigned __int8)iMinorVersion[3] <= '9' )
        {
          v38[1] = *iMinorVersion;
          v38[0] = 1;
          PStrCpy((int)v37, (unsigned __int8 *)v38);
          v36 = iMinorVersion[1];
          d_str = 1;
          PStrNCat(v37, (unsigned __int8)&d_str, 2u);
          PStrCpy((int)v34, (unsigned __int8 *)v37);
          PStrNCat(v34, (unsigned __int8)"\x01.", 3u);
          PStrCpy((int)v33, (unsigned __int8 *)v34);
          v36 = iMinorVersion[3];
          d_str = 1;
          PStrNCat(v33, (unsigned __int8)&d_str, 4u);
          LStrFromString(v7, (unsigned __int8 *)v33);
        }
      }
      goto LABEL_22;
    }
    if ( license_type_F8C == 2 )                // unknow
    {
LABEL_21:
      TForm1_BCEC40->dwordF90 = StrToInt(lic_info);
      TForm1_BCEC40->dwordF94 = StrToInt(count);
      TForm1_BCEC40->dwordF98 = StrToInt((int)iMinorVersion);
    }
LABEL_22:
    __writefsdword(0, v12);
  }
  v9 = TForm1_BCEC40->license_type_F8C;
  if ( (v9 == 1 || v9 == 3 || v9 == 4) && (!TForm1_BCEC40->username_F9C || !TForm1_BCEC40->user_limit_FA0) )
    TForm1_BCEC40->license_type_F8C = 0;
  __writefsdword(0, v13);
  __writefsdword(0, v14);
  LStrArrayClr(&loc_9FF19A);
  return LStrArrayClr(v16);
}

other

sub_A03F80

NoGames_FA4

NoPlugins_FA5

    TForm1_BCEC40->NoGames_FA4 = (*(int (__fastcall **)(const char *, const char *, int))(*(_DWORD *)TForm1_BCEC40->field_E08
                                                                                        + 0x10))(
                                   "NoGame",
                                   "Customization",
                                   1);
    v62 = 1;
    TForm1_BCEC40->NoPlugins_FA5 = (*(int (__fastcall **)(const char *, const char *, int))(*(_DWORD *)TForm1_BCEC40->field_E08
                                                                                          + 0x10))(
                                     "NoPingus",
                                     "Customization",
                                     1);

TFormAbout_FormCreate

about

  LStrCmp((*TForm1_ptr_00BA4964)->user_limit_FA0, (int *)"1");
  if ( v8 )
  {
    LStrLAsg(&v39, "(1 user)");
  }
  else
  {
    LStrCmp((*TForm1_ptr_00BA4964)->user_limit_FA0, (int *)"Unlimited");
    if ( v8 || (LStrCmp((*TForm1_ptr_00BA4964)->user_limit_FA0, (int *)"0"), v8) )
      LStrClr();
    else
      LStrCatN(&v39, 3, " users)", (*TForm1_ptr_00BA4964)->user_limit_FA0, "(");
  }
  license_type_F8C = (*TForm1_ptr_00BA4964)->license_type_F8C;
  if ( license_type_F8C == 1 || license_type_F8C == 3 || license_type_F8C == 4 )
  {
    if ( license_type_F8C == 1 )
    {
      v21 = "Professional Edition v";
      v20 = (void *)(*TForm1_ptr_00BA4964)->version_info_3A8;
      v19 = v38;
      IntToStr(v9, (int)&v33);
      LStrCatN(v34, 5, v33, " Build ");
      TControl_SetText(v11, (int *)v34[0]);
    }
    else if ( license_type_F8C == 3 )
    {
      v21 = "Educational Edition v";
      v20 = (void *)(*TForm1_ptr_00BA4964)->version_info_3A8;
      v19 = v38;
      IntToStr(v9, (int)&v31);
      LStrCatN(&v32, 5, v31, " Build ");
      TControl_SetText(v12, (int *)v32);
      LStrLAsg(&v39, "(for educational use only)");
    }
    else
    {
      v21 = "Personal Edition v";
      v20 = (void *)(*TForm1_ptr_00BA4964)->version_info_3A8;
      v19 = v38;
      IntToStr(v9, (int)&v29);
      LStrCatN(&v30, 5, v29, " Build ");
      TControl_SetText(v13, (int *)v30);
      LStrLAsg(&v39, "(for personal use only)");
    }
    LStrCatN(&v28, 4, v39, " ", (*TForm1_ptr_00BA4964)->username_F9C, "This version is registered to ");
    TControl_SetText(v14, (int *)v28);
    TControl_SetVisible(v40[0xC8], 0);
  }

py

keygen by Double Sine DoubleLabyrinth

#/usr/bin/env python3
'''
Author: Double Sine
License: GPLv3
'''
import random
from typing import Union
import os, sys, zipfile
# import base64

VariantBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
VariantBase64Dict = { i : VariantBase64Table[i] for i in range(len(VariantBase64Table)) }
VariantBase64ReverseDict = { VariantBase64Table[i] : i for i in range(len(VariantBase64Table)) }

def VariantBase64Encode(bs : bytes):
    result = b''
    blocks_count, left_bytes = divmod(len(bs), 3)

    for i in range(blocks_count):
        coding_int = int.from_bytes(bs[3 * i:3 * i + 3], 'little')
        block = VariantBase64Dict[coding_int & 0x3f]
        block += VariantBase64Dict[(coding_int >> 6) & 0x3f]
        block += VariantBase64Dict[(coding_int >> 12) & 0x3f]
        block += VariantBase64Dict[(coding_int >> 18) & 0x3f]
        result += block.encode()

    if left_bytes == 0:
        return result
    elif left_bytes == 1:
        coding_int = int.from_bytes(bs[3 * blocks_count:], 'little')
        block = VariantBase64Dict[coding_int & 0x3f]
        block += VariantBase64Dict[(coding_int >> 6) & 0x3f]
        result += block.encode()
        return result
    else:
        coding_int = int.from_bytes(bs[3 * blocks_count:], 'little')
        block = VariantBase64Dict[coding_int & 0x3f]
        block += VariantBase64Dict[(coding_int >> 6) & 0x3f]
        block += VariantBase64Dict[(coding_int >> 12) & 0x3f]
        result += block.encode()
        return result

def VariantBase64Decode(s : str):
    if isinstance(s,bytes):
        s=s.decode()
    result = b''
    blocks_count, left_bytes = divmod(len(s), 4)

    for i in range(blocks_count):
        block = VariantBase64ReverseDict[s[4 * i]]
        block += VariantBase64ReverseDict[s[4 * i + 1]] << 6
        block += VariantBase64ReverseDict[s[4 * i + 2]] << 12
        block += VariantBase64ReverseDict[s[4 * i + 3]] << 18
        result += block.to_bytes(3, 'little')

    if left_bytes == 0:
        return result
    elif left_bytes == 2:
        block = VariantBase64ReverseDict[s[4 * blocks_count]]
        block += VariantBase64ReverseDict[s[4 * blocks_count + 1]] << 6
        result += block.to_bytes(1, 'little')
        return result
    elif left_bytes == 3:
        block = VariantBase64ReverseDict[s[4 * blocks_count]]
        block += VariantBase64ReverseDict[s[4 * blocks_count + 1]] << 6
        block += VariantBase64ReverseDict[s[4 * blocks_count + 2]] << 12
        result += block.to_bytes(2, 'little')
        return result
    else:
        raise ValueError('Invalid encoding.')

def EncryptBytes(key : int, bs : bytes):
    result = bytearray()
    for i in range(len(bs)):
        result.append(bs[i] ^ ((key >> 8) & 0xff))
        key = result[-1] & key | 0x482D
    return bytes(result)

def DecryptBytes(key : int, bs : bytes):
    result = bytearray()
    for i in range(len(bs)):
        result.append(bs[i] ^ ((key >> 8) & 0xff))
        key = bs[i] & key | 0x482D
    return bytes(result)

class LicenseType:
    Professional = 1
    Educational = 3
    Persional = 4

def GenerateLicense(Type : LicenseType, Count :Union[int,str] , UserName : str, MajorVersion : int, MinorVersion,NoGames=0,NoPlugins=0):
    # 
    if isinstance(Count,str) :
        if Count.isdecimal():
            Count=int(Count)
    assert(Count >= 0) 
    # 'Unlimited'  or number
    '''

    LicenseString = '%d#%s|%d%d#%d#%d3%d6%d#%d#%d#%d#' % (Type, 
                                                          UserName, MajorVersion, MinorVersion, 
                                                          Count, 
                                                          MajorVersion, MinorVersion, MinorVersion,
                                                          0,    # Unknown
                                                          NoGames,    # No Games flag. 0 means "NoGames = false". But it does not work.
                                                          NoPlugins)    # No Plugins flag. 0 means "NoPlugins = false". But it does not work.
    '''
    x=0
    # x=random.randint(0,0xff)
    lic_format='{0}#{1}#{2}#{3}#{4}#{5}#{6}#'.format(
        Type,
        '{}|{}{}'.format(UserName, MajorVersion, MinorVersion),
        Count,# number or 'Unlimited'
        '{}3{}6{}'.format(MajorVersion,MinorVersion,MinorVersion),
        x,#unuse
        NoGames,
        NoPlugins,
    )
    LicenseString=lic_format
    EncodedLicenseString = VariantBase64Encode(EncryptBytes(0x787, LicenseString.encode())).decode()
    return EncodedLicenseString

def help():
    print('Usage:')
    print('    MobaXterm-Keygen.py <UserName> <Version>')
    print()
    print('    <UserName>:      The Name licensed to')
    print('    <Version>:       The Version of MobaXterm')
    print('                     Example:    24.2')
    print()


def main():
    if len(sys.argv) != 3:
        help()
        exit(0)
    else:
        MajorVersion, MinorVersion = sys.argv[2].split('.')[0:2]
        MajorVersion = int(MajorVersion)
        MinorVersion = int(MinorVersion)

        EncodedLicenseString=GenerateLicense(LicenseType.Professional, 
                        1,
                        sys.argv[1], #username
                        MajorVersion, 
                        MinorVersion)

        with zipfile.ZipFile('Custom.mxtpro', 'w') as f:
            f.writestr('Pro.key', data = EncodedLicenseString)
        print('[*] Success!')
        print('[*] File generated: %s' % os.path.join(os.getcwd(), 'Custom.mxtpro'))
        print('[*] Please move or copy the newly-generated file to MobaXterm\'s installation path.')
        print()
# else:
#     print('[*] ERROR: Please run this script directly')    





    
def test():
    EncodedLicenseString=GenerateLicense(LicenseType.Professional, 
                        1,
                        'test', 
                        24, 
                        2)    
    print('EncodedLicenseString:',EncodedLicenseString)
    with open('temp.key','w') as f:
          f.write(EncodedLicenseString)
    print('''cmd:\n
          MobaXterm_Personal_24.2.exe -chklic temp.key
          ## will auto generate 'Custom.mxtpro'
          ''')

if __name__ == '__main__':
    #main()

    test()

image-20240922201622421

相關文章