[ js.hook.js ]

javascript钩子; 劫持方法/伪造参数/篡改结果/还原劫持

目前为 2015-11-22 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name [ js.hook.js ]
  3. // @description javascript钩子; 劫持方法/伪造参数/篡改结果/还原劫持
  4. // @namespace js.hook.js
  5. // @version 0.0.1
  6. // @author vc1
  7. // ==/UserScript==
  8.  
  9.  
  10.  
  11. ;
  12. (function (name, ctx) {
  13.  
  14. /*
  15. *
  16. * [ js.hook.js ]
  17. *
  18. * javascript钩子
  19. *
  20. * * 劫持方法
  21. * * 伪造参数
  22. * * 篡改结果
  23. * * 还原劫持
  24. *
  25. * * 2015-11-22
  26. * * vc1
  27. *
  28. */
  29.  
  30. // 'use stric'
  31.  
  32. var definition = (function () {
  33.  
  34. /*
  35. * 入口方法
  36. *
  37. * hook(alert)
  38. * hook('window.alert')
  39. * hook('MyOjbect.User.info.edit')
  40. */
  41. function hook() {
  42. if (this instanceof hook) {
  43. return hook.prototype.__init__.apply(this, arguments);
  44. }
  45.  
  46. var t = hook.prototype.__getTarget__.apply(null, arguments);
  47.  
  48. // 已经劫持过了,返回已有的钩子
  49. if (hook.prototype.storage[t.fn_object_name] && hook.prototype.storage[t.fn_object_name][t.fn_name]) {
  50. return hook.prototype.storage[t.fn_object_name][t.fn_name].exports;
  51. }
  52.  
  53. return new hook(t.fn_object, t.fn_object_name, t.fn_name, t.fn_real);
  54. }
  55.  
  56. hook.prototype.storage = {};
  57. var eval = window.eval;
  58.  
  59. hook.prototype.__init__ = function (fn_object, fn_object_name, fn_name, fn_real) {
  60. // 原始方法正身
  61. this.fn_real = fn_real;
  62. // 被劫持的方法名
  63. this.fn_name = fn_name;
  64. // 被劫持的方法所在对象,默认 window
  65. this.fn_object = fn_object;
  66. // 所在对象名称
  67. this.fn_object_name = fn_object_name;
  68. // 伪造传入参数
  69. this.fakeArgFn = null;
  70. // 伪造返回结果
  71. this.fakeRstFn = null;
  72. // 对外暴露的功能
  73. this.exports = {
  74. fake: this.fake.bind(this),
  75. fakeArg: this.fakeArg.bind(this),
  76. fakeRst: this.fakeRst.bind(this),
  77. off: this.off.bind(this),
  78. offArg: this.offArg.bind(this),
  79. offRst: this.offRst.bind(this),
  80. };
  81.  
  82. var t = this;
  83. this.exports_var = Object.defineProperties({},
  84. {
  85. 'fn_real': {
  86. value: fn_real,
  87. enumerable: true
  88. },
  89. 'fn_name': {
  90. value: fn_name,
  91. enumerable: true
  92. },
  93. 'fn_object_name': {
  94. value: fn_object_name,
  95. enumerable: true
  96. },
  97. 'fn_object': {
  98. value: fn_object,
  99. enumerable: true
  100. },
  101. fakeArgFn: {
  102. get: function () {
  103. return t.fakeArgFn;
  104. },
  105. enumerable: true
  106. },
  107. fakeRstFn: {
  108. get: function () {
  109. return t.fakeRstFn;
  110. },
  111. enumerable: true
  112. }
  113. });
  114. // 保存当前钩子
  115. this.storage[fn_object_name] = this.storage[fn_object_name] || {};
  116. this.storage[fn_object_name][fn_name] = this;
  117.  
  118. return this.exports;
  119. };
  120.  
  121. // 支持多种输入形式
  122. hook.prototype.__getTarget__ = function () {
  123. var fn_real, // 原始方法正身
  124. fn_name = 'alert',
  125. // 被劫持的方法名
  126. fn_object_name = "Window",
  127. fn_object = window; // 被劫持的方法所在对象,默认 window
  128. if (arguments.length === 1) {
  129. var arg = arguments[0];
  130. // 传入字符串
  131. if (arg.__proto__ === String.prototype) {
  132. var dotidx = arg.lastIndexOf('.');
  133. if (~dotidx) { // 'window.alert'
  134. fn_object_name = arg.slice(0, dotidx);
  135. fn_object = eval(fn_object_name);
  136. fn_name = arg.slice(dotidx + 1);
  137. fn_real = fn_object[fn_name];
  138. } else { // 'alert'
  139. fn_name = arg;
  140. }
  141. } else { // 传入一个方法,所在对象默认 window
  142. fn_real = arg;
  143. fn_name = fn_real.name;
  144. }
  145. } else if (arguments.length == 2) { // 不推荐
  146. fn_object = arguments[0];
  147. fn_object_name = fn_object.constructor.name;
  148. fn_name = arguments[1];
  149. fn_real = fn_object[fn_name];
  150. }
  151.  
  152. if (!(fn_object && fn_name && fn_real)) {
  153. console.error(fn_object);
  154. console.error(fn_object_name);
  155. console.error(fn_name);
  156. console.error(fn_real);
  157. throw new Error('hook fail');
  158. }
  159.  
  160. return {
  161. 'fn_real': fn_real,
  162. 'fn_name': fn_name,
  163. 'fn_object_name': fn_object_name,
  164. 'fn_object': fn_object,
  165. };
  166. };
  167.  
  168. /*
  169. * 替换原始方法
  170. *
  171. * 作用等于 temp=alert; alert=function(){// your function}
  172. *
  173. * fakeFn(arguments, t.exports_var, scope, this)
  174. * 接收到的参数列表, 原始方法信息, 对象实例或原对象, 执行时的作用域
  175. *
  176. */
  177. hook.prototype.fake = function (fakeFn) {
  178. var t = this;
  179. var puppet = eval("(function " + this.fn_name + "() {" +
  180. "var scope = this instanceof t.fn_object.constructor ? this :" +
  181. " t.fn_object;" +
  182. "return fakeFn.call(scope, arguments, t.exports_var, scope, this);" +
  183. "})");
  184. this.fn_object[this.fn_name] = puppet;
  185. return this.exports;
  186. };
  187.  
  188. /*
  189. * 在原方法前,劫持传入的参数
  190. *
  191. * fakeArg('直接替换为要传入的参数', ...)
  192. * fakeArg(function(原参数,){
  193. * //
  194. * return [修改后的参数]
  195. * })
  196. *
  197. * 无返回则采用原始参数
  198. */
  199. hook.prototype.fakeArg = function (arg) {
  200. this.__fakeArgRst__();
  201. this.fakeArgFn = this.__getFun__(arguments);
  202. return this.exports;
  203. };
  204.  
  205. /*
  206. * 在原方法后,劫持返回的数据
  207. *
  208. * fakeRst('直接替换为要传入的参数')
  209. * fakeRst(function(原返回值){
  210. * //
  211. * return 修改后的返回值
  212. * })
  213. */
  214. hook.prototype.fakeRst = function (arg) {
  215. this.__fakeArgRst__();
  216. this.fakeRstFn = this.__getFun__(arg);
  217. return this.exports;
  218. };
  219.  
  220.  
  221. /*
  222. * 开启劫持arg/rst
  223. */
  224. hook.prototype.__fakeArgRst__ = function () {
  225. var t = this;
  226. var fakeArgRstFn = function (args, hook_var, scope, raw_this) {
  227. var t = hook_var;
  228. var faked_arg = t.fakeArgFn ? t.fakeArgFn.apply(scope, args) || args : args;
  229. faked_arg && [].slice.call(faked_arg).length === 0 && (faked_arg = [faked_arg]);
  230. var real_rst = t.fn_real.apply(scope, faked_arg);
  231. var faked_rst = t.fakeRstFn ? t.fakeRstFn.call(scope, real_rst) : real_rst;
  232. return faked_rst;
  233. };
  234. this.fake(fakeArgRstFn);
  235. };
  236.  
  237. /*
  238. * 关闭劫持
  239. *
  240. * 传入参数为空:关闭前后所有劫持 hook(alert).off()
  241. * 传入字符串 "arg" 或 "rst":关闭对应劫持 hook(alert).off('arg')
  242. * 传入方法:关闭对应劫持
  243. *
  244. * 前后劫持全部关闭后,还原被 hook 的方法
  245. */
  246. hook.prototype.off = function (filter) {
  247. if (!filter) {
  248. delete this.fakeArgFn;
  249. delete this.fakeRstFn;
  250. } else if (typeof filter === 'function' || filter.__proto__ === String.prototype) {
  251. (this.fakeArgFn === fn || filter === 'arg') && delete this.fakeArgFn;
  252. (this.fakeRstFn === fn || filter === 'rst') && delete this.fakeRstFn;
  253. }
  254.  
  255. if (!this.fakeArgFn && !this.fakeRstFn) {
  256. this.fn_object[this.fn_name] = this.fn_real;
  257. //delete this.storage[this.fn_object_name][this.fn_name];
  258. }
  259.  
  260. return this.exports;
  261. };
  262.  
  263. /*
  264. * 关闭前面的参数劫持
  265. *
  266. */
  267. hook.prototype.offArg = function (filter) {
  268. filter = filter || 'arg';
  269. this.off(filter);
  270. return this.exports;
  271. };
  272.  
  273. /*
  274. * 关闭后面的结果劫持
  275. *
  276. */
  277. hook.prototype.offRst = function (filter) {
  278. filter || 'rst';
  279. this.off(filter);
  280. return this.exports;
  281. };
  282.  
  283.  
  284. /*
  285. * 直接修改参数或返回结果
  286. */
  287. hook.prototype.__getcloser__ = function (args) {
  288. return function () {
  289. return args;
  290. };
  291. };
  292. hook.prototype.__getFun__ = function (arg) {
  293. return typeof arg[0] == 'function' ? arg[0] : this.__getcloser__(arg);
  294. };
  295.  
  296. return hook;
  297.  
  298. });
  299.  
  300. //检测上下文环境是否为AMD或CMD
  301. var hasDefine = typeof define === 'function',
  302. // 检测上下文环境是否为Node
  303. hasExports = typeof module !== 'undefined' && typeof module !== 'function' && module.exports;
  304. if (!name) {
  305. return definition();
  306. } else if (ctx) {
  307. // 设置环境后则挂载到此对象
  308. /*
  309. void function(name, ctx){
  310. ...
  311. }('hook', window);
  312. */
  313. ctx[name] = definition();
  314. } else if (hasDefine) {
  315. //AMD环境或CMD环境
  316. define(name, definition);
  317. } else if (hasExports) {
  318. //定义为普通Node模块
  319. module.exports = definition();
  320. }
  321. return definition();
  322.  
  323.  
  324.  
  325. // 效果演示
  326.  
  327. /*
  328. window.tool = {
  329. calc: function(msg, n) {
  330. console.warn('calc收到参数:' + msg + ', ' + n);
  331. var r = n * n;
  332. console.warn('calc结果:' + r);
  333. return r;
  334. }
  335. }
  336. var hook = window.hook_js;
  337. console.clear();
  338. console.info('一个计算器:');
  339. console.group('原始方法:\n\ntool.calc');
  340. console.log(tool.calc);
  341. console.info('设置参数:' + '专注于计算平方的计算器' + ', ' + 42);
  342. console.info('接收到的结果:' + tool.calc('专注于计算平方的计算器', 42));
  343. console.groupEnd();
  344. console.log('\n');
  345. console.group("劫持后:\n\nhook('window.tool.calc').fakeArg('这个计算器坏了', -1).fakeRst('<(ˉ^ˉ)> 告诉你坏了');");
  346. hook('window.tool.calc').fakeArg('这个计算器坏了', -1).fakeRst('<(ˉ^ˉ)> 告诉你坏了');
  347. console.log(tool.calc);
  348. console.info('设置参数:' + '专注于计算平方的计算器' + ', ' + 42);
  349. console.info('接收到的结果:' + tool.calc('专注于计算平方的计算器', 42));
  350. console.groupEnd();
  351. console.log('\n');
  352. console.group("还原后:\n\nhook('window.tool.calc').off();");
  353. hook('window.tool.calc').off();
  354. console.log(tool.calc);
  355. console.info('设置参数:' + '专注于计算平方的计算器' + ', ' + 42);
  356. console.info('接收到的结果:' + tool.calc('专注于计算平方的计算器', 42));
  357. console.groupEnd();
  358. console.log('\n');
  359. console.group("替换:\n\nhook('window.tool.calc').fake(function(){...})");
  360. hook('window.tool.calc').fake(function(args, t, scope, raw_scope){
  361. console.log('调用者:' + args.callee.caller);
  362. return t.fn_real.apply(scope, args);
  363. });
  364. console.log(tool.calc);
  365. console.info('设置参数:' + '专注于计算平方的计算器' + ', ' + 42);
  366. console.info('接收到的结果:' + (function calc_caller(){
  367. return tool.calc('专注于计算平方的计算器', 42);
  368. })());
  369. console.groupEnd();
  370. console.log('\n');
  371. */
  372.  
  373. })('hook_js', window);