关于 js:6. 网络与加密模块
一、AJAX
AJAX(Asynchronous JavaScript And XML) = 异步 JavaScript 与 XML(现在多为 JSON)
它允许网页在不重新加载整个页面的情况下,从服务器请求数据并更新页面内容。
主要用途:
-
提交表单时无需刷新页面。
-
动态加载评论、搜索建议、分页数据。
-
网页内容分块加载,提高用户体验。
1. AJAX 的核心实现方式:XMLHttpRequest
这是最基础、兼容性最强的 AJAX 实现方式(Fetch 是后来的替代方案)。
基本用法:
let xhr = new XMLHttpRequest();
xhr.open("POST", "https://example.com/api", true);
xhr.setRequestHeader("Content-Type", "application/json");xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {console.log("响应结果:", xhr.responseText);}
};xhr.send(JSON.stringify({ username: "admin", password: "123" }));
🔹 let xhr = new XMLHttpRequest();
创建一个新的 XMLHttpRequest
实例对象,用于发起 HTTP 请求(就是 AJAX 请求的载体)。
🔹 xhr.open("POST", "https://example.com/api", true);
open()
不会发起请求,只是初始化。
-
请求方法是
POST
(表示发送数据) -
请求地址是
https://example.com/api
-
第三个参数
true
表示 异步请求(常见做法,浏览器默认)
🔹 xhr.setRequestHeader("Content-Type", "application/json");
设置请求头。告诉服务器:我发送的是 JSON 数据。
🔹 xhr.onreadystatechange = function () { ... }
注册一个回调函数,监听请求的状态变化。
xhr.readyState
会变化,值可能是:
值 | 含义 |
---|---|
0 | 请求未初始化(尚未调用 open) |
1 | 请求已建立(调用了 open) |
2 | 服务器收到请求头 |
3 | 响应体正在下载 |
4 | 完整响应已收到(最重要) |
🔹 xhr.send(JSON.stringify({ username: "admin", password: "123" }));
-
发起真正的请求,并把数据发给服务器。
-
send()
参数必须是字符串(除非是FormData
等对象)。 -
JSON.stringify(...)
将对象转换为 JSON 字符串。
2. 浏览器和服务器交互
[浏览器 JS]
↓
构造参数(如用户名、密码)
↓
执行加密函数(如 AES、MD5、签名等)
↓
xhr.send(JSON.stringify(加密后的参数))
↓
[HTTP 请求:发送到服务器]
↓
[服务器]
接收密文 → 解密/验签 → 校验身份 → 返回数据
3. 重要属性与方法
属性 / 方法 | 作用说明 |
---|---|
open(method, url) | 初始化请求:设置请求方法和 URL |
setRequestHeader() | 设置请求头(如 Content-Type 、Token) |
send(body) | 发起请求,可传请求体 |
readyState | 请求状态:0-4(4为完成) |
status | 响应状态码(如 200) |
responseText | 响应体(字符串) |
onreadystatechange | 监听状态变化 |
onload | 请求成功触发 |
onerror | 请求失败触发 |
4. XHR 请求完整生命周期(readyState
)
readyState | 含义 |
---|---|
0 | UNSENT:未调用 open() |
1 | OPENED:已调用 open() |
2 | HEADERS_RECEIVED:收到响应头 |
3 | LOADING:下载中 |
4 | DONE:完成(可访问响应数据) |
5. 逆向分析中的关键 Hook 点
在逆向中,XHR 经常是参数加密函数的入口。可以这样监听它的行为:
Hook open
和 send
方法:
// Hook open 方法
const rawOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url, async) {this._url = url; // 保存请求 URLreturn rawOpen.apply(this, arguments);
};// Hook send 方法
const rawSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (body) {console.log("拦截到 XHR 请求:");console.log("URL:", this._url);console.log("Body:", body);debugger; // 可在浏览器中断点分析调用堆栈return rawSend.call(this, body);
};
补充监听 setRequestHeader
const rawSetHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function (key, value) {console.log("Header:", key, value);return rawSetHeader.call(this, key, value);
};
总结
关键点 | 逆向目的 |
---|---|
open(url) | 获取接口地址 |
send(body) | 拦截加密前参数 |
setRequestHeader | 获取 token、sign |
responseText | 如果返回数据加密,这里是解密入口 |
DevTools Initiator | 定位加密函数栈 |
Hook 方法 | 精准抓取请求参数与加密逻辑 |
二、Fetch
Fetch
是现代浏览器提供的基于 Promise 的 HTTP 请求接口,它是 XMLHttpRequest
的替代方案,更简洁、灵活。
1. 基本用法(发起 GET/POST 请求)
GET 请求:
fetch("https://example.com/api/user?id=123").then(response => response.json()).then(data => console.log(data));
POST 请求:
fetch("https://example.com/api/login", {method: "POST",headers: {"Content-Type": "application/json"},body: JSON.stringify({username: "admin",password: "123"})
})
.then(response => response.json())
.then(data => console.log(data));
2. Fetch 请求参数结构
fetch(url, {method: "POST", // 请求方法headers: { // 请求头"Content-Type": "application/json"},body: JSON.stringify(data), // 请求体credentials: "include", // 是否携带 cookiemode: "cors" // 跨域策略
});
参数 | 说明 |
---|---|
method | 请求方法,如 GET/POST |
headers | 请求头,如 token、Content-Type |
body | 请求体(不能用于 GET) |
credentials | 控制是否发送 Cookie:omit / same-origin / include |
mode | 跨域策略:cors / same-origin / no-cors |
3. 浏览器监听 Fetch 请求的方法
fetch
是浏览器原生函数,可以被重写 hook,这是分析参数加密的最佳入口之一。
Hook fetch
方法:
const rawFetch = window.fetch;
window.fetch = async function (...args) {console.log("拦截到 Fetch 请求:");console.log("URL:", args[0]);console.log("配置:", args[1]);debugger; // 断点观察调用栈return rawFetch.apply(this, args);
};
这段代码可以:
-
查看请求地址和加密后的 body
-
定位调用
fetch
前的参数构造函数 -
逆推出加密函数
4. Fetch 请求中的常见参数构造方式
在逆向中,最常见的参数类型包括:
1)JSON.stringify(data)
(大多数接口使用)
body: JSON.stringify({ id: 123, sign: "xxxxx" })
2)FormData
(用于表单上传、模拟 POST)
const form = new FormData();
form.append("username", "admin");
form.append("password", "123");
fetch(url, {method: "POST",body: form
});
3)URLSearchParams
(urlencoded 表单格式)
const params = new URLSearchParams();
params.append("username", "admin");
params.append("password", "123");fetch(url, {method: "POST",headers: { "Content-Type": "application/x-www-form-urlencoded" },body: params
});
4)自定义加密参数
let sign = getSign(username, password, timestamp); // 加密逻辑
body: JSON.stringify({username,password,sign
});
5. Fetch 的响应处理
fetch(url).then(res => res.text()) // 获取原始文本.then(text => {// 有些接口返回加密数据,这里可能是 base64、hex、AES密文console.log("原始响应:", text);});
如果发现响应是乱码或密文,说明:
-
前端还会解密响应数据
-
需要
hook
解密函数或继续抓栈定位
总结
关键点 | 逆向目的 |
---|---|
fetch(url, config) | 拦截请求地址与参数配置 |
body | 发现加密参数、定位加密函数 |
headers | 检查 Token、Content-Type |
debugger + Initiator | 精确找到构造请求的函数 |
fetch hook | 实时监听,抓取请求参数 |
三、XHR
-
AJAX 是一种技术思想,用于“在网页不刷新的情况下与服务器通信”。
-
XHR(XMLHttpRequest) 是实现 AJAX 的核心 API(工具)。
AJAX 是什么?AJAX = Asynchronous JavaScript And XML
中文叫“异步 JavaScript 与 XML”,但其实不只支持 XML,也可以传 JSON、HTML、纯文本。
本质上:AJAX 是一种在网页中异步请求数据,不刷新整个页面的技术方式。
它能实现的效果:
-
用户点击按钮,页面不刷新,但后台偷偷向服务器发请求。
-
比如搜索框自动补全、无限滚动、点赞、评论提交等。
XHR 是什么?XHR(XMLHttpRequest) 是浏览器提供的一个内建对象,用来发 AJAX 请求。
可以用它来:
-
发起 GET、POST 请求
-
设置请求头
-
监听响应
-
获取服务器返回的结果
示例代码:
let xhr = new XMLHttpRequest();
xhr.open("GET", "https://example.com/data", true);
xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {console.log(xhr.responseText); // 响应内容}
};
xhr.send(); // 发起请求
XHR 与 fetch
的区别
对比点 | XHR | fetch |
---|---|---|
是否支持 Promise | 不支持 | 支持 |
是否支持 Stream | 不支持 | 支持 |
请求拦截难度 | 易 hook | 易 hook |
跨域支持 | 支持设置 withCredentials | 使用 credentials |
前端项目中使用频率 | 老项目中多见 | 现代项目主流 |
四、请求监听方式
请求监听(Request Interception) 指的是:我们在浏览器中监听网页内部的网络请求,查看请求内容、加密参数、调用栈,甚至插桩劫持请求逻辑。
逆向的时候,往往看到的是:
xhr.send(加密参数);
但不知道这个加密参数是怎么来的。
监听请求就能:
-
看到最终加密参数的结构
-
追踪调用栈,找到加密函数入口
-
判断是 XHR 还是 Fetch,决定 hook 方案
-
动态打断点、劫持参数、还原加密
方式 1:Chrome DevTools → Network 面板监听
功能:
-
抓包请求(XHR / Fetch / WebSocket)
-
查看请求地址、请求体(payload)、响应内容
-
查看请求是通过什么方式发起的(XHR、Fetch、FormData)
操作:
-
F12 打开控制台
-
切换到 Network
-
刷新页面或触发行为(比如点击按钮)
-
找到类型为
XHR
或Fetch
的请求 -
查看:
-
Request Payload(加密数据)
-
Headers(请求头)
-
Preview / Response(返回数据)
-
Initiator(调用栈,追到加密函数)
-
进阶:
-
右键 → Break on request(触发断点)
-
Initiator → 点击函数跳转到源码
方式 2:Chrome DevTools → Hook XHR / Fetch
Hook XMLHttpRequest:
let open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async) {console.log("拦截请求地址:", method, url);this._url = url; // 保存地址,后面 send 中用return open.apply(this, arguments);
};let send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(body) {console.log("拦截请求体:", this._url, body);debugger; // 打断点分析 bodyreturn send.apply(this, arguments);
};
Hook Fetch:
let realFetch = window.fetch;
window.fetch = function(...args) {console.log("拦截 fetch 请求:", args);debugger;return realFetch.apply(this, args);
};
打开控制台,粘贴运行,然后执行页面动作就能抓到所有请求和加密参数。
方式 3:DevTools → XHR/Fetch 断点监听
操作步骤:
-
打开 DevTools
-
切到 Sources → XHR/Fetch Breakpoints
-
点击右侧的 “Fetch/XHR”
-
执行页面操作,请求发出时自动断点
-
查看调用栈 / 参数加密点 / 函数名
五、FormData
FormData
是浏览器提供的一个类,用来构造 表单格式的请求体,可以模拟 HTML <form>
的提交行为,特别适合文件上传、键值对参数提交。
1. 本质理解:
执行:
let form = new FormData();
form.append("username", "admin");
form.append("password", "123456");
最终发送到服务器的内容是类似于:
Content-Type: multipart/form-data; boundary=----xxx------xxx
Content-Disposition: form-data; name="username"admin
------xxx
Content-Disposition: form-data; name="password"123456
------xxx--
它不是 JSON,不是纯字符串,是“表单格式”,带有 Content-Disposition
和 boundary
边界。
2. 使用方式
基本用法:
let form = new FormData();
form.append("key1", "value1");
form.append("key2", "value2");fetch("https://example.com/api", {method: "POST",body: form
});
上传文件:
let fileInput = document.querySelector("input[type=file]");
let form = new FormData();
form.append("username", "admin");
form.append("avatar", fileInput.files[0]); // 上传文件xhr.send(form); // 以 multipart/form-data 格式发送
3. 如何逆向分析 FormData 参数?
Step 1:在 DevTools → Network → 找到请求类型为 multipart/form-data
会看到:
------WebKitFormBoundary...
Content-Disposition: form-data; name="username"admin
Step 2:Sources 或 Console 中 Hook 或打断点
可以这样 hook:
let append = FormData.prototype.append;
FormData.prototype.append = function(key, value) {console.log("添加参数:", key, value);return append.apply(this, arguments);
};
这段代码会拦截所有 form.append(...)
的行为,打印每个参数,用于分析是否存在加密值或隐藏参数。
总结
特性 | 说明 |
---|---|
自动设置 Content-Type | 浏览器自动添加 boundary,不能手动设置 Content-Type |
支持文件上传 | 可以 .append("file", File对象) |
可用于 XHR 和 fetch | 都可以传入 form 对象作为 body |
不能直接查看字符串内容 | form 不是普通对象,不能 JSON.stringify() |
和 JSON 对比:
项目 | FormData | JSON |
---|---|---|
内容格式 | multipart/form-data | application/json |
是否能传文件 | 是 | 否 |
参数格式 | 键值对(string, Blob) | 任意 JS 对象 |
能否直接 console.log | 否(需遍历) | 是 |
六、URLSearchParams
URLSearchParams
是浏览器提供的内建类,用来构造、解析、修改 URL 查询字符串(?a=1&b=2
这样的格式)。
1. 使用场景
场景 | 示例 |
---|---|
构造请求参数 | POST 请求参数体是 application/x-www-form-urlencoded |
解析 URL | 拿到 location.search 中的参数 |
拼接参数 | 动态生成接口调用的 query 参数 |
示例 1:构造 POST 请求体
let params = new URLSearchParams();
params.append("username", "admin");
params.append("password", "123456");fetch("https://example.com/api", {method: "POST",headers: {"Content-Type": "application/x-www-form-urlencoded"},body: params.toString() // username=admin&password=123456
});
params.toString()
会自动序列化成表单形式的字符串,非常常见于登录、注册、搜索接口。
示例 2:解析 URL 参数
let url = "https://example.com/page?uid=1001&token=abcd123";
let searchParams = new URLSearchParams(new URL(url).search);console.log(searchParams.get("uid")); // "1001"
console.log(searchParams.get("token")); // "abcd123"
2. 常用 API 一览
方法 | 说明 | 示例 |
---|---|---|
append(key, val) | 添加参数 | params.append("a", "1") |
get(key) | 获取参数 | params.get("a") |
set(key, val) | 修改/替换 | params.set("a", "2") |
delete(key) | 删除参数 | params.delete("a") |
has(key) | 是否存在参数 | params.has("a") |
toString() | 序列化字符串 | a=1&b=2 |
for...of 遍历 | 遍历所有参数 | for ([k,v] of params) |
3. 和 FormData
对比
项目 | FormData | URLSearchParams |
---|---|---|
内容类型 | multipart/form-data | application/x-www-form-urlencoded |
是否能传文件 | 是 | 否(只能字符串) |
用于哪类请求 | 上传类 / 文件 / 图片 | 登录、搜索、加密参数 |
能否用 toString() | 否(不能) | 是(生成 query 字符串) |
4. 逆向中如何利用?
在 DevTools 中抓包时会看到:
POST https://example.com/api
Content-Type: application/x-www-form-urlencoded
Request Payload:
username=admin&password=123456&sign=ab12cd34
那么 80% 的概率就是代码里用了:
let p = new URLSearchParams();
p.append("username", "admin");
p.append("password", "123456");
p.append("sign", sign_func("admin123456"));
关键:hook append()
,追踪 sign
来源!
Hook 分析技巧(追踪加密参数):
const _append = URLSearchParams.prototype.append;
URLSearchParams.prototype.append = function(key, value) {console.log("拦截 append 参数:", key, value);debugger; // 查看调用栈,追踪加密return _append.apply(this, arguments);
};
把这段代码贴到控制台后再执行请求,就能看到哪个参数是加密的,再反向分析加密函数。
实战例子:
let token = genToken(username, timestamp);
let params = new URLSearchParams();
params.append("username", username);
params.append("ts", timestamp);
params.append("token", token); // 加密生成xhr.send(params.toString());
抓包看到 token 是 asdu23ask1d21
,就可以在 hook 中抓到 token
的生成值,逆向 genToken()
函数逻辑。
总结
点 | 内容 |
---|---|
用途 | 构造 query 参数字符串(a=1&b=2),用于请求体或 URL |
类型 | application/x-www-form-urlencoded |
特点 | 轻量、易用、可序列化、适合加密拼参 |
逆向核心 | 通过 hook .append() 找加密参数的来源 |
七、Headers 构造方式
Headers
是浏览器提供的接口,用来构造和操作 HTTP 请求头部(Headers),它决定了请求的“身份”、“类型”、“来源”等重要信息。
常见使用场景
-
使用
fetch
或XHR
发起请求时,设置请求头 -
模拟浏览器行为(如添加
User-Agent
,Referer
) -
设置登录凭证(如
Authorization
,Cookie
) -
模拟
Content-Type
(用于表单、JSON、加密数据)
1. 基本用法
1)创建 Headers 对象
let headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("Authorization", "Bearer token_xyz");
2)传入 fetch 请求
fetch("https://example.com/api", {method: "POST",headers: headers,body: JSON.stringify({ username: "admin", password: "123" })
});
或者简写成:
fetch("https://example.com/api", {method: "POST",headers: {"Content-Type": "application/json","Authorization": "Bearer token_xyz"},body: JSON.stringify({ username: "admin", password: "123" })
});
两种写法等价,第二种是对象字面量自动转 Headers
。
常用请求头字段
Header | 说明 | 示例 |
---|---|---|
Content-Type | 请求体格式 | application/json 、application/x-www-form-urlencoded 、multipart/form-data |
User-Agent | 浏览器身份 | 模拟浏览器 UA |
Referer | 来源页面 | 某些接口必须带上指定页面 |
Cookie | 携带登录信息 | 模拟登录态 |
Authorization | token 或 Basic 认证 | Bearer token_xxx 或 Basic xxxxx== |
X-Requested-With | 区分 AJAX | XMLHttpRequest |
Headers API 方法
方法 | 说明 |
---|---|
append(name, value) | 添加头部 |
set(name, value) | 设置或覆盖头部 |
get(name) | 获取某个头部值 |
has(name) | 判断是否存在 |
delete(name) | 删除某个头部 |
forEach() | 遍历所有头部 |
示例:
headers.forEach((value, name) => {console.log(name + ": " + value);
});
和 XMLHttpRequest
配合
let xhr = new XMLHttpRequest();
xhr.open("POST", "/api", true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", "Bearer token_xyz");
xhr.send(JSON.stringify({ data: 123 }));
2. 请求头伪造与逆向中作用
在 JS 逆向/爬虫过程中,经常要模拟浏览器请求头,否则接口会返回 403 或提示“非法请求”:
-
User-Agent
→ 模拟浏览器设备 -
Referer
→ 验证页面来源,防爬关键头部 -
X-Requested-With: XMLHttpRequest
→ AJAX 请求标识 -
Token / Sign / Timestamp
→ 加密参数中放头部
3. 实战逆向技巧
1)DevTools 抓包对比
在 Chrome Network 面板中,点击请求 → Headers → Request Headers:
找到与正常请求差异的头部,例如:
X-Sign: ad83b23a3a 自定义加密头部
X-Time: 17123423452 时间戳
这类参数很可能是 JS 中加密生成的,需要你找到:
headers.append("X-Sign", genSign(data, ts));
2)Hook Headers.append
const oldAppend = Headers.prototype.append;
Headers.prototype.append = function(key, val) {console.log("Header添加:", key, val);debugger; // 查看调用栈,追踪生成过程return oldAppend.apply(this, arguments);
};
拦截加密头部(如 token、sign),反向追踪函数来源。
4. 项目案例
let headers = new Headers();
headers.append("X-Token", encryptToken(username, ts, key));
headers.append("X-Time", ts);
headers.append("Content-Type", "application/json");fetch("/api/protected", {method: "POST",headers: headers,body: JSON.stringify({ username, password })
});
请求被加密了,抓包看到 X-Token
是混淆值,就去找 encryptToken()
的源码,通常是 md5、AES、Base64 自定义函数。
总结
项目 | 内容 |
---|---|
用途 | 构造 HTTP 请求头,设置内容格式、认证、token 等 |
类型 | JS 内建对象,可单独构造,也可直接传对象 |
逆向重点 | 观察 Authorization / X-* 自定义字段是否加密 |
调试技巧 | DevTools 抓包对比 + Hook append() |
八、WebSocket 通信机制与劫持调试
WebSocket 是一种持久化的双向通信协议,适合实时数据交换(如聊天、弹幕、验证码行为上传等),逆向难点在于它绕过了传统 HTTP 抓包,必须使用调试和劫持手段来追踪。
1. WebSocket 与 HTTP 的区别
对比项 | HTTP | WebSocket |
---|---|---|
连接方式 | 请求一次就断开 | 一次连接,持续通信 |
通信方向 | 客户端主动请求 | 双向,服务端也可主动发消息 |
适合场景 | 表单提交、接口调用 | 聊天、弹幕、验证码行为上报 |
请求协议 | http:// / https:// | ws:// / wss:// |
抓包方式 | 普通 HTTP 抓包 | DevTools 或专用代理工具 |
2. WebSocket 使用机制
JS 创建连接
let ws = new WebSocket("wss://example.com/ws");ws.onopen = () => {console.log("连接成功");ws.send(JSON.stringify({ action: "auth", token: "abcd1234" }));
};ws.onmessage = (e) => {console.log("接收到消息:", e.data);
};ws.onerror = (e) => {console.error("发生错误", e);
};ws.onclose = () => {console.log("连接关闭");
};
常见的 WebSocket 用途:
场景 | 数据内容 | 分析目标 |
---|---|---|
验证码行为上传 | 滑动轨迹、坐标、行为时间等 | 找到加密逻辑、格式、字段名 |
聊天通信 | msg、uid、room | 伪造用户发送消息 |
登录验证 | token / device_id | 模拟身份登录 |
3. 如何抓包与调试 WebSocket?
方法一:Chrome DevTools → Network 面板 → WS
-
打开网页
-
按 F12 打开 DevTools → Network → WS(WebSocket)
-
刷新页面
-
找到连接名(一般是
/ws
或/socket
) -
点击查看:
-
Messages:每一条收发的消息(原始/解码后)
-
Frames:查看 WebSocket 的原始数据包
-
Payload:可能是字符串、JSON、加密串
方法二:控制台劫持 WebSocket
拦截发送数据(hook send
方法):
const originSend = WebSocket.prototype.send;
WebSocket.prototype.send = function(data) {console.log("[WS 拦截] 发送内容:", data);debugger; // 追踪发送数据的调用栈return originSend.apply(this, arguments);
};
拦截接收数据(hook onmessage
):
Object.defineProperty(WebSocket.prototype, 'onmessage', {set(fn) {const wrapper = function(event) {console.log("[WS 拦截] 收到消息:", event.data);debugger; // 查看接收的数据结构return fn.call(this, event);};this._onmessage = wrapper;return wrapper;},get() {return this._onmessage;}
});
4. 逆向分析技巧
Step1:确认连接地址和格式
let ws = new WebSocket("wss://xxx.com/socket?token=abc");
有些时候连接参数里就藏着 sign
或者 timestamp
,是加密生成的!
Step2:拦截 send
内容
ws.send(JSON.stringify({type: "track",data: encrypt(轨迹数组)
}));
可以通过 send
hook 抓到:
{"type": "track","data": "ajb19nASDAWJq1da=="
}
然后去找这个加密 encrypt()
函数。
Step3:解构收到的数据(解密)
ws.onmessage = (e) => {let msg = JSON.parse(e.data);let content = decrypt(msg.payload); // 模拟服务端逻辑
};
有些网站是前端解密数据展示,hook onmessage
可以观察服务端主动推送内容。
总结
项目 | 内容 |
---|---|
创建方式 | new WebSocket("wss://xxx") |
通信方式 | send() 发送,onmessage 接收 |
抓包方式 | Chrome DevTools → Network → WS |
劫持方式 | Hook send() / onmessage |
逆向重点 | 抓取并分析发送的数据结构与加密函数 |
九、btoa
/ atob
函数 | 全称 | 作用 |
---|---|---|
btoa() | binary to ASCII | 把字符串 → Base64 编码 |
atob() | ASCII to binary | 把 Base64 → 原始字符串 |
Base64 是一种将二进制数据用 ASCII 字符表示的方法,用于:
-
在网络上传输二进制数据(如图片、密钥)时变为字符串
-
编码数据避免乱码或安全字符(如 POST body、URL 参数)
1. 基本语法
btoa()
let encoded = btoa("hello");
console.log(encoded); // "aGVsbG8="
atob()
let decoded = atob("aGVsbG8=");
console.log(decoded); // "hello"
2. 实际用途举例
示例1:前端加密参数
let data = JSON.stringify({ username: "admin", password: "123" });
let encrypted = btoa(data); // 模拟加密
xhr.send(encrypted); // 加密参数传输
示例2:反爬遇到参数是 Base64
let encoded = "eyJ1c2VybmFtZSI6ImFkbWluIiwicCI6IjEyMyJ9"; // 看起来像一串 base64
let decoded = atob(encoded);
console.log(decoded); // {"username":"admin","p":"123"}
3. 注意事项
限制项 | 说明 |
---|---|
字符集限制 | 只能对 Latin1 范围(0~255)的字符使用,不能直接用于 Unicode 字符(中文) |
中文处理 | 需要用 encodeURIComponent 和 decodeURIComponent 包裹,避免乱码 |
处理中文的写法
// 正确加密中文
function encodeBase64(str) {return btoa(unescape(encodeURIComponent(str)));
}// 正确解密中文
function decodeBase64(base64) {return decodeURIComponent(escape(atob(base64)));
}let encrypted = encodeBase64("你好");
console.log(encrypted); // 5L2g5aW9
console.log(decodeBase64(encrypted)); // 你好
1)encodeURIComponent(str)
- 把 Unicode 字符串编码成 UTF-8 后,以 %XX 形式表示(URL 编码)。
示例:
encodeURIComponent("你好")
// 输出:"%E4%BD%A0%E5%A5%BD"
"你好"
→ UTF-8 编码 → 字节:[0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD]
2)unescape(...)
-
把
%XX
表示的字节还原成 JS 字符串(每字符代表一个字节); -
结果是一个 binary-like 字符串,符合
btoa()
要求。
unescape("%E4%BD%A0%E5%A5%BD")
// 输出:乱码样式的字符串,每个字符的 charCode 在 0~255
3)btoa(...)
- 现在就可以 Base64 编码这个“binary-like”的字符串。
btoa(unescape(encodeURIComponent("你好")));
// 输出:"5L2g5aW9"
这个字符串 "5L2g5aW9"
就是 "你好"
的 UTF-8 → Base64 编码。
总结
方法 | 含义 | 示例 |
---|---|---|
btoa(str) | 字符串 → base64 编码 | btoa('abc') → YWJj |
atob(base64) | base64 → 解码字符串 | atob('YWJj') → abc |
中文处理 | 必须先转 Unicode | encodeURIComponent + unescape |
十、window.crypto
window.crypto
(全名 Web Crypto API)是浏览器提供的原生加密接口,用于执行安全性更高的加密操作,包括:
-
生成随机数(
crypto.getRandomValues
) -
加密/解密(AES、RSA)
-
哈希(SHA-256 等)
-
密钥导入、导出、生成
1. 基本结构
window.crypto.subtle // 提供加密函数
window.crypto.getRandomValues() // 安全随机数生成
-
window.crypto.getRandomValues()
:安全随机数(强于 Math.random) -
window.crypto.subtle
:加密核心接口(SubtleCrypto)
2. 核心用途场景
功能 | 方法 |
---|---|
加密 / 解密 | encrypt , decrypt |
摘要(哈希) | digest |
密钥生成 | generateKey |
密钥导入 / 导出 | importKey , exportKey |
签名 / 验签 | sign , verify |
3. 常用函数详解
1)安全随机数(爬虫滑动轨迹等)
const array = new Uint8Array(10); // 创建一个长度为 10 的 Uint8Array 数组
crypto.getRandomValues(array); // 生成安全随机数并填充数组
console.log(array); // 打印安全随机数数组
2)SHA-256 哈希(常见签名算法)
const data = new TextEncoder().encode("abc123");
// 用 TextEncoder 把字符串 "abc123" 编码成 UTF-8 字节数组(Uint8Array)
// 输出类似:Uint8Array [97, 98, 99, 49, 50, 51]crypto.subtle.digest("SHA-256", data).then(hashBuffer => {
// 使用 Web Crypto API 的 subtle.digest 方法对字节数组进行 SHA-256 哈希计算
// 结果是一个 ArrayBuffer(二进制缓冲区),表示哈希后的原始数据const hashArray = Array.from(new Uint8Array(hashBuffer)); // 把 ArrayBuffer 转换成 Uint8Array,再转成普通数组// 每个元素是一个 0~255 的整数,代表哈希值的每个字节const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // 把每个字节转成两位十六进制字符串,不足两位的用 '0' 补齐// 然后把所有十六进制字符串拼接成一个完整的哈希字符串(长度为64)console.log(hashHex); // 打印最终的 SHA-256 哈希值,格式为 64 位十六进制字符串
});
3)AES-GCM 加密/解密(重点!常用于前端数据加密)
// 生成密钥
const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, // 指定生成 AES-GCM 密钥,密钥长度为 256 位true, // 密钥是否可以导出(true 表示可以)["encrypt", "decrypt"] // 密钥的用途是加密和解密
);// 加密数据
const iv = crypto.getRandomValues(new Uint8Array(12)); // 12字节IV
const encoded = new TextEncoder().encode("hello world");const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, // 使用 AES-GCM 加密算法和生成的 IVkey, // 用之前生成的密钥进行加密encoded // 待加密的明文字节数组("hello world")
);// 解密数据
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, // 使用 AES-GCM 解密算法和相同的 IVkey, // 使用之前生成的密钥进行解密ciphertext // 加密后的密文
);console.log(new TextDecoder().decode(decrypted)); // "hello world"
4. 注意事项
限制 | 描述 |
---|---|
只能异步操作 | 所有方法都返回 Promise |
二进制为主 | 输入输出都为 ArrayBuffer 类型 |
无法直接处理字符串 | 需配合 TextEncoder 和 TextDecoder |
5. JS逆向中出现的表现形式
经常会在网站中看到这样的代码片段:
await window.crypto.subtle.digest("SHA-256", data);
await window.crypto.subtle.encrypt({name: "AES-GCM", iv}, key, plaintext);
或者压缩/混淆后是:
e = window.crypto.subtle;
t = e.encrypt({name:"AES-GCM",iv:a}, n, r)
这些往往是在加密签名参数 w、payload、token 前执行的过程,必须跟踪输入输出、密钥、IV(偏移量)来逆推加密逻辑。
总结
功能 | 方法 | 用途 |
---|---|---|
随机数 | getRandomValues | 滑动轨迹、IV 生成 |
哈希 | digest | 签名摘要,如 SHA-256 |
加密 | encrypt | AES / RSA 加密请求参数 |
解密 | decrypt | 解密服务器回传数据 |
密钥导入 | importKey | 字符串密钥导入使用 |
密钥生成 | generateKey | 通信过程生成密钥 |
十一、自定义 base64 / md5 / AES / RSA 实现
1. 自定义 base64
编码/解码
base64
编码实现
function encodeBase64(str) {let charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // 创建 Base64 编码字符集:包含大写字母、小写字母、数字和 "+"、"/" 两个符号。// 这个字符集是 Base64 编码中使用的标准字符集。let binaryStr = ""; // 用于存储将字符串转换成的二进制表示的变量。// 转为二进制字符串for (let i = 0; i < str.length; i++) {binaryStr += str.charCodeAt(i).toString(2).padStart(8, '0');}// 遍历输入字符串的每个字符// `charCodeAt(i)` 获取字符的 Unicode 编码(整数),然后用 `.toString(2)` 转换为二进制字符串// `padStart(8, '0')` 确保每个二进制字符串都是 8 位,不足的补充 '0'// 例如:'h' -> 104 (charCodeAt),转为 '01101000' (8 位)let base64Str = ""; // 用于存储最终的 Base64 编码结果。// 将二进制字符串按 6 位一组进行处理for (let i = 0; i < binaryStr.length; i += 6) {let chunk = binaryStr.slice(i, i + 6); // 每次取 6 位二进制// 例如:'011010'、'000110'、'101100',等let index = parseInt(chunk, 2); // 将 6 位二进制字符串转换为十进制整数// 例如:'011010' -> 26(十进制)base64Str += charSet.charAt(index); // 使用得到的索引在 Base64 字符集(charSet)中找到相应字符// 例如:26 -> 'a'(字符集中的第 27 个字符)}// 添加 "=" 补齐while (base64Str.length % 4) {base64Str += "=";}// Base64 编码的最终结果长度必须是 4 的倍数,若不足,使用 '=' 字符进行补齐。// 例如:如果最后的编码长度是 6,则需要补充 2 个 '=',长度变为 8。return base64Str; // 返回最终的 Base64 编码字符串
}console.log(encodeBase64("hello")); // "aGVsbG8="
base64
解码实现
function decodeBase64(base64Str) {let charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // 创建 Base64 字符集,包含大写字母、小写字母、数字以及 '+' 和 '/' 两个符号。// 这是用于解码 Base64 字符串时的标准字符集。let binaryStr = ""; // 用于存储将 Base64 字符串转换成的二进制字符串。// 去掉补充的 "="base64Str = base64Str.replace(/=/g, ""); // 去掉字符串中的 '=' 补齐符号,因为它们只是为了保证 Base64 字符串的长度为 4 的倍数。// `replace(/=/g, "")` 会去除所有的 '='。// 转回二进制字符串for (let i = 0; i < base64Str.length; i++) {let index = charSet.indexOf(base64Str.charAt(i)); // `charSet.indexOf()` 获取当前 Base64 字符在字符集中的索引位置。// 例如:'a' 在字符集中的索引是 0,'b' 是 1,依此类推。binaryStr += index.toString(2).padStart(6, '0'); // 将索引转换为二进制字符串,并通过 `padStart(6, '0')` 确保每个二进制字符串是 6 位(不足补 '0')。// 例如:索引 0 转换为二进制是 '000000',索引 1 转换为二进制是 '000001',等等。}let decodedStr = ""; // 用于存储最终解码得到的原始字符串。// 将二进制字符串按 8 位一组解码for (let i = 0; i < binaryStr.length; i += 8) {let chunk = binaryStr.slice(i, i + 8); // 每次取 8 位二进制(一个字节)// 例如:'01101000','01100101' 等decodedStr += String.fromCharCode(parseInt(chunk, 2)); // `parseInt(chunk, 2)` 将 8 位二进制转换成十进制整数。// `String.fromCharCode()` 将十进制整数转换为字符。// 例如:'01101000' -> 104 -> 'h','01100101' -> 101 -> 'e',等等。}return decodedStr; // 返回解码后的原始字符串
}console.log(decodeBase64("aGVsbG8=")); // "hello"
2. 自定义 MD5
实现
MD5
(消息摘要算法)是常用于数据完整性校验的哈希算法,通常用于生成数据的唯一标识符。
MD5 算法实现
function md5(str) {// 定义常量和初始化变量const hex_chars = "0123456789abcdef";let K = [];let s = [];let i = 0;let x = [];for (i = 0; i < 64; i++) {if (i < 16) K[i] = Math.pow(2, 32 - i) - 1;else K[i] = Math.pow(2, 64 - i) - 1;}// 对数据进行填充和分块处理str = str + String.fromCharCode(0x80);return str;
}
注意: MD5
的实现较为复杂,这只是一个简化的框架。完整的 MD5
算法需要处理数据填充、分块、初始哈希值等多个步骤,通常我们会用现成的库如 CryptoJS
来实现 MD5
。
3. 自定义 AES
加密/解密
AES
(高级加密标准)是一种对称加密算法,用于加密和解密数据。你可以使用 JavaScript 提供的 Web Crypto API 进行 AES 加密,但也可以自定义实现。下面是自定义 AES
的实现方式:
AES 加密与解密实现(使用 crypto-js
库)
// 引入 CryptoJS 库
// <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1-crypto-js.js"></script>// AES 加密
function encryptAES(plaintext, key) {let encrypted = CryptoJS.AES.encrypt(plaintext, key);return encrypted.toString();
}// AES 解密
function decryptAES(ciphertext, key) {let bytes = CryptoJS.AES.decrypt(ciphertext, key);return bytes.toString(CryptoJS.enc.Utf8);
}let key = "mysecretkey12345"; // 密钥
let plaintext = "hello world"; // 明文let encrypted = encryptAES(plaintext, key);
console.log("Encrypted: ", encrypted);let decrypted = decryptAES(encrypted, key);
console.log("Decrypted: ", decrypted); // 输出: hello world
4. 自定义 RSA
加密/解密
RSA
是一种非对称加密算法,通常用于安全的密钥交换。与对称加密算法(如 AES)不同,RSA
使用一对密钥(公钥和私钥)。下面是 RSA
加密的实现:
RSA 加密/解密实现(使用 jsrsasign
库)
由于 RSA
算法涉及复杂的数学运算,JavaScript 并没有原生的实现。通常我们使用如 jsrsasign
等库来实现。
<script src="https://cdn.rawgit.com/kjur/jsrsasign/master/jsrsasign-all-min.js"></script><script>// 生成 RSA 密钥对var rsa = new RSAKey();rsa.generate(1024, '10001'); // 生成 1024 位的 RSA 密钥对// 使用公钥加密var encrypted = rsa.encrypt("hello world");console.log("Encrypted: ", encrypted);// 使用私钥解密var decrypted = rsa.decrypt(encrypted);console.log("Decrypted: ", decrypted); // 输出: hello world
</script>
总结
1)base64
编码/解码
-
将二进制数据转换为 ASCII 字符串,常用于 HTTP 请求、数据传输中。
-
编码和解码都基于字符集,并通过二进制和 Base64 字符集的转换来实现。
2)MD5
哈希
-
用于生成数据的唯一标识符(哈希值),通常用于校验数据完整性。
-
MD5
会将输入数据转为 128 位哈希值,但具有碰撞漏洞,已经不再推荐用于安全应用。
3)AES
加密/解密
-
对称加密算法,广泛应用于数据保护。使用同一个密钥进行加密和解密。
-
实现时通常需要选择加密模式(如 CBC、GCM 等)。
4)RSA
加密/解密
-
非对称加密算法,公钥用于加密,私钥用于解密。用于保护数据传输或密钥交换。
-
实现时涉及较为复杂的数学运算,常常依赖现成库(如
jsrsasign
)。
十二、流量还原与加密函数逆推
流量还原与加密函数逆推,其目标是分析网页或 APP 的加密请求数据是如何构造出来的,并通过调试还原出参数生成的全过程,以便模拟请求爬虫数据。
1. 目标与关键点
目标:
通过抓包和调试,找出:
-
哪些参数是加密的(如:
sign
,token
,w
,encrypt_data
等) -
参数是如何加密的(逻辑路径、加密算法、依赖变量)
-
还原整个 JS 加密函数(逆向核心)
2. 核心步骤详解
步骤 1:打开 DevTools,抓取目标请求
-
F12 打开 DevTools → 进入「Network」面板
-
找到目标接口(通常是 XHR 或 Fetch)
-
查看请求参数,判断哪些可能是加密参数
例子:
POST https://api.example.com/data
Form Data:
{"username": "admin","timestamp": 1715100000,"sign": "abc1234fdasf1adf3q1rq" ← 可疑(加密)
}
步骤 2:关键点定位:打断点或 console.log
重点跟踪:
-
XMLHttpRequest.prototype.send
-
fetch
-
参数构造位置(FormData / JSON.stringify)
-
CryptoJS.MD5()
,btoa()
,AES.encrypt()
,window.crypto
等调用
方法一:XHR 断点(推荐)
-
在 DevTools → Source 面板,按快捷键
Ctrl+Shift+F
-
搜索关键词:
-
send(
-
fetch(
-
XMLHttpRequest
-
sign
,token
,encrypt
,AES
,MD5
,Base64
-
-
选中一处调用 → 右键 "Add breakpoint"
方法二:Breakpoint → XHR/DOM 改写 hook(高级)
// 拦截 send 函数
const oldSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (body) {console.log("请求参数:", body); // 找加密入口debugger; // 打断点return oldSend.apply(this, arguments);
};
步骤 3:追踪加密函数
目标:从 send(body)
的 body
里,找到加密字段的生成路径,逐步往上追。
常见路径示例:
let params = {username: "admin",timestamp: Date.now(),
};
params.sign = md5(params.username + params.timestamp + "secret_key");xhr.send(JSON.stringify(params));
要逐步分析:
-
sign 是怎么计算的?
-
有没有 secret_key?在哪里?
-
是 md5、AES、RSA 还是自定义加密?
步骤 4:控制台调试还原逻辑
在控制台里调用关键函数,手动试验参数是否能得到一致结果:
// 控制台验证
md5("admin1715100000secret_key") === 请求中 sign
验证成功 → 说明已经逆向出加密逻辑
步骤 5:封装爬虫逻辑
# 用 Python 模拟构造请求参数
import hashlib, time, requeststimestamp = str(int(time.time() * 1000))
sign = hashlib.md5(("admin" + timestamp + "secret_key").encode()).hexdigest()data = {"username": "admin","timestamp": timestamp,"sign": sign,
}res = requests.post("https://api.example.com/data", json=data)
print(res.text)
3. 实战补充
1)美化/格式化混淆代码
-
页面压缩严重时,使用
Ctrl + P
→ 搜索.js
-
使用 Prettify (右键 → "格式化源代码") 重排代码结构
2)使用 debugger
动态插入断点
if (param.key === "target") {debugger;
}
3)Hook 常用加密函数
// Hook CryptoJS.MD5
CryptoJS.MD5 = (function (oldFn) {return function (input) {console.log("MD5 输入:", input);const res = oldFn(input);console.log("MD5 输出:", res.toString());return res;};
})(CryptoJS.MD5);
4. 常见加密参数入口点
参数名 | 常见算法 | 来源字段 |
---|---|---|
sign | md5 / hmac | token + 时间戳 + secret |
w | AES+Base64 | 行为参数、滑动轨迹 |
token | RSA / AES | 登录时保存的密钥 |
x-token | window.crypto | WebCrypto 生成非对称密钥 |
5. 总结流程图
DevTools - Network 抓包↓
找出加密参数(如 sign)↓
Sources 中全局搜索 send / fetch / sign↓
打断点 + 控制台 log 参数↓
分析加密过程(追函数 + 算法 + 依赖变量)↓
用 Python 模拟复现(MD5/AES/RSA 等)
相关文章:
关于 js:6. 网络与加密模块
一、AJAX AJAX(Asynchronous JavaScript And XML) 异步 JavaScript 与 XML(现在多为 JSON) 它允许网页在不重新加载整个页面的情况下,从服务器请求数据并更新页面内容。 主要用途: 提交表单时无需刷新页…...
量化交易系统开发经验分享--回测框架调研
一、前言 这段时间在集中做一个量化交易系统的开发任务,目前系统的MVP已经完成开发,后续会整理一些经验与成果和大家交流。刚好有一个前期做策略回测这块的调研,下面把调研的成果做一个整理总结先给大家分享一下,请批评指正。 在介…...
[学习]RTKLib详解:convkml.c、convrnx.c与geoid.c
本文是 RTKLlib详解 系列文章的一篇,目前该系列文章还在持续总结写作中,以发表的如下,有兴趣的可以翻阅。 [学习] RTKlib详解:功能、工具与源码结构解析 [学习]RTKLib详解:pntpos.c与postpos.c [学习]RTKLib详解&…...
【ajax基础】
提示:文章为 学习过程中的记录实践笔记。有问题欢迎指正。 文章目录 前言一、实现步骤二、完整示例三、封装总结 前言 AJAX 不是编程语言,是一种从网页访问web服务器的技术。 可以实现不刷新页面更新网页 在页面加载后从服务器请求/获取数据 在后台向服…...
Nodejs核心机制
文章目录 前言 前言 结合 Node.js 的核心机制进行说明: 解释事件循环的各个阶段。 答案 Node.js 事件循环分为 6 个阶段,按顺序执行: Timers:执行 setTimeout 和 setInterval 的回调。 Pending I/O Callbacks:处理系…...
Kubernetes 集群部署应用
部署 Nginx 应用 命令行的方式 1. 创建 deployment 控制器的 pod # --imagenginx:这个会从 docker.io 中拉取,这个网站拉不下来 # kubectl create deployment mynginx --imagenginx# 使用国内镜像源拉取 kubectl create deployment mynginx --imaged…...
【Linux篇】高并发编程终极指南:线程池优化、单例模式陷阱与死锁避坑实战
深入理解线程池设计与应用:高效并发编程的秘密 一. 线程池1.1 什么是线程池1.2 线程池的优点1.3 线程池的应用场景 二. 线程池设计三. 单例模式3.1 什么是单例模式3.2 单例模式特点3.3 实现单例模式方法3.3.1 饿汉实现⽅式3.3.2 懒汉实现⽅式 四. 线程安全和重入问题…...
学习和测试WebApi项目限制客户端ip访问接口(基于中间件)
WebApi项目需要限制仅允许有限的客户端访问接口,百度相关内容,网上很多介绍WebApi接口IP限流的文章,稍微调整就能用于限制IP访问,微软官网中也有文章介绍客户端 IP 安全列表(参考文献1),可以通过…...
闲鱼智能客服机器人-实现闲鱼平台7×24小时自动化值守
专为闲鱼平台打造的AI值守解决方案,实现闲鱼平台724小时自动化值守,支持多专家协同决策、智能议价和上下文感知对话。 🌟 核心特性 智能对话引擎 功能模块技术实现关键特性上下文感知会话历史存储轻量级对话记忆管理,完整对话历…...
Apache Ranger 2.2.0 编译
安装包下载: https://ranger.apache.org/download.html 编译环境: Linux centos7jdk 1.8maven 3.9.6gitpython 3 git 安装 yum -y install gitpython3安装 yum install epel-release -y yum install python3 python3-devel -y批量安装开发工具套件 …...
实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API
实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API 理论千遍,不如动手一遍!在前面几篇文章中,我们了解了 Serverless 的概念、FaaS 的核心原理以及 BaaS 的重要作用。现在,是时候把这些知识运用起来,亲手构建一个简单但完整的 Serverless 应用了。 …...
鱼眼相机生成-BEV鸟瞰图-入门教程
目录 原理介绍 1. IPM与BEV转换的核心原理 2. 尺度信息的来源 3. 尺度信息的准确性限制 4. 实际应用中的处理方法 代码实现: 360 BEV环视拼接算法 一、核心算法流程 三、实际应用挑战与优化 四、开源实现参考 原理介绍 1. IPM与BEV转换的核心…...
设计模式简述(十八)享元模式
享元模式 描述基本组件使用 描述 当内存中存在大量类似的对象时,可以考虑使用享元模式减少整体内存占用。 可以将相同的部分和不同的部分进行拆分,以达到多个对象共享相同部分内存的目的。 基本组件 通常享元对象通过共享的属性映射一个享元对象。 公…...
Google语法整理
以下是从整理出的 Google 语法: site:指定域名,如 “apache site:bbs.xuegod.cn”,可查询网站的收录情况 。 inurl:限定在 url 中搜索,如 “inurl:qq.txt”,可搜索 url 中包含特定内容的页面&a…...
【每日一题 | 2025年5.5 ~ 5.11】搜索相关题
个人主页:Guiat 归属专栏:每日一题 文章目录 1. 【5.5】P3717 [AHOI2017初中组] cover2. 【5.6】P1897 电梯里的尴尬3. 【5.7】P2689 东南西北4. 【5.8】P1145 约瑟夫5. 【5.9】P1088 [NOIP 2004 普及组] 火星人6. 【5.10】P1164 小A点菜7. 【5.11】P101…...
【MySQL】页结构详解:页的大小、分类、头尾信息、数据行、查询、记录及数据页的完整结构
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
C++ stl中的priority_queue的相关函数用法
文章目录 priority_queuepriority_queue定义方式priority_queue相关函数 priority_queue priority_queue 称为 优先级队列,默认使用vector作为底层存储数据的容器,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用…...
软件架构师知识点总结
一、综合知识 软件架构师综合知识总结-CSDN博客 二、案例 软件架构师案例知识点总结-CSDN博客 三、论文 1、题目类型:八大架构;系统开发(开发方法/模型、需求分析、测试等);系统可靠性、安全性、容错技术等&#…...
MySQL数据库常见面试题之三大范式
写在前面 此文章大部分不会引用最原始的概念,采用说人话的方式。 面试题:三大范式是什么?目的是什么?必须遵循吗? 假设有一张表(学号,姓名,课程,老师) 是…...
Scrapy 核心组件解析:Request Response 的深度应用与实战
Scrapy 是 Python 生态中最强大的爬虫框架之一,其核心组件 Request 和 Response 承担着数据抓取与处理的关键任务。本文深入解析 Scrapy 2.13.0 中 Request 和 Response 的高级用法,涵盖参数配置、回调函数、错误处理、子类扩展等,并结合 综合…...
mybatis执行sql过程
一、配置加载阶段 1. 读取全局配置(mybatis-config.xml) 入口类:SqlSessionFactoryBuilder.build()关键组件: XMLConfigBuilder:解析全局配置文件。Configuration:存储所有配…...
OceanBase 4.3版本向量数据库部署
OceanBase 4.3版本向量数据库部署 安装包准备最低资源配置重要的准备事项服务器配置操作系统内核参数BIOS设置磁盘挂载网卡设置 部署OAT工具初始化OBServer服务器使用oatcli部署OB集群安装OceanBase软件初始化OceanBase集群 启用向量检索功能 OceanBase最新的V4.3版本开始支持向…...
LeetCode 941. 有效的山脉数组 java题解
https://leetcode.cn/problems/valid-mountain-array/description/ 双指针 class Solution {public boolean validMountainArray(int[] arr) {int lenarr.length;if(len<3) return false;int left0,rightlen-1;while(left1<len&&arr[left]<arr[left1]){left…...
基于Java和高德开放平台的WebAPI集成实践-以搜索POI2.0为例
目录 前言 一、高德搜索API简介 1、高德开放平台 2、搜索功能介绍 3、部分API介绍 二、Uniapi集成高德API 1、API集成流程 2、访问接口的定义 3、业务调用集成 三、常见问题与优化 四、总结 前言 在当今数字化时代,地理信息系统(GISÿ…...
Docker拉取ubuntu22.04镜像使用ROS2 humble及仿真工具可视化进行导航
创建Ubuntu22.04 容器 docker pull ubuntu:22.04 #下载22.04镜像 docker images #查看已下载镜像 #根据镜像创建容器 sudo docker run -it -v /home/lab118/BD_ICL/tools_BD/cailib_data:/calib_data -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY:0 --nethost -e GDK_SCAL…...
PXE安装Ubuntu系统
文章目录 1. 服务器挂载Ubuntu镜像2. 修改dhcp配置文件3. 修改tftp配置文件4.复制网络驱动文件和其他配置文件5. http目录下配置文件6. 踩坑记录6.1 Failed to load ldlinux.c326.2 no space left on device6.3 为啥用pxe安装系统时,客户端需要较大的内存࿱…...
外网访问内网海康威视监控视频的方案:WebRTC + Coturn 搭建
外网访问内网海康威视监控视频的方案:WebRTC Coturn 需求背景 在仓库中有海康威视的监控摄像头,内网中是可以直接访问到监控摄像的画面,由于项目的需求,需要在外网中也能看到监控画面。 实现这个功能的意义在于远程操控设备的…...
缓存局部性保留
在操作系统中,线程切换相比进程切换更轻量级的关键原因之一是 缓存(Cache)的有效性,尤其是对 CPU 缓存(如 L1/L2/L3)和 TLB(Translation Lookaside Buffer)的影响。以下从缓存角度详…...
MyBatis源码解读5(3.1、缓存简介)
3.1、简介 我们需要记住一句话,程序与数据库之间的交互是性能瓶颈的关键,所以我们在做优化的时候,数据库的优化要做,但是优先级是最低的,比它优先级高的是方面是程序与数据库之间的交互,先从宏观上解决…...
【MySQL】行结构详解:InnoDb支持格式、如何存储、头信息区域、Null列表、变长字段以及与其他格式的对比
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
Docker 部署Nexus仓库 搭建Maven私服仓库 公司内部仓库
介绍 Nexus 是广泛使用的仓库管理工具,常用于管理 Java 构件(如 JAR、WAR、EAR 文件)。它可以作为一个本地的 Maven 仓库,用来存储和管理项目的依赖包和构建产物。支持多种仓库类型,能够帮助开发团队更高效地管理构件…...
PostgreSQL 的 pg_column_size 函数
PostgreSQL 的 pg_column_size 函数 pg_column_size 是 PostgreSQL 提供的一个系统函数,用于返回特定列或值在数据库内部存储时所占用的字节数。这个函数对于数据库优化、存储空间分析和性能调优非常有用。 函数语法 pg_column_size(anyelement)参数说明 anyele…...
【前端】【HTML】【总复习】一万六千字详解HTML 知识体系
🌐 HTML 知识体系 一、HTML 基础入门 1. HTML 简介与作用 HTML(HyperText Markup Language,超文本标记语言)是构建网页的基础语言。它的核心作用是: 定义网页内容的结构(标题、段落、图片、表格等)提供语义化标签,帮助搜索引擎与辅助设备理解页面内容配合 CSS 实现…...
支持向量机与逻辑回归的区别及 SVM 在图像分类中的应用
支持向量机与逻辑回归的区别及 SVM 在图像分类中的应用 在机器学习的多元算法领域中,支持向量机(SVM)和逻辑回归(LR)作为两种经典的监督学习算法,被广泛应用于各类分类任务。尽管它们有着相似的目标&#…...
MySQL基础面试题集锦
MySQL基础面试题集锦 一、SQL基础语法 1. 数据库和表操作 -- 创建数据库 CREATE DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;-- 创建表 CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL UNIQUE,email VARCH…...
【网络分析工具】网络工具wireshark、TCPdump、iperf使用详解
这里写目录标题 1. wireshark1.1. 过滤包1.2. 常见分析 2. tcpdump3. iperf 1. wireshark **ip.dst eq 10.0.0.21** 是用于网络流量分析工具(例如 Wireshark 或 tcpdump)的过滤器表达式。 它的作用是筛选出所有目标IP地址为 10.0.0.21 的数据包 IP.add…...
【漫话机器学习系列】253.超平面(Hyperplane)
超平面(Hyperplane)详解:从二维到高维空间 在机器学习、深度学习、支持向量机(SVM)等领域中,经常会遇到一个重要的数学概念——超平面(Hyperplane)。但超平面究竟是什么?…...
#微调重排序模型:Reranking从入门到实践
🧠 向所有学习者致敬! “学习不是装满一桶水,而是点燃一把火。” —— 叶芝 我的博客主页: https://lizheng.blog.csdn.net 🌐 欢迎点击加入AI人工智能社区! 🚀 让我们一起努力,共创…...
vector 常见用法及模拟
文章目录 1. vector的介绍与使用1.1 vector的构造1.2 vector iterator 的使用1.3 有关大小和容量的操作1.4 vector 增删查改1.5 vector 迭代器失效问题(重点)1.6 vector 中二维数组的使用 2. vector 的模拟实现2.1 拷贝构造和赋值重载的现代写法2.2 memc…...
可变参数模板
引入:为什么会有可变参数模板? 在C98/03中,也就是我们之前学的类模版和函数模版中只能含固定数量的模版参数,而C11中新增的可变参数模板能够不固定数量的模版参数! 一:可变参数模板的格式 // Args是一个模板…...
缓存(5):常见 缓存数据淘汰算法/缓存清空策略
主要的三种缓存数据淘汰算法 FIFO(first in first out):先进先出策略,最先进入缓存的数据在缓存空间不够的情况下(超出最大元素限制)会被优先被清除掉,以腾出新的空间接受新的数据。策略算法主要比较缓存元素的创建时…...
深入了解linux系统—— 自定义shell
shell的原理 我们知道,我们程序启动时创建的进程,它的父进程都是bash也就是shell命令行解释器; 那bash都做了哪些工作呢? 根据已有的知识,我们可以简单理解为: 输出命令行提示符获取并解析我们输入的指令…...
【通讯录教程】如何将号码快速导入手机通讯录,支持苹果和安卓手机,一次性导入大量号码进入手机通讯录,基于WPF的解决方案
以下是一个基于WPF的解决方案,用于将大量号码快速导入苹果和安卓手机通讯录: 项目应用场景 企业员工通讯录批量导入:HR需要将数百名员工的联系方式快速导入公司手机客户关系管理:销售人员需要将大量客户信息导入…...
Git初始化相关配置
Git配置 在Git安装完成后,windows操作系统上会多出一个Git Bash的软件,如果是linux或者是macOS,那么直接打开终端,在终端中敲击命令即可 # 检查git版本 git -v # 或 git --version在使用git时,需要配置一下用户名和邮…...
n8n中订阅MQTT数据
第一步:创建mqtt登录证证 第二步:创建mqtt trigger组件,并配置凭证和订阅主题 第三步:创建Code节点,编写格式转换代码 第四步:创建转发MQTT节点,并配置MQTT凭证 第五步:启用工作流 整…...
Docker、ECS 与 K8s 网段冲突:解决跨服务通信中的路由问题
🧩 问题背景 在阿里云的项目中,在项目初期搭建过程中遇到了一个让人头疼的网络冲突问题:同一个 VPC 中的 Docker 容器和 Kubernetes 集群由于使用相同的网段,导致k8s pod连接ECS容器之间的网络连接失败。 背景环境: …...
《智能网联汽车 自动驾驶系统设计运行条件》 GB/T 45312-2025——解读
目录 1. 标准概述 2. 核心概念 3. 标准核心内容 3.1 一般要求 3.2 ODC基础元素层级 3.3 ODC元素具体要求 3.4 附录A(ODC示例) 4. 技术挑战与实施建议 5. 标准意义 原文链接:国家标准|GB/T 45312-2025 (发布:2…...
AARRR用户增长模型(海盗指标)详解
目录 一、模型起源与概述二、五大阶段详解1. 获取(Acquisition)1.1 定义1.2 关键指标 2. 激活(Activation)2.1 定义2.2 关键指标 3. 留存(Retention)3.1 定义3.2 关键指标3.3 提升留存手段案例3.4 互联网留…...
CSS专题之自定义属性
前言 石匠敲击石头的第 12 次 CSS 自定义属性是现代 CSS 的一个强大特性,可以说是前端开发需知、必会的知识点,本篇文章就来好好梳理一下,如果哪里写的有问题欢迎指出。 什么是 CSS 自定义属性 CSS 自定义属性英文全称是 CSS Custom Proper…...
JVM——Java字节码基础
引入 Java字节码(Java Bytecode)是Java技术体系的核心枢纽,所有Java源码经过编译器处理后,最终都会转化为.class文件中的字节码指令。这些指令不依赖于具体的硬件架构和操作系统,而是由Java虚拟机(JVM&…...