过无限 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)
})()
- group(业务类型/接口组)
- 每个业务对应一个 group
- 一个 group 可以注册多个终端(SekiroClient)
- 同一个 group 可以挂载多个 Action(动作/接口)
- clientId(设备标识)
- 用于标识不同的设备
- 支持多个设备同时提供 API 服务
- 提供群控能力和负载均衡功能
- SekiroClient(客户端)
- 服务提供者的客户端,主要应用于手机/浏览器等场景
- 最终的 Sekiro 调用会转发到 SekiroClient
- 每个 client 必须拥有唯一的 clientId
- registerAction(注册接口)
- 用于注册业务接口
- 同一个 group 下可以注册多个接口
- 不同接口可以实现不同的功能
- resolve(响应方法)
- 用于将处理结果返回给服务端的方法
- request(请求对象)
- 包含服务端传递的请求参数
- 如果请求包含多个参数,可以通过键值对的方式获取
- 获取参数后可进行后续处理
- 查看分组列表
http://127.0.0.1:5620/business-demo/groupList
- 用途:查看所有已注册的业务分组
- 查看队列状态
http://127.0.0.1:5620/business-demo/clientQueue?group=rpc-test
- 用途:查看指定分组下的客户端队列状态
- 参数:group=分组名称
- 调用转发
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)
Comments NOTHING