Introduction

网站为了防止客户随便对服务器发起请求,就会对在请求携带加密的校验参数,服务器要去验证这个参数后才会返回响应。JS逆向就是通过阅读JS代码,查看这个加密参数如何生成。然后我们在本地模拟还原JS生成相关参数,向服务器发送请求。所以JS逆向的根本解决点在于JavaScript代码。

  1. 找到目标数据的接口
  2. 找到加密位置
  3. 本地实现加密

网站代码运行时间轴

加载html,加载js,用户触发事件,调用js,明文数据-加密函数-加密数据,发送XHR给服务器,接受服务器响应,解密函数,渲染

定位解密函数方法

全局搜索

image-20250723071449206

跟栈

页面加载html, 加载JS代码 –> JS初始化(自执行函数) –> 用户触发事件 –> 调用JS –> 加密 –> 发包 –> 服务器

–> 接受响应 –> 解密 –> 刷新网页进行渲染

发包函数:从Initator中找到send, 或者在xhr断点中写入url路径

  1. 从发包往前跟: 由于发包是在加密之后, 可以在发包函数查看栈, 从而找到加密函数
  2. 从发包往后跟: 找到解密函数
  3. 从用户触发事件往后跟, 找到加密函数
  4. 从网页渲染往后跟: 找到解密函数

image-20250723235526468

异步跟栈

由于某些加密函数在异步中(JS用Promise.then实现),所以要进到异步函数中查看

Hook注入

监控某些对象的属性是否发送改变

  • Object.defineProperty(object, ‘attribute’, description)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     var teacher = {
    name: 'ferry'
    }

    var tmp_teacher = ''

    // monitor the attribute name of object teacher
    Object.defineProperty(teacher, 'name', {
    get: function(){
    console.log("The attribute name was read.")
    },
    set: function(val){
    console.log("The attribute name was reset.")
    tmp_teacher = val
    }
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    (function () {
    `use strict`;
    var cookieTemp = '';
    Object.defineProperty(document, 'cookie', {
    set: function(val){
    if(val.indexOf('__dfp') != -1){
    debugger;
    }
    console.log("Hook捕获到cookie设置", val);
    cookieTemp = val;
    return val;
    },
    get: function(){
    return cookieTemp;
    },
    });
    })();

加密函数

  • bs64

    我们可以通过btoa函数对字符串进行加密,atob进行解密

  • md5

    123456

    1
    2
    3
    4
    5
    6
    7
    8
    32位[大]
    E10ADC3949BA59ABBE56E057F20F883E
    32位[小]
    e10adc3949ba59abbe56e057f20f883e
    16位[大]
    49BA59ABBE56E057
    16位[小]
    49ba59abbe56e057
  • 对称加密

    AES, DES, 3DES

  • 非对称加密

    RSA (私钥,公钥)

  • HEX(16进制):0-9, A-F

    bs64: A-Z, a-z, 0-9, +_=

代码混淆

混淆常量的值和名称,代码的执行流程

1
2
var ferry = "1"//全局变量
console.log(window["ferry"])

当出现字符串的时候,就可以将变量名进行混淆

1
2
3
4
var xxx = "ferry"
var ferry = "1"//全局变量

console.log(window[xxx]) //1
1
2
3
4
5
var xxx = ["ferry", "faye"]
var ferry = "1"//全局变量
var faye = "2"

console.log(window[xxx[1]]) //2

混淆和加密结合后,还能隐藏变量名,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
btoa("ferry")
'ZmVycnk='
btoa("ferry")
'ZmF5ZQ=='

var xxx = ['ZmVycnk=', 'ZmF5ZQ==']
function xx(i){
return atob(xxx[i])//用的时候不直接写变量名,通过调用该函数解密
}

var ferry = "1"//全局变量
var faye = "2"

console.log(window[xx(1)]) //2

eval混淆

eval是一个函数,它可以执行字符串格式的JS代码

所以,我们现在可以对整段JS代码进行加密了,然后在执行的时候再解密。

1
2
3
4
5
btoa("function f(){return '12'} f()")
'ZnVuY3Rpb24gZigpe3JldHVybiAnMTInfSBmKCk='

str="ZnVuY3Rpb24gZigpe3JldHVybiAnMTInfSBmKCk="
eval(atob(str)) //12

但是,只要我们找到eval这个方法,在这一行下断点,查看它的参数就一定是一个明文。

反调试

检测您是否在调试我:键盘监听,浏览器内外高度差,开发者工具是否为true,利用两句代码的时间差,hook查看函数、变量值时调用的toString方法,检测栈的层数

反调试方法:无限debugger, 死循环,引向错误逻辑

解决方法:

非虚拟机debugger:右键设置false,浏览器修改js代码,油猴编写js,代理抓包替换(Fiddler)

虚拟机debugger(eval, Function进入VM):hook替换

引向错误逻辑:看浏览器正常堆栈