先简单理解一下webpack
webpack中每个模块有一个唯一的id,是从0开始递增的。整个打包后的bundle.js是一个匿名函数自执行。参数则为一个数组。数组的每一项都为个function。function的内容则为每个模块的执行内容,并按照require的顺序排列。
下面通过自己的实践,总结出webpack3,webpack4,webpack5的区别,并记录下来,方便在js逆向过程快速定位版本,做出相应的逆向方案。
安装
npm install -g [email protected]
打包命令
// webpack 入口文件 出口文件
webpack index.js main.js
查看版本
webpack -v
let total = 0;
for (let i = 0; i < 10; i++) {
total += i;
}
console.log("Total: " + total);
将index.js
打包,生成main.js
,代码如下:
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
let total = 0;
for (let i = 0; i < 10; i++) {
total += i;
}
console.log("Total: " + total);
/***/ })
/******/ ]);
我们来看一下打包后的代码,整个打包后的代码就是一个超大的匿名函数,这种写法匿名函数和之后的圆括号包裹成为一个整体,表明匿名函数与之后调用函数的()为一个整体。
// 这是一个简单的匿名函数案例
(function(num1,num2){
var sum = num1 + num2;
}(1,2));
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
call方法的解释
Function.prototype.call() call()
方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
结论:仔细查看代码,发现模块作为参数传入执行,再观察wp3.js
文件,每一行都存在/******/注释,其加载器函数名是__webpack_require__
,代码总体读起来也是相对简单,变量名比较长,整体代码的长度只有78多行。
打包后的模块通过modules[moduleId].call()
运行,其中moduleId
就是模块下标id
安装webpack,由于webpack4.0开始需要依赖webpack-cli,所以需要一起安装。
npm install --save-dev webpack@4 webpack-cli@3.3.2
npx webpack --version
webpack --version
// 这是因为不支持旧版本加密套件导致的,需要开启旧版支持。
export NODE_OPTIONS=--openssl-legacy-provider
打包命令
// 默认把index.js打包main.js文件
webpack
原始代码:index.js
let total = 0;
for (let i = 0; i < 10; i++) {
total += i;
}
console.log("Total: " + total);
将index.js
打包,生成main.js
,代码如下:
!function (e) {
var t = {};
function r(n) {
if (t[n]) return t[n].exports;
var o = t[n] = {i: n, l: !1, exports: {}};
return e[n].call(o.exports, o, o.exports, r), o.l = !0, o.exports
}
r.m = e, r.c = t, r.d = function (e, t, n) {
r.o(e, t) || Object.defineProperty(e, t, {enumerable: !0, get: n})
}, r.r = function (e) {
"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {value: "Module"}), Object.defineProperty(e, "__esModule", {value: !0})
}, r.t = function (e, t) {
if (1 & t && (e = r(e)), 8 & t) return e;
if (4 & t && "object" == typeof e && e && e.__esModule) return e;
var n = Object.create(null);
if (r.r(n), Object.defineProperty(n, "default", {
enumerable: !0,
value: e
}), 2 & t && "string" != typeof e) for (var o in e) r.d(n, o, function (t) {
return e[t]
}.bind(null, o));
return n
}, r.n = function (e) {
var t = e && e.__esModule ? function () {
return e.default
} : function () {
return e
};
return r.d(t, "a", t), t
}, r.o = function (e, t) {
return Object.prototype.hasOwnProperty.call(e, t)
}, r.p = "", r(r.s = 0)
}([function (e, t) {
let r = 0;
for (let e = 0; e < 10; e++) r += e;
console.log("Total: " + r)
}]);
webpack4是目前打包版本中用的最多的,结构如下图:
和webpack3一样,打包后还是巨大自执行函数,模块作为参数传入执行,代码的复杂程度明显提升。
安装webpack5
npm init
npm install babel-loader @babel/core @babel/preset-env webpack --save-dev
编写配置文件webpack.config.js
这里要注意的是在不同模式下打包后代码是不同的
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
// Babel Loader 配置
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
// 其他加载器配置...
]
},
// 其他配置...
};
原始代码:index.js
let total = 0;
for (let i = 0; i < 10; i++) {
total += i;
}
console.log("Total: " + total);
打包命令
npx webpack
将index.js
打包,生成main.js
,打包后代码如下:
(() => {
for (var o = 0, l = 0; l < 10; l++) o += l;
console.log("Total: " + o)
})();
对比index.js
原代码几乎没有变化。
尝试让原始代码变的复杂一点再打包,如下:
moduleA.js
function multiply(a, b) {
return a * b;
}
module.exports = multiply;
moduleB.js
function divide(a, b) {
return a / b;
}
module.exports = divide;
index.js
const multiply = require('./moduleA');
const divide = require('./moduleB');
const result1 = multiply(5, 3);
const result2 = divide(10, 2);
console.log('Result 1:', result1);
console.log('Result 2:', result2);
打包后
(() => {
var r, o, t, e, n = {
615: r => {
r.exports = function (r, o) {
return r * o
}
}, 817: r => {
r.exports = function (r, o) {
return r / o
}
}
}, s = {};
function u(r) {
var o = s[r];
if (void 0 !== o) return o.exports;
var t = s[r] = {exports: {}};
return n[r](t, t.exports, u), t.exports
}
r = u(615), o = u(817), t = r(5, 3), e = o(10, 2), console.log("Result 1:", t), console.log("Result 2:", e)
})();
代码更加简洁,模块不再以参数的方式传入执行,所有模块代码都包含在匿名函数内,并且执行
原始代码:index.js
打包前代码总共5行
版本 | 打包后 |
---|---|
webpack3 | 代码77行,原始代码结构无变化,模块作为参数传入执行 |
webpack4 | 代码39行,原始代码结构做了压缩,模块作为参数传入执行 |
webpack5 | 代码4行,原始代码结构无变化,模块作为函数体并加载;但是当增加js源代码代码复杂度并打包后,打包后代码结构变化较大 |
js逆向第6例:猿人学第16题-webpack-控制台无限输出-window删除陷阱-纯手撕版
js逆向第3例:蝉妈妈大屏实时数据formDataSign加密