如何通过Tampermonkey快速查JavaScript加密⼊⼝
在很多情况下,我们可能想要在⽹页中⾃动执⾏某些代码,帮助我们完成⼀些操作。如⾃动抢票、⾃动刷单、⾃动爬⾍等等,这些操作绝⼤部分都是借助 JavaScript 来实现的。那么问题来了?在浏览器⾥⾯怎样才能⽅便地执⾏我们所期望执⾏的 JavaScript 代码呢?在这⾥推荐⼀个插件,叫做 Tampermonkey。这个插件的功能⾮常强⼤,利⽤它我们⼏乎可以在⽹页中执⾏任何 JavaScript 代码,实现我们想要的功能。
当然不仅仅是⾃动抢票、⾃动刷单、⾃动爬⾍,Tampermonkey 的⽤途远远不⽌这些,只要我们想要的功能能⽤ JavaScript 实
现,Tampermonkey 就可以帮我们做到。⽐如我们可以将 Tampermonkey 应⽤到 JavaScript 逆向分析中,去帮助我们更⽅便地分析⼀些JavaScript 加密和混淆代码。
本节我们就来介绍⼀下这个插件的使⽤⽅法,并结合⼀个实际案例,介绍下这个插件在 JavaScript 逆向分析中的⽤途。
Tampermonkey
Tampermonkey,中⽂也叫作「油猴」,它是⼀款浏览器插件,⽀持 Chrome。利⽤它我们可以在浏览
器加载页⾯时⾃动执⾏某些JavaScript 脚本。由于执⾏的是 JavaScript,所以我们⼏乎可以在⽹页中完成任何我们想实现的效果,如⾃动爬⾍、⾃动修改页⾯、⾃动响应事件等等。
安装
js代码加密软件安装完成之后,在 Chrome 浏览器的右上⾓会出现 Tampermonkey 的图标,这就代表安装成功了。
获取脚本
Tampermonkey 运⾏的是 JavaScript 脚本,每个⽹站都能有对应的脚本运⾏,不同的脚本能完成不同的功能。这些脚本我们可以⾃定义,同样也可以⽤已经写好的很多脚本,毕竟有些轮⼦有了,我们就不需要再去造了。
脚本编写
除了使⽤别⼈已经写好的脚本,我们也可以⾃⼰编写脚本来实现想要的功能。编写脚本难不难呢?其实就是写 JavaScript 代码,只要懂⼀些JavaScript 的语法就好了。另外除了懂 JavaScript 语法,我们还需要遵循脚本的⼀些写作规范,这其中就包括⼀些参数的设置。
下⾯我们就简单实现⼀个⼩的脚本,实现某个功能。
⾸先我们可以点击 Tampermonkey 插件图标,点击「管理⾯板」按钮,打开脚本管理页⾯。
界⾯类似显⽰如下图所⽰。
在这⾥显⽰了我们已经有的⼀些 Tampermonkey 脚本,包括我们⾃⾏创建的,也包括从第三⽅⽹站下载安装的。
另外这⾥也提供了编辑、调试、删除等管理功能,我们可以⽅便地对脚本进⾏管理。
接下来我们来创建⼀个新的脚本来试试,点击左侧的「+」号,会显⽰如图所⽰的页⾯。
初始化的代码如下:
// ==UserScript==
// @name New Userscript
// @namespace tampermonkey/
// @version 0.1
// @description try to take over the world!
// @author You
// @match www.tampermonkey/documentation.php?ext=dhdg
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Your
})();
这⾥最上⾯是⼀些注释,但这些注释是⾮常有⽤的,这部分内容叫做UserScript Header,我们可以在⾥⾯配置⼀些脚本的信息,如名称、版本、描述、⽣效站点等等。
下⾯简单介绍下UserScript Header的⼀些参数定义。
// @include www.tampermonkey/*
// @include *
// @include *
// @include *
@match:约等于 @include 标签,可以配置多个。
@exclude:不⽣效页⾯,可配置多个,优先级⾼于 @include 和 @match。
@require:附加脚本⽹址,相当于引⼊外部的脚本,这些脚本会在⾃定义脚本执⾏之前执⾏,⽐如引⼊⼀些必须的库,如 jQuery 等,这⾥可以⽀持配置多个 @require 参数。例如:
// @require code.jquery/jquery-2.1.4.min.js
// @require code.jquery/jquery-2.1.3.min.js#
// @require code.jquery/jquery-2.1.2.min.js#,
@resource:预加载资源,可通过 GM_getResourceURL 和 GM_getResourceText 读取。
@connect:允许被 GM_xmlhttpRequest 访问的域名,每⾏⼀个。
@run-at:脚本注⼊的时刻,如页⾯刚加载时,某个事件发⽣后等等。例如:
document-start:尽可能地早执⾏此脚本。
document-body:DOM 的 body 出现时执⾏。
document-end:DOMContentLoaded 事件发⽣时或发⽣后后执⾏。
document-idle:DOMContentLoaded 事件发⽣后执⾏,即 DOM 加载完成之后执⾏,这是默认的选项。
context-menu:如果在浏览器上下⽂菜单(仅限桌⾯ Chrome 浏览器)中点击该脚本,则会注⼊该脚本。注意:如果使⽤此值,则将忽略所有 @include 和 @exclude 语句。
@grant:⽤于添加 GM 函数到⽩名单,相当于授权某些 GM 函数的使⽤权限。例如:
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @grant unsafeWindow
// @grant window.close
// @grant window.focus
如果没有定义过 @grant 选项,Tampermonkey 会猜测所需要的函数使⽤情况。
@noframes:此标记使脚本在主页⾯上运⾏,但不会在 iframe 上运⾏。
@nocompat:由于部分代码可能是专门为专门的浏览器所写,通过此标记,Tampermonkey 会知道脚本可以运⾏的浏览器。例如:// @nocompat Chrome
这样就指定了脚本只在 Chrome 浏览器中运⾏。
除此之外,Tampermonkey 还定义了⼀些 API,使得我们可以⽅便地完成某个操作,如:
GM_log:将⽇志输出到控制台。
GM_setValue:将参数内容保存到 Storage 中。
GM_addValueChangeListener:为某个变量添加监听,当这个变量的值改变时,就会触发回调。
GM_xmlhttpRequest:发起 Ajax 请求。
GM_download:下载某个⽂件到磁盘。
GM_setClipboard:将某个内容保存到粘贴板。
在UserScript Header下⽅是 JavaScript 函数和调⽤的代码,其中'use strict'标明代码使⽤ JavaScript 的严格模式,在严格模式下可以消除Javascript 语法的⼀些不合理、不严谨之处,减少⼀些怪异⾏为,如不能直接使⽤未声明的变量,这样可以保证代码的运⾏安全,同时提⾼编译器的效率,提⾼运⾏速度。在下⽅// Your 这⾥我们就可以编写⾃⼰的代码了。
实战 JavaScript 逆向
下⾯我们来通过⼀个简单的 JavaScript 逆向案例来演⽰⼀下 Tampermonkey 的作⽤。
在 JavaScript 逆向的时候,我们经常会需要追踪某些⽅法的堆栈调⽤情况,但很多情况下,⼀些 JavaScript 的变量或者⽅法名经过混淆之后是⾮常难以捕捉的。
但如果我们能掌握⼀定的门路或规律,辅助以 Tampermonkey,就可以更轻松地出⼀些 JavaScript ⽅法的断点位置,从⽽加速逆向过程。
在逆向过程中,⼀个⾮常典型的技术就是 Hook 技术。Hook 技术中⽂⼜叫做钩⼦技术,它就是在程序运⾏的过程中,对其中的某个⽅法进⾏重写,在原先的⽅法前后加⼊我们⾃定义的代码。相当于在系统没有调⽤该函数之前,钩⼦程序就先捕获该消息,钩⼦函数先得到控制权,这时钩⼦函数既可以加⼯处理(改变)该函数的执⾏⾏为,还可以强制结束消息的传递。
如果觉得⽐较抽象,看完下⾯的 Hook 案例就懂了。
例如,我们接下来使⽤ Tampermonkey 实现对某个 JavaScript ⽅法的 Hook,轻松到某个⽅法执⾏的位置,从⽽快速定位到逆向⼊⼝。
页⾯长这样:
我们随便输⼊⽤户名密码,点击登录按钮,观察⼀下⽹络请求的变化。
可以看到如下结果:
看到实际上控制台提交了⼀个 POST 请求,内容为:
{"token":"eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiJ9"}
嗯,确实,没有诸如 username 和 password 的内容了,那这究竟是个啥?我要是做爬⾍的话?怎么模拟登录呢?
模拟登录的前提当然就是到当前 token ⽣成的逻辑了,那么问题来了,到底这个 token 和⽤户名、密码什么关系呢?我们怎么来寻其中的蛛丝马迹呢?
这⾥我们就可能思考了,本⾝输⼊的是⽤户名和密码,但是提交的时候却变成了⼀个 token,经过观察 token 的内容还很像 Base64 编码。这就代表,⽹站可能⾸先将⽤户名密码混为了⼀个新的字符串,然后最后经过了⼀次 Base64 编码,最后将其赋值为 token 来提交了。所以,初步观察我们可以得出这么多信息。
好,那就来验证下吧,看看⽹站 JavaScript 代码⾥⾯是咋实现的。
接下来我们看看⽹站的源码,打开 Sources ⾯板,好家伙,看起来都是 Webpack 打包之后的内容,经过了⼀些混淆,类似结果如下:
这么多混淆代码,总不能⼀点点扒着看吧?这得到猴年马⽉?那么遇到这种情形,这怎么去 token 的⽣成位置呢?
解决⽅法其实有两种。
Ajax 断点
由于这个请求正好是⼀个 Ajax 请求,所以我们可以添加⼀个 XHR 断点监听,把 POST 的⽹址加到断点监听上⾯去,在 Sources ⾯板右侧添加这么⼀个 XHR 断点,如图所⽰:
这时候如果我们再次点击登录按钮的时候,正好发起⼀次 Ajax 请求,就进⼊到断点了,然后再看堆栈信息就可以⼀步步到编码的⼊⼝了。
点击 Submit 之后,页⾯就进⼊了 Debug 状态停下来了,结果如下:
⼀步步,我们最后其实可以到⼊⼝其实是在 onSubmit ⽅法这⾥。但实际上,我们观察到,这⾥的断点的栈顶还会包括了⼀些 async Promise 等⽆关的内容,⽽我们真正想的是⽤户名和密码经过处理,再进⾏ Base64 编码的地⽅,这些请求的调⽤实际上和我们寻的⼊⼝是没有很⼤的关系的。
另外,如果我们想的⼊⼝位置并不伴随这⼀次 Ajax 请求,这个⽅法就没法⽤了。
这个⽅法是奏效的,但是我们先不讲 onSubmit ⽅法⾥⾯究竟是什么逻辑,下⼀个⽅法再来讲。
Hook Function
所以,这⾥介绍第⼆种可以快速定位⼊⼝的⽅法,那就是使⽤ Tampermonkey ⾃定义 JavaScript 实现某个 JavaScript ⽅法的 Hook。Hook 哪⾥呢?最明显的,Hook Base64 编码的位置就好了。
那么这⾥就涉及到⼀个⼩知识点,JavaScript ⾥⾯的 Base64 编码是怎么实现的。没错就是 btoa ⽅法,所以说,我们来 Hook btoa ⽅法就好了。
好,这⾥我们新建⼀个 Tampermonkey 脚本,内容如下:
// ==UserScript==
// @name HookBase64
// @namespace scrape.cuiqingcai/
// @version 0.1
// @description Hook Base64 encode function
// @author Germey
// @match scrape.cuiqingcai/login1
// @grant none
// ==/UserScript==
(function () {
'use strict'
function hook(object, attr) {
var func = object[attr]
object[attr] = function () {
console.log('hooked', object, attr)
var ret = func.apply(object, arguments)
debugger
return ret
}
}
hook(window, 'btoa')
})()
⾸先我们定义了⼀些 UserScript Header,包括 @name、@match 等等,这⾥⽐较重要的就是 @name,表⽰脚本名称;另外⼀个就是
@match,代表脚本⽣效的⽹址。
脚本的内容如上所⽰。我们定义了⼀个 hook ⽅法,传⼊ object 和 attr 参数,意思就是 Hook object 对象的 attr 参数。例如我们如果想 Hook ⼀个 alert ⽅法,那就把 object 设置为 window,把 attr 设置为 alert 字符串。这⾥我们想要 Hook Base64 的编码⽅法,在 JavaScript
中,Based64 编码是⽤ btoa ⽅法实现的,那么这⾥我们就只需要 Hook window 对象的 btoa ⽅法就好了。
那么 Hook 是怎么实现的呢?我们来看下,⾸先⼀句var func = object[attr],相当于我们先把它赋值为⼀个变量,我们调⽤ func ⽅法就可以实现和原来相同的功能。接着,我们再直接改写这个⽅法的定义,直接改写object[attr],将其改写成⼀个新的⽅法,在新的⽅法中,通
过func.apply⽅法⼜重新调⽤了原来的⽅法。这样我们就可以保证,前后⽅法的执⾏效果是不受什么影响的,之前这个⽅法该⼲啥就还是⼲啥的。但是和之前不同的是,我们⾃定义⽅法之后,现在可以在func⽅法执⾏的前后,再加⼊⾃⼰的代码,如console.log将信息输出到控制台,如debugger进⼊断点等等。这个过程中,我们先临时保存下来了func⽅法,然后定义⼀个新的⽅法,接管程序控制权,在其中⾃定义我们想要的实现,同时在新的⽅法⾥⾯再重新调回func⽅法,保证前后结果是不受影响的。所以,我们达到了在不影响原有⽅法效果的前提下,可以实现在⽅法的前后实现⾃定义的功能,就是 Hook 的过程。
最后,我们调⽤ hook ⽅法,传⼊ window 对象和 btoa 字符串,保存。
接下来刷新下页⾯,这时候我们就可以看到这个脚本就在当前页⾯⽣效了,如图所⽰。
接下来,打开控制台,切换到 Sources ⾯板,这时候我们可以看到站点下⾯的资源多了⼀个叫做 Tampermonkey 的⽬录,展开之后,发现就是我们刚刚⾃定义的脚本。
然后输⼊⽤户名、密码,点击提交。发现成功进⼊了断点模式停下来了,代码就卡在了我们⾃定义的debugger这⼀⾏代码的位置,如下图所⽰。
成功 Hook 住了,这说明 JavaScript 代码在执⾏过程中调⽤到了 btoa ⽅法。
看下控制台,如下图所⽰。
这⾥也输出了 window 对象和 btoa ⽅法,验证正确。
这样,我们就顺利到了 Base64 编码操作这个路⼝,然后看看堆栈信息,也已经不会出现 async、Promise 这样的调⽤,很清晰地呈现了btoa ⽅法逐层调⽤的过程,⾮常清晰明了了,如图所⽰。
各个底层的 encode ⽅法略过,这样我们也⾮常顺利地到了 onSubmit ⽅法⾥⾯的处理逻辑:
onSubmit: function() {
var e = c.encode(JSON.stringify(this.form));
this.$http.post(a["a"]., {
token: e
}).then((function(e) {
console.log("data", e)
}))
}
仔细看看,encode ⽅法其实就是调⽤了⼀下 btoa ⽅法,就是⼀个 Base64 编码的过程。
另外堆栈信息中可以清晰地看到 Hook 的⽅法在执⾏前传⼊的参数值,即 arguments。另外执⾏的之后的结果 ret 也可以轻松地到了,如图所⽰:
所以,现在我们知道了 token 和⽤户名、密码是什么关系了吧。
这⾥⼀⽬了然了,就是对表单进⾏了 JSON 序列化,然后调⽤了 encode 也就是 btoa ⽅法,并赋值为了 token,⼊⼝顺利解开。后⾯,我们只需要模拟这个过程就 OK 了。
所以,我们通过 Tampermonkey ⾃定义 JavaScript 脚本的⽅式实现了某个⽅法调⽤的 Hook,使得我们快速能定位到加密⼊⼝的位置,⾮常⽅便。
以后如果观察出来了⼀些门道,可以多使⽤这种⽅法来尝试,如 Hook encode ⽅法、decode ⽅法、stringify ⽅法、log ⽅法、alert ⽅法等等,简单⽽⼜⾼效。
以上便是通过 Tampermonkey 实现简单 Hook 的基础操作,当然这个仅仅是⼀个常见的基础案例,不过从中我们也可以总结出⼀些 Hook 的基本门道。
后⾯我们会继续介绍更多相关内容。
如果想了解更多内容,也可以关注下「夜幕团队」所开的 JavaScript 逆向课系列,⽂章:
参考来源
注明
本篇属于 JavaScript 逆向系列内容。由于某些原因,JavaScript 逆向是在爬⾍中⽐较敏感的内容,因此⽂章中不会选取当前市⾯上任何⼀个商业⽹站作为案例,都是通过⾃建平台⽰例的⽅式来单独讲解某个知识点。另外⼤家不要将相关技术应⽤到⾮法⽤途,惜命惜命。
作者:华为云享专家
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论