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 条评论
厉害喵