Edge 浏览器都不陌生, 微软自家的浏览器, 今天我们来使用 SSL/TLS 中最纯粹的力量, 破解 Secure Network VPN
介绍
Secure Network VPN 是微软官方与 Cloudflare 合作推出的 VPN 服务, 出口IP 为 Cloudflare WARP, 其他内容请自己看官方介绍
Secure Network VPN 官方介绍 | Cloudflare Blog | Cloudflare 企业合作
SSL/TLS 中最纯粹的力量
公式是数学中最纯粹的力量

那什么是 SSL/TLS 中最纯粹的力量?
答案是 SSLKEYLOGFILE它记录了 TLS 流量的解密密钥
在 Edge 中设置 SSLKEYLOGFILE
找到 Edge 图标, 右键选择属性
在 目标后加上
--ssl-key-log-file="文件路径"
然后打开 CMD / 终端
杀死正在运行的 Edge 进程
taskkill /f /im msedge.exe打开 Wireshark
选择网卡, 填写过滤器 port 443

启用 Secure Network VPN
点击修改好属性的 Edge 图标启动 Edge
打开 PC Edge 浏览器右上角的 更多按钮 (三个点 官方名称 "设置与其他")
点击 浏览器概要, 找到 Secure Network VPN
新版本在 更多工具 -> 浏览器健康助手

打开 VPN 开关
随便打开一个网站, 在右边选择 "始终为此站点打开 VPN"
然后刷新页面
解密 TLS 流量
Token 捕获
在过滤器中输入 tls.handshake.extensions_server_name == "cp5.cloudflare.com"

右键链接, 选择 协议首选项 -> Transport Layer Security -> 打开 Transport Layer Security 首选项
设置 (Pre)-Master-Secret log filename 为刚才设置的 SSLKEYLOGFILE 文件路径
点击确定
右键链接, 选择 跟踪流 -> TLS Stream, 已经可用看见 HTTP2 的明文内容

如图可见, 这是一个 HTTP2 PROXY, 请求方式为 CONNECT
找到 Header 中的 proxy-authorization, 将值复制出来, 就大功告成了
Token 申请
- 正式版请求路径前缀
https://edge.microsoft.com/vpntokenservice - 测试版请求路径前缀
https://edge-staging.microsoft.com/vpntokenservice
逆向工程
没实力,做不到
导入至 Golang Proxy
推荐使用 HTTP/1.1
Go Proxy via HTTP/1.1
package http
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"golang.org/x/net/proxy"
)
func init() {
proxy.RegisterDialerType("http", New)
proxy.RegisterDialerType("https", New)
}
type httpProxy struct {
u *url.URL
forward proxy.Dialer
}
func New(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
if u == nil {
return nil, fmt.Errorf("uri is empty")
}
if forward == nil {
forward = proxy.Direct
}
switch u.Scheme {
case "http", "https":
default:
return nil, fmt.Errorf("unsupported scheme")
}
s := &httpProxy{
u: u,
forward: forward,
}
return s, nil
}
func (s *httpProxy) Dial(network, address string) (net.Conn, error) {
switch network {
case "tcp", "tcp4", "tcp6":
default:
return nil, fmt.Errorf("unsupported network: %v", network)
}
c, err := s.forward.Dial("tcp", s.u.Host)
if err != nil {
return nil, err
}
if s.u.Scheme == "https" {
c = tls.Client(c, &tls.Config{
ServerName: s.u.Hostname(),
InsecureSkipVerify: true,
})
}
req := &http.Request{
Method: "CONNECT",
Host: address,
URL: &url.URL{Host: address},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Body: http.NoBody,
Close: true,
}
req.Header.Set("Proxy-Authorization", "PrivacyToken token="+s.u.RawQuery)
err = req.Write(c)
if err != nil {
c.Close()
return nil, err
}
response, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
c.Close()
return nil, err
}
if response.Body != nil {
response.Body.Close()
}
if response.StatusCode != 200 {
c.Close()
return nil, fmt.Errorf("proxy response code %d", response.StatusCode)
}
return c, nil
}
func (s *httpProxy) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
dialer, ok := s.forward.(proxy.ContextDialer)
if !ok {
return s.Dial(network, address)
}
c, err := dialer.DialContext(ctx, "tcp", s.u.Host)
if err != nil {
return nil, err
}
if s.u.Scheme == "https" {
c = tls.Client(c, &tls.Config{
ServerName: s.u.Hostname(),
InsecureSkipVerify: true,
})
}
req := (&http.Request{
Method: "CONNECT",
Host: address,
URL: &url.URL{Host: address},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Body: http.NoBody,
Close: true,
}).WithContext(ctx)
req.Header.Set("Proxy-Authorization", "PrivacyToken token="+s.u.RawQuery)
err = req.Write(c)
if err != nil {
c.Close()
return nil, err
}
response, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
c.Close()
return nil, err
}
if response.Body != nil {
response.Body.Close()
}
if response.StatusCode != 200 {
c.Close()
return nil, fmt.Errorf("proxy response code %d", response.StatusCode)
}
return c, nil
}
使用方法
token := "提取的 private token 等于号后面的内容"
u := &url.URL{
Scheme: "https",
Host: "cp5.cloudflare.com:443",
RawQuery: token,
}
dialer, err := proxy.FromURL(u, proxy.Direct)
if err != nil {
clog.Fatalf("[Proxy] Failed to initial proxy, error: %s", err)
return
}Go Proxy via HTTP/2
package h2
import (
"context"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/url"
"time"
"golang.org/x/net/http2"
"golang.org/x/net/proxy"
)
var (
transport = &http2.Transport{}
)
func init() {
proxy.RegisterDialerType("h2", New)
}
type h2Proxy struct {
u *url.URL
forward proxy.Dialer
laddr, raddr net.Addr
conn *http2.ClientConn
}
func New(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
if u == nil {
return nil, fmt.Errorf("uri is empty")
}
if forward == nil {
forward = proxy.Direct
}
switch u.Scheme {
case "h2":
default:
return nil, fmt.Errorf("unsupported scheme")
}
s := &h2Proxy{
u: u,
forward: forward,
}
err := s.reconnect()
if err != nil {
return nil, err
}
go s.keepAlives()
return s, nil
}
func (s *h2Proxy) reconnect() error {
conn, err := s.forward.Dial("tcp", s.u.Host)
if err != nil {
return err
}
tlsConn := tls.Client(conn, &tls.Config{
ServerName: s.u.Hostname(),
NextProtos: []string{"h2"},
})
err = tlsConn.Handshake()
if err != nil {
return err
}
h2, err := transport.NewClientConn(tlsConn)
if err != nil {
return err
}
if s.conn != nil {
s.conn.Close()
}
s.conn = h2
s.laddr = conn.LocalAddr()
s.raddr = conn.RemoteAddr()
return nil
}
func (s *h2Proxy) keepAlives() {
for {
if err := s.conn.Ping(context.Background()); err != nil {
s.reconnect()
}
time.Sleep(time.Second)
}
}
func (s *h2Proxy) Dial(network, address string) (net.Conn, error) {
return s.DialContext(context.Background(), network, address)
}
func (s *h2Proxy) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
switch network {
case "tcp", "tcp4", "tcp6":
default:
return nil, fmt.Errorf("unsupport network: %v", network)
}
r, w := io.Pipe()
req := (&http.Request{
Method: "CONNECT",
Host: address,
URL: &url.URL{Scheme: "https", Host: address},
Proto: "HTTP/2",
ProtoMajor: 2,
ProtoMinor: 0,
Header: make(http.Header),
Body: r,
ContentLength: -1,
}).WithContext(ctx)
req.Header.Set("Proxy-Authorization", "PrivacyToken token="+s.u.RawQuery)
response, err := s.conn.RoundTrip(req)
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
if response.Body != nil {
response.Body.Close()
}
return nil, fmt.Errorf("proxy response code %d", response.StatusCode)
}
return &wrappedConn{
rc: response.Body,
w: w,
laddr: s.laddr,
raddr: s.raddr,
}, nil
}
type wrappedConn struct {
rc io.ReadCloser
w io.WriteCloser
laddr, raddr net.Addr
}
func (c *wrappedConn) Read(b []byte) (int, error) {
return c.rc.Read(b)
}
func (c *wrappedConn) Write(b []byte) (int, error) {
return c.w.Write(b)
}
func (c *wrappedConn) Close() error {
c.rc.Close()
return c.w.Close()
}
func (c *wrappedConn) LocalAddr() net.Addr {
return c.laddr
}
func (c *wrappedConn) RemoteAddr() net.Addr {
return c.raddr
}
func (c *wrappedConn) SetDeadline(t time.Time) error {
return nil
}
func (c *wrappedConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *wrappedConn) SetWriteDeadline(t time.Time) error {
return nil
}使用方法
token := "提取的 private token 等于号后面的内容"
u := &url.URL{
Scheme: "h2",
Host: "cp5.cloudflare.com:443",
RawQuery: token,
}
dialer, err := proxy.FromURL(u, proxy.Direct)
if err != nil {
clog.Fatalf("[Proxy] Failed to initial proxy, error: %s", err)
return
}后记
Edge Security Network VPN 更新日志
v1
- Token 过期时间 30天
- IP属地 拜访地 (客户端IP来自哪个地区 IP就是那个地区)
- IP池 2个C段 (每个TCP链接IP都不同 使用 HTTP/1.1 可秒换IP 使用 HTTP/2 可保持同一IP)
v2
- Token 过期时间 4-7天
- IP属地 归属地/国际漫游 (Token注册与哪个地区 IP就是那个地区)
- IP池 1-2个
1 条评论
厉害喵