sort code

This commit is contained in:
lyc8503
2023-01-23 20:57:17 +08:00
parent 39608cd767
commit ec37957400
5 changed files with 220 additions and 100 deletions

87
core/EasyConnectClient.go Normal file
View 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)
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
View File

@@ -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)
}