首页
社区
课程
招聘
[原创] 某 MongoDB GUI 工具逆向记录
2020-12-31 14:32 22683

[原创] 某 MongoDB GUI 工具逆向记录

2020-12-31 14:32
22683

记录一下 某 MongoDB GUI 工具破解过程

 

系统: Windows

 

版本: 5.2.12 / 6.2.7

 

目录

软件界面

打开软件

 

 

选择菜单 Help -> Enter License

 

 

打开注册窗口

 

抓包处理

打开 Fiddler,Fiddler 的代理监听端口为 8888

 

 

配置全局代理

 

 

在注册窗口随意输入注册信息(这个注册信息是不会成功的),然后点击 Activate and Reload

 

用户名: 御风

 

注册码: 999999-9d5988960387b3f63481722566cb0e9f

1
2
3
4
5
# 网络上找的一些注册码
 
3.x.x 注册码: 999999-137f36b4127c02086570842ae5d2d28a
4.x.x 注册码: 999999-fa52c1e219e5c0bb298e48e3ef1cf1e2
5.x.x 注册码: 999999-9d5988960387b3f63481722566cb0e9f

 

抓到封包数据

 

封包分析

URL: https://www.nosqlbooster.com/users-mgr/mborders/validate

 

封包数据:

1
2
3
4
5
6
7
8
9
10
11
12
POST https://www.nosqlbooster.com/users-mgr/mborders/validate HTTP/1.1
Host: www.nosqlbooster.com
Connection: keep-alive
Content-Length: 365
Accept: application/json, text/javascript, */*; q=0.01
Origin: file://
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) NoSQLBoosterforMongoDB/5.2.12 Chrome/69.0.3497.128 Electron/4.2.9 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN
 
licTo=%E5%BE%A1%E9%A3%8E&licKey=999999-9d5988960387b3f63481722566cb0e9f&version=5.2.12&licType=commercial&from=Wed+May+29+2019+00%3A00%3A00+GMT%2B0800+(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)&mid=e08dbdad0e332c357e2ae6befe61173ab1b8fe87128f2ebb4a5eef623ef4efb8&os%5Bplatform%5D=win32&os%5Btype%5D=Windows_NT&os%5Brelease%5D=10.0.17763&os%5Bmem%5D=16

返回数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
HTTP/1.1 200 OK
Date: Wed, 30 Dec 2020 10:32:15 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Set-Cookie: __cfduid=d9cdb0afb47b06112696e5567618c38cd1609324334; expires=Fri, 29-Jan-21 10:32:14 GMT; path=/; domain=.nosqlbooster.com; HttpOnly; SameSite=Lax
X-Powered-By: Express
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 91
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
ETag: W/"49-VMnZb5ehG5ebII6ebazu2ldJcgU"
CF-Cache-Status: DYNAMIC
cf-request-id: 0754cda6380000c35eb095b000000001
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report?s=jcWJ70g9VsB0YEZ1xSrdUwwv2GGlesETclRM3JcOU4CihlPQ3QxiSihT4XA9T3MAhqekuXhdKetFCzFiKhPGgsqeIEEQkMqiUt3mCLz%2F6dxJ%2FMuKKQ%3D%3D"}],"group":"cf-nel","max_age":604800}
NEL: {"report_to":"cf-nel","max_age":604800}
Server: cloudflare
CF-RAY: 609b18838fd6c35e-SIN
Content-Length: 73
 
{"result":false,"reason":"Activation error: Your license key not found."}

尝试拦截封包把返回结果 result 值改为 true,结果注册成功

行为分析

使用 火绒剑 分析注册成功后的操作:

 

 

可以看到往 C:\Users\Yufeng\AppData\Roaming\NoSQLBooster for MongoDB\app.json 文件写入数据

 

打开文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
  "theme": "default",
  "aceTheme": 1,
  "lic_info_v5": {
    "from": "2019-05-29",
    "licKey": "999999-9d5988960387b3f63481722566cb0e9f",
    "licTo": "御风"
  },
  "lic_info_v6": {
    "from": "2020-12-30",
    "licTo": "trial"
  },
  "connections": [
    {
      "authMode": 0,
      "certRelated": {},
      "connectionType": "direct",
      "editable": true,
      "id": "5d0b6cfe93df6a2a642c81aa",
      "lastestConnectTime": 1609331045117,
      "name": "10.16.2.225",
      "uri": {
        "hosts": [
          {
            "host": "10.16.2.225",
            "port": 27017
          }
        ],
        "options": {},
        "scheme": "mongodb"
      }
    }
  ],
  "workspaceState": {
    "connConfigId": "5d0b6cfe93df6a2a642c81aa",
    "treeNodeId": "mb_conn_3"
  },
  "V5notifyMeUpdates": false
}

后台线程每隔一段时间就会重新请求 https://www.nosqlbooster.com/users-mgr/mborders/validate 校验是否注册成功,如果失败则删除 Key

 

原本打算把这里的授权信息写死,然后 Hook 网络操作部分,发现并不是启动时取这里的 Key 来校验,只能另外再想办法

软件框架

正当我没有头绪的时候,发现软件目录下有一些眼熟的文件:

1
2
3
4
5
locales\
resources\
chrome_100_percent.pak
chrome_200_percent.pak
resources.pak

这就是 Electron 嘛,直接进 resources 目录,果然看到了这些文件:

1
2
app.asar
electron.asar

那就好办了,app.asar 这个文件应该就是主要的界面文件了,解包出来康康就知道了

解包文件

asar 文件是个特殊的代码格式,可以把 resource/app 目录里的代码资源打包成一个文件

 

我们可以直接用解包工具,需要先安装 nodejs 环境,然后再安装 nodejs 模块,建议安装到全局

1
npm install asar -g

然后解包(以下代码二选一):

1
2
asar e <asar文件> <输出目录>
asar extract <asar文件> <输出目录>

我直接执行:

1
asar e app.asar app

解压到 app.asar 同一目录下的 app 目录,这样 Electron 启动的时候,会先加载 app 目录,如果找不到 app 目录,它才会加载 app.asar,这样一来我们都可以直接调试了

分析文件

直接搜索前面抓包拿到的接口地址: users-mgr/mborders/validate

 

发现在 app\shared\consts.js 文件中,打开后是一堆压缩过的 JS 代码,可以直接格式化:

1
2
3
exports.Server_VALIDATE_USER_Link = function() {
    return isProd() ? "https://www.nosqlbooster.com/users-mgr/mborders/validate": "http://localhost:7003/validate"
},

再次搜索 Server_VALIDATE_USER_Link,找到用到它的文件: app\frontend\utils\lm.js

 

同样格式化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function serverSideValidateKey(o) {
    return tslib_1.__awaiter(this, void 0, void 0,
    function() {
        var n, r, t;
        return tslib_1.__generator(this,
        function(e) {
            switch (e.label) {
            case 0:
                return index_1.isDev() && index_1.env_config.disableServerSideValidateKey ? [2, {
                    checkDone: !1
                }] : (r = {
                    licTo: o.licTo,
                    licKey: o.licKey,
                    version: index_1.getAppVersion(),
                    licType: lmCore_2.LicenseType[o.licType],
                    from: o.from && moment(o.from).startOf("date").toDate()
                },
                [4, lmCore_2.getMachineId()]);
            case 1:
                return r.mid = e.sent(),
                r.os = {
                    platform: (i = require("os")).platform(),
                    type: i.type(),
                    release: i.release(),
                    mem: Math.round(i.totalmem() / 1073741824)
                },
                n = r,
                t = index_1.consts.Server_VALIDATE_USER_Link(),
                [4, new Promise(function(r, e) {
                    $.ajax({
                        type: "POST",
                        url: t,
                        data: n,
                        dataType: "json",
                        timeout: 8e3,
                        success: function(e) {
                            e = e || {},
                            console.log("serverSideValidateLicKey result", t, e && e.result),
                            e.checkDone = !0,
                            r(e)
                        },
                        error: function(e, i, n) {
                            r({
                                checkDone: !1
                            }),
                            console.error("serverSideValidateKey", t, i, n)
                        }
                    })
                })];
            case 2:
                return [2, e.sent()]
            }
            var i
        })
    })
}

这里就能看到 ajax 请求了,那直接搞它吧

破解处理

把 ajax 直接去掉,不走 http 验证了,修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
function serverSideValidateKey(o) {
    return tslib_1.__awaiter(this, void 0, void 0,
    function() {
        var n, r, t;
        return tslib_1.__generator(this,
        function(e) {
            switch (e.label) {
            case 0:
                return index_1.isDev() && index_1.env_config.disableServerSideValidateKey ? [2, {
                    checkDone: !1
                }] : (r = {
                    licTo: o.licTo,
                    licKey: o.licKey,
                    version: index_1.getAppVersion(),
                    licType: lmCore_2.LicenseType[o.licType],
                    from: o.from && moment(o.from).startOf("date").toDate()
                },
                [4, lmCore_2.getMachineId()]);
            case 1:
                return r.mid = e.sent(),
                r.os = {
                    platform: (i = require("os")).platform(),
                    type: i.type(),
                    release: i.release(),
                    mem: Math.round(i.totalmem() / 1073741824)
                },
                n = r,
                t = index_1.consts.Server_VALIDATE_USER_Link(),
                [4, new Promise(function(r, e) {
 
                    // $.ajax({
                    //     type: "POST",
                    //     url: t,
                    //     data: n,
                    //     dataType: "json",
                    //     timeout: 8e3,
                    //     success: function(e) {
                    //         e = e || {},
                    //         console.log("serverSideValidateLicKey result", t, e && e.result),
                    //         e.checkDone = !0,
                    //         r(e)
                    //     },
                    //     error: function(e, i, n) {
                    //         r({
                    //             checkDone: !1
                    //         }),
                    //         console.error("serverSideValidateKey", t, i, n)
                    //     }
                    // })
 
                    r({
                        result: true,
                        checkDone: true
                    });
                })];
            case 2:
                return [2, e.sent()]
            }
            var i
        })
    })
}

保存后重启软件,输入注册码,发现输入前面的 999999-9d5988960387b3f63481722566cb0e9f 注册码是可用的,输入其他任意字符就报 Invalid License key 错误

 

于是搜索 Invalid License key 找到还是 app\frontend\utils\lm.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
var _rk = function(e, i) {
    if (e = e.trim(), i = i.trim(), !lmCore_1._vk(i)) return index_1.ez.messager.alertError("Invalid License key"),
    !1;
    var n = lmCore_1.readLicInfoFromLocal(),
    r = {
        from: n && n.from,
        licKey: i,
        licTo: e
    };
    return lmCore_1.saveLicInfoToLocal(r),
    doAfterLicenseInfoChanged(),
    !0
},

可以看到 lmCore_1._vk(i),这个 i 就是输入的注册码,这个函数应该就是校验,搜索定义看到:

1
2
3
lmCore_1 = require("../../shared/lmCore"),
lmCore_2 = require("../../shared/lmCore"),
lmCore_3 = require("../../shared/lmCore");

于是打开 "../../shared/lmCore" 文件,发现是混淆过的代码,直接上 ast 工具反混淆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
Object["defineProperty"](exports, "__esModule", {
  value: !0
});
 
var tslib_1 = require("tslib"),
    _ = require("lodash"),
    path = require("path"),
    fs = require("fs"),
    moment = require("moment"),
    commonUtils_1 = require("./commonUtils"),
    consts = require("./consts"),
    enums_1 = require("./enums"),
    config = require("./Configuration"),
    MAX_TRIAL_DAYS = 150,
    TRIAL_DAYS = 30,
    Trial_Days = function () {
  var e = (x = path["resolve"](commonUtils_1["userDataDir"](), "../mongobooster"), fs["existsSync"](x) ? 60 : TRIAL_DAYS),
      x,
      a = parseInt(process.env["MB_TRIAL_DAYS"]) || e;
  return MAX_TRIAL_DAYS < a ? MAX_TRIAL_DAYS : a;
}(),
    TEMP_LM_FILE_V3 = "pREQZNKj8C9MBv97Emb.dat",
    TEMP_LM_FILE_V4 = "pREQZNKj8C9MBv940mb.dat",
    TEMP_LM_FILE_V5 = "pREQZNKj8454acf83b3a50mb.dat",
    LicenseType;
 
function getLicenseTypeDescName(e) {
  var x,
      a = ((x = {})[LicenseType["small_team"]] = "6-User Small Team", x[LicenseType["medium_team"]] = "15-User Team", x);
 
  if (a[e]) {
    if ("JxsFw" === "JxsFw") return a[e];
    licInfo["from"] = moment().format("YYYY-MM-DD"), exports["saveLicInfoToLocal"](licInfo);
  }
 
  return _["startCase"](LicenseType[e]);
}
 
function getLicTypeFromKey(e) {
  return exports._vk(e) ? _.startsWith(e, "88") ? LicenseType["corporate"] : _["startsWith"](e, "66") ? LicenseType["site"] : _["startsWith"](e, "44") ? LicenseType["small_team"] : _["startsWith"](e, "45") ? LicenseType["medium_team"] : _["startsWith"](e, "3") ? LicenseType["personal"] : _.startsWith(e, "2") ? LicenseType["commercial"] : LicenseType["commercial"] : LicenseType["trial"];
}
 
!function (e) {
  e[e["trial"] = 11111] = "trial", e[e["commercial"] = 22222] = "commercial", e[e["personal"] = 33333] = "personal", e[e["small_team"] = 44444] = "small_team", e[e["medium_team"] = 45555] = "medium_team", e[e["site"] = 66666] = "site", e[e.corporate = 88888] = "corporate";
}(LicenseType = exports["LicenseType"] || (exports["LicenseType"] = {})), exports["getLicenseTypeDescName"] = getLicenseTypeDescName, exports["saveLicInfoToLocal"] = function (e) {
  config["setSection"](consts["ConfigSection"]["LIC_INFO"], e);
 
  var x = require("fs-jetpack"),
      a = path["resolve"](commonUtils_1["tempDir"](), TEMP_LM_FILE_V5);
 
  x["write"](a, e);
}, exports["trySyncTrialExpired"] = function (e) {
  console["log"]("call trySyncTrialExpired");
  var x = exports.getLicInfo();
 
  if (!x["isLicensed"] && !(x.trialLeftDays <= 0) && Trial_Days < e) {
    if ("iksgN" !== "iksgN") return require("node-machine-id");
    var a = {
      from: moment()["subtract"](e + 1, "days").format("YYYY-MM-DD"),
      licTo: "trial"
    };
    exports.saveLicInfoToLocal(a);
  }
};
 
var initAndSaveLicInfo = function () {
  var e = {
    from: moment()["format"]("YYYY-MM-DD"),
    licTo: "trial"
  };
  return exports["saveLicInfoToLocal"](e), e;
},
    readLicInfoFromTmpFile = function (x) {
  try {
    if ("GirfJ" !== "qhOkX") {
      var e = path.resolve(commonUtils_1["tempDir"](), x),
          a = require("fs-jetpack");
 
      if (a.exists(e)) {
        if ("HiZHE" !== "GZXfH") return _["pick"](a.read(e, "json") || {}, ["from", "licTo", "licKey"]);
 
        try {
          var r = path["resolve"](commonUtils_1.tempDir(), x),
              w = require("fs-jetpack");
 
          if (w["exists"](r)) return _["pick"](w.read(r, "json") || {}, ["from", "licTo", "licKey"]);
        } catch (e) {
          console["error"]("readLicInfoFromTmpFile raise error", x, e);
        }
      }
    } else console.error("readLicInfoFromTmpFile raise error", x, ex);
  } catch (e) {
    "GRJeY" === "TyAYc" ? licInfo = readLicInfoFromTmpFile(TEMP_LM_FILE_V4) : console["error"]("readLicInfoFromTmpFile raise error", x, e);
  }
};
 
function readLicInfoFromAppJson() {
  return _["pick"](config["getSection"](consts["ConfigSection"].LIC_INFO) || {}, ["from", "licTo", "licKey"]);
}
 
function readLicInfoFromLocal() {
  var e = function () {
    if ("IRgHL" === "pnjuE") return tslib_1["__generator"](this, function (e) {
      switch (e["label"]) {
        case 0:
          x = void 0, e.label = 1;
 
        case 1:
          return e.trys["push"]([1, 4,, 5]), [4, Promise["resolve"]()["then"](function () {
            return require("node-machine-id");
          })];
 
        case 2:
          return [4, (a = e["sent"]())["machineId"]()];
 
        case 3:
          return x = e["sent"](), [3, 5];
 
        case 4:
          return r = e["sent"](), console["error"]("get machine id", x), [3, 5];
 
        case 5:
          return [2, x];
      }
    });
    var x,
        a,
        r,
        e = readLicInfoFromTmpFile(TEMP_LM_FILE_V5),
        w = readLicInfoFromAppJson();
 
    if (_["isEmpty"](e)) {
      if (_["isEmpty"](w)) return initAndSaveLicInfo();
 
      if ("nABVe" === "nABVe") {
        var n = require("fs-jetpack"),
            c = path["resolve"](commonUtils_1["tempDir"](), TEMP_LM_FILE_V5);
 
        return n.write(c, w), w;
      }
 
      var o = "3N8uqpEMosNC3wWKy",
          t = "4N8uqpEMosNC4wWKy";
      return exports["_vk"](key, o) || exports["_vk"](key, t);
    }
 
    if ("gspzg" !== "qTvVd") return w && e["from"] === w["from"] || config["setSection"](consts["ConfigSection"]["LIC_INFO"], e), e;
    var s = path.resolve(commonUtils_1["userDataDir"](), "../mongobooster");
    return fs["existsSync"](s) ? 60 : TRIAL_DAYS;
  }();
 
  return e["from"] || (e["from"] = moment().format("YYYY-MM-DD"), exports["saveLicInfoToLocal"](e)), e;
}
 
function isValidOldVersionKey(e) {
  var x = "3N8uqpEMosNC3wWKy",
      a = "4N8uqpEMosNC4wWKy";
  return exports["_vk"](e, x) || exports["_vk"](e, a);
}
 
function licTrialExpired() {
  var e = exports["getLicInfo"]();
  return e.status !== enums_1.LicStatus["Licensed"] && (e["status"] === enums_1["LicStatus"]["TrialExpired"] || e["trialLeftDays"] <= 0);
}
 
function doGetLicInfo() {
  var a = readLicInfoFromLocal(),
      r = moment()["diff"](moment(a["from"]), "day"),
      w = Trial_Days - r;
  w < 0 && (w = 0);
 
  var e = function () {
    if ("JTmEq" !== "YKELI") return a ? a["licKey"] && exports._vk(a["licKey"]) ? enums_1["LicStatus"].Licensed : r < 0 ? enums_1["LicStatus"]["TrialExpired"] : w <= 0 ? "xSYKg" === "xSYKg" ? enums_1["LicStatus"]["TrialExpired"] : enums_1.LicStatus["TrialExpired"] : enums_1.LicStatus["InTrialPeriod"] : enums_1.LicStatus["TrialExpired"];
    if (!key) return !1;
    key = key["trim"]();
    var e = key["split"]("-"),
        x;
    return !(e["length"] < 2) && require("./lmKeyGen")._gk(parseInt(e[0]), secret) === e["slice"](0, 2)["join"]("-");
  }(),
      x = e === enums_1.LicStatus.Licensed;
 
  return {
    status: e,
    daysUsed: r,
    trialLeftDays: w,
    isLicensed: x,
    from: a.from,
    licTo: x && a["licTo"] || void 0,
    licKey: x && a.licKey || void 0,
    licType: a && getLicTypeFromKey(a["licKey"])
  };
}
 
function getMachineId() {
  return tslib_1["__awaiter"](this, void 0, void 0, function () {
    var x, a, r;
    if ("Fyayp" !== "IoptG") return tslib_1.__generator(this, function (e) {
      switch (e.label) {
        case 0:
          x = void 0, e["label"] = 1;
 
        case 1:
          return e["trys"]["push"]([1, 4,, 5]), [4, Promise.resolve()["then"](function () {
            return require("node-machine-id");
          })];
 
        case 2:
          return [4, (a = e.sent())["machineId"]()];
 
        case 3:
          return x = e["sent"](), [3, 5];
 
        case 4:
          return r = e["sent"](), console["error"]("get machine id", x), [3, 5];
 
        case 5:
          return [2, x];
      }
    });
    if (_["isEmpty"](appLicInfo)) return initAndSaveLicInfo();
 
    var e = require("fs-jetpack"),
        w = path["resolve"](commonUtils_1.tempDir(), TEMP_LM_FILE_V5);
 
    return e["write"](w, appLicInfo), appLicInfo;
  });
}
 
exports["readLicInfoFromLocal"] = readLicInfoFromLocal, exports["hasOldVersionLicensedKey"] = function () {
  var e = readLicInfoFromTmpFile(TEMP_LM_FILE_V3);
  return e || ("JDoKM" !== "YwmND" ? e = readLicInfoFromTmpFile(TEMP_LM_FILE_V4) : trialLeftDays = 0), !!e && !!e.licTo && !!e["licKey"];
}, exports._vk = function (e, x) {
  if (!e) return !1;
  var a = (e = e.trim())["split"]("-"),
      r;
  return !(a["length"] < 2) && require("./lmKeyGen")["_gk"](parseInt(a[0]), x) === a["slice"](0, 2)["join"]("-");
}, exports["isValidOldVersionKey"] = isValidOldVersionKey, exports["getLicInfo"] = _["debounce"](doGetLicInfo, 60000, {
  leading: !0,
  trailing: !1
}), exports["licTrialExpired"] = licTrialExpired, exports["doGetLicInfo"] = doGetLicInfo, exports["getMachineId"] = getMachineId;

找到 _vk 函数:

1
2
3
4
5
6
exports._vk = function (e, x) {
  if (!e) return !1;
  var a = (e = e.trim())["split"]("-"),
      r;
  return !(a["length"] < 2) && require("./lmKeyGen")["_gk"](parseInt(a[0]), x) === a["slice"](0, 2)["join"]("-");
}

可以看到它又引入了 "./lmKeyGen" 文件,也是混淆过的,一顿 ast 反混淆,拿到反混淆结果代码:

1
2
3
4
5
6
7
8
9
10
11
12
Object["defineProperty"](exports, "__esModule", {
  value: !0
});
 
var commonUtils_1 = require("./commonUtils"),
    SECRET_KEY_V5 = "5n8uqpBBosNC4wxyz";
 
exports["_gk"] = function (r, e) {
  var o = r["toString"]().trim();
  if (6 !== o.length) throw new Error("Length of licNo must be 6, e.g. 200001");
  return e = e || SECRET_KEY_V5, o + "-" + commonUtils_1["cipher"](o, commonUtils_1["shuffleString"](e), "hex");
};

这里简单分析一下:

  1. 引入 commonUtils 模块
  2. SECRET_KEY_V5 = "5n8uqpBBosNC4wxyz"
  3. 传入 r 参数是注册码("999999-9d5988960387b3f63481722566cb0e9f")的第一部分 "999999"
  4. 传入 e 参数是 undefined,然后赋值为 SECRET_KEY_V5
  5. 调用 commonUtils 模块里的 cipher 加密函数

大概代码:

1
commonUtils.cipher("999999", "5n8uqpBBosNC4wxyz", "hex")

找到 commonUtils.js 文件,发现只需要简单地格式化一下就行,然后扣出关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var crypto = require("crypto");
var reverser = Array.prototype.reverse;
var shuffler = [47, 16, 39, 28, 32, 4, 44, 11, 91, 98, 53, 24, 85, 75, 66, 41, 22, 9, 31, 46, 25, 6, 18, 43, 84, 70, 17, 35, 62, 60, 64, 77, 12, 0, 15, 14, 7, 61, 92, 5, 42, 78, 57, 81, 27, 93, 29, 86, 51, 23, 13, 38, 19, 88, 80, 79, 56, 69, 59, 37, 54, 96, 1, 36, 94, 97, 99, 87, 40, 45, 26, 2, 52, 3, 65, 8, 74, 71, 50, 76, 21, 68, 83, 33, 90, 20, 89, 72, 82, 67, 73, 95, 30, 34, 48, 10, 63, 49, 55, 58];
 
function cipher(e, r, t) {
    void 0 === r && (r = "MongoBooster"),
    void 0 === t && (t = "hex");
    var n = crypto.createCipher("aes-128-cbc", r),
    o = n.update(e);
    return Buffer.concat([o, n.final()]).toString(t)
}
 
function decipher(e, r, t) {
    void 0 === r && (r = "MongoBooster"),
    void 0 === t && (t = "hex");
    var n = crypto.createDecipher("aes-128-cbc", r);
    var o = Buffer.from(e, t);
    var a = n.update(o);
    return Buffer.concat([a, n.final()]).toString()
}
 
function shuffleString(e) {
    return shuffle(reverser.call(e.split(""))).join("")
}
 
function shuffle(r) {
    var t = [];
    shuffler.forEach(function(e) {
        e < r.length && t.push(r[e]);
    });
    var e = r.slice(shuffler.length);
    return t.concat(e)
}

在 nodejs 里跑一下代码看看:

1
console.log(cipher("999999", shuffleString("5n8uqpBBosNC4wxyz"), "hex"));

输出结果为:

1
9d5988960387b3f63481722566cb0e9f

跟我从网上找到的注册码("999999-9d5988960387b3f63481722566cb0e9f")尾巴是一样的,也就是后面这一串是前面代码的校验值

 

那就简单了,我只需要再做个注册机就完事了

 

lmCore.js 文件中还发现了注册码判断注册码是否企业版的代码:

1
2
3
function getLicTypeFromKey(e) {
  return exports._vk(e) ? _.startsWith(e, "88") ? LicenseType["corporate"] : _["startsWith"](e, "66") ? LicenseType["site"] : _["startsWith"](e, "44") ? LicenseType["small_team"] : _["startsWith"](e, "45") ? LicenseType["medium_team"] : _["startsWith"](e, "3") ? LicenseType["personal"] : _.startsWith(e, "2") ? LicenseType["commercial"] : LicenseType["commercial"] : LicenseType["trial"];
}

简单处理一下:

1
2
3
4
5
// _vk(e) 是判断注册码是否通过校验,为 false 的话就是 "trial"
 
function getLicTypeFromKey(e) {
  return true ? e.startsWith("88") ? "corporate" : e.startsWith("66") ? "site" : e.startsWith("44") ? "small_team" : e.startsWith("45") ? "medium_team" : e.startsWith("3") ? "personal" : e.startsWith("2") ? "commercial" : "commercial" : "trial";
}

注册码开头字符对应版本:

1
2
3
4
5
6
7
8
校验不通过: trial
88: corporate
66: site
44: small_team
45: medium_team
3: personal
2: commercial
其他: commercial

所以我还是用 "999999" 这个来计算吧

 

最后总结一下破解操作:

  1. 修改 app\frontend\utils\lm.js 文件中的网络验证
  2. 反混淆 app\shared\lmKeyGen.js 拿到 SECRET_KEY 来计算注册码

对应版本 SECRET_KEY:

1
2
5.2.12    5n8uqpBBosNC4wxyz
6.2.7     6n69cccbcdea1868a

我用 6.2.7 版本的计算一下:

1
2
3
console.log(cipher("999999", shuffleString("6n69cccbcdea1868a"), "hex"));
 
"96342b95d18d990e42e4e4a1a0bb403a"

输入注册码 "999999-96342b95d18d990e42e4e4a1a0bb403a" 试试:

 

 

但是为啥报错了呢

 

 

于是我再重复上面流程,终于在 app\shared\lmCore.js 这个文件里看到了这样的代码:

1
2
3
4
5
6
7
8
exports["_vk"] = function(x, e) {
    if (!x) return !1;
    if (_["startsWith"](x, "999999")) return !1;
    var r = (x = x["trim"]())["split"]("-"),
        w,
        c;
    return !(r["length"] < 2) && require("./lmKeyGen")["_gk"](parseInt(r[0]), e) === r["slice"](0, 2).join("-");
}

我看完直呼好家伙:

1
if (_["startsWith"](x, "999999")) return !1;

把这里去掉,或者用 "999998" 注册一个:

1
2
3
console.log(cipher("999998", shuffleString("6n69cccbcdea1868a"), "hex"));
 
"d1481bbffbf2d3427a241097f136dc4c"

再试试:

 

 

注册成功:

 


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2020-12-31 15:06 被KingSelyF编辑 ,原因:
收藏
点赞10
打赏
分享
最新回复 (8)
雪    币: 73
活跃值: (60)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
飘散 2021-3-6 10:24
2
0
大佬厉害
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_信 2021-3-6 14:47
3
0
厉害啊,大佬级,真正的高手
雪    币: 156
活跃值: (953)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
bluegatar 2021-3-11 12:48
4
0
大佬的内力深厚啊,js ast用的很溜
雪    币: 3126
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
我来自南方 2021-3-11 12:58
5
0
牛人颇多
雪    币: 1910
活跃值: (3322)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
KingSelyF 1 2021-4-27 18:58
7
0
mb_efajvpik 可以给个联系方式吗,大单
多大
雪    币: 214
活跃值: (1035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
零下八度 2023-4-19 16:36
9
0
推荐用 Studio 3T ,好用。
游客
登录 | 注册 方可回帖
返回