通过带有 AES-GCM 加密的模糊通道隧道端口到端口流量。
混淆模式
-
会话 Cookie HTTP GET(http 客户端)
-
Set-Cookie 会话 Cookie HTTP/2 200 OK (http-server)
-
WebSocket 握手“Sec-WebSocket-Key”(websocket-client)
-
WebSocket 握手“Sec-WebSocket-Accept”(websocket-server)
-
没有混淆,只使用 AES-GCM 加密消息(无)
上述每个选项默认启用 AES-GCM。
root@WOPR-KALI:/opt/gohide-dev# ./gohide -h
Usage of ./gohide:
-f string
listen fake server -r x.x.x.x:xxxx (ip/domain:port) (default "0.0.0.0:8081")
-key openssl passwd -1 -salt ok | md5sum
aes encryption secret: use '-k openssl passwd -1 -salt ok | md5sum' to derive key from password (default "5fe10ae58c5ad02a6113305f4e702d07")
-l string
listen port forward -l x.x.x.x:xxxx (ip/domain:port) (default "127.0.0.1:8080")
-m string
obfuscation mode (AES encrypted by default): websocket-client, websocket-server, http-client, http-server, none (default "none")
-pem string
path to .pem for TLS encryption mode: default = use hardcoded key pair 'CN:target.com', none = plaintext mode (default "default")
-r string
forward to remote fake server -r x.x.x.x:xxxx (ip/domain:port) (default "127.0.0.1:9999")
A 反向
root/opt/gohide# ./gohide -f 0.0.0.0:8081 -l 127.0.0.1:8080 -r target.com:9091 -m websocket-client -KALI:
Local Port Forward Listening: 127.0.0.1:8080
FakeSrv Listening: 0.0.0.0:8081
/opt/gohide# nc -v 127.0.0.1 8080 :
localhost [127.0.0.1] 8080 (http-alt) open
id
uid=0(root) gid=0(root) groups=0(root)
uname -a
Linux WOPR-KALI 5.3.0-kali2-amd64 #1 SMP Debian 5.3.9-1kali1 (2019-11-11) x86_64 GNU/Linux
netstat -pantwu
Active Internet connections (servers and established)
tcp 0 0 127.0.0.1:39684 127.0.0.1:8081 ESTABLISHED 14334/./gohide
B
root/opt/gohide# ./gohide -f 0.0.0.0:9091 -l 127.0.0.1:9090 -r target.com:8081 -m websocket-server -KALI:
Local Port Forward Listening: 127.0.0.1:9090
FakeSrv Listening: 0.0.0.0:9091
root@WOPR-KALI:/var/tmp
websocket-client(框 A 到框 B)
-
Sec-WebSocket-Key 包含 AES-GCM 加密内容,例如“uname -a”。
GET /news/api/latest HTTP/1.1
Host: cdn-tb0.gstatic.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: 6jZS+0Wg1IP3n33RievbomIuvh5ZdNMPjVowXm62
Sec-WebSocket-Version: 13
websocket-server(框 B 到框 A)
-
Sec-WebSocket-Accept 包含 AES-GCM 加密输出。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: URrP5l0Z3NIHXi+isjuIyTSKfoP60Vw5d2gqcmI=
http客户端
-
会话 cookie 标头包含 AES-GCM 加密内容
GET /news/api/latest HTTP/1.1
Host: cdn-tbn0.gstatic.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://www.bbc.co.uk/
Connection: keep-alive
Cookie: Session=R7IJ8y/EBgCanTo6fc0fxhNVDA27PFXYberJNW29; Secure; HttpOnly
http服务器
-
Set-Cookie 标头包含 AES-GCM 加密内容。
HTTP/2.0 200 OK
content-encoding: gzip
content-type: text/html; charset=utf-8
pragma: no-cache
server: nginx
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
cache-control: no-cache, no-store, must-revalidate
expires: Thu, 21 Nov 2019 01:07:15 GMT
date: Thu, 21 Nov 2019 01:07:15 GMT
content-length: 30330
vary: Accept-Encoding
X-Firefox-Spdy: h2
Set-Cookie: Session=gWMnQhh+1vkllaOxueOXx9/rLkpf3cmh5uUCmHhy; Secure; Path=/; HttpOnly
package main
import (
"io"
"net"
"fmt"
"io/ioutil"
"bufio"
"flag"
"time"
"regexp"
"strings"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/tls"
"crypto/x509"
b64 "encoding/base64"
)
var key []byte
var tlscfg *tls.Config
var r net.Conn
var fakeSrv net.Listener
func Encrypt(data []byte) []byte {
block, err := aes.NewCipher(key[:])
if err != nil {
panic(err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(err)
}
nonce := make([]byte, gcm.NonceSize())
_ , err = io.ReadFull(rand.Reader, nonce)
if err != nil {
panic(err)
}
ciphertext := gcm.Seal(nil, nonce, data, nil)
output := make([]byte, gcm.NonceSize() + len(ciphertext))
copy(output[:len(nonce)], nonce)
copy(output[len(nonce):], ciphertext)
return output
}
func Decrypt(data []byte) []byte {
block, err := aes.NewCipher(key[:])
if err != nil {
panic(err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(err)
}
if len(data) < gcm.NonceSize() {
panic(err)
}
plaintext , err := gcm.Open(nil, data[:gcm.NonceSize()], data[gcm.NonceSize():], nil)
if err != nil {
panic(err)
}
return plaintext
}
func obscure_send(data []byte, stype string) string {
switch stype {
case "websocket-client":
upgrade := "GET /news/api/latest HTTP/1.1n" +
"Host: cdn-tb0.gstatic.comn" +
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Geckon" +
"Upgrade: websocketn" +
"Connection: Upgraden" +
"Sec-WebSocket-Key: " + b64.StdEncoding.EncodeToString(Encrypt(data)) + "n" +
"Sec-WebSocket-Version: 13nn"
return string(upgrade)
case "websocket-server":
upgrade := "HTTP/1.1 101 Switching Protocolsn" +
"Upgrade: websocketn" +
"Connection: Upgraden" +
"Sec-WebSocket-Accept: " + b64.StdEncoding.EncodeToString(Encrypt(data)) + "nn"
return string(upgrade)
case "http-client":
get := "GET /news/api/latest HTTP/1.1n" +
"Host: cdn-tbn0.gstatic.comn" +
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Geckon" +
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8n" +
"Accept-Language: en-US,en;q=0.5n" +
"Accept-Encoding: gzip, deflaten" +
"Referer: https://www.google.com/n" +
"Connection: keep-aliven" +
"Upgrade-Insecure-Requests: 1" +
"Cookie: Session=" + b64.StdEncoding.EncodeToString(Encrypt(data)) + "; Secure; HttpOnlynn"
return string(get)
case "http-server":
response := "HTTP/1.1 200 OKn" +
"Content-Type: text/htmln" +
"Transfer-Encoding: chunkedn" +
"Connection: keep-aliven" +
"ETag: W/'5aa91b6d-19b00'n" +
"Cache-Control: no-cachen" +
"Access-Control-Allow-Origin: *n" +
"Server: CDN77-Turbon" +
"X-Cache: HITn" +
"X-Age: 21758851n" +
"Content-Encoding: gzipn" +
"Set-Cookie: Session=" + b64.StdEncoding.EncodeToString(Encrypt(data)) + "; Secure; Path=/; HttpOnlynn"
return string(response)
default:
return string(b64.StdEncoding.EncodeToString(Encrypt(data))) + "n"
}
}
func finder(pattern string, data []byte) []byte {
found, _ := regexp.Match(pattern, data)
if found == true {
re := regexp.MustCompile(pattern)
match := re.FindStringSubmatch(string(data))
decode , err := b64.StdEncoding.DecodeString(match[1])
if err != nil {
return nil
}
return Decrypt([]byte(decode))
}
return nil
}
func obscure_recv(data []byte, stype string) []byte {
switch stype {
default:
decode , _ := b64.StdEncoding.DecodeString(string(data))
return Decrypt([]byte(decode))
case "websocket-server":
pattern := `(?m)Sec-WebSocket-Key: ([^;]+)`
return finder(pattern, data)
case "websocket-client":
pattern := `(?m)Sec-WebSocket-Accept: ([^;]+)`
return finder(pattern, data)
case "http-client":
pattern := `(?m)Session=([^;]+);`
return finder(pattern, data)
case "http-server":
pattern := `(?m)Session=([^;]+);`
return finder(pattern, data)
}
}
func sham(stype string) []byte {
switch stype {
default:
o := "{}"
return []byte(o)
case "websocket-server":
o := "HTTP/1.1 101 Switching Protocolsn" +
"Upgrade: websocketn" +
"Connection: Upgraden" +
"Sec-WebSocket-Accept: s3pPSMdiTxaQ8kYGzzhNRbK+x0o=nn"
return []byte(o)
case "websocket-client":
o := "GET /news/api/latest HTTP/1.1n" +
"Host: cdn-tb0.gstatic.comn" +
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Geckon" +
"Upgrade: websocketn" +
"Connection: Upgraden" +
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==n" +
"Sec-WebSocket-Version: 13nn"
return []byte(o)
case "http-client":
o := "GET / HTTP/1.1n" +
"Host: cdn-tb0.gstatic.comn" +
"User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0n" +
"Accept: text/html,application/xhtml+xml,application/xmln" +
"Accept-Language: en-US,enn" +
"Accept-Encoding: gzip, deflaten" +
"Connection: keep-aliven" +
"Upgrade-Insecure-Requests: 1nn"
return []byte(o)
case "http-server":
o := "HTTP/1.1 200 OKn" +
"Content-Type: text/htmln" +
"Transfer-Encoding: chunkedn" +
"Connection: keep-aliven" +
"ETag: W/'5aa91b6d-19b00'n" +
"Cache-Control: no-cachen" +
"Access-Control-Allow-Origin: *n" +
"Server: CDN77-Turbon" +
"X-Cache: HITn" +
"X-Age: 21758851n" +
"Content-Encoding: gzipnn"
return []byte(o)
}
}
func setupTLS(pemPtr string) *tls.Config {
//default - do not use! set your own .pem!
certPem := []byte(`-----BEGIN CERTIFICATE-----
MIICRzCCAcygAwIBAgIUCU0DaqqroWAAL8wvvOJgvuSCAlgwCgYIKoZIzj0EAwIw
WjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKdGFyZ2V0LmNvbTAeFw0x
OTExMjEyMjU5MjlaFw0yOTExMTgyMjU5MjlaMFoxCzAJBgNVBAYTAkFVMRMwEQYD
VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM
dGQxEzARBgNVBAMMCnRhcmdldC5jb20wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATR
ZYVwyZIVj8EiPzsTR7OBS1uycga15tIK9eEvGv7xPrv2EmCc6XYecI1lSVHkEqMN
gVazeiDy5Wm90roP1r2IxB/hclp1WgpDXXJZql8VaFUR2/jAbvjPUgbwdbBQxfOj
UzBRMB0GA1UdDgQWBBTnQhFiG9cWSCZwl1sxfd3PMA3p5TAfBgNVHSMEGDAWgBTn
QhFiG9cWSCZwl1sxfd3PMA3p5TAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMC
A2kAMGYCMQD0fK2o96rREKiJCojOg73LSiX3FGMtLqCEHfBq9wyrerxWugwDp2Fg
P9h8NsbF81cCMQDPww/4ige6PoCtvcbYmj9UqynznYo7B788LBGzufA7KNFAfcTP
JTrHESOoiQ5j9N0=
-----END CERTIFICATE-----`)
keyPem := []byte(`-----BEGIN EC PARAMETERS-----
BgUrgQQAIg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDCWraBt3j/eJyRDPrf/2XrwON5jUDJyVlOGbWm+5pDBUyQtTNXakSyV
mafgjsOkQ3egBwYFK4EEACKhZANiAATRZYVwyZIVj8EiPzsTR7OBS1uycga15tIK
9eEvGv7xPrv2EmCc6XYecI1lSVHkEqMNgVazeiDy5Wm90roP1r2IxB/hclp1WgpD
XXJZql8VaFUR2/jAbvjPUgbwdbBQxfM=
-----END EC PRIVATE KEY-----`)
if pemPtr != "default" {
certPem , _ = ioutil.ReadFile(pemPtr)
keyPem , _ = ioutil.ReadFile(pemPtr)
}
cert, err := tls.X509KeyPair(certPem, keyPem)
if err != nil {
panic(err)
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(certPem)
if !ok {
panic("root ca error")
}
tlscfg := &tls.Config{
RootCAs: roots,
Certificates: []tls.Certificate{cert},
//InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
}
return tlscfg
}
func main() {
listenPtr := flag.String("l", "127.0.0.1:8080", "listen port forward -l x.x.x.x:xxxx (ip/domain:port)")
remotePtr := flag.String("r", "127.0.0.1:9999", "forward to remote fake server -r x.x.x.x:xxxx (ip/domain:port)")
fakeSrvPtr := flag.String("f", "0.0.0.0:8081", "listen fake server -r x.x.x.x:xxxx (ip/domain:port)")
modePtr := flag.String("m", "none", "obfuscation mode (AES encrypted by default): websocket-client, websocket-server, http-client, http-server, none")
keyPtr := flag.String("key", "5fe10ae58c5ad02a6113305f4e702d07", "aes encryption secret: use '-k `openssl passwd -1 -salt ok | md5sum`' to derive key from password")
pemPtr := flag.String("pem", "default", "path to .pem for TLS encryption mode: default = use hardcoded key pair 'CN:target.com', none = plaintext mode")
flag.Parse()
key = []byte(*keyPtr)
//OUTBOUND TRANSLATOR PIPE
or, ow := io.Pipe()
//REMOTE PIPE
rr, rw := io.Pipe()
//INBOUND TRANSLATOR PIPE
ir, iw := io.Pipe()
//LOCAL PIPE
lr, lw := io.Pipe()
//SETUP LOCAL FORWARDER
s , err := net.Listen("tcp", *listenPtr)
if err != nil {
panic(err)
}
fmt.Printf("Local Port Forward Listening: %sn", *listenPtr)
//TLS SETUP
if *pemPtr != "none" {
tlscfg = setupTLS(*pemPtr)
}
//SETUP LOCAL FAKESRV LISTENER
switch *pemPtr {
case "none":
fakeSrv, err = net.Listen("tcp", *fakeSrvPtr)
if err != nil {
panic(err)
}
default:
fakeSrv, err = tls.Listen("tcp", *fakeSrvPtr, tlscfg)
if err != nil {
panic(err)
}
}
if *pemPtr != "none" {
fmt.Printf("FakeSrv listening: %s, TLS mode using key: %sn", *fakeSrvPtr, *pemPtr)
} else {
fmt.Printf("FakeSrv listening: %s, plaintext moden", *fakeSrvPtr)
}
//PROXY LOCAL REQUESTS
go func() {
for {
l , err := s.Accept()
if err != nil {
continue
}
//LOCAL to OUTBOUND TRANSLATOR
go io.Copy(ow, l)
//INBOUND TRANSLATOR to LOCAL
go io.Copy(l, lr)
time.Sleep(400 * time.Millisecond)
}
}()
//LISTEN INCOMING RESPONSES
go func() {
for {
f , err := fakeSrv.Accept()
if err != nil {
continue
}
if strings.HasSuffix(*modePtr, "client") {
f.Write(sham(*modePtr))
}
//REMOTE to INBOUND TRANSLATOR
io.Copy(iw, f)
if strings.HasSuffix(*modePtr, "server") {
f.Write(sham(*modePtr))
}
f.Close()
time.Sleep(400 * time.Millisecond)
}
}()
//FORWARD LOCAL REQUESTS TO REMOTE FAKESRV
go func() {
for {
switch *pemPtr {
default:
r , err = tls.Dial("tcp", *remotePtr, tlscfg)
if err != nil {
time.Sleep(5 * time.Second)
continue
}
case "none":
r , err = net.Dial("tcp", *remotePtr)
if err != nil {
time.Sleep(5 * time.Second)
continue
}
}
//OUTBOUND TRANSLATOR to REMOTE
if _ , err := io.Copy(r, rr); err == nil {
r.Close()
}
time.Sleep(400 * time.Millisecond)
}
}()
//OUTBOUND TRANSLATOR
go func() {
for {
scanner := bufio.NewScanner(or)
for scanner.Scan() {
fmt.Fprintf(rw, obscure_send(scanner.Bytes(), *modePtr))
}
time.Sleep(400 * time.Millisecond)
}
}()
//INBOUND TRANSLATOR
go func() {
for {
scanner := bufio.NewScanner(ir)
for scanner.Scan() {
output := obscure_recv(scanner.Bytes(), *modePtr)
if output != nil {
fmt.Fprintf(lw, string(output) + "n")
}
}
time.Sleep(400 * time.Millisecond)
}
}()
for {
time.Sleep(60 * time.Second)
}
}
原文始发于微信公众号(Khan安全攻防实验室):AES-GCM 加密隧道