Compare commits

...

15 Commits

Author SHA1 Message Date
2a7946a5c9 fix 2022-10-16 15:58:02 +08:00
040fdfb7ef README.md 2022-10-16 12:25:40 +08:00
ce9f312a77 utls 2022-10-14 16:23:48 +08:00
91ac6f7e17 utls 2022-10-14 15:49:19 +08:00
a6bf1c0e7d fix bug 2022-09-09 15:57:32 +08:00
bd2cf3865d 增加防探测功能,增加流量加密功能 2022-09-09 15:30:01 +08:00
a064f72104 增加防探测功能,增加流量加密功能 2022-09-09 15:29:41 +08:00
c2ab6cbe5f update README.md 2022-09-08 00:12:01 +08:00
a983817b8d update README.md 2022-09-07 00:08:50 +08:00
116970c654 update README.md 2022-09-06 23:32:33 +08:00
d276407530 update 2022-09-06 14:28:39 +08:00
0a7d7dd290 update 2022-09-06 14:13:48 +08:00
f426b7b2cc update 2022-09-05 16:08:24 +08:00
c4c6f1c4ff 无多路复用版本 2022-09-05 14:21:33 +08:00
1c49851d13 无多路复用版本 2022-09-04 18:08:16 +08:00
16 changed files with 587 additions and 162 deletions

View File

@ -1,11 +1,11 @@
# Shadow-TLS # Shadow-TLS
### TLS伪装代理 -- 包装任意TCP连接为真正合法域名的TLS连接 ### TLS伪装代理 -- 包装任意TCP连接为真正合法域名的TLS连接
## 基本原理 ## 基本原理
与服务端连接后,服务端会请求指定合法的HTTPS域名(例如www.apple.com)并转发TLS握手流量,与客户端TLS握手成功后后续将转发实际的TCP流量,对审计设备(防火墙/上网行为管理软件/零信任网关)而言你是访问一个合法且是真实证书的HTTPS网站. 给审计设备表演一个访问指定网站的TLS握手,在后续的加密流量中传输自定义的数据,由于握手的证书是真实的证书,对审计设备(防火墙/上网行为分析/零信任网关)而言本次TCP连接是访问指定网站的HTTPS流量
## 使用场景 ## 使用场景
- 在有域名白名单的情况下需要将流量转发出去 - 在有域名白名单的情况下需要将流量转发出去
- 对抗网络审计设备的审查 - 审计设备会验证TLS证书合法性,自签证书无法通过审计设备的场景
## 使用方法 ## 使用方法
- 服务端示例: - 服务端示例:
@ -19,13 +19,76 @@
./shadowtls client -l 0.0.0.0:11222 -s 145.142.63.32:443 -d www.apple.com ./shadowtls client -l 0.0.0.0:11222 -s 145.142.63.32:443 -d www.apple.com
``` ```
## 功能特性 ## 探测防御
- TLS连接多路复用,减少TLS握手次数 - 设置参数密码-p 可开启探测防御功能,服务端检测到非被客户端发起的请求后将会作为标准的SNI代理服务器,转发用于伪装源站的所有流量,对主动探测者而言这台服务器是指定网站的官方服务器
- 若被大量请求可能造成服务器产生大量流量,注意风控
- 引入utls组件,伪装TLS ClientHello指纹为Chrome 102版本,进一步对抗探测
## 安全性特别说明 ## 流量加密
- 包装的TCP流量没有加密,如有需求请加密后再转发 - 设置加密密钥参数-k 可启用流量加密,密钥长度必须为16,24或32个字符
- 多路复用特性使用了smux的框架,有协议特征,有更进一步需求需修改源码二次加密
- TLS后续流量没有进行Application Data封装,深层次的协议分析可以发现此特征 ## 已知限制
- 若审计软件会对HTTPS证书进行替换使用审计软件自签的证书,本工具将不再适用
## 使用Nginx将本服务和其他443端口服务并存
- 编译带有stream和stream_ssl_preread模块的nginx
- 参考以下配置
```nginx
stream {
map $ssl_preread_server_name $backend_pool {
www.apple.com shadow;
defalut local_server;
}
upstream shadow{
server 127.0.2.1:2443;
}
upstream local_server{
server 127.0.2.1:8443;
}
server {
listen 443;
ssl_preread on;
proxy_bind $remote_addr transparent; # 加了这个才能传递客户端IP
proxy_pass $backend_pool;
proxy_connect_timeout 15s;
proxy_timeout 15s;
proxy_next_upstream_timeout 15s;
}
}
http {
...
server {
listen 127.0.2.1:8443 ssl http2;
server_name file.evan.run;
charset utf-8;
ssl_certificate cert.crt;
ssl_certificate_key private.key;
port_in_redirect off; #重要阻止nginx重定向到此Server listen的端口
location / {
root /root/file;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
}
}
}
```
- 添加策略路由:
```shell
ip rule add from 127.0.2.1 lookup 61
ip route add local 0.0.0.0/0 dev lo table 61
```
## 特别说明 ## 特别说明
- 感谢v2ex网友ihciah的思路灵感. - 感谢v2ex网友ihciah的思路灵感.

View File

@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"shadowTLS/shadow" "shadowTLS/shadow"
) )
@ -17,6 +18,10 @@ var (
Use: "client", Use: "client",
Short: "Client mode", Short: "Client mode",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if !verifyAndParseEncryptionKey() {
fmt.Println("[Client] Invalid encryption key length")
return
}
client := shadow.NewClient(clientParams.ListenAddr, clientParams.ServerAddr, clientParams.SNI) client := shadow.NewClient(clientParams.ListenAddr, clientParams.ServerAddr, clientParams.SNI)
client.Start() client.Start()
}, },

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os" "os"
"shadowTLS/shadow"
) )
var ( var (
@ -15,6 +16,8 @@ var (
func init() { func init() {
rootCmd.CompletionOptions.DisableDefaultCmd = true rootCmd.CompletionOptions.DisableDefaultCmd = true
rootCmd.PersistentFlags().StringVarP(&shadow.HandshakePassword, "password", "p", "", "Password for probe resist.Probe resist will be disabled if password is empty.")
rootCmd.PersistentFlags().StringVarP(&shadow.Key, "key", "k", "", "Encryption key for AES-CBC.Encryption will be enabled if key is set.Length of key must be 16,24 or 32.")
} }
func Execute() { func Execute() {
@ -23,3 +26,11 @@ func Execute() {
os.Exit(1) os.Exit(1)
} }
} }
func verifyAndParseEncryptionKey() bool {
if shadow.Key != "" && len(shadow.Key) != 16 && len(shadow.Key) != 24 && len(shadow.Key) != 32 {
return false
}
shadow.EncryptKey = []byte(shadow.Key)
return true
}

View File

@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"shadowTLS/shadow" "shadowTLS/shadow"
) )
@ -17,6 +18,10 @@ var (
Use: "server", Use: "server",
Short: "Server mode", Short: "Server mode",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if !verifyAndParseEncryptionKey() {
fmt.Println("[Server] Invalid encryption key length")
return
}
server := shadow.NewServer(serverParams.ListenAddr, serverParams.TargetAddr, serverParams.FakeAddr) server := shadow.NewServer(serverParams.ListenAddr, serverParams.TargetAddr, serverParams.FakeAddr)
server.Start() server.Start()
}, },

6
go.mod
View File

@ -3,11 +3,15 @@ module shadowTLS
go 1.18 go 1.18
require ( require (
github.com/refraction-networking/utls v1.1.3
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0
github.com/xtaci/smux v1.5.16
) )
require ( require (
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
) )

20
go.sum
View File

@ -1,12 +1,28 @@
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/refraction-networking/utls v1.1.3 h1:K9opY+iKxcGvHOBG2019wFEVtsNFh0f5WqHyc2i3iU0=
github.com/refraction-networking/utls v1.1.3/go.mod h1:+D89TUtA8+NKVFj1IXWr0p3tSdX1+SqUB7rL0QnGqyg=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/xtaci/smux v1.5.16 h1:FBPYOkW8ZTjLKUM4LI4xnnuuDC8CQ/dB04HD519WoEk= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
github.com/xtaci/smux v1.5.16/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

10
main.go
View File

@ -1,8 +1,14 @@
package main package main
import "shadowTLS/cmd" import (
"math/rand"
"shadowTLS/cmd"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() { func main() {
cmd.Execute() cmd.Execute()
} }

36
shadow/aes_util.go Normal file
View File

@ -0,0 +1,36 @@
package shadow
import (
"bytes"
"crypto/aes"
"crypto/cipher"
)
func AesEncryptCBC(origData []byte, key []byte) []byte {
block, _ := aes.NewCipher(key)
blockSize := block.BlockSize()
origData = pkcs5Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
encrypted := make([]byte, len(origData))
blockMode.CryptBlocks(encrypted, origData)
return encrypted
}
func AesDecryptCBC(encrypted []byte, key []byte) []byte {
block, _ := aes.NewCipher(key)
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
decrypted := make([]byte, len(encrypted))
blockMode.CryptBlocks(decrypted, encrypted)
decrypted = pkcs5UnPadding(decrypted)
return decrypted
}
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func pkcs5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

View File

@ -2,8 +2,10 @@ package shadow
import ( import (
"fmt" "fmt"
"github.com/refraction-networking/utls"
"io" "io"
"net" "net"
"time"
) )
type Client struct { type Client struct {
@ -22,8 +24,6 @@ func NewClient(listenAddress string, serverAddress string, fakeAddressSNI string
} }
func (c *Client) Start() { func (c *Client) Start() {
bridge := NewTLSBridge(c.ServerAddress, c.FakeAddressSNI)
listen, err := net.Listen("tcp", c.ListenAddress) listen, err := net.Listen("tcp", c.ListenAddress)
if err != nil { if err != nil {
fmt.Printf("[Client] Start client error: %v\n", err) fmt.Printf("[Client] Start client error: %v\n", err)
@ -37,15 +37,65 @@ func (c *Client) Start() {
fmt.Printf("[Client] accept error: %v\n", err) fmt.Printf("[Client] accept error: %v\n", err)
continue continue
} }
go handlerClient(conn, c.ServerAddress, c.FakeAddressSNI)
}
}
stream := bridge.GetStream() func handlerClient(conn net.Conn, serverAddress string, fakeAddressSNI string) {
if stream == nil { config := &tls.Config{
conn.Close() ServerName: fakeAddressSNI,
fmt.Println("[Client] connect to server error")
continue
} }
fmt.Printf("[Client] New TCP connection: %v <-> %v \n", conn.LocalAddr().String(), conn.RemoteAddr().String()) if HandshakePassword != "" {
go io.Copy(conn, stream) config.Rand = RandReaderObj
go io.Copy(stream, conn) }
rawConn, err := net.DialTimeout("tcp", serverAddress, time.Second*5)
if err != nil {
fmt.Printf("[Client] Dial server error: %v\n", err)
return
}
dial := tls.UClient(rawConn, config, tls.HelloChrome_102)
err = dial.Handshake()
if err != nil {
fmt.Printf("[Client] Handshake error: %v\n", err)
return
}
//dial.GetUnderlyingConn().SetDeadline(time.Now())
//dial.GetUnderlyingConn().SetDeadline(time.Time{})
p := &PackAppData{Conn: dial.GetUnderlyingConn()}
defer p.Close()
defer conn.Close()
exitCh := make(chan int, 1)
go MyCopy(conn, p, exitCh)
go MyCopy(p, conn, exitCh)
<-exitCh
}
func MyCopy(src io.ReadWriteCloser, dst io.ReadWriteCloser, ch chan int) {
buf := make([]byte, 32*1024)
for {
nr, er := src.Read(buf)
if er != nil {
if er == io.EOF {
break
} else {
fmt.Printf("Read err: %v\n", er)
break
}
} else {
nw, ew := dst.Write(buf[0:nr])
if ew != nil {
fmt.Printf("Write error:%v\n", ew)
break
}
if nr != nw {
fmt.Printf("Write less then buffered \n")
break
} }
} }
}
ch <- 1
}

View File

@ -1,28 +1,19 @@
package shadow package shadow
import ( import (
"bytes"
"crypto/md5"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
utls "github.com/refraction-networking/utls"
"io/ioutil"
"net" "net"
"net/http"
"testing" "testing"
"time" "time"
) )
func TestName(t *testing.T) { func TestHandshake(t *testing.T) {
dial, err := tls.DialWithDialer(&net.Dialer{
Timeout: time.Second * 5,
}, "tcp", "www.baidu.com:443", &tls.Config{
ServerName: "www.baidu.com",
})
err = dial.Handshake()
if err != nil {
fmt.Println(err)
}
time.Sleep(time.Minute)
}
func TestName2(t *testing.T) {
dial, err := tls.DialWithDialer(&net.Dialer{ dial, err := tls.DialWithDialer(&net.Dialer{
Timeout: time.Second * 5, Timeout: time.Second * 5,
}, "tcp", "evan.run:443", &tls.Config{ }, "tcp", "evan.run:443", &tls.Config{
@ -35,3 +26,163 @@ func TestName2(t *testing.T) {
} }
time.Sleep(time.Minute) time.Sleep(time.Minute)
} }
func TestMd5(t *testing.T) {
key := "Passwd"
passwd := []byte(key)
buf := make([]byte, 32)
srcCode := md5.Sum(RandomByte(16))
copy(buf[0:], srcCode[0:])
buffer := bytes.NewBuffer(srcCode[:])
sum := md5.Sum(passwd)
buffer.Write(sum[:])
hash := md5.Sum(buffer.Bytes())
copy(buf[16:], hash[0:])
fmt.Println(buf)
vBuf := make([]byte, 32)
copy(vBuf, buf[0:16])
verifyBuf := bytes.NewBuffer(vBuf)
verifyBuf.Write(sum[:])
verifyHash := md5.Sum(buffer.Bytes())
if bytes.Equal(verifyHash[:], buf[16:32]) {
fmt.Println("GOOD")
}
if VerifyKey(buf, key) {
fmt.Println("VerifyKey GOOD")
}
}
func TestAes(t *testing.T) {
key := []byte("1234567812345678")
data := []byte("AVC")
e := AesEncryptCBC(data, key)
d := AesDecryptCBC(e, key)
fmt.Println(string(d))
}
func TestTLSFingerprint(t *testing.T) {
transport := http.Transport{
DialTLS: func(network, adr string) (net.Conn, error) {
dial, err := net.Dial(network, adr)
if err != nil {
return nil, err
}
return wrapTLSClient(dial, time.Second*5)
},
}
client := http.Client{
Transport: &transport,
CheckRedirect: nil,
Jar: nil,
Timeout: 0,
}
get, err := client.Get("https://client.tlsfingerprint.io:8443/")
if err != nil {
return
}
all, err := ioutil.ReadAll(get.Body)
if err != nil {
return
}
fmt.Println(string(all))
}
func wrapTLSClient(conn net.Conn, timeout time.Duration) (net.Conn, error) {
var err error
conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{})
tlsConn := utls.UClient(conn, &utls.Config{ServerName: "client.tlsfingerprint.io"}, utls.HelloCustom)
//fingerprinter := &utls.Fingerprinter{}
//generatedSpec, err := fingerprinter.FingerprintClientHello([]byte{0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0x0c, 0x81, 0xa3, 0x5c, 0x8b, 0x44, 0xf7, 0x74, 0x77, 0x7a, 0x51, 0x0f, 0x6f, 0xf4, 0xef, 0xb2, 0xb0, 0x40, 0x15, 0x8e, 0x66, 0xeb, 0xbe, 0x84, 0x6e, 0x18, 0x4b, 0x41, 0x2d, 0x6c, 0xb1, 0x97, 0x20, 0x85, 0x63, 0x63, 0x8b, 0xa6, 0x08, 0x50, 0xd2, 0xbe, 0xd9, 0xd3, 0x15, 0x8a, 0xbe, 0xdb, 0x62, 0xef, 0x39, 0x01, 0x7b, 0xdb, 0xd7, 0xe9, 0x78, 0xc0, 0x8d, 0x3d, 0x32, 0xbe, 0x8d, 0xfc, 0xef, 0x00, 0x20, 0x6a, 0x6a, 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x01, 0x93, 0x7a, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x13, 0x00, 0x00, 0x10, 0x73, 0x61, 0x6e, 0x6b, 0x75, 0x61, 0x69, 0x2e, 0x65, 0x76, 0x61, 0x6e, 0x2e, 0x72, 0x75, 0x6e, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0xea, 0xea, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0b, 0x00, 0x09, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, 0xea, 0xea, 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20, 0x0c, 0x4d, 0x88, 0xfa, 0x97, 0xa0, 0x2e, 0xbe, 0xac, 0x9a, 0xae, 0x1d, 0xae, 0x00, 0x2d, 0xd0, 0x57, 0x40, 0x8f, 0x06, 0xcb, 0x31, 0xf3, 0x8e, 0x7d, 0xec, 0x93, 0xfb, 0xd7, 0x95, 0x0a, 0x40, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x07, 0x06, 0xea, 0xea, 0x03, 0x04, 0x03, 0x03, 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02, 0x44, 0x69, 0x00, 0x05, 0x00, 0x03, 0x02, 0x68, 0x32, 0xda, 0xda, 0x00, 0x01, 0x00, 0x00, 0x15, 0x00, 0xca, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
//tlsConn.ApplyPreset(generatedSpec)
spec := &utls.ClientHelloSpec{
CipherSuites: []uint16{
utls.GREASE_PLACEHOLDER,
utls.TLS_AES_128_GCM_SHA256,
utls.TLS_AES_256_GCM_SHA384,
utls.TLS_CHACHA20_POLY1305_SHA256,
utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
utls.TLS_RSA_WITH_AES_128_GCM_SHA256,
utls.TLS_RSA_WITH_AES_256_GCM_SHA384,
utls.TLS_RSA_WITH_AES_128_CBC_SHA,
utls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
CompressionMethods: []byte{
0x00, // compressionNone
},
Extensions: []utls.TLSExtension{
&utls.UtlsGREASEExtension{},
&utls.SNIExtension{},
&utls.UtlsExtendedMasterSecretExtension{},
&utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient},
&utls.SupportedCurvesExtension{[]utls.CurveID{
utls.GREASE_PLACEHOLDER,
utls.X25519,
utls.CurveP256,
utls.CurveP384,
}},
&utls.SupportedPointsExtension{SupportedPoints: []byte{
0x00, // pointFormatUncompressed
}},
&utls.SessionTicketExtension{},
&utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}},
&utls.StatusRequestExtension{},
&utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{
utls.ECDSAWithP256AndSHA256,
utls.PSSWithSHA256,
utls.PKCS1WithSHA256,
utls.ECDSAWithP384AndSHA384,
utls.PSSWithSHA384,
utls.PKCS1WithSHA384,
utls.PSSWithSHA512,
utls.PKCS1WithSHA512,
}},
&utls.SCTExtension{},
&utls.KeyShareExtension{[]utls.KeyShare{
{Group: utls.CurveID(utls.GREASE_PLACEHOLDER), Data: []byte{0}},
{Group: utls.X25519},
}},
&utls.PSKKeyExchangeModesExtension{[]uint8{
utls.PskModeDHE,
}},
&utls.SupportedVersionsExtension{[]uint16{
utls.GREASE_PLACEHOLDER,
VersionTLS13,
VersionTLS12,
}},
&utls.UtlsCompressCertExtension{[]utls.CertCompressionAlgo{
utls.CertCompressionBrotli,
}},
&utls.ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}},
&utls.UtlsGREASEExtension{},
&utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle},
},
}
tlsConn.ApplyPreset(spec)
if err = tlsConn.Handshake(); err != nil {
fmt.Println(err.Error())
tlsConn.Close()
return nil, err
}
return tlsConn, err
}

8
shadow/global.go Normal file
View File

@ -0,0 +1,8 @@
package shadow
var (
HandshakePassword = ""
RandReaderObj = &RandReader{}
EncryptKey = []byte{}
Key = ""
)

94
shadow/packer.go Normal file
View File

@ -0,0 +1,94 @@
package shadow
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
)
var (
AppDataHeader = []byte{0x17, 0x3, 0x3}
HeaderLength = len(AppDataHeader)
)
type PackAppData struct {
Conn net.Conn
}
func (m PackAppData) Read(p []byte) (n int, err error) {
buf := make([]byte, 48*1024+HeaderLength+2)
headRead, err := io.ReadAtLeast(m.Conn, buf[0:HeaderLength+2], HeaderLength+2)
if err != nil {
if err != io.EOF {
fmt.Printf("Read header error: %v\n", err)
}
return 0, err
}
if headRead < HeaderLength+2 {
return 0, errors.New("Read header failed")
}
if bytes.Equal(buf[0:HeaderLength], AppDataHeader) {
payLoadLength := int(binary.BigEndian.Uint16(buf[HeaderLength : HeaderLength+2]))
sum := 0
for sum < payLoadLength {
r, e := m.Conn.Read(buf[HeaderLength+2+sum : HeaderLength+2+payLoadLength])
if e != nil {
if e == io.EOF {
break
} else {
return 0, e
}
}
sum += r
}
if len(EncryptKey) > 0 {
encryptedData := buf[HeaderLength+2 : HeaderLength+2+sum]
decrypted := AesDecryptCBC(encryptedData, EncryptKey)
copy(p[0:], decrypted)
sum = len(decrypted)
} else {
copy(p[0:], buf[HeaderLength+2:HeaderLength+2+sum])
}
return sum, err
} else {
fmt.Printf("Invalid header")
return 0, errors.New("invalid header")
}
}
func (m PackAppData) Write(p []byte) (n int, err error) {
var sendData []byte
if len(EncryptKey) > 0 {
sendData = AesEncryptCBC(p, EncryptKey)
} else {
sendData = p
}
lenNum := make([]byte, 2)
binary.BigEndian.PutUint16(lenNum, uint16(len(sendData)))
packetBuf := bytes.NewBuffer(AppDataHeader)
packetBuf.Write(lenNum)
packetBuf.Write(sendData)
write, err := m.Conn.Write(packetBuf.Bytes())
if len(EncryptKey) > 0 {
if write != packetBuf.Len() {
write = 0
} else {
write = len(p)
}
} else {
write = write - HeaderLength - 2
}
return write, err
}
func (m PackAppData) Close() error {
return m.Conn.Close()
}

49
shadow/rand.go Normal file
View File

@ -0,0 +1,49 @@
package shadow
import (
"bytes"
"crypto/md5"
"io"
"math/rand"
)
type RandReader struct {
io.Reader
}
func (r RandReader) Read(p []byte) (n int, err error) {
buf := make([]byte, 32)
randBytes := md5.Sum(RandomByte(16))
copy(buf[0:], randBytes[:])
preHashData := bytes.NewBuffer(randBytes[:])
sum := md5.Sum([]byte(HandshakePassword))
preHashData.Write(sum[:])
hash := md5.Sum(preHashData.Bytes())
copy(buf[16:], hash[:])
copy(p, buf)
return 32, nil
}
func RandomByte(size int) []byte {
buf := make([]byte, size)
for i := 0; i < size; i++ {
buf[i] = byte(rand.Intn(255))
}
return buf
}
func VerifyKey(p []byte, key string) bool {
if len(p) != 32 {
return false
}
buf := make([]byte, 16)
copy(buf, p[0:16])
buffer := bytes.NewBuffer(buf)
sum := md5.Sum([]byte(key))
buffer.Write(sum[:])
hash := md5.Sum(buffer.Bytes())
return bytes.Equal(hash[:], p[16:])
}

View File

@ -2,7 +2,6 @@ package shadow
import ( import (
"fmt" "fmt"
"github.com/xtaci/smux"
"io" "io"
"net" "net"
"time" "time"
@ -52,53 +51,61 @@ func handler(conn net.Conn, targetAddress string, fakeAddress string) {
} }
waitCh := make(chan int, 1) waitCh := make(chan int, 1)
go processHandshake(conn, fakeConn, waitCh) go processHandshake(conn, fakeConn, waitCh, "client")
go processHandshake(fakeConn, conn, waitCh) go processHandshake(fakeConn, conn, waitCh, "server")
<-waitCh <-waitCh
//Clean up previous buffered data //Clean up previous buffered data
conn.SetDeadline(time.Now()) conn.SetDeadline(time.Now())
conn.SetDeadline(time.Time{}) conn.SetDeadline(time.Time{})
fakeConn.Close()
//Process real tcp connection realConnection, err := net.Dial("tcp", targetAddress)
session, err := smux.Server(conn, nil)
if err != nil { if err != nil {
fmt.Printf("[Server] smux error: %v\n", err) fmt.Printf("[Server] Dial target error : %v\n", err)
return return
} }
for {
stream, err := session.AcceptStream()
if err != nil { if err != nil {
fmt.Printf("[Server] AcceptStream error: %v\n", err) return
break
}
go handlerMux(stream, targetAddress)
} }
p := &PackAppData{Conn: conn}
defer p.Close()
defer realConnection.Close()
exit := make(chan int, 1)
go MyCopy(p, realConnection, exit)
go MyCopy(realConnection, p, exit)
<-exit
} }
func processHandshake(src net.Conn, dst net.Conn, waitCh chan int) { func processHandshake(src net.Conn, dst net.Conn, waitCh chan int, srcType string) {
buf := make([]byte, 32*1024) buf := make([]byte, 32*1024)
verifyPass := false
for { for {
nr, er := src.Read(buf) nr, er := src.Read(buf)
if nr > 0 { if nr > 0 {
header := ParseAndVerifyTLSHeader(buf[0:nr])
nw, ew := dst.Write(buf[0:nr]) nw, ew := dst.Write(buf[0:nr])
if header != nil && header.Type == ChangeCipherSpec { if srcType == "client" {
//fmt.Println(header.toString()) header := ParseAndVerifyTLSHeader(buf[0:nr])
fmt.Println("[Server] handshake complete") if header != nil {
if header.ChangeCipherSpecNext == AppData { if header != nil && header.Type == Handshake && header.HandshakeType == ClientHello && !verifyPass {
dst.Close() verifyPass = VerifyKey(header.Rand, HandshakePassword)
waitCh <- 1
} else {
src.Close()
waitCh <- 1
return
} }
if header.Type == ChangeCipherSpec {
if HandshakePassword != "" && !verifyPass {
fmt.Println("[Server] Probe detected,pass through all traffic.")
} else {
fmt.Println("[Server] handshake complete")
waitCh <- 1
break break
} }
}
//fmt.Println(header.toString())
}
}
if nw < 0 || nr < nw { if nw < 0 || nr < nw {
nw = 0 nw = 0
if ew == nil { if ew == nil {
@ -121,19 +128,4 @@ func processHandshake(src net.Conn, dst net.Conn, waitCh chan int) {
break break
} }
} }
waitCh <- 1
}
func handlerMux(conn *smux.Stream, targetAddress string) {
realConnection, err := net.Dial("tcp", targetAddress)
if err != nil {
fmt.Printf("[Server] Dial target error : %v\n", err)
return
}
if err != nil {
return
}
go io.Copy(realConnection, conn)
go io.Copy(conn, realConnection)
} }

View File

@ -1,75 +0,0 @@
package shadow
import (
"crypto/tls"
"github.com/xtaci/smux"
"net"
"sync"
"time"
)
type TLSBridge struct {
session *smux.Session
locker sync.Mutex
serverAddress string
fakeAddressSNI string
}
func NewTLSBridge(serverAddress string, fakeAddressSNI string) *TLSBridge {
t := &TLSBridge{
session: nil,
locker: sync.Mutex{},
serverAddress: serverAddress,
fakeAddressSNI: fakeAddressSNI,
}
return t
}
func (t *TLSBridge) dial() error {
if t.session != nil {
t.session.Close()
}
dial, err := tls.DialWithDialer(&net.Dialer{
Timeout: time.Second * 5,
}, "tcp", t.serverAddress, &tls.Config{
ServerName: t.fakeAddressSNI,
})
if err != nil {
return err
}
err = dial.Handshake()
if err != nil {
return err
}
dial.NetConn().SetDeadline(time.Now())
dial.NetConn().SetDeadline(time.Time{})
time.Sleep(time.Millisecond * 100)
session, err := smux.Client(dial.NetConn(), nil)
if err != nil {
return err
}
//force openStream to prevent first connection problem
session.OpenStream()
t.session = session
return nil
}
func (t *TLSBridge) GetStream() *smux.Stream {
t.locker.Lock()
defer t.locker.Unlock()
if t.session == nil {
err := t.dial()
if err != nil {
return nil
}
}
openStream, err := t.session.OpenStream()
if err != nil {
t.session.Close()
t.session = nil
}
return openStream
}

View File

@ -19,6 +19,10 @@ const (
ServerHello = 2 ServerHello = 2
ClientHello = 1 ClientHello = 1
Certificate = 11
ServerKeyExchange = 12
ServerHelloDone = 14
EncryptedHandshake = 99
) )
type TLSHeader struct { type TLSHeader struct {
@ -27,6 +31,7 @@ type TLSHeader struct {
Length int Length int
HandshakeType uint8 HandshakeType uint8
ChangeCipherSpecNext uint8 ChangeCipherSpecNext uint8
Rand []byte
} }
func (t *TLSHeader) toString() string { func (t *TLSHeader) toString() string {
@ -88,12 +93,17 @@ func ParseAndVerifyTLSHeader(data []byte) *TLSHeader {
if header.Type == Handshake { if header.Type == Handshake {
header.HandshakeType = data[5] header.HandshakeType = data[5]
//Check Handshake type //Check Handshake type
if header.HandshakeType != ServerHello && header.HandshakeType != ClientHello { if header.HandshakeType != ServerHello && header.HandshakeType != ClientHello && header.HandshakeType != Certificate && header.HandshakeType != ServerKeyExchange && header.HandshakeType != ServerHelloDone {
return nil header.HandshakeType = EncryptedHandshake
}
if header.HandshakeType == ClientHello {
header.Rand = data[11:43]
} }
} }
if header.Type == ChangeCipherSpec { if header.Type == ChangeCipherSpec {
if len(data) > 6 {
header.ChangeCipherSpecNext = data[6] header.ChangeCipherSpecNext = data[6]
} }
}
return header return header
} }