JS逆向高阶补充

news2024/12/31 7:29:11

文章目录

  • JS逆向高阶补充
    • eval函数
    • Hook函数
        • 案例1: Hook eval
        • 案例2: Hook JSON.stringify
        • 案例3: Hook JSON.parse
        • 案例4: Hook Cookie
    • Promise对象
        • (1)回调函数
        • (2)基本语法
        • (3)then函数
        • (4)链式应用
        • (5)Promise与Ajax请求
    • webpack
    • 字体加密
      • 什么是字体加密
    • 补环境
      • 【1】补环境介绍
      • 【2】Proxy代理
      • 初始补环境代码
    • 瑞数
      • 瑞数之无限debugger
      • 瑞数流程
      • 瑞数5案例解析
        • 入口定位
        • 补环境

JS逆向高阶补充

eval函数

eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行。

如果参数是一个表达式,eval() 函数将执行表达式。如果参数是Javascript语句,eval()将执行 Javascript 语句。

eval(string)
// 
eval('[1,2,3,4,5].map(x=>x*x)')

http://tools.jb51.net/password/evalencode

Hook函数

在 JS 逆向中,我们通常把替换原函数的过程都称为 Hook。

function foo() {
    console.log("foo功能...")
    return 123
}
foo()

var _foo = foo

foo = function () {
    console.log("截断开始...")
    debugger;
    _foo()
    console.log("截断结束...")
}
function foo(a, b, c, d, e, f) {
    console.log("foo功能...")
    console.log(a, b, c, d, e, f)
    return 123
}

var _foo = foo

foo = function () {
    console.log("截断开始...")
    // _foo(arguments)
    _foo.apply(this,arguments)
    console.log("截断结束...")
}

foo(1,2,3,4,5,6,7)
案例1: Hook eval
console.log("程序开始")
// eval("console.log('yuan')")
window["e"+"v"+"a"+"l"]("console.log('eval yuan')")
console.log("程序结束")
var _eval = eval

eval = function (src) {
    console.log("eval截断开始...")
    debugger;
    _eval.apply(this, src)
    console.log("eval截断结束...")
}
案例2: Hook JSON.stringify

JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串,在某些站点的加密过程中可能会遇到,以下代码演示了遇到 JSON.stringify() 时,则插入断点:

(function() {
    var stringify = JSON.stringify;
    JSON.stringify = function(params) {
        console.log("Hook JSON.stringif:::", params);
        debugger;
        return stringify(params);
    }
})();
案例3: Hook JSON.parse

JSON.parse() 方法用于将一个 JSON 字符串转换为对象,在某些站点的加密过程中可能会遇到,以下代码演示了遇到 JSON.parse() 时,则插入断点:

(function() {
    var parse = JSON.parse;
    JSON.parse = function(params) {
        console.log("Hook JSON.parse::: ", params);
        debugger;
        return parse(params);
    }
})();
案例4: Hook Cookie

一般使用Object.defineProperty()来进行属性操作的hook。那么我们了解一下该方法的使用。

Object.defineProperty(obj, prop, descriptor)

// 参数
obj:对象;
prop:对象的属性名;
descriptor:属性描述符;

一般hook使用的是get和set方法,下边简单演示一下

var people = {
    name: '张三',
};
var count
Object.defineProperty(people, 'age', {
    get: function () {
        console.log('获取值!');
        return count;
    },
    set: function (val) {
        console.log('设置值!',val);
        count = val;
    },
});
console.log(people.age);
people.age = 18;
console.log(people.age);

通过这样的方法,我们就可以在设置某个值的时候,添加一些代码,比如 debugger;,让其断下,然后利用调用栈进行调试,找到参数加密、或者参数生成的地方,需要注意的是,网站加载时首先要运行我们的 Hook 代码,再运行网站自己的代码,才能够成功断下,这个过程我们可以称之为 Hook 代码的注入。

(function(){
   'use strict'
    var _cookie = "";
    Object.defineProperty(document, 'cookie', {
        set: function(val) {
            console.log(val);
            debugger
            _cookie = val;
            return val;
        },
        get: function() {
            return _cookie;
        },
});
})()

Promise对象

  • Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
  • Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。
  • Promise是一个构造函数,通过new来实例化,主要解决异步编程。
  • 一个Promise对象有三种状态:pending(等待中)、fulfilled(已成功)或rejected(已失败)。当Promise对象处于pending状态时,它表示尚未完成,但可能会在未来某个时间完成。
(1)回调函数

了解promise应该先懂回调,简单理解回调函数能够实现异步编程(可以控制函数调用顺序)。紧接着你应该知道回调地狱,或者函数瀑布,就类似如下的代码:

setTimeout(function () {
    console.log("apple");
    setTimeout(function () {
        console.log("banana");
        setTimeout(function () {
            console.log("cherry");
        }, 1000);
    }, 2000);
}, 3000);
console.log("下一个操作")

Promise 的出现就能够将上面嵌套格式的代码变成了较为有顺序的从上到下风格的代码。

(2)基本语法
 new Promise(function(resolve, reject) { });
  • Promise 构造函数只有一个参数,这个参数是一个函数,这个函数在构造之后会直接被异步运行,所以我们称之为起始函数。起始函数包含两个参数 resolve 和 reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。异步任务执行成功时调用resolve函数返回结果,反之调用reject
  • Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。

案例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        function sayHi() {
            var promise = new Promise(function (resolve, reject) {
                // 模拟Ajax请求
                // var data = "hello world"
                // console.log("请求完成,响应数据为:", data)
                // resolve(data)
                setTimeout(function () {
                    var data = "hello world"
                    resolve(data)
                }, Math.random() * 5000)
            })
            return promise
        }

        //  出发promise对象
        s = sayHi()
        s.then(item => console.log(item + "!!!")) // 异步操作,不影响下面继续执行
        // 控制台打印s
       /* async function test() {
            try {
                const result = await sayHi();
                console.log("result:::", result)
            } catch (error) {
                // 当 Promise 状态变为 rejected 时执行的逻辑
                // 可以访问到错误信息 error
            }
        }

        test();*/

        console.log("下一个操作!")
    </script>
</head>
<body>

</body>
</html>
(3)then函数

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数**。**
Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

.then(function (){}, function (){}); 

如果初始函数里面没有指定resolve或者reject函数,那么 .then 函数是不会被调用的,因为之后状态变成了resolved或者rejected才会调用对应的回调函数。

const promise = new Promise((resolve, reject) => {
    console.log("apple");
    //resolve()
});
promise.then(() => {
    console.log("banana");
});
console.log("cherry");
// 输出 "apple"  "cherry"
// 这里没有输出"banana",因为promise实例的状态没有改变,所以没有调用对应状态的回调函数

放开resolve()函数再试一下。结果先cherry后banana是因为 .then方法是微任务,宏任务执行完毕后开始才开始执行微任务。

把上面的resolve函数换成reject()函数:

 const promise = new Promise((resolve, reject) => {
      console.log("apple");
      reject() 
  });
  promise.then(() => {
      console.log("banana");
  });
 console.log("cherry");

因为.then方法并没有定义rejected状态相对应的回调函数。因为.then方法的第2个参数才是rejected状态的回调函数。所以改为

const promise = new Promise((resolve, reject) => {
    console.log("apple");
    reject()
});
promise.then(() => {
    console.log("banana success");
},() => {
    console.log("banana reject");
});
console.log("cherry");

如果我们此时再在reject后面加上resolve输出结果和上面一样。这是因为状态只会改变一次,之后不会更改的。如果把最开始的代码resolve放中间会怎样?

const promise = new Promise((resolve, reject) => {
    console.log("apple");
    resolve("apple")  // resolve仅仅设置了状态
    console.log("APPLE");
});
promise.then((res) => {
    console.log(res+" banana");
}, () => {
    console.log("banana");
});
console.log("cherry");

promise执行流程:

1. 构造函数中的输出执行是同步的,输出 apple,执行 resolve 函数,将 Promise 对象状态置为resolved,输出APPLE。
2. 注册这个Promise对象的回调then函数。
3. 宏任务继续,打印cherry,整个脚本执行完,stack 清空。
4. eventloop 检查到 stack为空,再检查 microtask队列中是否有任务,发现了 Promise 对象的 then 回调函数产生的 microtask,推入stack,执行。输出apple banana,eventloop的列队为空,stack为空,脚本执行完毕。
(4)链式应用

使用Promise可以更好地处理异步操作,例如网络请求,文件读取等。它避免了回调地狱(callback hell)的问题,使得代码更加容易理解和维护。

案例1:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <script>


        function A() {
            var p = new Promise(function (resolve, reject) {

                setTimeout(function () {
                    console.log("A请求")
                    var res = "A-data"
                    resolve(res)
                }, 1000)
            })
            return p
        }


        function B() {
            return new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log("B请求")
                    var res = "B-data"
                    resolve(res)
                }, 2000)
            })
        }

        function C() {
            return new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log("C请求")
                    var res = "C-data"
                    resolve(res)
                }, 3000)
            })
        }

        function D() {
            return new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log("D请求")
                    var res = "D-data"
                    resolve(res)
                }, 3000)
            })
        }

        function E() {
            return new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log("E请求")
                    var res = "E-data"
                    resolve(res)
                }, 3000)
            })
        }


        /*var p1 = A()

        var p2 = p1.then(function (res) {
            console.log("A获取结果:", res)
            return B()
        })

        var p3 = p2.then(function (res) {
            console.log("B获取结果:", res)
            return C()
        })

        p3.then(function (res) {
            console.log("C获取结果:", res)

        })*/

        A().then(function (res) {
            console.log("A获取结果:", res)
            return B()
        }).then(function (res) {
            console.log("B获取结果:", res)
            return C()
        }).then(function (res) {
            console.log("C获取结果:", res)
            return D()
        }).then(function (res) {
            console.log("D获取结果:", res)
            return E()
        }).then(function (res) {
            console.log("E获取结果:", res)
        })

    </script>
</head>
<body>


</body>
</html>

案例2:

 // 第一层:获取用户信息
function getUserInfo(userId) {
    return new Promise((resolve, reject) => {
        // 模拟异步操作,获取用户信息
        setTimeout(() => {
            const userInfo = {
                id: userId,
                name: "John Doe",
                email: "johndoe@example.com"
            };
            resolve(userInfo);
        }, 1000);
    });
}

// 第二层:获取用户订单列表
function getUserOrders(userId) {
    return new Promise((resolve, reject) => {
        // 模拟异步操作,获取用户订单列表
        setTimeout(() => {
            const orders = [
                {id: 1, product: "Product A"},
                {id: 2, product: "Product B"},
                {id: 3, product: "Product C"}
            ];
            resolve(orders);
        }, 2000);
    });
}

// 第三层:获取订单详情
function getOrderDetails(orderId) {
    return new Promise((resolve, reject) => {
        // 模拟异步操作,获取订单详情
        setTimeout(() => {
            const orderDetails = {
                id: orderId,
                status: "Delivered",
                address: "123 Main St"
            };
            resolve(orderDetails);
        }, 3000);
    });
}

// 应用示例
const userId = 123;

getUserInfo(userId)
    .then(userInfo => {
        console.log("User Info:", userInfo);
        return getUserOrders(userInfo.id);
    })
    .then(orders => {
        console.log("User Orders:", orders);
        const orderId = orders[0].id;
        return getOrderDetails(orderId);
    })
    .then(orderDetails => {
        console.log("Order Details:", orderDetails);
    })
    .catch(error => {
        console.error("Error:", error);
    });

console.log("后续操作!!!")

(5)Promise与Ajax请求

原生Ajax:

 function fetchData(url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.onload = function () {
            if (xhr.status === 200) {
                resolve(xhr.responseText);
            } else {
                reject(Error(xhr.statusText));
            }
        };
        xhr.onerror = function () {
            reject(Error('Network Error'));
        };
        xhr.send();
    });
}

// 调用fetchData()函数并处理Promise对象的结果
fetchData('https://v0.yiketianqi.com/api?unescape=1&version=v9&appid=47284135&appsecret=jlmX3A6s').then(function (response) {
    // 如果Promise对象成功解决,执行这里的代码
    var data = JSON.parse(response)
    console.log("data:::", data)

}).catch(function (error) {
    // 如果Promise对象被拒绝,执行这里的代码
    console.log('Error loading data:', error);
});

jQuery版本:

function fetchData(url) {
  return new Promise(function (resolve, reject) {
      $.ajax({
          url: url,
          success: function (res) {
              console.log("res:", res)
              resolve(res)
          }
      })
  });
}

// 调用fetchData()函数并处理Promise对象的结果
url="https://v0.yiketianqi.com/api?unescape=1&version=v9&appid=47284135&appsecret=jlmX3A6s"
fetchData(url).then(function (response) {
    // 如果Promise对象成功解决,执行这里的代码
    const container = $('#container');
    const html = response.data.map(item => `<div><span>${item.day}</span><span>${item.wea}</span></div>`).join('');
    container.append(html);
})

webpack

Webpack是一个现代的静态模块打包工具,它主要用于前端开发中的模块化打包和构建。通过Webpack,开发者可以将多个模块(包括JavaScript、CSS、图片等)进行打包,生成优化后的静态资源文件,以供在浏览器中加载和运行。

Webpack的主要功能和特点包括:

  1. 模块化支持:Webpack将应用程序拆分为多个模块,通过模块化的方式管理和加载依赖关系。它支持CommonJS、ES module、AMD等多种模块化规范,并且能够将这些模块打包成最终的静态资源文件。
  2. 打包和压缩:Webpack可以将多个模块打包成一个或多个最终的静态资源文件。它支持对JavaScript、CSS、图片等资源进行压缩、合并和优化,以减小文件大小,提升加载速度和性能。
  3. 资源加载管理:Webpack可以处理各种类型的资源文件,例如JavaScript、CSS、图片、字体等。通过加载器(Loader)的配置,Webpack可以对这些资源文件进行转换和处理,使其能够被应用程序正确地引用和加载。
/*
!function(形参){加载器}([模块1,模块2,...])
!function(形参){加载器}({"k1":"模块1","k2":"模块2"}) 
*/
window = global;
!function (e) {
    var t = {};

    function n(r) {
        if (t[r])
            return t[r].exports;
        var o = t[r] = {
            i: r,
            l: !1,
            exports: {}
        };
        e[r].call(o.exports, o, o.exports, n);
        return o.exports.exports; 
    }

    window.loader = n;

    // n("1002");
}([ function () {
        console.log("foo");
        this.exports = 100; 
    },
    function () {
        console.log("bar");
        this.exports = 200;
    }]
);

console.log(window.loader(0));
console.log(window.loader(1));
window = global;
!function (e) {
    var t = {};

    function n(r) {
        if (t[r])
            return t[r].exports;
        var o = t[r] = {
            i: r,
            l: !1,
            exports: {}
        };
        e[r].call(o.exports, o, o.exports, n);
        return o.exports.exports; // 返回 o.exports.exports,而不是整个 o.exports 对象
    }
    
    window.loader = n;

    // n("1002");
}({
    "1001": function () {
        console.log("foo");
        this.exports = 100; // 直接修改 exports 变量
    },
    "1002": function () {
        console.log("bar");
        this.exports = 200;
    }
});

console.log(window.loader("1001"));

有时候copy过来的webpack里面的代码,可以判断下是否是加/解密所需,不需要,并且处理起来麻烦的话可以选择删掉

字体加密

什么是字体加密

字体加密是页面和前端字体文件想配合完成的一个反爬策略。通过css对其中一些重要数据进行加密,使我们在代码获取的和在页面上看到的数据是不同的。

前端人员通过使用font-face来达到这个目的,font-face是CSS3中的一个模块,他主要是把自己定义的Web字体嵌入到你的网页中。而font-face的格式为:

@font-face {
	font-family: <FontName>;      # 定义字体的名称。             
	src: <source> [<format>][, []]*;  # 定义该字体下载的网址,包括ttf,eof,woff格式等
}

我们要打开ttf,eof,woff这些格式的字体文件有两种方式:

  • https://font.qqe2.com/#
  • https://www.bejson.com/ui/font/

字体解密可以基于fontTools 库和dddocr 库来识别出加密值和实际值的对应关系

from fontTools.ttLib import TTFont
import ddddocr
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont


def convert_cmap_to_image(cmap_code, font_path_or_data):
    """
    :param cmap_code:
    :param font_path_or_data: 文件路径/字节流
    :return:
    """
    img_size = 1024
    img = Image.new("1", (img_size, img_size), 255)  # 创建一个黑白图像对象
    draw = ImageDraw.Draw(img)  # 创建绘图对象
    # 根据输入类型选择加载方式
    if isinstance(font_path_or_data, str):  # 文件路径
        font = ImageFont.truetype(font_path_or_data, int(img_size * 0.6)) # 调整大小
    elif isinstance(font_path_or_data, bytes):  # 二进制数据
        font = ImageFont.truetype(BytesIO(font_path_or_data), img_size)   # 调整大小
    else:
        raise ValueError("Unsupported input type. Expected str or bytes.")
    character = chr(cmap_code)  # 将 cmap code 转换为字符
    bbox = draw.textbbox((0, 0), character, font=font)  # 获取文本在图像中的边界框
    width = bbox[2] - bbox[0]  # 文本的宽度
    height = bbox[3] - bbox[1]  # 文本的高度
    draw.text(((img_size - width) // 2, (img_size - height) // 7), character, font=font, fill=0)  # 绘制文本,并居中显示 需要根据文件调整长宽和大小
    return img


def extract_text_from_font(font_input):
    """

    :param font_input: 文件地址/字节流对象
    :return:
    """
    if isinstance(font_input, str):  # 处理文件路径的情况
        font = TTFont(font_input)
    elif isinstance(font_input, bytes):  # 处理二进制数据流的情况
        font = TTFont(BytesIO(font_input))
    else:
        raise ValueError("Unsupported input type. Expected str or bytes.")
    # font.saveXML("xxx.xml")
    ocr = ddddocr.DdddOcr(beta=True, show_ad=False)  # 实例化 ddddocr 对象
    font_map = {}
    for cmap_code, glyph_name in font.getBestCmap().items():
        bytes_io = BytesIO()
        image = convert_cmap_to_image(cmap_code, font_input)  # 将字体字符转换为图像
        image.save(bytes_io, "PNG")

        text = ocr.classification(bytes_io.getvalue())  # 图像识别
        # image.save(f"./img/{text}.png", "PNG")  # 保存图像
        print(f"Unicode码点:{cmap_code} - Unicode字符:{glyph_name},识别结果:{text}")
        font_map[cmap_code] = text

    return font_map

补环境

【1】补环境介绍

浏览器环境: 是指 JS代码在浏览器中的运行时环境,它包括V8自动构建的对象(即ECMAScript的内容,如Date、Array),浏览器(内置)传递给V8的操作DOM和BOM的对象(如document、navigator);

Node环境:是基于V8引擎的Js运行时环境,它包括V8与其自己的内置API,如fs,http,path;

Node环境浏览器环境 的异同点可以简单概括如图:

在这里插入图片描述

所以我们所说的 “补浏览器环境” 其实是补浏览器有 而Node没有的环境,即 补BOM和DOM的对象;

当我们每次把辛辛苦苦扣出来的 “js加密算法代码”,并且放在浏览器环境中能正确执行后,就需要将它放到Node环境 中去执行,而由于Node环境浏览器环境之间存在差异,会导致部分JS代码在浏览器中运行的结果 与在node中运行得到的结果不一样,从而影响我们最终逆向成果;

附注:window对象结构图

【2】Proxy代理

JavaScript中的Proxy是一种内置对象,它允许你在访问或操作对象之前拦截和自定义底层操作的行为。通过使用Proxy,你可以修改对象的默认行为,添加额外的逻辑或进行验证,以实现更高级的操作和控制。

Proxy对象包装了另一个对象(目标对象),并允许你定义一个处理程序(handler)来拦截对目标对象的操作。处理程序是一个带有特定方法的对象,这些方法被称为"捕获器"(traps),它们会在执行相应的操作时被调用。

var window = {
    username: "tt",
    age: 22
}
window = new Proxy(window, {
    get(target, p, receiver) {
        console.log("target: ", target);
        console.log("p: ", p);
        // return window['username'];/// 这里如果这样写. 有递归风险的...
        // return Reflect.get(...arguments);
        return Reflect.get(target, p);
    },
    set(target, p, value, receiver) {
        console.log("设置操作")
        Reflect.set(target, p, value);
    }
}); 

console.log(window.username);
console.log(window.age);
window.username = "json"
window.age = 18

function browser_proxy(obj) {
    return new Proxy(obj, {
        get: function (target, property1) {
            console.log('获取对象-->', obj, '属性-->', property1, '值-->', target[property1])
            debugger
            return Reflect.get(target, property1)
        },
        set: function (target, property1, value) {
            console.log('设置对象-->', obj, '属性-->', property1, '值-->', target[property1])
            debugger
            Reflect.set(target, property1, value)
        }
    })
}

基于Proxy的特性,衍生了两种补环境思路:

  1. Proxy代理浏览器所有的BOM、DOM对象及其属性,再配合node vm2模块提供的纯净V8环境,就相当于在node中,对整个浏览器环境对象进行了代理,JS代码使用任何浏览器环境 api都能被我们所拦截。然后我们针对拦截到的环境检测点去补。
  2. 搭建补环境框架,用JS模拟浏览器基于原型链去伪造实现各个BOM、DOM对象,然后将这些JS组织起来,形成一个纯JS版浏览器环境,补环境越完善,越接近真实浏览器环境,能通杀的js环境检测就越多。

基于上面的语法,我们构建一套完整的代理补环境的功能函数

function setProxyArr(proxyObjArr) {
    for (let i = 0; i < proxyObjArr.length; i++) {
        const handler = `{
      get: function(target, property, receiver) {
        console.log("方法:", "get  ", "对象:", "${proxyObjArr[i]}", "  属性:", property, "  属性类型:", typeof property, ", 属性值:", target[property], ", 属性值类型:", typeof target[property]);
        return target[property];
      },
      set: function(target, property, value, receiver) {
        console.log("方法:", "set  ", "对象:", "${proxyObjArr[i]}", "  属性:", property, "  属性类型:", typeof property, ", 属性值:", value, ", 属性值类型:", typeof target[property]);
        return Reflect.set(...arguments);
      }
    }`;
        eval(`try {
            ${proxyObjArr[i]};
            ${proxyObjArr[i]} = new Proxy(${proxyObjArr[i]}, ${handler});
        } catch (e) {
            ${proxyObjArr[i]} = {};
            ${proxyObjArr[i]} = new Proxy(${proxyObjArr[i]}, ${handler});
        }`);
    }
}

初始补环境代码

delete __dirname
delete __filename

top = self = window = global

delete global

document = {}
location = {}
navigator = {}
history = {}
screen = {}

function getEnv(proxy_array) {
    for (var i = 0; i < proxy_array.length; i++) {
        handler = `{\n
            get: function(target, property, receiver) {\n
                   console.log('方法:get','    对象:${proxy_array[i]}','    属性:',property,'    属性类型:',typeof property,'    属性值类型:',typeof target[property]);
                   return target[property];
            },
            set: function(target, property, value, receiver){\n
                    console.log('方法:set','    对象:${proxy_array[i]}','    属性:',property,'    属性类型:',typeof property,'    属性值类型:',typeof target[property]);
                    return Reflect.set(...arguments);
            }
        }`
        eval(`
            try {
                ${proxy_array[i]};
                ${proxy_array[i]} = new Proxy(${proxy_array[i]}, ${handler});
            } catch (e) {
                ${proxy_array[i]} = {};
                ${proxy_array[i]} = new Proxy(${proxy_array[i]}, ${handler});
            }
        `)
    }
}

proxy_array = ['window', 'document', 'location', 'navigator', 'history', 'screen']
getEnv(proxy_array)

瑞数

瑞数信息是一家专注于提供互联网动态业务应用安全防护解决方案的公司**

瑞数动态安全 Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态令牌等技术对服务器网页底层代码持续动态变换,增加服务器行为的“不可预测性”,实现了从用户端到服务器端的全方位“主动防护”,为各类 Web、HTML5 提供强大的安全保护。

瑞数可以理解为是我们进入到JS逆向世界的标志

过瑞数的方法基本上有以下几种

  • 自动化工具
  • 补环境
  • 纯算

瑞数之无限debugger

// (1)
let _Function = Function;

Function = function (s) {
    if (s === "debugger") {
        console.log(s)
        return null

    }
    return _Function(s)
}

// (2)
let _constructor = constructor

Function.prototype.constructor = function (s) {
    if (s === "debugger") {
        console.log(s)
        return null
    }
    return _constructor(s)
}


// (3)
Function.prototype._constructor = Function.prototype.constructor;
Function.prototype.constructor = function () {
    if (arguments && typeof arguments[0] === 'string') {
        if ("debugger" === arguments[0]) {
            return
        }
    }
    return Function.prototype._constructor.apply(this, arguments)
};

瑞数流程

  1. rs的网站会请求两次page_url(文档请求),第1次page_url(文档请求)会返回一个cookie1,和一个响应体(HTML源码),以及请求响应码是 202(瑞数3、4代)或者 412(瑞数5代、6代)

在这里插入图片描述

  1. 响应体(HTML源码)包括3个部分:
  • 一个meta标签,其content内容很长且是动态的(每次请求会变化),会在eval执行第二层JS代码时使用到;
  • 一个外链js文件,一般同一页面中其内容是固定的,下面的自执行函数会解密文件内容生成eval执行时需要的JS源码,也就是第二层vm代码;
  • 一个大自执行函数(每次请求首页都会动态变化),主要是解密外链JS内容,给window添加一些属性如$_ts,会在vm中使用;
  • 这三个要素用于在本地生成一个cookie2,用于第2次请求
  1. 第2次page_url(文档请求)携带cookie1和ncookie2,获取真正的页面内容!
    在这里插入图片描述

瑞数 3、4 代有以 T 和 S 结尾的两个 Cookie,其中以 S 开头的 Cookie 是第一次的 201 那个请求返回的,以 T 开头的 Cookie 是由 JS 生成的,动态变化的,T 和 S 前面一般会跟 80 或 443 的数字,Cookie 值第一个数字为瑞数的版本。

瑞数 5 代也有以 T 和 S 结尾的两个 Cookie,但有些特殊的 5 代瑞数也有以 O 和 P 结尾的,同样的,以 O 开头的是第一次的 412 那个请求返回的,以 P 开头的是由 JS 生成的,Cookie 值第一个数字同样为瑞数的版本。

瑞数5案例解析

入口定位

先定位入口,这里涉及一个VM环境

  • “VM”表示的是Virtual Machine(虚拟机),这些文件通常表示由浏览器生成和执行的虚拟机脚本环境中的临时脚本。这些脚本并不是项目源代码的一部分,也不是实际存在的物理文件。 它们在浏览器的内存中创建并执行

  • 比如说,当你在调试一个网页时,如果在某些动态生成并执行的JS代码上设定了断点,Chrome调试器会在一个以"VM"开头的文件中显示这些代码,例如"VM111.js"。这个"VM"文件的存在只是为了调试目的,它并不存在于服务器端,也不会被存储在本地,而是存在于浏览器内存中。一般情况下,这类文件的出现是因为浏览器对JavaScript代码的处理方式,如动态编译或者JavaScript堆栈跟踪。

  • 通过eval函数或者new Function方法,Chrome浏览器会创建一个"VM"文件来展示这段临时执行的代码

通过脚本断点可以断住ts和自执行函数,因为第一次响应的index.html是动态的,所以为了后面调试,我们先试用文件替换本地化。

接下来在index.html中直接使用正则快速定位入口函数:

# 1. 搜索call
# 2. 正则搜索
\S{4} = \S{4}\[\S{4}\[\d{2}\]\]\(\S{4},

在这里插入图片描述

代码混淆后,找对应入口会比较麻烦,如果上述两个方式都不凑效,那么就只能通过Hook 来找了

这个位置通常在分析瑞数的时候作为入口,图中 _$BH 实际上是 eval 方法,传入的第一个参数 _KaTeX parse error: Expected group after '_' at position 30: …w 对象==,第二个对象 ==_̲wt 是我们前面看到的 VM 虚拟机中的 IIFE 自执行代码。

debug 到这块后, 在入口位置单步调试,即可进入到VM环境!

在这里插入图片描述

后面的断点调试就在这个环境中。

补环境

在这里插入图片描述

注意content的值和auto.js是关联的,必须保持一致!

然后就是一步步补充需要的环境,基本上set 相关返回空值, get 相关根据情况判断,用得上的就在浏览器中把需要的值扣出来,用不上的返回空值就行,补环境要点就在于,跳过与加密解密相关的逻辑,不报错就行,相关的就要补全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2054638.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

wireShark tcpdump 抓包 ,检测dns

开两个窗口执行 1窗口。tcpdump -i any port 53 -w clirent.pcap2窗口。nslookup ip 过滤目标ip or 域名 统计域名DNS 将 生成的 clirent.pcap使用wireshark 分析 1.queryType : A ipv4 AAAAipv6

[word] 复杂文本如何仅全选word中的表格 (简单跟做即可)

问题描述 在word文档中&#xff0c;有各种形式的文本&#xff0c;有纯文本&#xff0c;有表格&#xff0c;有图片或者更多其它形式参杂在一起&#xff0c;本篇记录解决如何只全选中文档中的所有表格形式的部分&#xff0c;从而方便对表格进行批量修改和操作 环境说明 word版…

【系统架构设计】开发方法(二)

【系统架构设计】开发方法&#xff08;二&#xff09; 软件生命周期软件开发模型统一过程敏捷方法极限编程特征驱动开发Scrum水晶方法开放式源码ASD 软件重用基于架构的软件设计形式化方法 软件生命周期 软件开发模型 统一过程 敏捷方法 所谓敏捷开发&#xff0c;个人认为&…

day01- Web开发介绍-HTML-CS

Web开发介绍 1 什么是web开发 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站。 所以Web开发说白了&#xff0c;就是开发网站的&#xff0c;例如下图所示的网站&#xff1a;淘宝&#xff0c;京东等等 那么我们…

ROS_package 、CMakeLists.txt、package.xml、ROS_node之间的关系

一、整体框架结构 二、关系描述 1、ROS.cpp 里面初始化了一个ROS节点&#xff0c;注意我的源文件里面只初始化了一个节点 // ROS.cpp #include "ros/ros.h"int main(int argc, char **argv) {ros::init(argc, argv, "node_name"); // 指定节点名称为 &…

C++图笔记(三)有向无环图(及最小生成树(略))以及剩下的排序

目录 一&#xff0c;定义&#xff1a; 1&#xff0c;有向无环图 2&#xff0c;拓朴排序 1&#xff0c;每个顶点出现且仅仅出现一次。 2&#xff0c;若存在一条从顶点 A 到顶点 B 的路径&#xff0c;那么在序列中顶点 A 出现在顶点 B 的前面。 二&#xff0c;DAG的性质 性质1…

Clash-Docker在Ubuntu下的使用

说明 最近折腾nas&#xff0c;docker镜像被墙之后很多镜像拉取不了&#xff0c;需要添加代理&#xff0c;这里使用Clash docker版。 安装&运行 docker pull dreamacro/clash:v1.18.0 docker run -d --name clash --network host dreamacro/clash:v1.18.0修改配置文件 d…

java使用itext 直接生成pdf

itext 使用 需求背景itext 的使用依赖简单示例基础设置&#xff08;页面大小、边距、字体等&#xff09;段落内部&#xff0c;特殊设置关键字 字体或颜色生成动态表格页脚展示页数其他设置密码添加水印&#xff08;背景图&#xff09;目录Header, Footer分割 PDF合并 PDF 需求背…

动态路由//

静态路由的缺点 1.配置量大 2.不能基于拓扑的变化进行实时更新 总结:只能在简单的小型网络中进行配置 动态路由的优点 实时基于拓扑的变化而进行路由表的收敛 动态路由的缺点 1.额外的占用链路资源:动态路由协议需要路由器之间不断交换路由信息&#xff0c;这会占用网络…

在PasteSpider中使用gitee的webhook实现类似Jenkins的CI/CD持续部署

准备事宜 1.已经在PasteSpider中配置好了相关的项目和服务 2.在gitee中有创建对应的代码仓库 实现原理 1.webhook是时下非常流行的一个推送模式&#xff0c;webhook其实就是往某一个特定的地址推送消息&#xff0c;这样可以避免频繁的轮询&#xff0c;在事件发生后的第一时间告…

Microsoft Edge 使用方法与秘诀概览

目录 ​编辑引言 Microsoft Edge 功能与技巧概览 掌握这些设置技巧&#xff0c;让 Edge 浏览器的体验更干净 1. 使用阅读视图 2. 开启广告过滤 3. 管理扩展 4. 个性化新标签页 5. 使用网页截图 6. 清理浏览器缓存 7. 管理启动设置 8. 自定义地址栏建议 9. 使用内置笔…

OLAP引擎之Druid

Apache Druid 简介 Apache Druid 是一个开源的、分布式的实时分析数据库&#xff0c;专为大规模数据的快速查询和实时分析而设计。Druid 将数据存储、索引和查询处理功能结合在一起&#xff0c;支持对流数据和批量数据进行快速的、低延迟的分析查询。它特别适用于需要高并发、…

C语言内存操作函数

目录 一. C语言内存操作函数 1. memcpy的使用和模拟实现 2. memmove函数 3. memset函数 4. memcmp函数 一. C语言内存操作函数 随着知识的不断积累&#xff0c;我们所想要实现的目标程序就会更加复杂&#xff0c;今天我们来学习一个新的知识叫做C语言内存操作函数&#x…

eNSP 华为交换机生成树协议

华为交换机生成树协议 生成树协议原理与作用 选举一个交换机作为根网桥&#xff08;生成树的根&#xff09;&#xff0c;计算出到其他所有交换机的最佳路径&#xff0c;把备用路径的端口设为堵塞状态&#xff08;逻辑上关闭备用路径&#xff09;&#xff0c;当最佳路径故障再…

制造企业为什么要数字化转型?面临哪些困难?

如何界定制造企业 制造业&#xff08;Manufacturing Industry&#xff09;是指机械工业时代利用某种资源&#xff08;物料、能源、设备、工具、资金、技术、信息和人力等&#xff09;&#xff0c;按照市场要求&#xff0c;通过制造过程&#xff0c;转化为可供人们使用和利用的…

01:电容的什么,各类电容的优缺点

1.电容是什么&#xff1f; 电容是由两块不连通的导体&#xff0c;已经中间的不导电材料组成 电容结构&#xff1a; 1.2电容的容量计算公式 C ε s d \displaystyle\frac{εs}{d} dεs​ 1.3常见电容的种类 1.4各类电容的特点

【大模型从入门到精通31】开源库框架LangChain RAG 系统中的问答技术1

这里写目录标题 引言问答与语言模型一般流程整合方法 通过高级问答技术增强 RAG 系统利用 VectorDB 进行文档检索实现问答链初始化语言模型配置 RetrievalQA 链 问答实战 引言 检索增强生成 (RAG) 系统已经革新了我们与大型数据集互动的方式&#xff0c;使得开发高度复杂的聊天…

深入探索MyBatis的动态代理模式

文章目录 深入探索MyBatis的动态代理模式引言一、 MyBatis动态代理概述动态代理的优势 二、准备工作文件存放结构视图1、Mybatis的主配置文件 mybatis-config.xml2、db.properties文件:3、mybatis-config.xml引用properties文件: 三、MyBatis动态代理的实现原理1. Mapper接口定…

基于STM32F103的FreeRTOS系列(十)·消息队列的使用详细介绍以及代码编写

目录 1. 消息队列简介 1.1 概念 1.2 数据存储 1.3 阻塞机制 1.3.1 出队阻塞 1.3.2 入队阻塞 1.4 操作示意图 1.4.1 创建队列 1.4.2 向队列发送第一个消息 1.4.3 向队列发送第二个消息 1.4.4 从队列读取消息 1.5 消息队列的控制块 2. 常用API函数介绍…

android13顶部状态栏里面调节背光,不隐藏状态栏面板

总纲 android13 rom 开发总纲说明 目录 1.前言 2.代码分析 3.修改方法 4.编译运行 5.彩蛋 1.前言 android13顶部状态栏里面调节背光,这个时候状态栏面板会被隐藏掉,有些需求就需要不隐藏这个面板。 2.代码分析 查找亮度条属性 id/brightness_slider ./frameworks/b…