本文以电脑端学习通(超星)客户端的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 稳定性 指南提供了更深入的解释。
以下我们了解几种需要认识的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] length
:utf8name
的长度(以字节为单位)或NAPI_AUTO_LENGTH
(如果以 null 结尾)。[in] cb
:调用此函数对象时应调用的原生函数。napi_callback
提供了更多详细信息。[in] data
:用户提供的数据上下文。这将在稍后调用时传回函数。[out] result
:napi_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
的其余部分将填充代表undefined
的napi_value
值。argv
可以选择性地通过传递NULL
来忽略。[out] thisArg
:接收调用的 JavaScriptthis
参数。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] value
:napi_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内删除。