mirror of
https://github.com/lyc8503/EasierConnect.git
synced 2025-12-24 12:57:54 +08:00
sort code
This commit is contained in:
87
core/EasyConnectClient.go
Normal file
87
core/EasyConnectClient.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
type EasyConnectClient struct {
|
||||
queryConn net.Conn
|
||||
clientIp []byte
|
||||
token *[48]byte
|
||||
twfId string
|
||||
|
||||
endpoint *EasyConnectEndpoint
|
||||
ipStack *stack.Stack
|
||||
|
||||
server string
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func NewEasyConnectClient(server string) *EasyConnectClient {
|
||||
return &EasyConnectClient{
|
||||
server: server,
|
||||
}
|
||||
}
|
||||
|
||||
func (client *EasyConnectClient) Login(username string, password string) ([]byte, error) {
|
||||
client.username = username
|
||||
client.password = password
|
||||
|
||||
// Web login part (Get TWFID & ECAgent Token => Final token used in binary stream)
|
||||
twfId, err := WebLogin(client.server, client.username, client.password)
|
||||
|
||||
// Store TWFID for AuthSMS
|
||||
client.twfId = twfId
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.LoginByTwfId(twfId)
|
||||
}
|
||||
|
||||
func (client *EasyConnectClient) AuthSMSCode(code string) ([]byte, error) {
|
||||
if client.twfId == "" {
|
||||
return nil, errors.New("SMS Auth not required")
|
||||
}
|
||||
|
||||
twfId, err := AuthSms(client.server, client.username, client.password, client.twfId, code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.LoginByTwfId(twfId)
|
||||
}
|
||||
|
||||
func (client *EasyConnectClient) LoginByTwfId(twfId string) ([]byte, error) {
|
||||
agentToken, err := ECAgentToken(client.server, twfId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.token = (*[48]byte)([]byte(agentToken + twfId))
|
||||
|
||||
// Query IP (keep the connection used so it's not closed too early, otherwise i/o stream will be closed)
|
||||
client.clientIp, client.queryConn, err = QueryIp(client.server, client.token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.clientIp, nil
|
||||
}
|
||||
|
||||
func (client *EasyConnectClient) ServeSocks5(socksBind string, debugDump bool) {
|
||||
// Link-level endpoint used in gvisor netstack
|
||||
client.endpoint = &EasyConnectEndpoint{}
|
||||
client.ipStack = SetupStack(client.clientIp, client.endpoint)
|
||||
|
||||
// Sangfor Easyconnect protocol
|
||||
StartProtocol(client.endpoint, client.server, client.token,
|
||||
&[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, debugDump)
|
||||
|
||||
// Socks5 server
|
||||
ServeSocks5(client.ipStack, client.clientIp, socksBind)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
tls "github.com/refraction-networking/utls"
|
||||
)
|
||||
@@ -43,16 +44,12 @@ func TLSConn(server string) (*tls.UConn, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func MustTLSConn(server string) *tls.UConn {
|
||||
func QueryIp(server string, token *[48]byte) ([]byte, *tls.UConn, error) {
|
||||
conn, err := TLSConn(server)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
debug.PrintStack()
|
||||
return nil, nil, err
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func MustQueryIp(server string, token *[48]byte) ([]byte, *tls.UConn) {
|
||||
conn := MustTLSConn(server)
|
||||
// defer conn.Close()
|
||||
// Query IP conn CAN NOT be closed, otherwise tx/rx handshake will fail
|
||||
|
||||
@@ -63,7 +60,8 @@ func MustQueryIp(server string, token *[48]byte) ([]byte, *tls.UConn) {
|
||||
|
||||
n, err := conn.Write(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
debug.PrintStack()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Printf("query ip: wrote %d bytes", n)
|
||||
@@ -72,17 +70,19 @@ func MustQueryIp(server string, token *[48]byte) ([]byte, *tls.UConn) {
|
||||
reply := make([]byte, 0x80)
|
||||
n, err = conn.Read(reply)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
debug.PrintStack()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Printf("query ip: read %d bytes", n)
|
||||
DumpHex(reply[:n])
|
||||
|
||||
if reply[0] != 0x00 {
|
||||
panic("unexpected query ip reply.")
|
||||
debug.PrintStack()
|
||||
return nil, nil, errors.New("unexpected query ip reply")
|
||||
}
|
||||
|
||||
return reply[4:8], conn
|
||||
return reply[4:8], conn, nil
|
||||
}
|
||||
|
||||
func BlockRXStream(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectEndpoint, debug bool) error {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
@@ -13,13 +13,16 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
)
|
||||
|
||||
func WebLogin(server string, username string, password string) string {
|
||||
var ERR_NEXT_AUTH_SMS = errors.New("SMS Code required.")
|
||||
|
||||
func WebLogin(server string, username string, password string) (string, error) {
|
||||
server = "https://" + server
|
||||
|
||||
c := &http.Client{
|
||||
@@ -32,7 +35,8 @@ func WebLogin(server string, username string, password string) string {
|
||||
|
||||
resp, err := c.Get(addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
debug.PrintStack()
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
@@ -68,7 +72,8 @@ func WebLogin(server string, username string, password string) string {
|
||||
|
||||
encryptedPassword, err := rsa.EncryptPKCS1v15(rand.Reader, &pubKey, []byte(password))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
debug.PrintStack()
|
||||
return "", err
|
||||
}
|
||||
encryptedPasswordHex := hex.EncodeToString(encryptedPassword)
|
||||
log.Printf("Encrypted Password: %s", encryptedPasswordHex)
|
||||
@@ -89,78 +94,104 @@ func WebLogin(server string, username string, password string) string {
|
||||
|
||||
resp, err = c.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
debug.PrintStack()
|
||||
return "", err
|
||||
}
|
||||
|
||||
n, _ = resp.Body.Read(buf)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if strings.Contains(string(buf[:n]), "<Result>0</Result>") {
|
||||
panic("Login FAILED: " + string(buf[:n]))
|
||||
}
|
||||
// log.Printf("First stage login response: %s", string(buf[:n]))
|
||||
|
||||
if strings.Contains(string(buf[:n]), "<NextAuth>-1</NextAuth>") {
|
||||
log.Print("No NextAuth found.")
|
||||
} else if strings.Contains(string(buf[:n]), "<NextService>auth/sms</NextService>") {
|
||||
log.Print("SMS code required")
|
||||
// SMS Code Process
|
||||
if strings.Contains(string(buf[:n]), "<NextService>auth/sms</NextService>") {
|
||||
log.Print("SMS code required.")
|
||||
|
||||
addr = server + "/por/login_sms.csp?apiversion=1"
|
||||
log.Printf("SMS Request: " + addr)
|
||||
req, err := http.NewRequest("POST", addr, nil)
|
||||
req, err = http.NewRequest("POST", addr, nil)
|
||||
req.Header.Set("Cookie", "TWFID="+twfId)
|
||||
|
||||
resp, err = c.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
debug.PrintStack()
|
||||
return "", err
|
||||
}
|
||||
|
||||
n, _ = resp.Body.Read(buf)
|
||||
n, _ := resp.Body.Read(buf)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !strings.Contains(string(buf[:n]), "验证码已发送到您的手机") {
|
||||
panic("unexpected sms resp: " + string(buf[:n]))
|
||||
debug.PrintStack()
|
||||
return "", errors.New("unexpected sms resp: " + string(buf[:n]))
|
||||
}
|
||||
|
||||
log.Printf("SMS Code is sent or still valid.")
|
||||
|
||||
fmt.Print(">>>Please enter your sms code<<<:")
|
||||
smsCode := ""
|
||||
fmt.Scan(&smsCode)
|
||||
|
||||
addr = server + "/por/login_sms1.csp?apiversion=1"
|
||||
log.Printf("SMS Request: " + addr)
|
||||
form := url.Values{
|
||||
"svpn_inputsms": {smsCode},
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("POST", addr, strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Cookie", "TWFID="+twfId)
|
||||
|
||||
resp, err = c.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
n, _ = resp.Body.Read(buf)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !strings.Contains(string(buf[:n]), "Auth sms suc") {
|
||||
panic("SMS Code verification FAILED: " + string(buf[:n]))
|
||||
}
|
||||
|
||||
twfId = string(regexp.MustCompile(`<TwfID>(.*)</TwfID>`).FindSubmatch(buf[:n])[1])
|
||||
log.Print("SMS Code verification SUCCESS")
|
||||
return twfId, ERR_NEXT_AUTH_SMS
|
||||
}
|
||||
|
||||
if strings.Contains(string(buf[:n]), "<NextAuth>-1</NextAuth>") || !strings.Contains(string(buf[:n]), "<NextAuth>") {
|
||||
log.Print("No NextAuth found.")
|
||||
} else {
|
||||
panic("Not implemented auth: " + string(buf[:n]))
|
||||
debug.PrintStack()
|
||||
return "", errors.New("Not implemented auth: " + string(buf[:n]))
|
||||
}
|
||||
|
||||
if !strings.Contains(string(buf[:n]), "<Result>1</Result>") {
|
||||
debug.PrintStack()
|
||||
return "", errors.New("Login FAILED: " + string(buf[:n]))
|
||||
}
|
||||
|
||||
twfIdMatch := regexp.MustCompile(`<TwfID>(.*)</TwfID>`).FindSubmatch(buf[:n])
|
||||
if twfIdMatch != nil {
|
||||
twfId = string(twfIdMatch[1])
|
||||
log.Printf("Update twfId: %s", twfId)
|
||||
}
|
||||
|
||||
log.Printf("Web Login process done.")
|
||||
|
||||
return twfId
|
||||
return twfId, nil
|
||||
}
|
||||
|
||||
func ECAgentToken(server string, twfId string) string {
|
||||
func AuthSms(server string, username string, password string, twfId string, smsCode string) (string, error) {
|
||||
c := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
|
||||
buf := make([]byte, 40960)
|
||||
|
||||
addr := "https://" + server + "/por/login_sms1.csp?apiversion=1"
|
||||
log.Printf("SMS Request: " + addr)
|
||||
form := url.Values{
|
||||
"svpn_inputsms": {smsCode},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", addr, strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Cookie", "TWFID="+twfId)
|
||||
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
debug.PrintStack()
|
||||
return "", err
|
||||
}
|
||||
|
||||
n, _ := resp.Body.Read(buf)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !strings.Contains(string(buf[:n]), "Auth sms suc") {
|
||||
debug.PrintStack()
|
||||
return "", errors.New("SMS Code verification FAILED: " + string(buf[:n]))
|
||||
}
|
||||
|
||||
twfId = string(regexp.MustCompile(`<TwfID>(.*)</TwfID>`).FindSubmatch(buf[:n])[1])
|
||||
log.Print("SMS Code verification SUCCESS")
|
||||
|
||||
return twfId, nil
|
||||
}
|
||||
|
||||
func ECAgentToken(server string, twfId string) (string, error) {
|
||||
dialConn, err := net.Dial("tcp", server)
|
||||
defer dialConn.Close()
|
||||
conn := utls.UClient(dialConn, &utls.Config{InsecureSkipVerify: true}, utls.HelloGolang)
|
||||
@@ -177,8 +208,9 @@ func ECAgentToken(server string, twfId string) string {
|
||||
buf := make([]byte, 40960)
|
||||
n, err := conn.Read(buf)
|
||||
if n == 0 || err != nil {
|
||||
panic("ECAgent Request invalid: error " + err.Error() + "\n" + string(buf[:n]))
|
||||
debug.PrintStack()
|
||||
return "", errors.New("ECAgent Request invalid: error " + err.Error() + "\n" + string(buf[:n]))
|
||||
}
|
||||
|
||||
return hex.EncodeToString(conn.HandshakeState.ServerHello.SessionId)[:31] + "\x00"
|
||||
return hex.EncodeToString(conn.HandshakeState.ServerHello.SessionId)[:31] + "\x00", nil
|
||||
}
|
||||
|
||||
@@ -4,37 +4,30 @@ import (
|
||||
"EasierConnect/core"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Process(host, port, username, password, socksBind string) {
|
||||
// CLI args
|
||||
//host, port, username, password, socksBind := "", 0, "", "", ""
|
||||
|
||||
_, debugDump := os.LookupEnv("DEBUG_DUMP")
|
||||
|
||||
if host == "" || username == "" || password == "" {
|
||||
log.Fatal("Missing required cli args, refer to `EasierConnect --help`.")
|
||||
}
|
||||
server := fmt.Sprintf("%s:%s", host, port)
|
||||
client := core.NewEasyConnectClient(server)
|
||||
|
||||
// Web login part (Get TWFID & ECAgent Token => Final token used in binary stream)
|
||||
twfId := core.WebLogin(server, username, password)
|
||||
agentToken := core.ECAgentToken(server, twfId)
|
||||
token := (*[48]byte)([]byte(agentToken + twfId))
|
||||
var ip []byte
|
||||
var err error
|
||||
ip, err = client.Login(username, password)
|
||||
if err == core.ERR_NEXT_AUTH_SMS {
|
||||
// TODO: input sms code via gui
|
||||
fmt.Print(">>>Please enter your sms code<<<:")
|
||||
smsCode := ""
|
||||
fmt.Scan(&smsCode)
|
||||
|
||||
// Query IP (keep the connection used, so it's not closed too early, otherwise i/o stream will be closed)
|
||||
ip, conn := core.MustQueryIp(server, token)
|
||||
defer conn.Close()
|
||||
log.Printf("IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
|
||||
ip, err = client.AuthSMSCode(smsCode)
|
||||
}
|
||||
|
||||
// Link-level endpoint used in gvisor netstack
|
||||
endpoint := &core.EasyConnectEndpoint{}
|
||||
ipStack := core.SetupStack(ip, endpoint)
|
||||
if err != nil {
|
||||
// TODO: show error in gui
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Sangfor Easyconnect protocol
|
||||
core.StartProtocol(endpoint, server, token, &[4]byte{ip[3], ip[2], ip[1], ip[0]}, debugDump)
|
||||
log.Printf("Login success, your IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
|
||||
|
||||
// Socks5 server
|
||||
core.ServeSocks5(ipStack, ip, socksBind)
|
||||
client.ServeSocks5(socksBind, false)
|
||||
}
|
||||
|
||||
42
main.go
42
main.go
@@ -9,38 +9,46 @@ import (
|
||||
|
||||
func main() {
|
||||
// CLI args
|
||||
host, port, username, password, socksBind := "", 0, "", "", ""
|
||||
host, port, username, password, socksBind, twfId := "", 0, "", "", "", ""
|
||||
flag.StringVar(&host, "server", "", "EasyConnect server address (e.g. vpn.nju.edu.cn)")
|
||||
flag.StringVar(&username, "username", "", "Your username")
|
||||
flag.StringVar(&password, "password", "", "Your password")
|
||||
flag.StringVar(&socksBind, "socks-bind", ":1080", "The addr socks5 server listens on (e.g. 0.0.0.0:1080)")
|
||||
flag.StringVar(&twfId, "twf-id", "", "Login using twfID captured (mostly for debug usage)")
|
||||
flag.IntVar(&port, "port", 443, "EasyConnect port address (e.g. 443)")
|
||||
debugDump := false
|
||||
flag.BoolVar(&debugDump, "debug-dump", false, "Enable traffic debug dump (only for debug usage)")
|
||||
flag.Parse()
|
||||
|
||||
if host == "" || username == "" || password == "" {
|
||||
if host == "" || ((username == "" || password == "") && twfId == "") {
|
||||
log.Fatal("Missing required cli args, refer to `EasierConnect --help`.")
|
||||
}
|
||||
server := fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
// Web login part (Get TWFID & ECAgent Token => Final token used in binary stream)
|
||||
twfId := core.WebLogin(server, username, password)
|
||||
agentToken := core.ECAgentToken(server, twfId)
|
||||
token := (*[48]byte)([]byte(agentToken + twfId))
|
||||
client := core.NewEasyConnectClient(server)
|
||||
|
||||
// Query IP (keep the connection used so it's not closed too early, otherwise i/o stream will be closed)
|
||||
ip, conn := core.MustQueryIp(server, token)
|
||||
defer conn.Close()
|
||||
log.Printf("IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
|
||||
var ip []byte
|
||||
var err error
|
||||
if twfId != "" {
|
||||
if len(twfId) != 16 {
|
||||
panic("len(twfid) should be 16!")
|
||||
}
|
||||
ip, err = client.LoginByTwfId(twfId)
|
||||
} else {
|
||||
ip, err = client.Login(username, password)
|
||||
if err == core.ERR_NEXT_AUTH_SMS {
|
||||
fmt.Print(">>>Please enter your sms code<<<:")
|
||||
smsCode := ""
|
||||
fmt.Scan(&smsCode)
|
||||
|
||||
// Link-level endpoint used in gvisor netstack
|
||||
endpoint := &core.EasyConnectEndpoint{}
|
||||
ipStack := core.SetupStack(ip, endpoint)
|
||||
ip, err = client.AuthSMSCode(smsCode)
|
||||
}
|
||||
}
|
||||
|
||||
// Sangfor Easyconnect protocol
|
||||
core.StartProtocol(endpoint, server, token, &[4]byte{ip[3], ip[2], ip[1], ip[0]}, debugDump)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
log.Printf("Login success, your IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
|
||||
|
||||
// Socks5 server
|
||||
core.ServeSocks5(ipStack, ip, socksBind)
|
||||
client.ServeSocks5(socksBind, debugDump)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user