(0, fn)(...) 是什么
这是 webpack / Babel 生成代码中常见的间接调用写法:
(0, s.p)(args)
等价于:
const _fn = s.p;
_fn(args); // 普通函数调用,this === undefined(严格模式)
为什么不直接 s.p(args)?
s.p(args) 会将 this 绑定为 s;逗号运算符写法 (0, s.p) 会把方法”脱离”对象,变成普通函数调用,this 不再指向 s。
这在以下场景有实际意义:
s.p是从模块导入的函数(如rxjs.from),不应绑this- 打包工具为了兼容 ES Modules 的 strict mode 语义,确保
this === undefined - Tree-shaking 友好:逗号写法对某些打包器更易分析
常见形式:
(0, rxjs.of)(1, 2, 3) // 等价 rxjs.of(1, 2, 3)
(0, lodash.map)(arr, fn) // 等价 lodash.map(arr, fn)
(0, react.createElement)(...) // 等价 react.createElement(...)
(0, o.Z)(list) // 混淆后的模块导出函数
读到这种代码,直接把 (0, expr) 去掉,剩下的 expr(...) 就是实际调用。
真实打包代码示例
下面是一段典型的混淆打包代码(来自实际页面逆向):
(0, s.p)(
(t = window.$).when.apply(t,
(0, o.Z)(window.beforeSubmitQueue.map(function(e) {
return e();
}))
)
).switchMap(
window.MarkLib.submitMarkAnswer.bind(window.MarkLib, !0, e)
).filter(function(t) {
var n = t.fire_status, r = void 0 === n || n;
var a = t.reject, o = void 0 !== a && a;
return !e && w(!1), r && !o;
}).map(JSON.stringify.bind(JSON)).do(function(t) {
return y({
type: "markpage/saveAnswer",
payload: { isTemporary: 1, packetId: Number(p), ... }
});
}).subscribe();
逐步还原
第一步:去掉 (0, expr) 包装
s.p(...) // s.p 可能是 rxjs.from / rxjs.fromPromise
o.Z(list) // o.Z 是某个导出函数,根据上下文判断
第二步:提取 window 上的对象引用
const { $, beforeSubmitQueue, MarkLib } = window;
第三步:还原 .apply 和 .bind
// (t = window.$).when.apply(t, args) → $.when(...args)
// fn.bind(obj, true, e) → () => obj.fn(true, e)
第四步:改写旧版 RxJS API
| 旧版 | 新版 |
|---|---|
.do(fn) | .tap(fn) |
fromPromise(p) | from(p) |
.map(fn) | .map(fn) 不变 |
还原结果:
const { $, beforeSubmitQueue, MarkLib } = window;
from($.when(...beforeSubmitQueue.map(fn => fn())))
.pipe(
switchMap(() => MarkLib.submitMarkAnswer(true, e)),
filter(({ fire_status = true, reject = false }) => {
if (!e) w(false);
return fire_status && !reject;
}),
map(JSON.stringify),
tap(answer => {
y({
type: "markpage/saveAnswer",
payload: {
isTemporary: 1,
packetId: Number(p),
pageId: Number(a),
round: f,
stage: k,
answer: j(answer, c, u)
},
callback() { if (!e) w(false); }
});
})
)
.subscribe();
逻辑说明
还原后的业务逻辑只有一句话:
执行提交前钩子(
beforeSubmitQueue)→ 提交答案(submitMarkAnswer)→ 过滤无效结果 → 序列化 → 保存到 Redux store
关键点:
$.when(...tasks):jQuery Deferred,等待所有 before-hooks 完成switchMap:前一个 Observable 完成后切换到新的,丢弃之前未完成的filter:fire_status不为 false 且reject不为 true 才继续.subscribe():没有 subscribe,RxJS 链不会执行(冷 Observable)
其他常见混淆模式
// void 0 === undefined
void 0 // → undefined
// !! 转布尔
!!value // → Boolean(value)
// 逗号运算符返回最后一个值
(a(), b(), c()) // 执行 a、b、c,返回 c 的值
// 短路赋值
x = void 0 === x ? defaultVal : x // → x ?? defaultVal(现代写法)
读打包代码时,把这些模式认出来后,理解代码逻辑会快很多。
