增加防探测功能,增加流量加密功能
This commit is contained in:
19
README.md
19
README.md
@ -1,11 +1,11 @@
|
|||||||
# Shadow-TLS
|
# Shadow-TLS
|
||||||
### TLS伪装代理 -- 包装任意TCP连接为真正合法域名的TLS连接
|
### TLS伪装代理 -- 包装任意TCP连接为真正合法域名的TLS连接
|
||||||
## 基本原理
|
## 基本原理
|
||||||
与服务端连接后,服务端会请求指定合法的HTTPS域名(例如www.apple.com)并转发TLS握手流量(给防火墙/审计设备表演一个真正的TLS握手),与客户端TLS握手成功后后续将转发实际的TCP流量,对审计设备(防火墙/上网行为管理软件/零信任网关)而言你是访问一个合法且是真实证书的HTTPS网站.
|
给审计设备表演一个访问指定网站的TLS握手,在后续的加密流量中传输自定义的数据,由于握手的证书是真实的证书,对审计设备(防火墙/上网行为分析/零信任网关)而言本次TCP连接是访问指定网站的HTTPS流量
|
||||||
|
|
||||||
## 使用场景
|
## 使用场景
|
||||||
- 在有域名白名单的情况下需要将流量转发出去
|
- 在有域名白名单的情况下需要将流量转发出去
|
||||||
- 对抗网络审计设备的审查
|
- 审计设备会验证TLS证书合法性,自签证书无法通过审计设备的场景
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
- 服务端示例:
|
- 服务端示例:
|
||||||
@ -18,6 +18,17 @@
|
|||||||
```shell
|
```shell
|
||||||
./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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 探测防御
|
||||||
|
- 设置参数密码-p 可开启探测防御功能,服务端检测到非被客户端发起的请求后将会作为标准的SNI代理服务器,转发用于伪装源站的所有流量,对主动探测者而言这台服务器是指定网站的官方服务器
|
||||||
|
- 若被大量请求可能造成服务器产生大量流量,注意风控
|
||||||
|
|
||||||
|
## 流量加密
|
||||||
|
- 设置加密密钥参数-k 可启用流量加密,密钥长度必须为16,24或32个字符
|
||||||
|
|
||||||
|
## 已知限制
|
||||||
|
- 若审计软件会对HTTPS证书进行替换使用审计软件自签的证书,本工具将不再适用
|
||||||
|
|
||||||
## 使用Nginx将本服务和其他443端口服务并存
|
## 使用Nginx将本服务和其他443端口服务并存
|
||||||
- 编译带有stream和stream_ssl_preread模块的nginx
|
- 编译带有stream和stream_ssl_preread模块的nginx
|
||||||
- 参考以下配置
|
- 参考以下配置
|
||||||
@ -69,10 +80,6 @@ http {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 安全性特别说明
|
|
||||||
- 包装的TCP流量没有加密,因由上层处理,推荐配合gost使用
|
|
||||||
|
|
||||||
## 特别说明
|
## 特别说明
|
||||||
- 感谢v2ex网友ihciah的思路灵感.
|
- 感谢v2ex网友ihciah的思路灵感.
|
||||||
- 仅供技术研究,请勿用于非法用途.
|
- 仅供技术研究,请勿用于非法用途.
|
@ -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()
|
||||||
},
|
},
|
||||||
|
11
cmd/root.go
11
cmd/root.go
@ -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
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
},
|
},
|
||||||
|
10
main.go
10
main.go
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,15 @@ func (c *Client) Start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handlerClient(conn net.Conn, serverAddress string, fakeAddressSNI string) {
|
func handlerClient(conn net.Conn, serverAddress string, fakeAddressSNI string) {
|
||||||
|
config := &tls.Config{
|
||||||
|
ServerName: fakeAddressSNI,
|
||||||
|
}
|
||||||
|
if HandshakePassword != "" {
|
||||||
|
config.Rand = RandReaderObj
|
||||||
|
}
|
||||||
dial, err := tls.DialWithDialer(&net.Dialer{
|
dial, err := tls.DialWithDialer(&net.Dialer{
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
}, "tcp", serverAddress, &tls.Config{
|
}, "tcp", serverAddress, config)
|
||||||
ServerName: fakeAddressSNI,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[Client] Dial server error: %v\n", err)
|
fmt.Printf("[Client] Dial server error: %v\n", err)
|
||||||
return
|
return
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package shadow
|
package shadow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -8,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestName(t *testing.T) {
|
func TestHandshake(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{
|
||||||
@ -22,15 +24,43 @@ func TestName(t *testing.T) {
|
|||||||
time.Sleep(time.Minute)
|
time.Sleep(time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestName2(t *testing.T) {
|
func TestMd5(t *testing.T) {
|
||||||
b := []byte("ABC")
|
key := "Passwd"
|
||||||
encrypt, err := AesEncrypt(b, []byte("1234567812345678"))
|
passwd := []byte(key)
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
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")
|
||||||
}
|
}
|
||||||
decrypt, err := AesDecrypt(encrypt, []byte("1234567812345678"))
|
if VerifyKey(buf, key) {
|
||||||
if err != nil {
|
fmt.Println("VerifyKey GOOD")
|
||||||
fmt.Println(err)
|
|
||||||
}
|
}
|
||||||
fmt.Println(string(decrypt))
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAes(t *testing.T) {
|
||||||
|
key := []byte("1234567812345678")
|
||||||
|
data := []byte("AVC")
|
||||||
|
|
||||||
|
e := AesEncryptCBC(data, key)
|
||||||
|
d := AesDecryptCBC(e, key)
|
||||||
|
|
||||||
|
fmt.Println(string(d))
|
||||||
}
|
}
|
||||||
|
8
shadow/global.go
Normal file
8
shadow/global.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package shadow
|
||||||
|
|
||||||
|
var (
|
||||||
|
HandshakePassword = ""
|
||||||
|
RandReaderObj = &RandReader{}
|
||||||
|
EncryptKey = []byte{}
|
||||||
|
Key = ""
|
||||||
|
)
|
@ -47,6 +47,12 @@ func (m PackAppData) Read(p []byte) (n int, err error) {
|
|||||||
copy(p[sum:], buf[HeaderLength+2+sum:HeaderLength+2+sum+r])
|
copy(p[sum:], buf[HeaderLength+2+sum:HeaderLength+2+sum+r])
|
||||||
sum += r
|
sum += r
|
||||||
}
|
}
|
||||||
|
if len(EncryptKey) > 0 {
|
||||||
|
encryptedData := p[0:sum]
|
||||||
|
decrypted := AesDecryptCBC(encryptedData, EncryptKey)
|
||||||
|
copy(p[0:], decrypted)
|
||||||
|
sum = len(decrypted)
|
||||||
|
}
|
||||||
return sum, err
|
return sum, err
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Invalid header")
|
fmt.Printf("Invalid header")
|
||||||
@ -55,15 +61,30 @@ func (m PackAppData) Read(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m PackAppData) Write(p []byte) (n int, err error) {
|
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)
|
lenNum := make([]byte, 2)
|
||||||
binary.BigEndian.PutUint16(lenNum, uint16(len(p)))
|
binary.BigEndian.PutUint16(lenNum, uint16(len(sendData)))
|
||||||
|
|
||||||
packetBuf := bytes.NewBuffer(AppDataHeader)
|
packetBuf := bytes.NewBuffer(AppDataHeader)
|
||||||
packetBuf.Write(lenNum)
|
packetBuf.Write(lenNum)
|
||||||
packetBuf.Write(p)
|
packetBuf.Write(sendData)
|
||||||
|
|
||||||
write, err := m.Conn.Write(packetBuf.Bytes())
|
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
|
write = write - HeaderLength - 2
|
||||||
|
}
|
||||||
return write, err
|
return write, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
shadow/rand.go
Normal file
49
shadow/rand.go
Normal 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:])
|
||||||
|
}
|
@ -79,62 +79,11 @@ func handler(conn net.Conn, targetAddress string, fakeAddress string) {
|
|||||||
go MyCopy(p, realConnection, exit)
|
go MyCopy(p, realConnection, exit)
|
||||||
go MyCopy(realConnection, p, exit)
|
go MyCopy(realConnection, p, exit)
|
||||||
<-exit
|
<-exit
|
||||||
|
|
||||||
//go func() {
|
|
||||||
// buf := make([]byte, 64*1024)
|
|
||||||
// for {
|
|
||||||
// nr, er := realConnection.Read(buf)
|
|
||||||
// if er != nil {
|
|
||||||
// if er == io.EOF {
|
|
||||||
// continue
|
|
||||||
// } else {
|
|
||||||
// fmt.Println("read err:", er)
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// lenNum := make([]byte, 2)
|
|
||||||
// binary.BigEndian.PutUint16(lenNum, uint16(nr))
|
|
||||||
//
|
|
||||||
// packetBuf := bytes.NewBuffer(AppDataHeader)
|
|
||||||
// packetBuf.Write(lenNum)
|
|
||||||
// packetBuf.Write(buf[0:nr])
|
|
||||||
//
|
|
||||||
// _, ew := conn.Write(packetBuf.Bytes())
|
|
||||||
// if ew != nil {
|
|
||||||
// fmt.Printf("err2:%v\n", ew)
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}()
|
|
||||||
//
|
|
||||||
//go func() {
|
|
||||||
// result := bytes.NewBuffer(nil)
|
|
||||||
// var buf [65542]byte // 由于 标识数据包长度 的只有两个字节 故数据包最大为 2^16+4(魔数)+2(长度标识)
|
|
||||||
// for {
|
|
||||||
// n, er := conn.Read(buf[0:])
|
|
||||||
// result.Write(buf[0:n])
|
|
||||||
// if er != nil {
|
|
||||||
// if er == io.EOF {
|
|
||||||
// continue
|
|
||||||
// } else {
|
|
||||||
// fmt.Println("read err:", er)
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// scanner := bufio.NewScanner(result)
|
|
||||||
// scanner.Split(packetSlitFunc)
|
|
||||||
// for scanner.Scan() {
|
|
||||||
// realConnection.Write(scanner.Bytes()[HeaderLength+2:])
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// result.Reset()
|
|
||||||
// }
|
|
||||||
//}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processHandshake(src net.Conn, dst net.Conn, waitCh chan int, srcType string) {
|
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 {
|
||||||
@ -142,11 +91,18 @@ func processHandshake(src net.Conn, dst net.Conn, waitCh chan int, srcType strin
|
|||||||
if srcType == "client" {
|
if srcType == "client" {
|
||||||
header := ParseAndVerifyTLSHeader(buf[0:nr])
|
header := ParseAndVerifyTLSHeader(buf[0:nr])
|
||||||
if header != nil {
|
if header != nil {
|
||||||
|
if header != nil && header.Type == Handshake && header.HandshakeType == ClientHello && !verifyPass {
|
||||||
|
verifyPass = VerifyKey(header.Rand, HandshakePassword)
|
||||||
|
}
|
||||||
if header.Type == ChangeCipherSpec {
|
if header.Type == ChangeCipherSpec {
|
||||||
|
if HandshakePassword != "" && !verifyPass {
|
||||||
|
fmt.Println("[Server] Probe detected,pass through all traffic.")
|
||||||
|
} else {
|
||||||
fmt.Println("[Server] handshake complete")
|
fmt.Println("[Server] handshake complete")
|
||||||
waitCh <- 1
|
waitCh <- 1
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//fmt.Println(header.toString())
|
//fmt.Println(header.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,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 {
|
||||||
@ -95,6 +96,9 @@ func ParseAndVerifyTLSHeader(data []byte) *TLSHeader {
|
|||||||
if header.HandshakeType != ServerHello && header.HandshakeType != ClientHello && header.HandshakeType != Certificate && header.HandshakeType != ServerKeyExchange && header.HandshakeType != ServerHelloDone {
|
if header.HandshakeType != ServerHello && header.HandshakeType != ClientHello && header.HandshakeType != Certificate && header.HandshakeType != ServerKeyExchange && header.HandshakeType != ServerHelloDone {
|
||||||
header.HandshakeType = EncryptedHandshake
|
header.HandshakeType = EncryptedHandshake
|
||||||
}
|
}
|
||||||
|
if header.HandshakeType == ClientHello {
|
||||||
|
header.Rand = data[11:43]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if header.Type == ChangeCipherSpec {
|
if header.Type == ChangeCipherSpec {
|
||||||
if len(data) > 6 {
|
if len(data) > 6 {
|
||||||
|
Reference in New Issue
Block a user