JS 逆向

Sherry 发布于 2025-03-27 34 次阅读 文章 最后更新于 2025-03-27 6723 字


过无限 debugger

  • 代码行号右击不在此处暂停
  • 鼠标右击条件断点 让当前代码条件不成立
  • 方法置空 前提:需要知道执行的是什么方法
  • 替换文件方式把断点的位置注释
  • hook 代码完成

HOOK

Hook json

var _parese = JSON.parse
JSON.parse = function (dd){
    console.log('hook json.parse',dd)
    debugger
    return _parese(dd)
}

有可能第一次 hook 不到,向上找堆栈

Object. defineProperty 修改对象

Var user = {
    age:'123'
}

//Object.defineProperty直接在一个对象上定义一个新的属性,或者修改现有的数据
Object.defineProperty(user,'age',{
    get:function (){
        return'2334545'
    },
    set:function (new_data){
        console.log('修改当前对象数据')
    }
})

Hook cookie

(function (){
    cookie_data = document.cookie
    Object.defineProperty(document,'cookie',{
        get: function () {
            return cookie_data
        },
        set: function (val) {
            if (val.indexof('v')!=-1){
                debugger
            }
            console.log('新获取的cookie值', val)
            cookie_data = val
        }  
    })
})()

Hook XHR
定义了一个变量 open 保留原始 XMLHttpRequest. open 方法,然后重写 XMLHttpRequest. open 方法,判断如果字符串值在 URL里首次出现的位置不为-1,即 URL 里包含 analysis 字符串,则执行 debugger 语句,会立即断下。

(function () {
    var open = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function (method, url, async) {
        if (url.indexOf("analysis") != -1) {    //是否存在参数    
            debugger;
        }
        return open.apply(this, arguments);
    };
})();

拦截器

  • 请求拦截器:在发送请求之前,可以借助一些函数来对请求的内容和参数做一些检测。若有问题可以直接取消请求。
  • 响应拦截器:当服务器返回响应数据时,响应拦截器会在我们拿到结果前预先处理响应数据。例如对响应数据做一些格式化处理,或者当响应失败时,可以做一些失败提醒和纪录。
// npm install axios
axios = require('axios')
//设置请求拦截器
axios.interceptors.request.use(function (config) {
    console.log('请求拦截器 成功')
    config.headers['sign'] = 'lili'
    return config;
}, function (error) {
    console.log('请求拦截器 失败')
    return Promise.reject(error);
});

//设置响应拦截器
axios.interceptors.response.use(function (response) {
    console.log('响应拦截器 成功')
    console.log('调解密函数进行解密数据')
    //return response;
    return response.data; //修改响应数据
}, function (error) {
    console.log('响应拦截器 失败')
    return Promise.reject(error);
});

//发送请求
axios.get('http://httpbin.org/get').then(res=>console.log(res))

调用 js 和扣代码

import execjs

# 方法一:直接执行JS代码
def basic_usage():
    # 初始化JavaScript运行环境
    ctx = execjs.compile('''
        function add(x, y) {
            return x + y;
        }
    ''')

    # 调用JS函数
    result = ctx.call('add', 1, 2)
    print(result)  # 输出: 3

# 方法二:从JS文件读取并执行
def execute_js_file():
    # 读取JS文件
    with open('example.js', 'r', encoding='utf-8') as f:
        js_code = f.read()

    # 编译JS代码
    ctx = execjs.compile(js_code)

    # 调用其中的函数
    result = ctx.call('yourFunction', 'params')
    print(result)

# 方法三:在JS中使用外部变量
def use_external_vars():
    ctx = execjs.compile('''
        function greet(name) {
            return "Hello, " + name + "!";
        }
    ''')

    name = "张三"
    result = ctx.call('greet', name)
    print(result)  # 输出: Hello, 张三!

# 方法四:执行复杂的JS代码
def complex_example():
    js_code = '''
    function encryptData(data) {
        // 这里可以是复杂的加密算法
        var result = "";
        for(var i = 0; i < data.length; i++) {
            result += String.fromCharCode(data.charCodeAt(i) + 1);
        }
        return result;
    }
    '''

    ctx = execjs.compile(js_code)
    encrypted = ctx.call('encryptData', '需要加密的数据')
    print(encrypted)

Express 开放接口

var express = require('express');
var app = express();

app.get('/', function (req, res) {
    res.send('Hello World');
});

app.listen(3000, function () {
    console.log('Server is running on port 3000');
});

补环境

浏览器独有环境:
window,navigtor,location,document,screen

window = gloabl
document = {}
navigator {}

吐环境代理

var aa = {
    name:'Sherry'
    age:18
}

var p = new Proxy(target,{
    get: function (target, propertyKey, receiver) {
     // 1 原对象
     // 2 访问属性
     // 3 代理器处理对象
     console.log(target, propertyKey, receiver)
 },
    set: function(target,propertyKey,value,receiver){
      // 1. 原对象
      // 2. 设置的属性
      // 3. 设置的值
      // 4. 代理器代理的对象
      console.log(target, propertyKey, value, receiver)
  }
})
// 代理器封装
function get_enviroment(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, ' +
            // '"  属性值:", ' + 'target[property], ' +
            '"  属性值类型:", typeof target[property]);\n' +
            '        return target[property];\n' +
            '    },\n' +
            '    set: function(target, property, value, receiver) {\n' +
            '        console.log("方法:", "set  ", "对象:", ' +
            '"' + proxy_array[i] + '" ,' +
            '"  属性:", property, ' +
            '"  属性类型:", ' + 'typeof property, ' +
            // '"  属性值:", ' + 'target[property], ' +
            '"  属性值类型:", typeof target[property]);\n' +
            '        return Reflect.set(...arguments);\n' +
            '    }\n' +
            '}'
        eval('try{\n' + proxy_array[i] + ';\n'
        + proxy_array[i] + '=new Proxy(' + proxy_array[i] + ', ' + handler + ')}catch (e) {\n' + proxy_array[i] + '={};\n'
        + proxy_array[i] + '=new Proxy(' + proxy_array[i] + ', ' + handler + ')}')
    }
}
proxy_array = ['window', 'document', 'location', 'navigator', 'history','screen']
get_enviroment(proxy_array)

补隐式原型

navigator = {}
navigator.__proto__.userAgent = "123456"
console.log(Object.getOwnPropertyDescriptor(navigator, 'userAgent'));

确保输出 undefined

补显式原型

var Navigator = function(){}
Navigator.prototype = {'userAgent':123456}
var navigator = new Navigator()
console.log(Object.getOwnPropertyDescriptor(navigator, 'userAgent'));

补方法

Document = function () {}

// Object.defineProperty 给对象定性新属性
Object.defineProperty(Document.prototype, 'createElement', {
  value: function () {},
  writable: true,
  enumerable: true,
  configurable: true
});
HTMLDocument = function () {}

//可以将一个指定对象的原型(即内部的[[Prototype]]属性)设置为另一个对象
Object.setPrototypeOf(HTMLDocument.prototype, Document.prototype)

document = new HTMLDocument()
console.log(Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'createElement'));

toString 检测

document = {
  createElement: function () {}
}
document.createElement.toString = function(){
  return 'function createElement() { [native code] }'
}
console.log(document.createElement.toString());

jsdom 补环境

npm install jsdom
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
title = dom.window.document.querySelector("p").textContent
console.log(title)

v_tools 使用

https://github.com/cilame/v_jstools

配置 v_jstools

生成临时环境


关闭日志

摘要算法

MD5

var crypt = require('crypto-js')
function get_md5() {
    var text = 'python'
    console.log(crypt.MD5(text).toString())
}
get_md5()

SHA

// 引用 crypto-js 加密模块
var CryptoJS = require('crypto-js')
function SHA1Encrypt() {
    var text = "I love python!"
    return CryptoJS.SHA1(text).toString();
}
console.log(SHA1Encrypt())

HMAC

// 引用 crypto-js 加密模块
var CryptoJS = require('crypto-js')

function HMACEncrypt() {
    var text = "I love python!"
    var key = "secret"   // 密钥文件
    return CryptoJS.HmacMD5(text, key).toString();
    // return CryptoJS.HmacSHA1(text, key).toString();
    // return CryptoJS.HmacSHA256(text, key).toString();
}

console.log(HMACEncrypt())

对称加密

模式

ECB

CBC

CBC

OFB

CTR

算法

DES
56 位密钥,由于密钥太短,被逐渐被弃用。

  • mode 支持:CBC, CFB, CTR, CTRGladman, ECB, QFB 等。
  • padding 支持:ZeroPadding, NoPadding, AnsiX 923, so 10126, lso 97971, Pkcs 7 等。

加密

var crypt = require('crypto-js')

function enc(data) {
    var key = crypt.enc.Utf8.parse('12345678'),
        iv = crypt.enc.Utf8.parse('12345678'),
        data = crypt.enc.Utf8.parse(data);
    aa = crypt.DES.encrypt(data, key, {
        iv:iv,
        mode:crypt.mode.CBC,
        padding:crypt.pad.Pkcs7
    })
    return aa.toString()
}

解密

var crypt = require('crypto-js')

function enc(data) {
    var key = crypt.enc.Utf8.parse('12345678'),
        iv = crypt.enc.Utf8.parse('12345678'),
        data = data
        ;
    aa = crypt.DES.decrypt(data, key, {
        iv:iv,
        mode:crypt.mode.CBC,
        padding:crypt.pad.Pkcs7
    })
    return aa.toString(crypt.enc.Utf8)
}

python 实现

import binascii
# 加密模式 CBC,填充方式 PAD_PKCS5
from pyDes import des, CBC, PAD_PKCS5

def des_encrypt(key, text, iv):
    k = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
    en = k.encrypt(text, padmode=PAD_PKCS5)
    return binascii.b2a_hex(en)

def des_decrypt(key, text, iv):
    k = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
    de = k.decrypt(binascii.a2b_hex(text), padmode=PAD_PKCS5)
    return de

if __name__ == '__main__':
    secret_key = '12345678'    # 密钥
    text = 'hello world'       # 加密对象
    iv = secret_key           # 偏移量
    secret_str = des_encrypt(secret_key, text, iv)
    print('加密字符串:', secret_str)
    clear_str = des_decrypt(secret_key, secret_str, iv)
    print('解密字符串:', clear_str)

# 加密字符串:b'302d3abf2421169239f829b38a9545f1'
# 解密字符串:b'I love Python!'

3DES

AES
有 128 位、192 位、256 位密钥,现在比较流行。密钥长、可以增加破解的难度和成本
加密

var crypt = require('crypto-js')

function enc(data) {
    var key = crypt.enc.Utf8.parse('12345678'),
        iv = crypt.enc.Utf8.parse('12345678'),
        data = crypt.enc.Utf8.parse(data);
    aa = crypt.AES.encrypt(data, key, {
        iv:iv,
        mode:crypt.mode.CBC,
        padding:crypt.pad.Pkcs7
    })
    return aa.toString()
}

解密

var crypt = require('crypto-js')

function enc(data) {
    var key = crypt.enc.Utf8.parse('12345678'),
        iv = crypt.enc.Utf8.parse('12345678'),
        data = data
        ;
    aa = crypt.AES.decrypt(data, key, {
        iv:iv,
        mode:crypt.mode.CBC,
        padding:crypt.pad.Pkcs7
    })
    return aa.toString(crypt.enc.Utf8)
}

RC4
对称加密

非对称加密

  • 搜索关键饲 new JSEncrypt()、JSEncrypt 等,一般会使用 JSEncrypt 库,会有 new 一个实例对象的操作
  • 搜索关键词 setPublicKey、setKey、setPrivateKey、getPublicKey等,一般实现的代码里都含有设置密钥的过程。

RSA

私钥开头:MIIB
公钥开头:MFww

// 引用 node-rsa 加密模块
var NodeRSA = require('node-rsa');

/**
 * RSA加密函数
 * 使用公钥对文本进行加密
 * @returns {string} 返回base64编码的加密数据
 */
function rsaEncrypt() {
    pubKey = new NodeRSA(publicKey,'pkcs8-public');  // 使用公钥创建RSA对象
    var encryptedData = pubKey.encrypt(text, 'base64');  // 加密文本并使用base64编码
    return encryptedData
}

/**
 * RSA解密函数
 * 使用私钥对加密数据进行解密
 * @returns {string} 返回utf8编码的解密文本
 */
function rsaDecrypt() {
    priKey = new NodeRSA(privatekey,'pkcs8-private');  // 使用私钥创建RSA对象
    var decryptedData = priKey.decrypt(encryptedData, 'utf8');  // 解密数据并转换为utf8编码
    return decryptedData
}

// 初始化RSA密钥对
var key = new NodeRSA({b: 512});        //生成512位秘钥
var publicKey = key.exportKey('pkcs8-public');    //导出公钥
var privatekey = key.exportKey('pkcs8-private');  //导出私钥
var text = "I love Python!"  // 待加密的原始文本

// 执行加密和解密操作
var encryptedData = rsaEncrypt()  // 加密数据
var decryptedData = rsaDecrypt()  // 解密数据

// 输出结果
console.log("公钥:\n", publicKey)        // 打印公钥
console.log("私钥:\n", privatekey)       // 打印私钥
console.log("加密字符串: ", encryptedData)   // 打印加密后的数据
console.log("解密字符串: ", decryptedData)   // 打印解密后的数据 

webpack

将所有资源,打包成 js 进行渲染出来。
如果一个页面大部分是 script 标签构成,80%以上是 webpack 打包。

  • 整个打包的文件是一个自执行函数
  • 方法里面是 webpack 的加载器
  • 存放的模块就是 s 的每个功能,可以有两中方式,数组和键值对的形式

    通过观察加载器,来判断 webpack,将对应调用的函数拿过来,放在模块中调用

导出包

先都置空,防止有缓存

在对应方法出添加条件断点,付为 0,只要让他将方法给 aaa 就行

在下一个方法处也下断点,防止都拿了

一件生成模块

result '{';
for (let x of Object.keys(aaa)){
result = result + '"' + x + '"' + ":" + aaa[x] + ','    
}
result = result +'}'

之后放在导出的加载器模块中

SM 国密

SM2

// npm install sm-crypto --save

const sm2 = require('sm-crypto').sm2

// 1 - C1C3C2, 0 - C1C2C3, 默认为1
const cipherMode = 1

// 获取密钥对
let keypair = sm2.generateKeyPairHex()
let publicKey = keypair.publicKey   // 公钥
let privateKey = keypair.privateKey // 私钥

let msgString = "this is the data to be encrypted"
let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode)    // 加密结果
let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) // 解密结果

console.log("encryptData: ", encryptData)
console.log("decryptData: ", decryptData)

SM3

var sm3 = require('sm-crypto').sm3
var data = sm3('python')
console.log(data.Length)

SM4

var sm4 = require('sm-crypto').sm4
data = sm4.encrypt(inArray:'python',key:'0123456789ABCDEF0123456789ABCDEF',options:ecb')
console.log(data)

var data = sm4.decrypt (data,key:0123456789ABCDEF0123456789ABCDEF',options:'ecb')
console.log(data)

微信小程序

小程序包位置

C:\Users\Sherry\Documents\WeChat Files\wxid_k6ebdirizs8122\Applet

刷小程序版本

C:\Users\HelloCTF_OS\AppData\Roaming\Tencent\WeChat\XPlugin\Plugins\RadiumWMPF

反编译

node wuWxapkg.js [-d] <path/to/.wxapkg>

https://github.com/laveleyQAQ/WeChatOpenDevTools-Python

(WeChatDevTool) C:\software\WeChatOpenDevTools-Python-main>python main.py -h

    请选择要执行的方法:
                        [+] python  main.py -h  查看帮助
                        [+] python  main.py -x  开启小程序F12
                        [+] python  main.py -c  开启内置浏览器F12
                        [+] python  main.py -all   开启内置浏览器F12与小程序F12

options:
  -h, --help  show this help message and exit
  -x          开启小程序F12
  -c          开启内置浏览器F12
  -all        开启内置浏览器F12与小程序F12

运行

python main.py -x

开启内置浏览器

python  main.py -c

混淆

ob 混淆

  • 一般由一个大数组或者含有大数组的函数、一个自执行函数、解密函数和加密后的函数四部分组成
  • 函数名和变量名通常以 x 或者 x 开头,后接 1~6 位数字或字母组合
  • 自执行函数,进行移位操作,有明显的 puSh、shit 关键字
npm install javascript-obfuscator -g
const JavaScriptObfuscator = require('javascript-obfuscator');

const sourceCode = `
function myFunction() {
    var myVariable = 'Hello, World!';
    console.log(myVariable);
}
myFunction();
`;

const options = {
    compact: true, // 启用压缩
    controlFlowFlattening: true, // 控制流 平坦化
    controlFlowFlatteningThreshold: 0.75, // 控制流平坦化阈值
    deadCodeInjection: true, // 使死代码注入
    deadCodeInjectionThreshold: 0.4, // 死代码注入阈值
    identifierNamesGenerator: 'hexadecimal', // 生成标识符的方式
    log: false, // 关闭日志
    rotateStringArray: true, // 旋转字符串数组
    selfDefending: true, // 启用自我防御
    stringArray: true, // 启用字符串数组
    stringArrayThreshold: 0.75, // 字符串数组阈值
    // domainLock: ['example.com'], // 锁定域名
    debugProtection: true, // 调试保护
    disableConsoleOutput: true, // 禁用控制台输出
    unicodeEscapeSequence: false, // 禁用 Unicode 转义序列
    rotateUnicodeArray: true, // 旋转 Unicode 数组
};

const obfuscatedCode = JavaScriptObfuscator.obfuscate(sourceCode, options).getObfuscatedCode();

console.log(obfuscatedCode);

重构函数不要进行格式化(自执行方法)
解密函数也不要进行格式化
需要进行压缩代码

AST

[!tip]

代码转为机器码过程中的操作

//举例
var a 42;
var b 5;
function addA(d){
    return a d;
}
var c addA(2)+b;


序号 类型原名称 中文名称 描述
1 Program 程序主体 整段代码的主体,AST 的根节点
2 VariableDeclaration 变量声明 声明一个变量,如 var、let、const
3 FunctionDeclaration 函数声明 声明一个函数,如 function foo (){}
4 ExpressionStatement 表达式语句 通常是调用一个函数,如 console.log ()
5 BlockStatement 块语句 包裹在 {} 块内的代码
6 BreakStatement 中断语句 通常指 break
7 ContinueStatement 持续语句 通常指 continue
8 ReturnStatement 返回语句 通常指 return
9 SwitchStatement Switch 语句 switch-case 结构
10 IfStatement If 控制流语句 if-else 条件结构
11 Identifier 标识符 变量名、函数名等标识
12 BinaryExpression 二元表达式 包含两个操作数的表达式,如 a + b
13 UnaryExpression 一元表达式 只有一个操作数的表达式,如 !a
14 CallExpression 调用表达式 函数调用,如 foo ()
15 MemberExpression 成员表达式 访问对象属性,如 obj. prop
16 ArrayExpression 数组表达式 数组字面量,如 [1, 2, 3]
17 ObjectExpression 对象表达式 对象字面量,如 {a: 1}
18 Literal 字面量 具体的值,如数字、字符串等
19 ForStatement For 循环语句 for 循环结构
20 WhileStatement While 循环语句 while 循环结构
21 DoWhileStatement Do-While 循环语句 do-while 循环结构
22 TryStatement Try 语句 try-catch 异常处理结构
23 ThrowStatement 抛出语句 抛出异常,如 throw new Error ()
24 AssignmentExpression 赋值表达式 变量赋值,如 a = 1
25 UpdateExpression 更新表达式 自增自减,如 i++, --j
26 LogicalExpression 逻辑表达式 逻辑运算,如 a && b
27 ConditionalExpression 条件表达式 三元运算符,如 a ? b : c
28 NewExpression New 表达式 创建实例,如 new Object ()
29 ThisExpression This 表达式 this 关键字
30 SequenceExpression 序列表达式 逗号分隔的表达式,如 a, b, c

babel 学习

npm install @babel/core -s
包名 功能描述
@babel/core Babel 编译器本身,提供了 babel 的编译 API
@babel/parser 将 JavaScript 代码解析成 AST 语法树
@babel/traverse 遍历、修改 AST 语法树的各个节点
@babel/generator 将 AST 还原成 JavaScript 代码
@babel/types 判断、验证节点的类型、构建新 AST 节点等
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const t = require('@babel/types');

// 示例:将代码中的所有数字字面量加倍
const code = `
function calculateTotal(price) {
    const tax = 0.1;
    const shipping = 5;
    return price + (price * tax) + shipping;
}
`;

// 1. 解析代码
const ast = parser.parse(code);

// 2. 遍历和修改 AST
traverse(ast, {
    // 处理数字字面量
    NumericLiteral(path) {
        // 验证节点类型
        if (t.isNumericLiteral(path.node)) {
            // 创建新的数字字面量节点
            const newNode = t.numericLiteral(path.node.value * 2);
            // 替换原节点
            path.replaceWith(newNode);
        }
    }
});

// 3. 生成新代码
const output = generate(ast, {
    comments: true,
    compact: false
});

console.log('转换后的代码:');
console.log(output.code);
// 输出:
// function calculateTotal(price) {
//   const tax = 0.2;
//   const shipping = 10;
//   return price + price * tax + shipping;
// }

babel 解混淆

const parse = require('@babel/parser').parse
const generator = require('@babel/generator').default;
const traverse = require('@babel/traverse').default;
const types = require('@babel/types')
const fs = require('fs');
const { exit } = require('process');

// 自行将后面讲的三个特征的代码放到这

// 待反混淆的文件

//////////////////

//////////////////

// 语法数转JS代码
let { code } = generator(ast, { compact: false });
// console.log(code);

// 保存
fs.writeFile('./ast/demo/output.js', code, (err) => {
});

RPC

Websocket

客户端

// 创建 WebSocket 连接
const ws = new WebSocket('ws://localhost:8080');

// 连接建立时触发
ws.onopen = function() {
    console.log('已连接到服务器');

    // 发送消息到服务器
    ws.send('你好,服务器!');
};

// 接收服务器消息
ws.onmessage = function(event) {
    console.log('收到服务器消息:', event.data);
};

// 错误处理
ws.onerror = function(error) {
    console.error('WebSocket错误:', error);
};

// 连接关闭
ws.onclose = function() {
    console.log('连接已关闭');
};

// 示例:发送消息的函数
function sendMessage(message) {
    if (ws.readyState === WebSocket.OPEN) {
        ws.send(message);
    } else {
        console.log('连接未建立,无法发送消息');
    }
}

服务端

import asyncio
import websockets  # 修改导入的模块名

async def echo(websocket):
    await websocket.send('hello')
    return True 

async def recv_msg(web):
    while 1:
        print(await web.recv())

async def main(websocket, path):
    await echo(websocket)  # 修正拼写错误

if __name__ == '__main__':
    ws_ser = websockets.serve(main, '127.0.0.1', 8080)  # 端口号应该是整数
    loop = asyncio.get_event_loop()
    loop.run_until_complete(ws_ser)
    # 创建连接对象,不断监听数据,让方法保持长连接
    loop.run_forever()

自定义 RPC

var web = new WebSocket(url:'ws://127.0.0.1:8080)
web.onmessage = function(event){
    var data = event,data
    // 页面解密的方法
    var res = b(data)
    // 发送正确的数据给py
    web.send(res)
}

替换文件

一般网页注入自执行方法

!(function () {
    if (window.flag) {
    } else {
        const websocket = new WebSocket('ws://127.0.0.1:8080');

        // 创建一个标记用来判断是否创建套接字
        window.flag = true;

        // 接收服务端发送的信息
        websocket.onmessage = function (event) {
            var data = event.data;

            // 调用js解密
            var res = b(data);
            console.log(res);

            // 发送解密数据给服务端
            websocket.send(res);
        }
    }
}());

Sekiro-RPC

客户端代码
http://file.virjar.com/sekiro_web_client.js?_=123

function SekiroClient(e){if(this.wsURL=e,this.handlers={},this.socket={},!e)throw new Error("wsURL can not be empty!!");this.webSocketFactory=this.resolveWebSocketFactory(),this.connect()}SekiroClient.prototype.resolveWebSocketFactory=function(){if("object"==typeof window){var e=window.WebSocket?window.WebSocket:window.MozWebSocket;return function(o){function t(o){this.mSocket=new e(o)}return t.prototype.close=function(){this.mSocket.close()},t.prototype.onmessage=function(e){this.mSocket.onmessage=e},t.prototype.onopen=function(e){this.mSocket.onopen=e},t.prototype.onclose=function(e){this.mSocket.onclose=e},t.prototype.send=function(e){this.mSocket.send(e)},new t(o)}}if("object"==typeof weex)try{console.log("test webSocket for weex");var o=weex.requireModule("webSocket");return console.log("find webSocket for weex:"+o),function(e){try{o.close()}catch(e){}return o.WebSocket(e,""),o}}catch(e){console.log(e)}if("object"==typeof WebSocket)return function(o){return new e(o)};throw new Error("the js environment do not support websocket")},SekiroClient.prototype.connect=function(){console.log("sekiro: begin of connect to wsURL: "+this.wsURL);var e=this;try{this.socket=this.webSocketFactory(this.wsURL)}catch(o){return console.log("sekiro: create connection failed,reconnect after 2s:"+o),void setTimeout(function(){e.connect()},2e3)}this.socket.onmessage(function(o){e.handleSekiroRequest(o.data)}),this.socket.onopen(function(e){console.log("sekiro: open a sekiro client connection")}),this.socket.onclose(function(o){console.log("sekiro: disconnected ,reconnection after 2s"),setTimeout(function(){e.connect()},2e3)})},SekiroClient.prototype.handleSekiroRequest=function(e){console.log("receive sekiro request: "+e);var o=JSON.parse(e),t=o.__sekiro_seq__;if(o.action){var n=o.action;if(this.handlers[n]){var s=this.handlers[n],i=this;try{s(o,function(e){try{i.sendSuccess(t,e)}catch(e){i.sendFailed(t,"e:"+e)}},function(e){i.sendFailed(t,e)})}catch(e){console.log("error: "+e),i.sendFailed(t,":"+e)}}else this.sendFailed(t,"no action handler: "+n+" defined")}else this.sendFailed(t,"need request param {action}")},SekiroClient.prototype.sendSuccess=function(e,o){var t;if("string"==typeof o)try{t=JSON.parse(o)}catch(e){(t={}).data=o}else"object"==typeof o?t=o:(t={}).data=o;(Array.isArray(t)||"string"==typeof t)&&(t={data:t,code:0}),t.code?t.code=0:(t.status,t.status=0),t.__sekiro_seq__=e;var n=JSON.stringify(t);console.log("response :"+n),this.socket.send(n)},SekiroClient.prototype.sendFailed=function(e,o){"string"!=typeof o&&(o=JSON.stringify(o));var t={};t.message=o,t.status=-1,t.__sekiro_seq__=e;var n=JSON.stringify(t);console.log("sekiro: response :"+n),this.socket.send(n)},SekiroClient.prototype.registerAction=function(e,o){if("string"!=typeof e)throw new Error("an action must be string");if("function"!=typeof o)throw new Error("a handler must be function");return console.log("sekiro: register action: "+e),this.handlers[e]=o,this};

中间件,注入浏览器环境

// 生成唯一标记uuid编号
function guid() {
    function S4() {
        return ((1+Math.random())*0x10000|0).toString(16).substring(1);
    }
    return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}

// 连接服务端
var client = new SekiroClient("ws://127.0.0.1:5620/business-demo/register?group=rpc-test&clientId="+guid());

// 业务接口
client.registerAction("登陆",function(request, resolve, reject){
    // 解密方法
    resolve(""+new Date());
});

完整拼接

(function () {

    function SekiroClient(wsURL) {
        this.wsURL = wsURL;
        this.handlers = {};
        this.socket = {};
        this.base64 = false;
        // check
        if (!wsURL) {
            throw new Error('wsURL can not be empty!!')
        }
        this.webSocketFactory = this.resolveWebSocketFactory();
        this.connect()
    }

    SekiroClient.prototype.resolveWebSocketFactory = function () {
        if (typeof window === 'object') {
            var theWebSocket = window.WebSocket ? window.WebSocket : window.MozWebSocket;
            return function (wsURL) {

                function WindowWebSocketWrapper(wsURL) {
                    this.mSocket = new theWebSocket(wsURL);
                }

                WindowWebSocketWrapper.prototype.close = function () {
                    this.mSocket.close();
                };

                WindowWebSocketWrapper.prototype.onmessage = function (onMessageFunction) {
                    this.mSocket.onmessage = onMessageFunction;
                };

                WindowWebSocketWrapper.prototype.onopen = function (onOpenFunction) {
                    this.mSocket.onopen = onOpenFunction;
                };
                WindowWebSocketWrapper.prototype.onclose = function (onCloseFunction) {
                    this.mSocket.onclose = onCloseFunction;
                };

                WindowWebSocketWrapper.prototype.send = function (message) {
                    this.mSocket.send(message);
                };

                return new WindowWebSocketWrapper(wsURL);
            }
        }
        if (typeof weex === 'object') {
            // this is weex env : https://weex.apache.org/zh/docs/modules/websockets.html
            try {
                console.log("test webSocket for weex");
                var ws = weex.requireModule('webSocket');
                console.log("find webSocket for weex:" + ws);
                return function (wsURL) {
                    try {
                        ws.close();
                    } catch (e) {
                    }
                    ws.WebSocket(wsURL, '');
                    return ws;
                }
            } catch (e) {
                console.log(e);
                //ignore
            }
        }
        //TODO support ReactNative
        if (typeof WebSocket === 'object') {
            return function (wsURL) {
                return new theWebSocket(wsURL);
            }
        }
        // weex 鍜� PC鐜鐨剋ebsocket API涓嶅畬鍏ㄤ竴鑷达紝鎵€浠ュ仛浜嗘娊璞″吋瀹�
        throw new Error("the js environment do not support websocket");
    };

    SekiroClient.prototype.connect = function () {
        console.log('sekiro: begin of connect to wsURL: ' + this.wsURL);
        var _this = this;
        // 涓峜heck close锛岃
        // if (this.socket && this.socket.readyState === 1) {
        //     this.socket.close();
        // }
        try {
            this.socket = this.webSocketFactory(this.wsURL);
        } catch (e) {
            console.log("sekiro: create connection failed,reconnect after 2s");
            setTimeout(function () {
                _this.connect()
            }, 2000)
        }

        this.socket.onmessage(function (event) {
            _this.handleSekiroRequest(event.data)
        });

        this.socket.onopen(function (event) {
            console.log('sekiro: open a sekiro client connection')
        });

        this.socket.onclose(function (event) {
            console.log('sekiro: disconnected ,reconnection after 2s');
            setTimeout(function () {
                _this.connect()
            }, 2000)
        });
    };

    SekiroClient.prototype.handleSekiroRequest = function (requestJson) {
        console.log("receive sekiro request: " + requestJson);
        var request = JSON.parse(requestJson);
        var seq = request['__sekiro_seq__'];

        if (!request['action']) {
            this.sendFailed(seq, 'need request param {action}');
            return
        }
        var action = request['action'];
        if (!this.handlers[action]) {
            this.sendFailed(seq, 'no action handler: ' + action + ' defined');
            return
        }

        var theHandler = this.handlers[action];
        var _this = this;
        try {
            theHandler(request, function (response) {
                try {
                    _this.sendSuccess(seq, response)
                } catch (e) {
                    _this.sendFailed(seq, "e:" + e);
                }
            }, function (errorMessage) {
                _this.sendFailed(seq, errorMessage)
            })
        } catch (e) {
            console.log("error: " + e);
            _this.sendFailed(seq, ":" + e);
        }
    };

    SekiroClient.prototype.sendSuccess = function (seq, response) {
        var responseJson;
        if (typeof response == 'string') {
            try {
                responseJson = JSON.parse(response);
            } catch (e) {
                responseJson = {};
                responseJson['data'] = response;
            }
        } else if (typeof response == 'object') {
            responseJson = response;
        } else {
            responseJson = {};
            responseJson['data'] = response;
        }

        if (typeof response == 'string') {
            responseJson = {};
            responseJson['data'] = response;
        }

        if (Array.isArray(responseJson)) {
            responseJson = {
                data: responseJson,
                code: 0
            }
        }

        if (responseJson['code']) {
            responseJson['code'] = 0;
        } else if (responseJson['status']) {
            responseJson['status'] = 0;
        } else {
            responseJson['status'] = 0;
        }
        responseJson['__sekiro_seq__'] = seq;
        var responseText = JSON.stringify(responseJson);
        console.log("response :" + responseText);


        if (responseText.length < 1024 * 6) {
            this.socket.send(responseText);
            return;
        }

        if (this.base64) {
            responseText = this.base64Encode(responseText)
        }

        //澶ф姤鏂囪鍒嗘浼犺緭
        var segmentSize = 1024 * 5;
        var i = 0, totalFrameIndex = Math.floor(responseText.length / segmentSize) + 1;

        for (; i < totalFrameIndex; i++) {
            var frameData = JSON.stringify({
                    __sekiro_frame_total: totalFrameIndex,
                    __sekiro_index: i,
                    __sekiro_seq__: seq,
                    __sekiro_base64: this.base64,
                    __sekiro_is_frame: true,
                    __sekiro_content: responseText.substring(i * segmentSize, (i + 1) * segmentSize)
                }
            );
            console.log("frame: " + frameData);
            this.socket.send(frameData);
        }
    };

    SekiroClient.prototype.sendFailed = function (seq, errorMessage) {
        if (typeof errorMessage != 'string') {
            errorMessage = JSON.stringify(errorMessage);
        }
        var responseJson = {};
        responseJson['message'] = errorMessage;
        responseJson['status'] = -1;
        responseJson['__sekiro_seq__'] = seq;
        var responseText = JSON.stringify(responseJson);
        console.log("sekiro: response :" + responseText);
        this.socket.send(responseText)
    };

    SekiroClient.prototype.registerAction = function (action, handler) {
        if (typeof action !== 'string') {
            throw new Error("an action must be string");
        }
        if (typeof handler !== 'function') {
            throw new Error("a handler must be function");
        }
        console.log("sekiro: register action: " + action);
        this.handlers[action] = handler;
        return this;
    };

    SekiroClient.prototype.encodeWithBase64 = function () {
        this.base64 = arguments && arguments.length > 0 && arguments[0];
    };

    SekiroClient.prototype.base64Encode = function (s) {
        if (arguments.length !== 1) {
            throw "SyntaxError: exactly one argument required";
        }

        s = String(s);
        if (s.length === 0) {
            return s;
        }

        function _get_chars(ch, y) {
            if (ch < 0x80) y.push(ch);
            else if (ch < 0x800) {
                y.push(0xc0 + ((ch >> 6) & 0x1f));
                y.push(0x80 + (ch & 0x3f));
            } else {
                y.push(0xe0 + ((ch >> 12) & 0xf));
                y.push(0x80 + ((ch >> 6) & 0x3f));
                y.push(0x80 + (ch & 0x3f));
            }
        }

        var _PADCHAR = "=",
            _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
            _VERSION = "1.1";//Mr. Ruan fix to 1.1 to support asian char(utf8)

        //s = _encode_utf8(s);
        var i,
            b10,
            y = [],
            x = [],
            len = s.length;
        i = 0;
        while (i < len) {
            _get_chars(s.charCodeAt(i), y);
            while (y.length >= 3) {
                var ch1 = y.shift();
                var ch2 = y.shift();
                var ch3 = y.shift();
                b10 = (ch1 << 16) | (ch2 << 8) | ch3;
                x.push(_ALPHA.charAt(b10 >> 18));
                x.push(_ALPHA.charAt((b10 >> 12) & 0x3F));
                x.push(_ALPHA.charAt((b10 >> 6) & 0x3f));
                x.push(_ALPHA.charAt(b10 & 0x3f));
            }
            i++;
        }


        switch (y.length) {
            case 1:
                var ch = y.shift();
                b10 = ch << 16;
                x.push(_ALPHA.charAt(b10 >> 18) + _ALPHA.charAt((b10 >> 12) & 0x3F) + _PADCHAR + _PADCHAR);
                break;

            case 2:
                var ch1 = y.shift();
                var ch2 = y.shift();
                b10 = (ch1 << 16) | (ch2 << 8);
                x.push(_ALPHA.charAt(b10 >> 18) + _ALPHA.charAt((b10 >> 12) & 0x3F) + _ALPHA.charAt((b10 >> 6) & 0x3f) + _PADCHAR);
                break;
        }

        return x.join("");
    };

    function startRpc() {
        if (window.flag) {
        } else {
            function guid() {
                function S4() {
                    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
                }

                return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
            }

            // 创建一个标记用来判断是否创建套接字
            window.flag = true;
            var client = new SekiroClient("ws://127.0.0.1:5620/business-demo/register?group=rpc-test&clientId=" + guid());
            client.registerAction("成员", function (request, resolve, reject) {
                var res = request['data']

                resolve(方法);    
            })
        }
    }

    setTimeout(startRpc, 1000)
})()
  1. group(业务类型/接口组)
    • 每个业务对应一个 group
    • 一个 group 可以注册多个终端(SekiroClient)
    • 同一个 group 可以挂载多个 Action(动作/接口)
  2. clientId(设备标识)
    • 用于标识不同的设备
    • 支持多个设备同时提供 API 服务
    • 提供群控能力和负载均衡功能
  3. SekiroClient(客户端)
    • 服务提供者的客户端,主要应用于手机/浏览器等场景
    • 最终的 Sekiro 调用会转发到 SekiroClient
    • 每个 client 必须拥有唯一的 clientId
  4. registerAction(注册接口)
    • 用于注册业务接口
    • 同一个 group 下可以注册多个接口
    • 不同接口可以实现不同的功能
  5. resolve(响应方法)
    • 用于将处理结果返回给服务端的方法
  6. request(请求对象)
    • 包含服务端传递的请求参数
    • 如果请求包含多个参数,可以通过键值对的方式获取
    • 获取参数后可进行后续处理

  1. 查看分组列表
    http://127.0.0.1:5620/business-demo/groupList
    
  • 用途:查看所有已注册的业务分组
  1. 查看队列状态
    http://127.0.0.1:5620/business-demo/clientQueue?group=rpc-test
    
  • 用途:查看指定分组下的客户端队列状态
  • 参数:group=分组名称
  1. 调用转发
    http://127.0.0.1:5620/business-demo/invoke?group=rpc-test&action=test
    
  • 用途:向指定分组的指定动作发送调用请求
  • 参数:
    • group:分组名称
    • action:要调用的动作名称

python 调用

import requests
params = {
    "group":"rpc-test",
    "action":"clientTime",
}
res = requests.get("http://127.0.0.1:5620/business-demo/invoke",params=params)
print(res.text)

字体反爬

<style>
    @font-face{
    }
</style>


字体下载下来一般把后缀改成 woff
在线字体编辑器-JSON在线编辑器

找到字体对应关系

字体读取

from fontTools.ttlib import TTFont
# 加载字体文件:
font = TTFont('file.woff')
# 转为xml文件: 可以用来查看字体的字形轮廓、字符映射、元数据等字体相关的信息
font.saveXML('file.xml')
from fontTools.ttLib import TTFont
# 加载字体文件:
font = TTFont('file.woff')
kv = font.keys()
print(kv)

只要了解字型数据和字符映射

<cmap>
<glyf>

获取字符映射

aa = font.getBestCmap()

获取字形映射

# 一个数组
aa = font['glyf']['uni36'].coordinates

vm

npm install vm2
var {VM,VMScript} = require('vm2')
var script = VMScript('var aa=1; aa')
var vm = new VM()
console.log(vm.run(script))

wasm

使用 pywasm 来调用 wasm 文件

import pywasm
import time
import math
# pywasm.on_debug()
runtime = pywasm.load('./111.wasm')

# 模仿 crypto.js 中的调用方式
page = 1
timestamp = int(round(time.time()))  # 获取当前时间戳(秒级)
r = runtime.exec('encrypt', [page, timestamp])  # 将参数放在列表中传入
print(r)

cookie 反爬

  • 浏览器向服务器发送请求,服务器在响应头带上响应 cookie, 下次请求的时候需要再带上 cookie 去进行请求
  • 浏览器向服务器发送请求,服务器返回的是一段 is 代码,浏览器需要解析 js 代码,在通过 js 代码在生成 cookie 信息,进行逆向

如果 http only 打勾,就是由服务器返回的

阿里系

特征:acw
请求次数:2 次

加速乐

特征:_ jsl
请求次数:3 次

  • 第一次请求返回状态码为:521,返回 JSFuck

    响应 cookie:__ jsluid_s,js 生成:__ jsl_clearance_s

  • 第二次带上上次的 cookie,返回 ob 混淆 js 代码

    重新生成__jsl_clearance_s

  • 第三次正常请求,带上第二次的 cookie

瑞数

cookie 名字
最后两位会变,一个服务器返回,一个 js 生成(首字母是数字,数字就是代数)

  • FSSBBIl1UgzbN7N80S
  • FSSBBIl1UgzbN7N80T

两次无限 debugger

第一次页面请求状态码为 202,响应 cookie FSSBBIl1UgzbN7N80S 返回一段包含 js 代码的 html

瑞数四


一个外链 js,一个自执行 (生成 cookie)