Featured image of post Emeditor 反盗版校验分析

Emeditor 反盗版校验分析

本文深入逆向分析了 Emeditor 的反盗版机制,追踪了 WinVerifyTrust 校验及 DLL 消息通信流程

Emeditor 反盗版校验分析

版本 build 11.24 v25.4.3 由于这个玩意十年来开裂方案众多,包括但不限于算本地校验,一码多用,公钥替换等等,故如何开裂或者本地注册我们就不看了,只看看最抽象的反盗版。

1. 通过导入表追踪 WinVerifyTrust

// Emeditor 主程序
// 在emeddlgs.dll中有高度相似的函数。
__int64 __fastcall sub_141803100(__int64 a1)
{
  char v2; // al
  __int64 result; // rax
  unsigned int va; // edi
  UINT n130; // edx
  HWND ActiveWindow; // rax
  int v7; // ecx
  GUID pgActionID; // [rsp+30h] [rbp-D0h] BYREF
  _QWORD v9[2]; // [rsp+40h] [rbp-C0h] BYREF
  __int128 v10; // [rsp+50h] [rbp-B0h]
  __int128 pWVTData; // [rsp+60h] [rbp-A0h] BYREF
  __int128 v12; // [rsp+70h] [rbp-90h]
  __int64 v13; // [rsp+80h] [rbp-80h]
  _QWORD *v14; // [rsp+88h] [rbp-78h]
  __int128 v15; // [rsp+90h] [rbp-70h]
  __int128 v16; // [rsp+A0h] [rbp-60h]
  __int64 v17; // [rsp+B0h] [rbp-50h]
  wchar_t String[80]; // [rsp+C0h] [rbp-40h] BYREF
  WCHAR Buffer[800]; // [rsp+160h] [rbp+60h] BYREF
  __int64 v20; // [rsp+7B8h] [rbp+6B8h] BYREF

  if ( byte_141C8644F ) // 是否是UWP版本
    return 1;
  v2 = byte_141C3A79E;
  if ( byte_141C3A79E == -1 )
  {
    v2 = sub_140F57E00(L"SDS", 0) == 0x78C19A68; // 跳过签名检查
    byte_141C3A79E = v2;
  }
  if ( v2 == 1 )
    return 1;
  result = sub_140F58980(a1, 0xFFFFFFFFLL);
  if ( !(_DWORD)result )
    return result;
  v9[0] = 32;
  v9[1] = a1;
  v17 = 0;
  v13 = 1;
  pWVTData = 0;
  v12 = 0;
  v14 = v9;
  v16 = 0;
  LODWORD(pWVTData) = 88;
  v10 = 0;
  DWORD2(v12) = 2;
  v15 = 0;
  DWORD2(v16) = 16;
  pgActionID.Data1 = 0xAAC56B;
  *(_DWORD *)&pgActionID.Data2 = 0x11D0CD44;
  *(_DWORD *)pgActionID.Data4 = 0xC000C28C;
  *(_DWORD *)&pgActionID.Data4[4] = 0xEE95C24F;
  // {00AAC56B-CD44-11d0-8CC2-00C04FC295EE}
  va = WinVerifyTrust(0, &pgActionID, &pWVTData); // 检查签名
  /*
  .text:00000001418031F4                 call    cs:WinVerifyTrust
  .text:00000001418031FA                 mov     edi, eax
  .text:00000001418031FC                 test     eax, eax
  .text:00000001418031FE                 jz      loc_1418032C8
  */
  if ( !va )
    return 1;
  memset(Buffer, 0, sizeof(Buffer));
  n130 = 130;
  if ( va == (unsigned int)CERT_E_CHAINING )
    n130 = 222;
  LoadStringW(hInstance, n130, Buffer, 800);
  if ( va != (unsigned int)CERT_E_CHAINING )
  {
    swprintf(String, 0x50u, L" (0x%x)", va);
    sub_1418FE480(Buffer, 800, String);
  }
  LODWORD(v20) = 0;
  ActiveWindow = GetActiveWindow();
  v7 = sub_140F94A60(ActiveWindow, (__int64)Buffer, a1, (__int64)&v20, 2, 65534);
  result = v7 == 1;
  if ( (_DWORD)v20 )
  {
    if ( v7 == 1 )
      byte_141C3A79E = 1;
  }
  return result;
}

如果需要绕过检查并最少修改,最少影响,可以直接把 test eax, eax 修改为 xor eax, eax。没什么话讲,根据微软文档: If the trust provider verifies that the subject is trusted for the specified action, the return value is zero.,该函数会在签名正常时返回 0,因为不确定 eax 是否还有别的影响。另外,dll 和主程序都要修补。 如果主程序没过验证,根据代码逻辑,会根据语言载入对应资源 dll 中的字符串,弹报错窗口。

报错窗口

dll 没过验证会在打开注册信息或关于时弹报错窗,与上面的弹窗类似。

2. 公钥差异

公钥:

.rdata:0000000141987530 aBeginPublicKey db '-----BEGIN PUBLIC KEY-----',0Ah
.rdata:0000000141987530                                         ; DATA XREF: sub_140024CC0+2Bo
.rdata:000000014198754B                 db 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEdFXkM+nFOhoI8bjAsThl/59LXkYK7f2F',0Ah
.rdata:000000014198758C                 db '1baTcak6GyFecRF0CXvvF2UzIozW1LZ/8bxd1XnBUvry5X6zchitsQ80ZppPrCBv',0Ah
.rdata:00000001419875CD                 db '7SqnMf/A2QscH1uA5kf1iPPezfOONKla',0Ah
.rdata:00000001419875EE                 db '-----END PUBLIC KEY-----',0Ah

如果是已注册,且 dll 和主程序公钥不同,在打开注册信息或关于时会被弹盗版窗口,并根据语言打开 url: https://zh-cn.emeditor.com/crack-keygen-serial/ 。此处一时半会没找到对应函数,估计也是哪个虚函数的,通过消息发送的。。。

3. 反盗版弹窗

总购买弹窗,弹出不同的购买网页

__int64 __fastcall purchase_dlg_140F1EA80(__int64 a1, unsigned int a2, __int16 id)
{
  __int64 v4; // rsi
  int Locale; // edx
  int n1041; // eax
  int v8; // edx
  int n10; // edx
  const wchar_t *ko; // r8
  const wchar_t *_purchase; // r8
  __int64 n883; // rax

  v4 = a2;
  sub_1418FE3F0(a1, a2, L"https://");
  Locale = ::Locale;
  if ( (id == 4058 || id == 1111 || id == 919 || id == 333 || id == 1) && (::Locale == 1031 || ::Locale == 1049) )
    Locale = 0;
  if ( id == 2 )
  {
    n1041 = 0;
    if ( Locale == 1041 )
      n1041 = 1041;
    Locale = n1041;
  }
  if ( Locale <= 1042 )
  {
    if ( Locale == 1042 )
    {
      ko = L"ko";
      goto LABEL_27;
    }
    v8 = Locale - 1028;
    if ( !v8 )
    {
      ko = L"zh-tw";
      goto LABEL_27;
    }
    n10 = v8 - 3;
    if ( !n10 )
    {
      ko = L"de";
      goto LABEL_27;
    }
    if ( n10 == 10 )
    {
      ko = L"jp";
      goto LABEL_27;
    }
    goto LABEL_24;
  }
  if ( Locale == 1049 )
  {
    ko = L"ru";
  }
  else
  {
    if ( Locale != 2052 )
    {
LABEL_24:
      ko = L"www";
      goto LABEL_27;
    }
    ko = L"zh-cn";
  }
LABEL_27:
  sub_1418FE480(a1, v4, ko);
  sub_1418FE480(a1, v4, L".emeditor.com/");
  switch ( id )
  {
    case 4248:
      _purchase = L"#purchase";
      break;
    case 1111:
      _purchase = L"crack-keygen-serial/";
      break;
    case 919:
      _purchase = L"increase-virtual-memory/";
      break;
    case 333:
      _purchase = L"text-editor-features/history/emeditor-free/";
      break;
    case 1:
      _purchase = L"category/emeditor-core/";
      break;
    case 2:
      _purchase = L"forums/forum/beta/";
      break;
    default:
      n883 = 883;
      if ( id != 883 )
        return n883;
      _purchase = L"#download";
      break;
  }
  return sub_1418FE480(a1, v4, _purchase);
}

基本上是本地化处理+字符串拼接。我们主要关心 传入的 id,最重要的是其等于 1111 的情况。

xref,找到总购买函数第三个参数为 1111 的情况

__int64 __fastcall sub_140F5CA60(HWND *lpParameter, int n2)
{
  [...折叠一下,这样你才知道看的是代码...]
  if ( g_check_flag == 3 )
  {
    sub_140F76B00((__int64)lpParameter, 0x457u, 48, 0);
    purchase_dlg_140F1EA80((__int64)File, 0x100u, 1111);
    sub_140F4F570(lpParameter[1], File, 0);
    LODWORD(v9) = 0;
  }
  else if ( n7_1 > 7 )
  {
    LODWORD(v9) = -1;
  }
  if ( n2 == 2 || n7 <= 1 )
  {
    sub_140F99220(lpParameter);
    sub_1408D8FE0(&SystemTimeAsFileTime, 1);
    v17 = sub_1408D90F0((HMODULE *)&SystemTimeAsFileTime, (const CHAR *)5);
    if ( v17 )
    {
      hMutex = (void *)sub_140422E10(0, L"DlgMutex", 1);
      if ( hMutex )
      {
        n1812 = ((__int64 (__fastcall *)(HWND, _QWORD))v17)(lpParameter[1], (unsigned int)n7);
        n2032 = n1812;
        if ( n1812 != 2 )
        {
          if ( n1812 == 1009 )
          {
            sub_140F5CD60(lpParameter, 1);
            LODWORD(v9) = n2032;
          }
          else if ( n1812 == 1812 && sub_140F04C90(lpParameter) )
          {
            LODWORD(v9) = 1;
          }
          if ( byte_141C8644F )
          {
            if ( n2032 == 2032 )
            {
              byte_141C865E5 = 1;
              byte_141C865EA = 1;
              sub_141783840(lpParameter);
              LODWORD(v9) = 1009;
            }
          }
        }
        ReleaseMutex(hMutex);
      }
    }
    else
    {
      sub_140F5CA20(v16, v15);
    }
    sub_1408D90E0(&SystemTimeAsFileTime);
  }
  else
  {
    sub_140F97B40(v10, 10, 0, n7);
  }
  return (unsigned int)v9;
}

如果 g_check_flag 为 3 ,emeditor 会调用 sub_140F76B00 创建一个强制前台(SetForegroundWindow),所有按钮事件均为向主程序发送关闭消息的窗口,弹盗版窗。此处的 g_check_flag 值得关注,我们 xref 下。

4. g_check_flag 分析

mov cs:g_check_flag, 3

第一处

__int64 __fastcall sub_140F0C210(_BYTE *lpParameter)
{
  if ( !byte_141CA7881 )
  {
    byte_141CA7881 = 1;
    g_check_flag = 3;
    sub_140F5CA60(lpParameter, 1);
    byte_141CA7881 = 0;
  }
  return 0;
}

第二处

__int64 __fastcall sub_140F0C250(__int64 n768)
{
  g_check_flag = 3;
  sub_140F97B40(n768, 37, 0, 0);
  return 0;
}

非常有趣,此处 sub_140F0C210 将 g_check_flag 设置为 3 后调用前面的 sub_140F5CA60,那还说啥了,再 xref 呗。

函数sub_140E8E000与内部big_sw1的引用

sub_140E8E000是一个数千行的超级无敌大状态机。此处注意到在 id 为 32929 时会调用前面的 sub_140F0C250,这里截图没截全。x 一下 big_sw1,看看 sub_140F0C210 调用前哪里对 big_sw1 有写操作。

_BOOL8 __fastcall sub_140E8E000(
        char *lpParameter,
        int a2,
        __int64 n32797,
        HMENU n50332098,
        LPFILETIME lpCreationTime,
        __int64 *a6,
        unsigned int item)

根据代码,可以知道 32929 这个值是作为 sub_140E8E000 第三个参数直接传递进来的。

sub_140E8E000 是一个虚函数,要找会非常红温。但是我们知道 32928,也就是 80A0h。其要么是硬编码的 mov r8d, 80A0h,要么是通过 windows 消息机制传递 mov edx, 80A0hcall PostMessageW 或者 call SendMessageW。此处搜 80A1h 也是一样的,会找到 sub_140F0C250 的相关引用。但 ida 分析都是一坨。

在 EmEditor 主程序里搜索,无结果: EmEditor 主程序中的搜索结果 在 emeddlgs.dll 中搜索,看到了 edx: emeddlgs.dll 搜索结果

__int64 __fastcall sub_180363EF0(_QWORD *p_hWnd)
{
  [...折叠一下,这样你才知道看的是代码...]
  if ( (unsigned __int8)sub_1803F9BF0(v3) )
  {
    v9 = p_hWnd + 9;
    if ( p_hWnd[12] <= 7u )
      v12 = p_hWnd + 9;
    else
      v12 = (_QWORD *)*v9;
    v13 = p_hWnd[11];
    v10 = p_hWnd + 5;
    *(_OWORD *)wParam = 0;
    v31 = 0;
    n7_1 = 0;
    if ( p_hWnd[8] <= 0xFu )
      v14 = p_hWnd + 5;
    else
      v14 = (_QWORD *)*v10;
    sub_1803623B0(wParam, v14, p_hWnd[7]);
    v15 = sub_1803321BC(&v33);
    v16 = sub_1803F9F30(v15);
    v17 = sub_1803321BC(v16);
    *(_QWORD *)&v29 = v12;
    *((_QWORD *)&v29 + 1) = v13;
    v11 = p_hWnd + 1;
    sub_180355890(&v28, v17, (struct_a1 *)(p_hWnd + 1), (__int64)wParam, &v29);
    if ( v28.byte20 )
    {
      if ( v28.ill_check == 19 )
      {
        n36 = 0;
        n32929 = 32929;
      }
      else if ( v28.ill_check == 20 )
      {
        n32929 = 32911;
        n36 = 36;
      }
      else
      {
        n36 = 0;
        if ( v28.ill_check == 21 )
          n32929 = 32928;  // 看这里
        else
          n32929 = 32930;
      }
      PostMessageW((HWND)*p_hWnd, n32929, n36, 0);
      v21 = v28.byte20 == 0;
    }
    else
    {
      *(_OWORD *)wParam_2 = 0;
      v26 = 0;
      n7 = 0;
      v18 = &v28;
      if ( *((_QWORD *)&v28.oword10 + 1) > 7u )
        v18 = *(struct_a1 **)&v28.ill_check;
      sub_18032B7C4(wParam_2, v18, *(_QWORD *)&v28.oword10);
      wParam_1 = wParam_2;
      if ( n7 > 7 )
        wParam_1 = (WPARAM *)wParam_2[0];
      SendMessageW((HWND)*p_hWnd, 0x80A5u, (WPARAM)wParam_1, 0);
      if ( n7 > 7 )
      {
        if ( 2 * n7 + 2 < 0x1000 )
        {
          v20 = (void *)wParam_2[0];
        }
        else
        {
          v20 = *(void **)(wParam_2[0] - 8);
          if ( wParam_2[0] - (unsigned __int64)v20 - 8 > 0x1F )
            __fastfail(5u);
        }
        sub_1803F8518(v20);
      }
      v26 = 0;
      n7 = 7;
      LOWORD(wParam_2[0]) = 0;
      v21 = v28.byte20 == 0;
    }
    if ( v21 )
      sub_18032A2F4(&v28);
    sub_1803321D4(&v33);
  }
  else
  {
    v4 = sub_1803321BC(&v33);
    v5 = sub_1803F9920(v4, wParam_2);
    v6 = sub_1803F9760(v5);
    v7 = sub_18042C880(v6);
    *(_QWORD *)&v29 = v6;
    *((_QWORD *)&v29 + 1) = v7;
    sub_1803541B0(wParam, &v29);
    sub_1803F9580(wParam_2);
    wParam_3 = wParam;
    if ( n7_1 > 7 )
      wParam_3 = (WPARAM *)wParam[0];
    SendMessageW((HWND)*p_hWnd, 0x80A5u, (WPARAM)wParam_3, 0);
    sub_18032A2F4(wParam);
    sub_1803321D4(&v33);
    v9 = p_hWnd + 9;
    v10 = p_hWnd + 5;
    v11 = p_hWnd + 1;
  }
  sub_1803F80B8();
  sub_18032A2F4(v9);
  sub_180333260(v10);
  sub_180333260(v11);
  sub_1803F8518(p_hWnd);
  return 0;
}

在这里 ill_check 的值实际上由 v31 决定。再追就不礼貌了,指针太几把多了,ida 还很弱智的全识别成数值了。知道消息由 dll 传入就行。我们可以选择将其 patch 为一个无效的消息。

或者将之后的任意一处 patch 掉,比如跳过 g_check_flag 检查,手动设置为合法值或者把购买函数和紫砂函数 patch 等等等。。。

回到主程序,g_check_flag 总处理函数

这是一套完全不按顺序的抽象检测:

__int64 sub_140F02E80(_BYTE *lpParameter, int n2, ...)
{
  double _XMM2; // xmm2_8
  bool v4; // zf
  int g_check_flag; // eax
  unsigned __int16 n1108; // dx
  __int64 v7; // rdx
  unsigned __int64 n2_2; // rsi
  int v9; // esi
  int v10; // eax
  __int64 v11; // rcx
  int n5; // eax
  UINT n354; // edx
  int n1009; // eax
  HWND LastActivePopup; // rax
  HWND LastActivePopup_1; // rsi
  __int64 v17; // rcx
  WCHAR *pszPath; // rbx
  struct _SYSTEMTIME SystemTime; // [rsp+30h] [rbp-D0h] BYREF
  __m128i i; // [rsp+40h] [rbp-C0h] BYREF
  _BYTE v22[80]; // [rsp+80h] [rbp-80h] BYREF
  WCHAR Buffer[264]; // [rsp+D0h] [rbp-30h] BYREF
  _BYTE v24[2016]; // [rsp+2E0h] [rbp+1E0h] BYREF
  int n2_1; // [rsp+AD8h] [rbp+9D8h] BYREF
  struct _FILETIME SystemTimeAsFileTime; // [rsp+AE0h] [rbp+9E0h] BYREF
  va_list SystemTimeAsFileTimea; // [rsp+AE0h] [rbp+9E0h]
  FILETIME FileTime2; // [rsp+AE8h] [rbp+9E8h] BYREF
  va_list FileTime2a; // [rsp+AE8h] [rbp+9E8h]
  FILETIME FileTime1; // [rsp+AF0h] [rbp+9F0h] BYREF
  va_list FileTime1a; // [rsp+AF0h] [rbp+9F0h]
  va_list va3; // [rsp+AF8h] [rbp+9F8h] BYREF

  va_start(va3, n2);
  va_start(FileTime1a, n2);
  va_start(FileTime2a, n2);
  va_start(SystemTimeAsFileTimea, n2);
  SystemTimeAsFileTime = va_arg(FileTime2a, struct _FILETIME);
  va_copy(FileTime1a, FileTime2a);
  FileTime2 = va_arg(FileTime1a, FILETIME);
  va_copy(va3, FileTime1a);
  FileTime1 = va_arg(va3, FILETIME);
  n2_1 = n2;
  if ( dword_141C3A784 )
  {
    if ( lpParameter[31022] )
      goto LABEL_6;
    v4 = *((_QWORD *)lpParameter + 2287) == 0;
  }
  else
  {
    v4 = lpParameter[31022] == 0;
  }
  if ( v4 )
    return 0;
LABEL_6:
  if ( byte_141C8644F && !byte_141C865E4 ) // 这里检查 UWP 版本和另一个诡异的全局变量?
    goto LABEL_45;
  if ( *((_QWORD *)lpParameter + 2287) )
    *((_QWORD *)lpParameter + 2287) = 0;
  g_check_flag = g_check_flag;
  if ( lpParameter[31017] )
  {
    if ( (g_check_flag & 0xFFFFFFFC) != 0 || g_check_flag == 2 )
    {
      n1108 = 1108;
      if ( ((g_check_flag - 2) & 0xFFFFFFFD) != 0 )
        n1108 = 1137;
      sub_140F76B00((__int64)lpParameter, n1108, 48, 0);
      PostMessageW(*((HWND *)lpParameter + 1), 0x16u, 1u, 4097);
    }
    goto LABEL_45;
  }
  if ( g_check_flag == 1 )
  {
    if ( (unsigned __int8)sub_1418BC860(&i, v22, 14, v24, 1000) )
    {
      n2_2 = sub_1419110F0(&i, v7, _XMM2);
      if ( (n2_2 < 2 || (unsigned int)sub_1403BF240(&i, L"r-", 2)) && ((n2_2 - 20) & 0xFFFFFFFFFFFFFFFBuLL) != 0 )
      {
        GetLocalTime(&SystemTime);
        v9 = sub_141803610(SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay);
        v10 = sub_1418036E0(&i);
        if ( v10 )
        {
          v11 = (unsigned int)(v9 + 30);
          if ( v10 <= (int)v11 )
          {
            n5 = v10 - v9;
            n354 = 354;
            if ( n5 <= 5 )
            {
LABEL_29:
              if ( lpParameter[31021] || (unsigned int)sub_140F02800(lpParameter, n354) == 7 )
                return 0;
              goto LABEL_31;
            }
            sub_140F97B40(v11, 11, 0, n5);
          }
        }
      }
    }
  }
  else
  {
    if ( g_check_flag != 6 )
      goto LABEL_32;
    if ( sub_140F57E00(L"PromptRegExpired2", 1) )
    {
      n354 = 355;
      goto LABEL_29;
    }
  }
LABEL_31:
  g_check_flag = g_check_flag;
LABEL_32:
  if ( g_check_flag != 1 )
  {
    if ( g_check_flag == 4
      && LoadStringW(hInstance, 0x474u, Buffer, 260)
      && (unsigned int)sub_140F76AD0(lpParameter, Buffer, 52) == 6 )
    {
      sub_140F4F570(*((HWND *)lpParameter + 1), L"https://support.emeditor.com/", 0);
    }
    n1009 = sub_140F5CA60((HWND *)lpParameter, 0);
    if ( n1009 == -1 )
    {
      if ( g_check_flag != 3 )
      {
        sub_140F76B00((__int64)lpParameter, 0x14Fu, 16, 0);
        sub_140F580A0(L"Edition", 4);
LABEL_52:
        PostMessageW(*((HWND *)lpParameter + 1), 0x16u, 1u, 4097);
        return 0;
      }
    }
    else
    {
      if ( n1009 == 1009 )
      {
        if ( lpParameter[31022] )
          sub_140F580A0(L"Edition", 2);
        return 0;
      }
      if ( g_check_flag == 3 && !n1009 )
        goto LABEL_52;
    }
  }
LABEL_45:
  if ( lpParameter[31020] )
  {
    LastActivePopup = GetLastActivePopup(*((HWND *)lpParameter + 1));
    LastActivePopup_1 = LastActivePopup;
    if ( LastActivePopup )
    {
      if ( LastActivePopup != *((HWND *)lpParameter + 1) && IsWindow(LastActivePopup) )
        SendMessageW(LastActivePopup_1, 0x805Eu, 0, g_check_flag);
    }
  }
  if ( lpParameter[31022] )
  {
    sub_140F580A0(L"Edition", 2);
    v17 = *((_QWORD *)lpParameter + 1);
    lpParameter[31022] = 0;
    if ( (unsigned int)sub_140F955E0(v17) == 1 )
      goto LABEL_52;
  }
  else if ( (unsigned __int8)sub_140F5AA80() )
  {
    if ( !qword_141C865C0 )
    {
      pszPath = (WCHAR *)j__malloc_base(0x10000u);
      if ( pszPath )
        *pszPath = 0;
      if ( GetTempPathW(0x8000u, pszPath) )
      {
        PathCchAppendEx(pszPath, 0x8000u, L"eeinst.exe", 1u);
        DeleteFileW(pszPath);
      }
      j__free_base(pszPath);
    }
    FileTime2 = 0;
    n2_1 = 2;
    if ( (unsigned int)sub_140F5AC30((FILETIME *)FileTime2a, &n2_1) )
    {
      if ( sub_140F57E00(L"NewVerAvailable", 0) )
      {
        sub_140F034C0(lpParameter);
      }
      else
      {
        SystemTimeAsFileTime = 0;
        GetSystemTimeAsFileTime((LPFILETIME)SystemTimeAsFileTimea);
        FileTime1 = SystemTimeAsFileTime;
        if ( !FileTime2.dwHighDateTime && !FileTime2.dwLowDateTime
          || (FileTime2 = (FILETIME)((FileTime2.dwLowDateTime | ((unsigned __int64)FileTime2.dwHighDateTime << 32))
                                   + 864000000000LL * n2_1),
              CompareFileTime((const FILETIME *)FileTime1a, (const FILETIME *)FileTime2a) >= 0) )
        {
          sub_140F033C0(lpParameter, 0);
        }
      }
    }
  }
  return 0;
}

值 1 根据看雪 EmEditor 24.5.3-25.1.x Stripe注册码 以及官方的 EmEditor 帮助使用 Stripe 登录 EmEditor,我们知道"r-“开头的注册码是特殊的 Stripe 注册码。
值 2 和大于 3 的值放到一起检查,说明 2 和大于 3 的值都是无效值。
值 3 之前说过,查到盗版了。
这里屎山代码发力了,虽然排除了大于 3 的值,但又在先前为值 4 和值 6 加入了额外检查。 值 4 不知道是啥,但会进行日期的一个检的查。并且还试图弹窗和打开 support。
值 6 弹一个 PromptRegExpired2 的窗口,可能是试用期过期。

0 则是是一切安好。 这里逻辑极其怪异,包括但不限于:(g_check_flag & 0xFFFFFFFC) != 0 || g_check_flag == 2 混写位运算和排除代码,乱序值检查,0 到 6 七个值只有 0, 1, 3, 4, 6 是有效的等等。

总结:依托答辩,要么是不会写代码,要么是编译器优化的太过抽象。我真就搞不懂了一个值的检查就5个有效值怎么乱成这个鬼样子。

Built with Hugo
Theme Stack designed by Jimmy