JS `WebTransport` (HTTP/3) `QUIC` 协议栈逆向与流量分析

好的,各位观众老爷们,今天咱们来聊聊JS WebTransport (HTTP/3) QUIC 协议栈的逆向与流量分析。这玩意儿听起来高大上,但其实就是个让你的网页应用更快更流畅地跟服务器眉来眼去的家伙。准备好了吗?咱们这就开车!

第一部分:WebTransport 是个啥?

想象一下,你正在用微信跟朋友聊天,消息嗖嗖地来,嗖嗖地走,延迟低得让你感觉像面对面聊天。WebTransport 就是想在网页应用里实现类似的效果。

  • 传统的 HTTP/1.1 和 HTTP/2 有啥问题?

    • 延迟高: 每次请求都要建立新的连接,建立连接就要握手,握手就要时间,时间就是金钱啊!
    • 不支持双向通信: 只能客户端发请求,服务端回复。想让服务端主动推数据给客户端?没门!需要长轮询或者 WebSocket 这种“歪门邪道”。
    • 队头阻塞(Head-of-Line Blocking): 如果一个 TCP 包丢了,后面的包都要等着。
  • WebTransport 的优势

    • 低延迟: 基于 QUIC 协议,连接建立速度快如闪电。
    • 双向通信: 服务器可以主动给客户端推送数据,就像微信发消息一样。
    • 多路复用: 可以在一个连接上同时传输多个数据流,互不影响,妈妈再也不用担心队头阻塞了。
    • 可靠性保证: 可以选择可靠传输和不可靠传输。

第二部分:QUIC 协议:WebTransport 的基石

QUIC (Quick UDP Internet Connections) 是 Google 发明的,现在已经成了 IETF 的标准。它是 WebTransport 的底层传输协议,负责把数据可靠地送到对方手上。

  • QUIC 的核心特性

    • 基于 UDP: 抛弃了传统的 TCP,直接用 UDP,更加灵活。
    • TLS 1.3 加密: 从一开始就加密,安全性有保障。
    • 连接迁移: 即使你的 IP 地址变了(比如你从 Wi-Fi 切换到 4G),连接也不会断。
    • 前向纠错(FEC): 允许丢失少量数据包,而无需重新传输,提高了抗丢包能力。
    • 拥塞控制: 防止网络拥塞,根据网络状况动态调整发送速率。
  • QUIC 协议的帧结构

    QUIC 把数据分成一个个帧来传输。帧的类型有很多,比如:

    | 帧类型 | 描述
    | INITIAL | QUIC 连接建立的第一个包,包含连接 ID 等信息。 WebTransport 和 HTTP/3 之间的关系,类似于 WebSocket 和 HTTP/1.1 的关系。

第三部分:JS WebTransport API 初探

现在浏览器已经支持 WebTransport API 了,我们可以用 JS 代码来玩玩。

  • 创建 WebTransport 连接

    const url = 'https://example.com:4433/demo'; // WebTransport 服务器地址
    const transport = new WebTransport(url);
    
    transport.ready
      .then(() => {
        console.log('WebTransport 连接已建立!');
      })
      .catch((error) => {
        console.error('WebTransport 连接失败:', error);
      });
    
    transport.closed.then(() => {
        console.log("WebTransport 连接已关闭");
    }).catch((error) => {
        console.error("WebTransport 连接关闭失败", error);
    });

    这段代码会尝试连接到 https://example.com:4433/demo 这个 WebTransport 服务器。transport.ready 是一个 Promise,当连接建立成功时,它会 resolve。 transport.closed 也是一个 Promise, 当连接关闭时 resolve.

  • 创建可靠的单向流(Reliable Unidirectional Streams)

    这种流只能单向传输数据,而且保证数据可靠到达。

    transport.ready.then(() => {
      const stream = transport.createUnidirectionalStream();
      const writer = stream.getWriter();
    
      writer.write(new TextEncoder().encode('Hello, WebTransport!'))
        .then(() => {
          console.log('数据已发送!');
          return writer.close();
        })
        .catch((error) => {
          console.error('发送数据失败:', error);
        });
    });

    这段代码创建了一个单向流,然后向服务器发送了一段文本消息。

  • 创建不可靠的单向流(Unreliable Datagrams)

    这种方式适合传输实时性要求高,但可以容忍少量数据丢失的场景,比如游戏里的位置信息。

    transport.ready.then(() => {
      const encoder = new TextEncoder();
      const data = encoder.encode('Hello, unreliable world!');
      transport.datagrams.writable.getWriter().then(writer => {
        writer.write(data);
        writer.close();
      });
    });
  • 接收数据

    transport.ready.then(() => {
        transport.incomingUnidirectionalStreams.pipeTo(new WritableStream({
            write: async (stream) => {
                const reader = stream.getReader();
                let result;
                let decoder = new TextDecoder();
    
                while (!(result = await reader.read()).done) {
                    const value = decoder.decode(result.value);
                    console.log("Received:", value);
                }
                console.log("Stream complete");
            }
        }));
    });

    这段代码监听服务器发送过来的数据,并将其打印到控制台。

第四部分:WebTransport 流量分析

想知道 WebTransport 的数据是怎么在网络上传输的?我们需要抓包工具来帮忙。

  • 抓包工具

    • Wireshark: 强大的网络协议分析工具,可以抓取和分析各种网络流量。
    • Chrome DevTools: 浏览器自带的开发者工具,可以查看网络请求和响应。
  • 分析 QUIC 数据包

    用 Wireshark 抓取 WebTransport 的流量,你会看到很多 UDP 数据包。这些数据包就是 QUIC 协议的数据。

    • QUIC Header: 每个 QUIC 数据包都有一个 Header,包含了连接 ID、包序号等信息。
    • QUIC Frame: Header 后面跟着的就是 QUIC Frame,包含了实际的数据。

    你可以用 Wireshark 的过滤器来筛选 QUIC 数据包:quic。然后,你可以展开每个数据包,查看 Header 和 Frame 的内容。

  • 观察连接建立过程

    QUIC 连接建立需要几次握手,你可以观察这些握手过程的数据包。

    • Initial Packet: 客户端发送的第一个包,包含客户端的连接 ID。
    • Retry Packet: 如果服务器要求客户端重试,会发送 Retry Packet。
    • Handshake Packet: 客户端和服务器交换密钥的包。
  • 分析数据传输过程

    观察数据是如何被分割成帧,以及帧的类型。

    • STREAM Frame: 传输数据的帧。
    • ACK Frame: 确认收到数据的帧。
    • PING Frame: 用于检测连接是否存活的帧。

第五部分:JS WebTransport 逆向工程

有些时候,我们可能需要分析一些使用了 WebTransport 的 JS 代码,来看看它是怎么实现的。

  • 定位 WebTransport 代码

    • 在 Chrome DevTools 里,打开 Sources 面板,搜索 WebTransport 关键词。
    • 查看代码里有没有使用 new WebTransport() 创建连接。
  • 分析代码逻辑

    • 看看代码里是怎么创建流的:createUnidirectionalStream()createBidirectionalStream()
    • 看看代码里是怎么发送和接收数据的:writer.write()reader.read()
    • 注意错误处理逻辑,看看代码里是怎么处理连接失败和数据传输错误的。
  • Hook WebTransport API

    可以用 JS 代码来 Hook WebTransport API,拦截和修改 WebTransport 的行为。

    // 保存原始的 WebTransport 函数
    const originalWebTransport = window.WebTransport;
    
    // 重写 WebTransport 函数
    window.WebTransport = function(url) {
      console.log('WebTransport 连接到:', url);
      const transport = new originalWebTransport(url);
    
      // Hook transport.createUnidirectionalStream
      const originalCreateUnidirectionalStream = transport.createUnidirectionalStream;
      transport.createUnidirectionalStream = function() {
        console.log('创建单向流');
        return originalCreateUnidirectionalStream.apply(transport);
      };
    
      // Hook transport.send
      const originalSend = transport.datagrams.writable.getWriter().write; // 需要先获取 writer
      transport.datagrams.writable.getWriter().write = function(data) {
        console.log('发送数据:', data);
        return originalSend.apply(this, [data]);
      };
    
      return transport;
    };

    这段代码 Hook 了 WebTransport 构造函数和 createUnidirectionalStream 方法,以及 send 方法。每次创建 WebTransport 连接、创建单向流和发送数据时,都会打印一些信息到控制台。

第六部分:一些实用技巧

  • 使用 WebTransport 的场景

    • 实时游戏: 传输玩家的位置、动作等信息。
    • 音视频流: 传输音视频数据,实现实时直播、视频会议等功能。
    • 实时数据推送: 服务器主动推送数据给客户端,比如股票行情、新闻推送等。
  • WebTransport 的安全性

    • WebTransport 基于 QUIC 协议,QUIC 协议使用 TLS 1.3 加密,所以 WebTransport 的安全性是有保障的。
    • 但是,开发者仍然需要注意一些安全问题,比如防止跨站脚本攻击(XSS)和跨站请求伪造(CSRF)。
  • WebTransport 的兼容性

    • 目前,主流浏览器都已经支持 WebTransport API 了,但是有些老版本的浏览器可能不支持。
    • 可以使用 Feature Detection 来检测浏览器是否支持 WebTransport API。
    if ('WebTransport' in window) {
      console.log('浏览器支持 WebTransport API');
    } else {
      console.log('浏览器不支持 WebTransport API');
    }

第七部分:总结

WebTransport 是一个很有前途的协议,它可以让网页应用更加快速、流畅、实时。虽然它的学习曲线有点陡峭,但是只要你掌握了 QUIC 协议和 WebTransport API,就能开发出很多有趣的应用。

今天就讲到这里,希望大家有所收获!如果有什么问题,欢迎提问。

第八部分:参考资料

第九部分:代码示例补充

为了更方便大家理解,这里补充一些更完整的代码示例。

  • 一个简单的 WebTransport 服务器 (Node.js)

const { createServer } = require('http');
const { listen } = require('quictransport');
const fs = require('fs');

async function main() {
  const key = fs.readFileSync('key.pem');
  const cert = fs.readFileSync('cert.pem');

  const server = createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello, world!');
  });

  server.on('upgrade', async (req, socket, head) => {
    if (req.headers['sec-webtransport-http3-draft']) {
      try {
        const wt = await listen({
          socket,
          head,
          cert: cert.toString(),
          key: key.toString(),
          // allowInsecure: true, // 仅用于测试,生产环境不要开启
        }, async (session) => {
          console.log('New WebTransport session');

          session.datagrams.readable.pipeTo(new WritableStream({
            write: (chunk) => {
              console.log('Datagram received:', new TextDecoder().decode(chunk));
            }
          }));

          session.incomingUnidirectionalStreams.pipeTo(new WritableStream({
            write: async (stream) => {
              const reader = stream.getReader();
              let result;
              let decoder = new TextDecoder();

              while (!(result = await reader.read()).done) {
                const value = decoder.decode(result.value);
                console.log("Unidirectional Stream Received:", value);
              }
              console.log("Unidirectional Stream complete");
            }
          }));

          session.incomingBidirectionalStreams.pipeTo(new WritableStream({
            write: async (stream) => {
              console.log("New Bidirectional Stream");
              const reader = stream.readable.getReader();
              const writer = stream.writable.getWriter();

              let result;
              let decoder = new TextDecoder();

              while (!(result = await reader.read()).done) {
                const value

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注