分享会目标
对JS逆向有个系统性的认知,通过实战对JS逆向有个整体的感官。当然这里还有许多内容没有囊括,需要感兴趣的同学继续探索。
温馨提示:技术无罪,切勿犯罪。
JS逆向目标
模拟正常的请求。即绕过目标网站的反爬措施,欺骗目标网站为正常用户请求,获取到正常的数据返回。
从具体操作来说,就是绕过检测,找到加密位置,拿到加密过程,并在本地运行起来,正常请求得到返回数据。
JS基础之JS组成
JavaScript组成中最值得我们注意的是三个部分:DOM、BOM、ECMAScript。每个浏览器的实现都会包含这三个部分,而本地的开发环境只有JS引擎,会产生什么后果呢?
DOM
DOM(Document Object Model)就是文档对象模型,描述的是处理网页内容的方法和接口,如对某个dom树节点的增删,监听某个按钮点击事件等等。对应的是document对象。document根节点包含子节点:forms、embeds、anchors、images、links。
BOM
BOM(Browser Object Model)就是浏览器对象模型,描述的是浏览器行为控制的方法和接口,如网页前进后退等。核心表现是window对象。而window对象还有另一重身份,是一个全局对象,在网页中定义的任意对象都以window作为其global对象。
ECMAScript
JS核心模型,描述了JS的语法和基本对象,每个JS引擎都会实现。
常见加密方式
了解常见的加密方式及其特征有助于逆向时快速判断和破解。你能总结各加密方式多少个特征呢?
BASE64
base64不是加密方法,而是一个编码格式,但把AES等加密方法的结果再进行一次base64转换是很常见的方式,值得各位逆向好手注意。
MD系列
成员有md2、md4、md5等。严格意义上不能算是一种加密方式,而是一个取盐校验的方法,取出摘要信息进行校验,无法还原全部信息,作用不可逆。
SHA系列
成员有sha1、sha256、sha512。同MD系列。
对称加密
加密和解密共用一个密钥。成员有AES、DES、3DES。
非对称加密
有一对密钥,公钥和私钥。同一个明文可以通过公钥加密成多个密文,多个密文也可以通过私钥解密成同一个明文。公钥是公之于众的,私钥只掌握在发布者手中。常见是RSA。
反调试
反调试就是阻止调试的行为。作为网络攻防的防守端,网站服务器会在敏感的数据接口请求前做相应的检测,并做出相应的对抗行为。反调试手段分为显性和隐性。
检测点举例
- 键盘监听,如监听F12点击事件。
- 检测浏览器内外高度差值。
- 检测开发者人员工具变量是否为true,打开开发者工具值就true了。
- 利用console(),console调试器不打开时候是不会被调用的。
- 利用代码运行时间差,断点后两行代码之间运行时间有明显差异。
- 利用toString(),调试器在看值的时候,鼠标放在代码变量上会显示一些提示,此时会调用变量对象的toString()。调试器不打开时候一般不会调用。
- 检测栈的层数,利用caller属性,函数执行时候有个属性caller值就是调用该函数的对象。
- 检测非浏览器环境。本地运行时候环境与浏览器环境有出入。
显性反调试
- 关键文件动态改变:常见的有文件路径后面加时间戳,无法断点调试。
- 无限debugger,卡死你浏览器。debugger是在打开开发者工具时候才会运行。分为非虚拟机、虚拟机形式。
- 死循环:无限递归、两个方法无限调用、计时器、打开新页面、写你的历史记录卡死、url操作卡死。
- 混淆代码:把代码变得无可读性。常见混淆方式有aa混淆,jj混淆,JSFuck。
隐性(暗桩)
- 引向错误逻辑,就是你触发了他反调试的判断后调用非正常逻辑会调用的函数链条。
JS逆向之加密定位
作为网络攻防中的攻击方,第一步是找到目标网站的加密位置。
正常网页加载运行流程
加载html -> 加载js -> 运行js初始化 -> 用户触发某个事件,调用了某段js -> 一系列js运行,对明文操作 -> 加密函数加密 -> 一系列js运行,组装请求,给服务器发送请求(XHR send) -> 接收服务器数据 -> 解密函数解密 -> 刷新网页渲染
search
全局搜索。定位位置比较贴近加密函数,但搜索到结果比较多要筛选,若变量名混淆了则没办法搜索到了。容易定位到拼装封包的步骤。
xhr
定位的位置在发包函数,可以方便的跟调用栈。只能用于xhr的包。
Initiator
通过调试器的Initiator,也就是堆栈跟踪,定位加密位置。
dom事件
定位的位置比较靠前,容易定位到用户输入明文的位置。没办法太好的使用调用栈。
hook
hook就是替换原方法,加入自己的处理逻辑。要注意代码注入的时机问题。一般可以有两种方式:
- 覆盖原函数。
- 通过Object.defineProperty()替换一个对象的属性,重写属性的get、set方法。
(function() {
'use strict';
var cookieTemp = "";
Object.defineProperty(document, 'cookie', {
set: function(val) {
console.log('Hook捕获到cookie设置->', val);
cookieTemp = val;
return val;
},
get: function()
{
return cookieTemp;
}
});
})();
JS逆向之加密破解
找到目标网站的加密位置后,第二步就是要确定破解的方式了。一般有两种处理方式,手动构造或扣代码。
手动构造加密函数
顾名思义,就是自己写代码。一些简单的网站用的是常规的加密方法如base64,可以直接自己写代码替代。
扣代码
目的就是把加密过程中会使用到的JS代码扒下来,在本地成功运行。最值得注意的两件事:是否扣全、性能如何?
导出方法
多数目标方法都会使用到上下文中定义加载好的变量和函数等,直接扣取方法出来是无法运行的。更稳妥的方式是在整个相关的结构加载好后,再进行方法导出来使用。
- 这里分为两种情况:一是目标函数在一个大函数里,二是目标函数在一个对象里。而整体处理的思路都是等上下文执行完后用全局变量把相应对象导出。
- 使用全局变量形式把目标函数导出使用。
- 上下文代码需要保证先执行,加载完全。两种方式:先执行A(),或者把A()改成自执行函数:
// 假设存在目标函数B
function A(){
var B = function(){
console.log("这是一个加密函数")
}
}
// 方式一:使用全局变量导出目标函数
// 此时C()执行失败,但先执行A()后执行C()可运行
var C;
function A(){
var B = function(){
console.log("这是一个加密函数");
}
C = B;
}
// 方式二:改写成自执行函数
var C;
!(function A(){
var B = function(){
console.log("这是一个加密函数");
}
C = B;
})()
- 而目标函数在对象里的处理方式差不多,可以选择用全局函数把目标函数导出,或者用全局函数把目标对象暴露出来。
目标网站一
[学生空间](https://sk.open.com.cn/stuspace-auth/#/login)
目标参数
登录密码:password
定位
你能想到的最快找到加密方式的方法是什么?
目标网站二
[G妹游戏](https://www.gm99.com/)
目标参数
登录密码:password
定位
- 搜索方式:”password:”,还想到其他关键词吗?
- dom事件方式
- Initiator方式
目标网站三
目标参数
登录密码:password
扣代码
要如何把目标服务器的代码本地运行起来呢?
课后练习
土巴兔: 请用尽可能多的方式定位password,并实现在本地加密。