本文以电脑端学习通(超星)客户端的JavaScript加密逻辑为研究对象,通过逆向工程手段系统分析了其前端数据加密流程,探究了NodeJS C++相关API与逆向方案,使得Electron写的客户端可以打开devTools从而方便与前端-后端通讯分析。

Why Reverse

之前为了方便,抓了抓学习通的API,但是之前好像还是没有加密js,所以直接改了改代码就开了DevTools,不料不久后学习通就加密了Js,为了学习与分享,于是就有了本文。

NodeJs层加载

以下是Jscx.js的内容,这个文件负责劫持js加载

"use strict";

const fs = require("fs");
const v8 = require("v8");
const path = require("path");
const Module = require("module");

let compileExt;
if (process.platform == "darwin") {
  compileExt = require("../mac/CompileExt");
} else {
  compileExt = require("../win/CompileExt");
}

v8.setFlagsFromString("--no-lazy");

const COMPILED_EXTNAME = ".jscx";

Module._extensions[COMPILED_EXTNAME] = function (fileModule, filename) {
  function require(id) {
    return fileModule.require(id);
  }
  require.resolve = function (request, options) {
    return Module._resolveFilename(request, fileModule, false, options);
  };
  require.main = process.isMainFrame;

  require.extensions = Module._extensions;
  require.cache = Module._cache;

  const dirname = path.dirname(filename);
  const proDirPath = path.join(__dirname, "../../../");
  compileExt.RunFile(
    filename,
    dirname,
    fileModule.exports,
    require,
    fileModule,
    process,
    global,
    proDirPath
  );
};
  • 注册自定义扩展名处理程序

代码实现了 Module._extensions[COMPILED_EXTNAME] = function (fileModule, filename),为.jscx注册了默认加载器,当Nodejs加载.jscx的时候,会调用此加载器而不是默认加载器。

  • 使用Native模块来解密文件

CompileExt是C++编写的NodeJs Native,该代码调用RunFile解密并运行加密的代码(.jscx 文件)。

What is Node-API

Node-API(以前称为 N-API)是用于构建原生插件的 API。它独立于底层 JavaScript 运行时(例如 V8),并作为 Node.js 本身的一部分进行维护。此 API 将在 Node.js 的各个版本中保持稳定的应用二进制接口 (ABI)。它旨在将插件与底层 JavaScript 引擎的变化隔离开来,并允许为一个主要版本编译的模块无需重新编译即可在以后的 Node.js 主要版本上运行。ABI 稳定性 指南提供了更深入的解释。

摘选自 Node-API | Node.js v23 文档

以下我们了解几种需要认识的N-API与几种结构

napi_create_function

napi_status napi_create_function(napi_env env,
                                 const char* utf8name,
                                 size_t length,
                                 napi_callback cb,
                                 void* data,
                                 napi_value* result);
  • [in] env:调用 API 的环境。

  • [in] utf8Name:编码为 UTF8 的函数的可选名称。这在 JavaScript 中是可见的,作为新函数对象的 name 属性。

  • [in] lengthutf8name 的长度(以字节为单位)或 NAPI_AUTO_LENGTH(如果以 null 结尾)。

  • [in] cb:调用此函数对象时应调用的原生函数。napi_callback 提供了更多详细信息。

  • [in] data:用户提供的数据上下文。这将在稍后调用时传回函数。

  • [out] resultnapi_value 表示新创建函数的 JavaScript 函数对象。

如果 API 成功,则返回 napi_ok

此 API 允许插件作者以原生代码创建函数对象。这是允许从 JavaScript 调用加载项的原生代码的主要机制。

在此调用之后,新创建的函数不会自动从脚本中可见。相反,必须在 JavaScript 可见的任何对象上显式设置属性,以便可以从脚本访问该函数。

napi_set_named_property

napi_status napi_set_named_property(napi_env env,
                                    napi_value object,
                                    const char* utf8Name,
                                    napi_value value); 
  • [in] env:调用 Node-API 调用的环境。

  • [in] object:要对其设置属性的对象。

  • [in] utf8Name:要设置的属性的名称。

  • [in] value:属性值。

如果 API 成功,则返回 napi_ok

此方法等效于使用从作为 utf8Name 传入的字符串创建的 napi_value 调用 napi_set_property

napi_get_cb_info

napi_status napi_get_cb_info(napi_env env,
                             napi_callback_info cbinfo,
                             size_t* argc,
                             napi_value* argv,
                             napi_value* thisArg,
                             void** data)
  • [in] env:调用 API 的环境。

  • [in] cbinfo:传递给回调函数的回调信息。

  • [in-out] argc:指定所提供的 argv 数组的长度并接收参数的实际计数。argc 可以选择性地通过传递 NULL 来忽略。

  • [out] argv:参数将被复制到的 napi_value 的 C 数组。如果参数数量多于提供的数量,则只复制请求数量的参数。如果提供的参数比声明的少,则 argv 的其余部分将填充代表 undefinednapi_value 值。argv 可以选择性地通过传递 NULL 来忽略。

  • [out] thisArg:接收调用的 JavaScript this 参数。thisArg 可以选择性地通过传递 NULL 来忽略。

  • [out] data:接收回调的数据指针。data 可以选择性地通过传递 NULL 来忽略。

如果 API 成功,则返回 napi_ok

此方法在回调函数中用于检索有关调用的详细信息,例如来自给定回调信息的参数和 this 指针。

napi_status napi_get_value_string_utf8(napi_env env,
                                       napi_value value,
                                       char* buf,
                                       size_t bufsize,
                                       size_t* result) 
  • [in] env:调用 API 的环境。

  • [in] valuenapi_value 代表 JavaScript 字符串。

  • [in] buf:将 UTF8 编码的字符串写入的缓冲区。如果传入 NULL,则在 result 中返回以字节为单位的字符串长度,不包括空终止符。

  • [in] bufsize:目标缓冲区的大小。当此值不足时,返回的字符串将被截断并以 null 终止。

  • [out] result:复制到缓冲区中的字节数,不包括空终止符。

如果 API 成功,则返回 napi_ok。如果传入非 string napi_value,则返回 napi_string_expected

此 API 返回对应于传入值的 UTF8 编码字符串。

napi_callback

用户提供的原生函数的函数指针类型,这些函数将通过 Node-API 公开给 JavaScript。回调函数应满足以下签名:

typedef napi_value (*napi_callback)(napi_env, napi_callback_info); 

create_addon

负责注册NodeJs Native函数,和JNI的JNI_OnLoad一样。

逆向 CompileExt.node

这个.node在Windows下相当于一个.dll。

我们使用IDA,通过 napi_create_function 等关键字样找到 create_addon 函数

int __cdecl create_addon(int env)
{
  int out; // [esp+8h] [ebp-4h] BYREF
  int result; // [esp+14h] [ebp+8h]

  if ( napi_create_function(env, 0, 0, callback_GetDbKey, 0, &out)
    || napi_set_named_property(env, result, "GetDbKey", out)
    || napi_create_function(env, 0, 0, callback_RunFile, 0, &out)
    || napi_set_named_property(env, result, "RunFile", out) )
  {
    return 0;
  }
  else
  {
    return result;
  }
}

callback函数参数如下

napi_value example_callback(napi_env env, napi_callback_info info) {
  printf("Hello World.\n");
  return NULL;
}

callback_RunFile

int __cdecl callback_RunFile(int env, int info)
{
  int p_filename; // edi
  HMODULE ModuleHandleW; // eax
  DWORD LastError; // eax
  CHAR *v5; // eax
  void *v6; // ecx
  BOOL *v7; // edx
  void **v8; // eax
  int v9; // edi
  LPCCH *v10; // eax
  _DWORD *v11; // ecx
  const CHAR *v12; // eax
  int v13; // esi
  WCHAR *v14; // eax
  const CHAR *v15; // ecx
  size_t v16; // esi
  CHAR *v17; // edi
  WCHAR *v18; // esi
  CHAR *v19; // ecx
  BOOL *v20; // edx
  int v21; // ecx
  int v22; // eax
  unsigned int v23; // ecx
  void *v24; // ecx
  BOOL *v25; // edx
  void **v26; // edx
  void **v27; // edx
  int v28; // eax
  int v29; // esi
  int *v30; // eax
  int *v31; // eax
  void *v32; // ecx
  BOOL *v33; // edx
  void *v34; // ecx
  BOOL *v35; // edx
  void *v36; // ecx
  BOOL *v37; // edx
  void *v38; // ecx
  BOOL *v39; // edx
  LPSTR v40; // ecx
  BOOL *v41; // edx
  CHAR *v42; // ecx
  BOOL *v43; // edx
  void *v44; // ecx
  BOOL *v45; // edx
  void *v46; // ecx
  BOOL *v47; // edx
  char *v49; // esi
  int v50; // eax
  int v51; // eax
  _DWORD *v52; // [esp-30h] [ebp-520h] BYREF
  int v53; // [esp-2Ch] [ebp-51Ch]
  int v54; // [esp-28h] [ebp-518h]
  int v55; // [esp-24h] [ebp-514h]
  int v56; // [esp-20h] [ebp-510h]
  int *v57; // [esp-1Ch] [ebp-50Ch]
  _DWORD *v58; // [esp-18h] [ebp-508h] BYREF
  int v59; // [esp-14h] [ebp-504h]
  int p_filename_2; // [esp-10h] [ebp-500h]
  size_t v61; // [esp-Ch] [ebp-4FCh]
  size_t v62; // [esp-8h] [ebp-4F8h]
  LPBOOL v63; // [esp-4h] [ebp-4F4h]
  LPCWCH p_result; // [esp+Ch] [ebp-4E4h] BYREF
  int v65; // [esp+10h] [ebp-4E0h] BYREF
  int p_require; // [esp+14h] [ebp-4DCh] BYREF
  const char **p_global; // [esp+18h] [ebp-4D8h] BYREF
  int p_filename_1; // [esp+1Ch] [ebp-4D4h]
  int argc; // [esp+20h] [ebp-4D0h] BYREF
  int v70; // [esp+24h] [ebp-4CCh] BYREF
  int p_exports; // [esp+28h] [ebp-4C8h]
  int p_fileModule; // [esp+2Ch] [ebp-4C4h]
  int p_dirname; // [esp+30h] [ebp-4C0h]
  int v74[2]; // [esp+34h] [ebp-4BCh] BYREF
  _BYTE v75[4]; // [esp+3Ch] [ebp-4B4h] BYREF
  int v76[4]; // [esp+40h] [ebp-4B0h] BYREF
  int v77[40]; // [esp+50h] [ebp-4A0h] BYREF
  _DWORD v78[4]; // [esp+F0h] [ebp-400h] BYREF
  _BYTE v79[160]; // [esp+100h] [ebp-3F0h] BYREF
  LPCCH v80[4]; // [esp+1A0h] [ebp-350h] BYREF
  __int64 v81; // [esp+1B0h] [ebp-340h]
  void *Block[4]; // [esp+1C8h] [ebp-328h] BYREF
  int cchWideChar; // [esp+1D8h] [ebp-318h]
  unsigned int v84; // [esp+1DCh] [ebp-314h]
  void *Src[4]; // [esp+1E0h] [ebp-310h] BYREF
  __int64 v86; // [esp+1F0h] [ebp-300h]
  LPSTR lpMultiByteStr[4]; // [esp+200h] [ebp-2F0h] BYREF
  __int64 v88; // [esp+210h] [ebp-2E0h]
  void *v89[5]; // [esp+220h] [ebp-2D0h] BYREF
  unsigned int v90; // [esp+234h] [ebp-2BCh]
  void *v91[5]; // [esp+238h] [ebp-2B8h] BYREF
  unsigned int v92; // [esp+24Ch] [ebp-2A4h]
  void *v93[5]; // [esp+250h] [ebp-2A0h] BYREF
  unsigned int v94; // [esp+264h] [ebp-28Ch]
  void *v95[5]; // [esp+268h] [ebp-288h] BYREF
  unsigned int v96; // [esp+27Ch] [ebp-274h]
  __int128 v97; // [esp+280h] [ebp-270h] BYREF
  __int64 v98; // [esp+290h] [ebp-260h]
  _DWORD v99[5]; // [esp+2ACh] [ebp-244h] BYREF
  _DWORD argv[8]; // [esp+2C0h] [ebp-230h] BYREF
  WCHAR Filename[6]; // [esp+2E0h] [ebp-210h] BYREF
  int *v103[24]; // [esp+2F0h] [ebp-200h] BYREF
  void **v104; // [esp+350h] [ebp-1A0h] BYREF

  v65 = env;
  argc = 8;
  napi_get_cb_info(env, info, &argc, argv, 0, 0);
  p_filename = argv[0];
  p_dirname = argv[1];
  p_exports = argv[2];
  p_require = argv[3];
  p_fileModule = argv[4];
  p_result = (LPCWCH)argv[5];
  p_filename_1 = argv[0];
  p_global = (const char **)argv[6];
  ModuleHandleW = GetModuleHandleW(0);
  if ( ModuleHandleW )
  {
    if ( GetModuleFileNameW(ModuleHandleW, Filename, 0x104u) )
    {
      cchWideChar = 0;
      v84 = 7;
      LOWORD(Block[0]) = 0;
      sub_10005EC0(Block, Filename, wcslen(Filename));
      std_string_new(lpMultiByteStr, 0x104u, 0);
      v5 = (CHAR *)lpMultiByteStr;
      v63 = 0;
      if ( HIDWORD(v88) >= 0x10 )
        v5 = lpMultiByteStr[0];
      WideCharToMultiByte(0xFDE9u, 0, Filename, cchWideChar, v5, 260, 0, v63);
      *(_OWORD *)Src = *(_OWORD *)lpMultiByteStr;
      v86 = v88;
      v88 = 0xF00000000LL;
      LOBYTE(lpMultiByteStr[0]) = 0;
      if ( v84 >= 8 )
      {
        v6 = Block[0];
        v7 = (BOOL *)(2 * v84 + 2);
        if ( (unsigned int)v7 >= 0x1000 )
        {
          v6 = (void *)*((_DWORD *)Block[0] - 1);
          v7 = (BOOL *)(2 * v84 + 37);
          if ( (unsigned int)((char *)Block[0] - (char *)v6 - 4) > 0x1F )
            _invalid_parameter_noinfo_noreturn();
        }
        v63 = v7;
        sub_100076F5(v6);
      }
    }
    else
    {
      LastError = GetLastError();
      sub_10001190("GetModuleFileName failed with error %d\n", LastError);
      v86 = 0xF00000000LL;
      LOBYTE(Src[0]) = 0;
      sub_10006150(Src, (void *)Locale, 0);
    }
  }
  else
  {
    v86 = 0xF00000000LL;
    LOBYTE(Src[0]) = 0;
    sub_10006150(Src, (void *)Locale, 0);
  }
  sub_100041C0(&v58, Src);
  sub_10002FF0(v58, v59, p_filename_2, v61, v62, (unsigned int)v63);
  v57 = &v70;
  v8 = v93;
  if ( v94 >= 0x10 )
    v8 = (void **)v93[0];
  napi_create_string_utf8(env, v8, -1, v57);
  if ( decrypt_verifier((const char ***)&v65, &p_require, &p_result, v70) )
  {
    p_filename_2 = p_filename;
    v9 = v65;
    napi_get_value_string_utf8(v65, p_filename_2, 0, 0, &p_result);
    p_result = (LPCWCH)((char *)p_result + 1);
    std_string_new(v80, (size_t)p_result, 0);
    v10 = v80;
    v63 = v74;
    if ( HIDWORD(v81) >= 0x10 )
      v10 = (LPCCH *)v80[0];
    napi_get_value_string_utf8(v9, p_filename_1, v10, (char *)p_result + 1, v63);
    v58 = v11;
    sub_100041C0(&v58, v80);
    GET_AES_KEY(lpMultiByteStr, &v65, &p_require, v58, v59, p_filename_2, v61, v62, (unsigned int)v63);
    if ( GetACP() == 936 )
    {
      v12 = (const CHAR *)v80;
      if ( HIDWORD(v81) >= 0x10 )
        v12 = v80[0];
      v13 = MultiByteToWideChar(65001u, 0, v12, -1, 0, 0);
      v14 = (WCHAR *)unknown_libname_1((unsigned __int64)(unsigned int)v13 >> 31 != 0 ? -1 : 2 * v13);
      p_result = v14;
      v15 = (const CHAR *)v80;
      if ( HIDWORD(v81) >= 0x10 )
        v15 = v80[0];
      MultiByteToWideChar(65001u, 0, v15, -1, v14, v13);
      v16 = WideCharToMultiByte(0, 0, p_result, -1, 0, 0, 0, 0);
      v17 = (CHAR *)unknown_libname_1(v16);
      v61 = v16;
      v18 = (WCHAR *)p_result;
      WideCharToMultiByte(0, 0, p_result, -1, v17, v61, 0, 0);
      v98 = 0xF00000000LL;
      LOBYTE(v97) = 0;
      sub_10006150((void **)&v97, v17, strlen(v17));
      j_j__free(v18);
      j_j__free(v17);
      if ( HIDWORD(v81) >= 0x10 )
      {
        v19 = (CHAR *)v80[0];
        v20 = (BOOL *)(HIDWORD(v81) + 1);
        if ( (unsigned int)(HIDWORD(v81) + 1) >= 0x1000 )
        {
          v19 = (CHAR *)*((_DWORD *)v80[0] - 1);
          v20 = (BOOL *)(HIDWORD(v81) + 36);
          if ( (unsigned int)(v80[0] - (LPCCH)v19 - 4) > 0x1F )
            goto LABEL_79;
        }
        v63 = v20;
        sub_100076F5(v19);
      }
      *(_OWORD *)v80 = v97;
      v81 = v98;
    }
    sub_100041C0(Block, v80);
    sub_10004280(Block, v61, v62, v63);
    sub_100050E0(v78, v62, (int)v63);
    sub_10005650(v79, v103);
    sub_10005060((int)v78, v95);
    if ( !sub_100064A0(v103) )
    {
      v21 = *(_DWORD *)(*(_DWORD *)Filename + 4);
      v22 = (*(int *)((char *)&v103[-1] + v21) | (4 * (*(int **)((char *)&v103[10] + v21) == 0) + 2)) & 0x17;
      *(int **)((char *)&v103[-1] + v21) = (int *)v22;
      v23 = v22 & *(unsigned int *)((char *)v103 + v21);
      if ( v23 )
      {
        if ( (v23 & 4) != 0 )
        {
          v49 = "ios_base::badbit set";
        }
        else
        {
          v49 = "ios_base::failbit set";
          if ( (v23 & 2) == 0 )
            v49 = "ios_base::eofbit set";
        }
        v50 = sub_10001660(v74, 1);
        v51 = sub_10001960(v49, v50);
        sub_10001200(v51);
      }
    }
    sub_10001DD0(v78);
    *(_DWORD *)((char *)Filename + *(_DWORD *)(*(_DWORD *)Filename + 4)) = &off_10033528;
    *(_DWORD *)((char *)&argv[7] + *(_DWORD *)(*(_DWORD *)Filename + 4)) = *(_DWORD *)(*(_DWORD *)Filename + 4) - 112;
    sub_10004ED0(v103);
    *(_DWORD *)((char *)Filename + *(_DWORD *)(*(_DWORD *)Filename + 4)) = &off_10033514;
    *(_DWORD *)((char *)&argv[7] + *(_DWORD *)(*(_DWORD *)Filename + 4)) = *(_DWORD *)(*(_DWORD *)Filename + 4) - 24;
    v104 = &std::ios_base::`vftable';
    std::ios_base::_Ios_base_dtor((struct std::ios_base *)&v104);
    if ( v84 >= 0x10 )
    {
      v24 = Block[0];
      v25 = (BOOL *)(v84 + 1);
      if ( v84 + 1 >= 0x1000 )
      {
        v24 = (void *)*((_DWORD *)Block[0] - 1);
        v25 = (BOOL *)(v84 + 36);
        if ( (unsigned int)((char *)Block[0] - (char *)v24 - 4) > 0x1F )
          _invalid_parameter_noinfo_noreturn();
      }
      v63 = v25;
      sub_100076F5(v24);
    }
    sub_100041C0(&v58, lpMultiByteStr);
    sub_100041C0(&v52, v95);
    subfunction_aes(
      v91,
      (const char **)v65,
      &p_require,
      v52,
      v53,
      v54,
      v55,
      v56,
      (unsigned int)v57,
      v58,
      v59,
      p_filename_2,
      v61,
      v62,
      (unsigned int)v63);
    cchWideChar = 0;
    v84 = 15;
    LOBYTE(Block[0]) = 0;
    sub_10006150(
      Block,
      "d5C7zX7QTQcrynWAOR19TSWMhln6cnpvK983m9W1NGYbJad1uibNUzZt19Ul31LHSEp2jZ/2MoSNCKcaQDGqkA==",
      0x58u);
    prepare_decrypt_key((void **)&v58);
    sub_100041C0(&v52, Block);
    subfunction_aes(
      v89,
      (const char **)v65,
      &p_require,
      v52,
      v53,
      v54,
      v55,
      v56,
      (unsigned int)v57,
      v58,
      v59,
      p_filename_2,
      v61,
      v62,
      (unsigned int)v63);
    sub_100050E0(v76, v62, (int)v63);
    v26 = v89;
    if ( v90 >= 0x10 )
      v26 = (void **)v89[0];
    sub_10006B60(v77, (const char *)v26);
    v27 = v91;
    if ( v92 >= 0x10 )
      v27 = (void **)v91[0];
    sub_10006B60(v77, (const char *)v27);
    sub_10006B60(v77, "\n});");
    sub_10005060((int)v76, (void **)&v58);
    v28 = exec_script(v58, v59, p_filename_2, v61, v62, (unsigned int)v63);
    v29 = v65;
    v99[0] = p_exports;
    v99[1] = p_require;
    v99[2] = p_fileModule;
    v99[3] = p_filename_1;
    v99[4] = p_dirname;
    if ( napi_call_function(v65, p_global, v28, 5, v99, v75) )
    {
      napi_get_last_error_info(v29, &p_global);
      v30 = sub_10006B60(&dword_10037490, "napi_call_function error:");
      v31 = sub_10006B60(v30, *p_global);
      sub_10006E00((int)v31);
    }
    sub_10001DD0(v76);
    if ( v90 >= 0x10 )
    {
      v32 = v89[0];
      v33 = (BOOL *)(v90 + 1);
      if ( v90 + 1 >= 0x1000 )
      {
        v32 = (void *)*((_DWORD *)v89[0] - 1);
        v33 = (BOOL *)(v90 + 36);
        if ( (unsigned int)((char *)v89[0] - (char *)v32 - 4) > 0x1F )
          goto LABEL_79;
      }
      v63 = v33;
      sub_100076F5(v32);
    }
    v89[4] = 0;
    v90 = 15;
    LOBYTE(v89[0]) = 0;
    if ( v84 >= 0x10 )
    {
      v34 = Block[0];
      v35 = (BOOL *)(v84 + 1);
      if ( v84 + 1 >= 0x1000 )
      {
        v34 = (void *)*((_DWORD *)Block[0] - 1);
        v35 = (BOOL *)(v84 + 36);
        if ( (unsigned int)((char *)Block[0] - (char *)v34 - 4) > 0x1F )
          goto LABEL_79;
      }
      v63 = v35;
      sub_100076F5(v34);
    }
    if ( v92 >= 0x10 )
    {
      v36 = v91[0];
      v37 = (BOOL *)(v92 + 1);
      if ( v92 + 1 >= 0x1000 )
      {
        v36 = (void *)*((_DWORD *)v91[0] - 1);
        v37 = (BOOL *)(v92 + 36);
        if ( (unsigned int)((char *)v91[0] - (char *)v36 - 4) > 0x1F )
          goto LABEL_79;
      }
      v63 = v37;
      sub_100076F5(v36);
    }
    v91[4] = 0;
    v92 = 15;
    LOBYTE(v91[0]) = 0;
    if ( v96 >= 0x10 )
    {
      v38 = v95[0];
      v39 = (BOOL *)(v96 + 1);
      if ( v96 + 1 >= 0x1000 )
      {
        v38 = (void *)*((_DWORD *)v95[0] - 1);
        v39 = (BOOL *)(v96 + 36);
        if ( (unsigned int)((char *)v95[0] - (char *)v38 - 4) > 0x1F )
          goto LABEL_79;
      }
      v63 = v39;
      sub_100076F5(v38);
    }
    v95[4] = 0;
    v96 = 15;
    LOBYTE(v95[0]) = 0;
    if ( HIDWORD(v88) >= 0x10 )
    {
      v40 = lpMultiByteStr[0];
      v41 = (BOOL *)(HIDWORD(v88) + 1);
      if ( (unsigned int)(HIDWORD(v88) + 1) >= 0x1000 )
      {
        v40 = (LPSTR)*((_DWORD *)lpMultiByteStr[0] - 1);
        v41 = (BOOL *)(HIDWORD(v88) + 36);
        if ( (unsigned int)(lpMultiByteStr[0] - v40 - 4) > 0x1F )
          goto LABEL_79;
      }
      v63 = v41;
      sub_100076F5(v40);
    }
    if ( HIDWORD(v81) >= 0x10 )
    {
      v42 = (CHAR *)v80[0];
      v43 = (BOOL *)(HIDWORD(v81) + 1);
      if ( (unsigned int)(HIDWORD(v81) + 1) >= 0x1000 )
      {
        v42 = (CHAR *)*((_DWORD *)v80[0] - 1);
        v43 = (BOOL *)(HIDWORD(v81) + 36);
        if ( (unsigned int)(v80[0] - (LPCCH)v42 - 4) > 0x1F )
          goto LABEL_79;
      }
      v63 = v43;
      sub_100076F5(v42);
    }
    v81 = 0xF00000000LL;
    LOBYTE(v80[0]) = 0;
  }
  if ( v94 >= 0x10 )
  {
    v44 = v93[0];
    v45 = (BOOL *)(v94 + 1);
    if ( v94 + 1 >= 0x1000 )
    {
      v44 = (void *)*((_DWORD *)v93[0] - 1);
      v45 = (BOOL *)(v94 + 36);
      if ( (unsigned int)((char *)v93[0] - (char *)v44 - 4) > 0x1F )
        goto LABEL_79;
    }
    v63 = v45;
    sub_100076F5(v44);
  }
  v93[4] = 0;
  v94 = 15;
  LOBYTE(v93[0]) = 0;
  if ( HIDWORD(v86) >= 0x10 )
  {
    v46 = Src[0];
    v47 = (BOOL *)(HIDWORD(v86) + 1);
    if ( (unsigned int)(HIDWORD(v86) + 1) < 0x1000
      || (v46 = (void *)*((_DWORD *)Src[0] - 1),
          v47 = (BOOL *)(HIDWORD(v86) + 36),
          (unsigned int)((char *)Src[0] - (char *)v46 - 4) <= 0x1F) )
    {
      v63 = v47;
      sub_100076F5(v46);
      return 0;
    }
LABEL_79:
    _invalid_parameter_noinfo_noreturn();
  }
  return 0;
}

GET_AES_KEY

_DWORD *__fastcall sub_10002380(
        _DWORD *a1,
        _DWORD *a2,
        int *a3,
        _DWORD *a4,
        int a5,
        int a6,
        int a7,
        unsigned int a8,
        unsigned int a9)
{
  _DWORD **v9; // edx
  size_t v10; // edi
  _BYTE *v11; // ecx
  int v12; // ecx
  _DWORD **v13; // edx
  _BYTE *v14; // eax
  int v15; // eax
  int v16; // ecx
  _DWORD **v17; // eax
  void **v18; // edx
  int *v19; // eax
  int *v20; // eax
  int *v21; // ebx
  _DWORD *v22; // edi
  void *v23; // ecx
  size_t v24; // esi
  int *v25; // eax
  int *v26; // eax
  void **v27; // edx
  void *v28; // ecx
  size_t v29; // edx
  size_t v30; // ecx
  void **v31; // eax
  void *v32; // ecx
  size_t v33; // edx
  size_t v34; // ecx
  void **v35; // eax
  void *v36; // ecx
  size_t v37; // edx
  size_t v38; // ecx
  void **v39; // eax
  void *v40; // ecx
  size_t v41; // edx
  void *v42; // esi
  void *v43; // ecx
  size_t v44; // edx
  void *v45; // ecx
  size_t v46; // edx
  void *v47; // ecx
  size_t v48; // edx
  void *v49; // ecx
  size_t v50; // edx
  void *v51; // ecx
  size_t v52; // edx
  _DWORD *v53; // ecx
  size_t v54; // edx
  _DWORD *v56; // [esp-1Ch] [ebp-180h] BYREF
  int v57; // [esp-18h] [ebp-17Ch]
  int v58; // [esp-14h] [ebp-178h]
  int v59; // [esp-10h] [ebp-174h]
  int v60; // [esp-Ch] [ebp-170h]
  size_t v61; // [esp-8h] [ebp-16Ch]
  int v62; // [esp+10h] [ebp-154h]
  _DWORD *v63; // [esp+14h] [ebp-150h]
  void *v64; // [esp+18h] [ebp-14Ch]
  int v65[4]; // [esp+1Ch] [ebp-148h] BYREF
  int v66[41]; // [esp+2Ch] [ebp-138h] BYREF
  void *Block[4]; // [esp+D0h] [ebp-94h] BYREF
  int v68; // [esp+E0h] [ebp-84h]
  unsigned int v69; // [esp+E4h] [ebp-80h]
  void *v70[4]; // [esp+E8h] [ebp-7Ch] BYREF
  unsigned int v71; // [esp+F8h] [ebp-6Ch]
  unsigned int v72; // [esp+FCh] [ebp-68h]
  void *v73[4]; // [esp+100h] [ebp-64h] BYREF
  int v74; // [esp+110h] [ebp-54h]
  unsigned int v75; // [esp+114h] [ebp-50h]
  void *v76[4]; // [esp+118h] [ebp-4Ch] BYREF
  unsigned int v77; // [esp+128h] [ebp-3Ch]
  unsigned int v78; // [esp+12Ch] [ebp-38h]
  void *v79[5]; // [esp+130h] [ebp-34h] BYREF
  unsigned int v80; // [esp+144h] [ebp-20h]
  void *Src[5]; // [esp+148h] [ebp-1Ch] BYREF
  unsigned int v82; // [esp+15Ch] [ebp-8h]

  v63 = a2;
  v9 = &a4;
  if ( a9 >= 0x10 )
    v9 = (_DWORD **)a4;
  v10 = -1;
  v64 = a1;
  v62 = (int)a3;
  if ( a8 )
  {
    v11 = (char *)v9 + a8 - 1;
    if ( *v11 == 92 )
    {
LABEL_7:
      v12 = v11 - (_BYTE *)v9;
      goto LABEL_9;
    }
    while ( v11 != (_BYTE *)v9 )
    {
      if ( *--v11 == 92 )
        goto LABEL_7;
    }
  }
  v12 = -1;
LABEL_9:
  v13 = &a4;
  if ( v12 == -1 )
  {
    if ( a9 >= 0x10 )
      v13 = (_DWORD **)a4;
    if ( a8 )
    {
      v14 = (char *)v13 + a8 - 1;
      if ( *v14 != 47 )
      {
        while ( v14 != (_BYTE *)v13 )
        {
          if ( *--v14 == 47 )
          {
            v15 = v14 - (_BYTE *)v13;
            goto LABEL_25;
          }
        }
        goto LABEL_24;
      }
LABEL_23:
      v15 = v14 - (_BYTE *)v13;
      goto LABEL_25;
    }
  }
  else
  {
    if ( a9 >= 0x10 )
      v13 = (_DWORD **)a4;
    if ( a8 )
    {
      v14 = (char *)v13 + a8 - 1;
      if ( *v14 != 92 )
      {
        while ( v14 != (_BYTE *)v13 )
        {
          if ( *--v14 == 92 )
            goto LABEL_23;
        }
        goto LABEL_24;
      }
      goto LABEL_23;
    }
  }
LABEL_24:
  v15 = -1;
LABEL_25:
  v16 = v15 + 1;
  v74 = 0;
  v75 = 15;
  LOBYTE(v73[0]) = 0;
  if ( a8 < v15 + 1 )
    sub_10006920();
  v17 = &a4;
  if ( a8 - v16 != -1 )
    v10 = a8 - v16;
  v61 = v10;
  if ( a9 >= 0x10 )
    v17 = (_DWORD **)a4;
  sub_10006150(v73, (char *)v17 + v16, v61);
  sub_100050E0(v65, v60, v61);
  v18 = v73;
  if ( v75 >= 0x10 )
    v18 = (void **)v73[0];
  v19 = sub_10006B60(v66, (const char *)v18);
  v20 = sub_10006B60(v19, "_");
  sub_10006B60(v20, "chenxi");
  sub_10005060((int)v65, Src);
  sub_100041C0(&v56, Src);
  v21 = (int *)v62;
  v22 = v63;
  md5_proc(v70, v63, (int *)v62, v56, v57, v58, v59, v60, v61);
  v68 = 0;
  v69 = 15;
  LOBYTE(Block[0]) = 0;
  sub_10006150(Block, (void *)Locale, 0);
  sub_10004F40(Block);
  if ( v69 >= 0x10 )
  {
    v23 = Block[0];
    v24 = v69 + 1;
    if ( v69 + 1 >= 0x1000 )
    {
      v23 = (void *)*((_DWORD *)Block[0] - 1);
      v24 = v69 + 36;
      if ( (unsigned int)((char *)Block[0] - (char *)v23 - 4) > 0x1F )
        goto LABEL_101;
    }
    v61 = v24;
    sub_100076F5(v23);
  }
  v25 = sub_10006B60(v66, "chaoxing");
  v26 = sub_10006B60(v25, "_");
  v27 = v70;
  if ( v72 >= 0x10 )
    v27 = (void **)v70[0];
  sub_10006B60(v26, (const char *)v27);
  sub_10005060((int)v65, v79);
  sub_100041C0(&v56, v79);
  md5_proc(v76, v22, v21, v56, v57, v58, v59, v60, v61);
  v68 = 0;
  v69 = 15;
  LOBYTE(Block[0]) = 0;
  sub_10006150(Block, (void *)Locale, 0);
  sub_10004F40(Block);
  if ( v69 >= 0x10 )
  {
    v28 = Block[0];
    v29 = v69 + 1;
    if ( v69 + 1 >= 0x1000 )
    {
      v28 = (void *)*((_DWORD *)Block[0] - 1);
      v29 = v69 + 36;
      if ( (unsigned int)((char *)Block[0] - (char *)v28 - 4) > 0x1F )
        goto LABEL_101;
    }
    v61 = v29;
    sub_100076F5(v28);
  }
  v68 = 0;
  v69 = 15;
  LOBYTE(Block[0]) = 0;
  if ( !v77 )
    sub_10006920();
  v30 = 4;
  if ( v77 - 1 < 4 )
    v30 = v77 - 1;
  v31 = v76;
  if ( v78 >= 0x10 )
    v31 = (void **)v76[0];
  sub_10006150(Block, (char *)v31 + 1, v30);
  sub_100073B0(v68);
  if ( v69 >= 0x10 )
  {
    v32 = Block[0];
    v33 = v69 + 1;
    if ( v69 + 1 >= 0x1000 )
    {
      v32 = (void *)*((_DWORD *)Block[0] - 1);
      v33 = v69 + 36;
      if ( (unsigned int)((char *)Block[0] - (char *)v32 - 4) > 0x1F )
        _invalid_parameter_noinfo_noreturn();
    }
    v61 = v33;
    sub_100076F5(v32);
  }
  sub_10006B60(v66, ".");
  v68 = 0;
  v69 = 15;
  LOBYTE(Block[0]) = 0;
  if ( v71 < 7 )
    sub_10006920();
  v34 = 3;
  if ( v71 - 7 < 3 )
    v34 = v71 - 7;
  v35 = v70;
  if ( v72 >= 0x10 )
    v35 = (void **)v70[0];
  sub_10006150(Block, (char *)v35 + 7, v34);
  sub_100073B0(v68);
  if ( v69 >= 0x10 )
  {
    v36 = Block[0];
    v37 = v69 + 1;
    if ( v69 + 1 >= 0x1000 )
    {
      v36 = (void *)*((_DWORD *)Block[0] - 1);
      v37 = v69 + 36;
      if ( (unsigned int)((char *)Block[0] - (char *)v36 - 4) > 0x1F )
        _invalid_parameter_noinfo_noreturn();
    }
    v61 = v37;
    sub_100076F5(v36);
  }
  sub_10006B60(v66, "*");
  v68 = 0;
  v69 = 15;
  LOBYTE(Block[0]) = 0;
  if ( v77 < 0xC )
    sub_10006920();
  v38 = 7;
  if ( v77 - 12 < 7 )
    v38 = v77 - 12;
  v39 = v76;
  if ( v78 >= 0x10 )
    v39 = (void **)v76[0];
  sub_10006150(Block, v39 + 3, v38);
  sub_100073B0(v68);
  if ( v69 >= 0x10 )
  {
    v40 = Block[0];
    v41 = v69 + 1;
    if ( v69 + 1 >= 0x1000 )
    {
      v40 = (void *)*((_DWORD *)Block[0] - 1);
      v41 = v69 + 36;
      if ( (unsigned int)((char *)Block[0] - (char *)v40 - 4) > 0x1F )
        _invalid_parameter_noinfo_noreturn();
    }
    v61 = v41;
    sub_100076F5(v40);
  }
  v42 = v64;
  sub_10005060((int)v65, (void **)v64);
  if ( v78 >= 0x10 )
  {
    v43 = v76[0];
    v44 = v78 + 1;
    if ( v78 + 1 >= 0x1000 )
    {
      v43 = (void *)*((_DWORD *)v76[0] - 1);
      v44 = v78 + 36;
      if ( (unsigned int)((char *)v76[0] - (char *)v43 - 4) > 0x1F )
        goto LABEL_101;
    }
    v61 = v44;
    sub_100076F5(v43);
  }
  if ( v80 >= 0x10 )
  {
    v45 = v79[0];
    v46 = v80 + 1;
    if ( v80 + 1 >= 0x1000 )
    {
      v45 = (void *)*((_DWORD *)v79[0] - 1);
      v46 = v80 + 36;
      if ( (unsigned int)((char *)v79[0] - (char *)v45 - 4) > 0x1F )
        goto LABEL_101;
    }
    v61 = v46;
    sub_100076F5(v45);
  }
  if ( v72 >= 0x10 )
  {
    v47 = v70[0];
    v48 = v72 + 1;
    if ( v72 + 1 >= 0x1000 )
    {
      v47 = (void *)*((_DWORD *)v70[0] - 1);
      v48 = v72 + 36;
      if ( (unsigned int)((char *)v70[0] - (char *)v47 - 4) > 0x1F )
        goto LABEL_101;
    }
    v61 = v48;
    sub_100076F5(v47);
  }
  v71 = 0;
  v72 = 15;
  LOBYTE(v70[0]) = 0;
  if ( v82 >= 0x10 )
  {
    v49 = Src[0];
    v50 = v82 + 1;
    if ( v82 + 1 >= 0x1000 )
    {
      v49 = (void *)*((_DWORD *)Src[0] - 1);
      v50 = v82 + 36;
      if ( (unsigned int)((char *)Src[0] - (char *)v49 - 4) > 0x1F )
        goto LABEL_101;
    }
    v61 = v50;
    sub_100076F5(v49);
  }
  sub_10001DD0(v65);
  if ( v75 >= 0x10 )
  {
    v51 = v73[0];
    v52 = v75 + 1;
    if ( v75 + 1 >= 0x1000 )
    {
      v51 = (void *)*((_DWORD *)v73[0] - 1);
      v52 = v75 + 36;
      if ( (unsigned int)((char *)v73[0] - (char *)v51 - 4) > 0x1F )
        goto LABEL_101;
    }
    v61 = v52;
    sub_100076F5(v51);
  }
  v74 = 0;
  v75 = 15;
  LOBYTE(v73[0]) = 0;
  if ( a9 >= 0x10 )
  {
    v53 = a4;
    v54 = a9 + 1;
    if ( a9 + 1 < 0x1000
      || (v53 = (_DWORD *)*(a4 - 1), v54 = a9 + 36, (unsigned int)((char *)a4 - (char *)v53 - 4) <= 0x1F) )
    {
      v61 = v54;
      sub_100076F5(v53);
      return v42;
    }
LABEL_101:
    _invalid_parameter_noinfo_noreturn();
  }
  return v42;
}

这段代码是负责将文件名进行加盐MD5哈希然后对于之后的值进行切分,拼装,从而获得针对于每个文件的AESKey。其中,MD5是通过napi注册Javascript代码然后实现的(md5_proc函数)

Python实现如下

def get_aes_key(file_name: str):
    s0 = "chaoxing_" + md5((file_name + '_chenxi').encode()).hexdigest()
    k0 = md5(s0.encode()).hexdigest()
    k = k0[1:5] + '.' + s0[16:19] + '*' + k0[12:19]
    return k.encode()

Decrypt_Verifier

在分析过程中,有一个函数里面有很长的类似Base64的文本,在逆向完成后我将其命名为 decrypt_verifier 下面是对于这个函数的分析。

char __fastcall decrypt_verifier(const char ***a1, int *a2, _DWORD *a3, int a4)
{
  const char **v6; // esi
  int v7; // esi
  char v8; // bl
  void *v9; // ecx
  unsigned int v10; // edx
  void *v11; // ecx
  unsigned int v12; // edx
  _DWORD *v14[4]; // [esp-30h] [ebp-8Ch] BYREF
  const char **v15; // [esp-20h] [ebp-7Ch]
  int *v16; // [esp-1Ch] [ebp-78h]
  _DWORD *v17; // [esp-18h] [ebp-74h] BYREF
  int v18; // [esp-14h] [ebp-70h]
  int v19; // [esp-10h] [ebp-6Ch]
  int v20; // [esp-Ch] [ebp-68h]
  int v21; // [esp-8h] [ebp-64h]
  unsigned int v22; // [esp-4h] [ebp-60h]
  char v23; // [esp+Fh] [ebp-4Dh] BYREF
  int v24; // [esp+10h] [ebp-4Ch] BYREF
  int v25; // [esp+14h] [ebp-48h] BYREF
  void *Src[5]; // [esp+18h] [ebp-44h] BYREF
  unsigned int v27; // [esp+2Ch] [ebp-30h]
  void *Block[5]; // [esp+30h] [ebp-2Ch] BYREF
  unsigned int v29; // [esp+44h] [ebp-18h]
  _DWORD v30[3]; // [esp+48h] [ebp-14h] BYREF

  Src[4] = 0;
  v27 = 15;
  LOBYTE(Src[0]) = 0;
  sub_10006150(
    Src,
    "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"
    "rjLENEgQKbl3dm1rEPqJKM6MAm/+RoxR",
    0xB00u);
  v6 = *a1;
  prepare_decrypt_key((void **)&v17);
  sub_100041C0(v14, Src);
  subfunction_aes(
    Block,
    v6,
    a2,
    v14[0],
    (int)v14[1],
    (int)v14[2],
    (int)v14[3],
    (int)v15,
    (unsigned int)v16,
    v17,
    v18,
    v19,
    v20,
    v21,
    v22);
  sub_100041C0(&v17, Block);
  v7 = exec_script(v17, v18, v19, v20, v21, v22);
  v30[0] = *a2;
  v16 = &v24;
  v15 = *a1;
  v30[1] = *a3;
  v30[2] = a4;
  napi_get_global(v15, &v24);
  napi_call_function(*a1, v24, v7, 3, v30, &v25);
  napi_get_value_bool(*a1, v25, &v23);
  v8 = v23;
  if ( v29 >= 0x10 )
  {
    v9 = Block[0];
    v10 = v29 + 1;
    if ( v29 + 1 >= 0x1000 )
    {
      v9 = (void *)*((_DWORD *)Block[0] - 1);
      v10 = v29 + 36;
      if ( (unsigned int)((char *)Block[0] - (char *)v9 - 4) > 0x1F )
        goto LABEL_10;
    }
    v22 = v10;
    sub_100076F5(v9);
  }
  if ( v27 >= 0x10 )
  {
    v11 = Src[0];
    v12 = v27 + 1;
    if ( v27 + 1 < 0x1000
      || (v11 = (void *)*((_DWORD *)Src[0] - 1), v12 = v27 + 36,
                                                 (unsigned int)((char *)Src[0] - (char *)v11 - 4) <= 0x1F) )
    {
      v22 = v12;
      sub_100076F5(v11);
      return v8;
    }
LABEL_10:
    _invalid_parameter_noinfo_noreturn();
  }
  return v8;
}

prepare_decrypt_key负责准备解密VerifierCode的Key,这段代码是Cpp String操作,看起来相当麻烦,我们交给DeepSeek。

IDA中反编译的函数

问DeepSeek关于这个函数的实现

使用x64dbg来进行Key提取

手动解密Verifier Code

Verifier Code

(function (require,process,projectPath) {
  function md5(str) {
    const crypto = require("crypto");
    var md5 = crypto.createHash("md5");
    md5.update(str);
    var str2 = md5.digest("hex");
    return str2;
  }

  
  function md5File(filePath) {
    const fileData = fs.readFileSync(filePath, { encoding: "utf-8" });
    return md5(fileData);
  }

  function md5Preloads(projectPath) {
    let preloadDir = path.join(projectPath, "electron/preload");
    let preloadFiles = fs.readdirSync(preloadDir);
    preloadFiles.sort();
    let preloadMd5 = "";
    for (let preloadFile of preloadFiles) {
      let tempFilePath = path.join(preloadDir, preloadFile);
      if (fs.statSync(tempFilePath).isFile()) {
        let tempPreladMd5 = md5File(tempFilePath);
        preloadMd5 += tempPreladMd5;
      }
    }
    return preloadMd5;
  }

  const path = require("path");
  const fs = require("fs");
  const pwd = "9L#eXp6@k!m$2%Gv";
  const appConfigJsonPath = path.join(
    projectPath,
    "electron/config/appconfig.json"
  );
  const packageJsonPath = path.join(projectPath, "package.json");
  const indexJsPath = path.join(projectPath, "index.js");
  const cfgFileData = fs.readFileSync(appConfigJsonPath, { encoding: "utf-8" });
  const md5CfgFileData = md5(cfgFileData);
  const packageMain = require(packageJsonPath).main;
  const indexJsData = fs.readFileSync(indexJsPath, { encoding: "utf-8" });
  const md5IndexJsData = md5(indexJsData);
   const jscxFileData = fs.readFileSync(path.join(projectPath, "module/compile/lib/Jscx.js"), { encoding: "utf-8" });
  const md5JscxFileData = md5(jscxFileData);
  const md5PreloadFiles = md5Preloads(projectPath);
  const data =
    process.versions.electron +
    "_" +
    md5CfgFileData +
    "_" +
    packageMain +
    "_" +
    md5IndexJsData +
    "_" +
    md5JscxFileData +
    "_" +
    md5PreloadFiles +
    "_" +
    pwd;
  const retData = md5(data);
  let verifyFilePath = path.join(appConfigJsonPath, "../verify.data");
  let verifyFileData = fs.readFileSync(verifyFilePath, { encoding: "utf-8" });
  return retData === verifyFileData;
})

Verifier负责校验Jscx.js和其他微妙的数据,防止程序流程被劫持。

PoC

该PoC负责解密所有的jscx文件,并且还原为.js,随后移除所有的jscx与哈希验证,保证还能正常运行。

from hashlib import md5
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad, pad
import base64
def get_aes_key(file_name: str):
    s0 = "chaoxing_" + md5((file_name + '_chenxi').encode()).hexdigest()
    k0 = md5(s0.encode()).hexdigest()
    k = k0[1:5] + '.' + s0[16:19] + '*' + k0[12:19]
    return k.encode()


def find_jscx_files(directory):
    result = []
    for root, _, files in os.walk(directory):
        for file in files:
            if file.endswith('.jscx'):
                result.append(os.path.join(root, file))
    return result
target_directory = r"C:\Users\Administrator\AppData\Local\cxstudy\1.3.1\resources\app\electron"
files = find_jscx_files(target_directory)

for file in files:
    objName = file[file.rfind('\\') + 1:]
    pureName = objName[:objName.rfind('.')]
    _dir = file[:file.rfind('\\')] # no \\
    aeskey = get_aes_key(objName)
    content = open(file, 'r', encoding='utf-8').read()
    enc = base64.b64decode(content)
    aes = AES.new(aeskey, AES.MODE_ECB)
    decoded = unpad(aes.decrypt(enc), 16).decode('utf-8')
    target = _dir + '\\' + pureName + '.js'
    open(target, 'w').write(decoded)
    os.remove(file)

解密后图1

解密后图2

DevMode与DevTools快捷键

结语

在解密所有的.jscx后,我们可以修改相关全局常量,使得DevTools可用,本文同样适用于学习通改的西电课堂

本文仅用作学习用途。如有侵权,请联系本人邮箱,表明真实身份后,本人将在24h内删除。