Skip to content

بروتوكول Trojan

Trojan هو بروتوكول مُصمّم ليكون غير قابل للتمييز عن حركة TLS/HTTPS العادية. يعتمد كلياً على طبقة نقل TLS للتشفير — يُرسل البروتوكول نفسه المصادقة والأوامر بنص عادي عبر نفق TLS. عندما يصل طلب غير صالح، يمكن للخادم "التراجع" إلى خادم ويب حقيقي، مما يجعل الوكيل غير قابل للكشف.

نظرة عامة

  • الاتجاه: وارد + صادر
  • النقل: TCP، مقبس UNIX (يجب استخدام نقل TLS/REALITY)
  • التشفير: بدون (مُفوّض لطبقة نقل TLS)
  • المصادقة: سلسلة سداسية عشرية SHA224(كلمة المرور)
  • Mux: غير مدعوم أصلاً (لكن تغليف XUDP ممكن عبر طبقات أخرى)
  • التراجع: تراجع مُدمج متعدد المستويات (SNI، ALPN، المسار)

صيغة السلك (Wire Format)

طلب TCP (من العميل إلى الخادم)

+----------------------------------------------+------+-----+----------+------+
| SHA224(password) hex                         | CRLF | Cmd | Address  | CRLF |
| 56 bytes ASCII                               | 2B   | 1B  | variable | 2B   |
+----------------------------------------------+------+-----+----------+------+
| Payload ...                                                                 |
+-----------------------------------------------------------------------------+

المصدر: proxy/trojan/protocol.go:64-95

الحقلالحجمالوصف
تجزئة كلمة المرور56 بايتSHA-224 مُرمّز بالسداسي عشري لكلمة المرور النصية
CRLF2 بايت\r\n (0x0D 0x0A)
الأمر1 بايت0x01 = TCP Connect، 0x03 = UDP Associate
العنوانمتغيربأسلوب SOCKS5: نوع العنوان(1 بايت) + العنوان + المنفذ(2 بايت, BE)
CRLF2 بايت\r\n (0x0D 0x0A)

أنواع العناوين (مطابقة لـ SOCKS5):

البايتالنوع
0x01IPv4 (4 بايت)
0x03نطاق (1 بايت للطول + سلسلة نصية)
0x04IPv6 (16 بايت)

المصدر: proxy/trojan/protocol.go:16-21

بعد الترويسة، تكون البيانات المتبقية على تدفق TCP هي الحمولة الخام.

استجابة TCP

يُرسل الخادم الحمولة الخام — بدون ترويسة استجابة. وذلك لأن البروتوكول مصمّم ليبدو كحركة TLS عادية.

تأطير UDP

عندما يكون الأمر UDP (0x03)، تُؤطّر الحمولة لكل حزمة:

+----------+---------+------+---------+
| Address  | Length  | CRLF | Payload |
| variable | 2B BE   | 2B   | Length  |
+----------+---------+------+---------+
| next packet ...                     |
+-------------------------------------+

المصدر: proxy/trojan/protocol.go:125-150

الحقلالحجمالوصف
العنوانمتغيربأسلوب SOCKS5: نوع العنوان(1 بايت) + العنوان + المنفذ(2 بايت, BE)
الطول2 بايتuint16 بترتيب big-endian، طول الحمولة (أقصى 8192)
CRLF2 بايت\r\n
الحمولةبايتات بحسب الطولحزمة UDP

يقرأ PacketReader على جانب الخادم كل حزمة مُؤطّرة ويُرفق الوجهة كـ buffer.UDP:

go
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
    addr, port, err := addrParser.ReadAddressPort(nil, r)
    // ...
    remain := int(binary.BigEndian.Uint16(lengthBuf[:]))
    if remain > maxLength { return nil, errors.New("oversize payload") }
    // read CRLF, then read `remain` bytes of payload
}

المصدر: proxy/trojan/protocol.go:220-262

مصادقة كلمة المرور

تُجزّأ كلمة المرور بـ SHA-224 وتُرمّز بالسداسي عشري لإنتاج سلسلة ASCII بطول 56 بايت:

go
func hexSha224(password string) []byte {
    buf := make([]byte, 56)
    hash := sha256.New224()
    hash.Write([]byte(password))
    hex.Encode(buf, hash.Sum(nil))
    return buf
}

المصدر: proxy/trojan/config.go:43-49

يخزّن MemoryAccount كلاً من كلمة المرور النصية والمفتاح السداسي عشري المحسوب مسبقاً بطول 56 بايت.

المُدقق (Validator)

يستخدم Validator مثيلين من sync.Map:

  • users: يربط تجزئة SHA224 المُرمّزة بالسداسي عشري -> *protocol.MemoryUser
  • email: يربط البريد الإلكتروني بأحرف صغيرة -> *protocol.MemoryUser

المصدر: proxy/trojan/validator.go:12-82

المعالج الوارد (الخادم)

الملف: proxy/trojan/server.go

معالجة الاتصال

mermaid
sequenceDiagram
    participant C as Client
    participant S as Server
    participant F as Fallback

    C->>S: TLS ClientHello
    S->>C: TLS ServerHello + Certificate
    C->>S: SHA224(password) + CRLF + Command + Address + CRLF + Payload

    alt Valid password
        S->>S: Parse command & address
        alt TCP Command
            S->>C: Raw proxied response
        else UDP Command
            S->>C: Framed UDP packets
        end
    else Invalid or short data
        S->>F: Forward entire connection to fallback
    end

يقرأ الخادم المخزن المؤقت الأول (حتى buf.Size بايت) ويُجري تحققاً سريعاً:

go
if firstLen < 58 || first.Byte(56) != '\r' {
    // Not trojan protocol - fallback
    shouldFallback = true
} else {
    user = s.validator.Get(hexString(first.BytesTo(56)))
    if user == nil {
        shouldFallback = true
    }
}

المصدر: proxy/trojan/server.go:176-201

النقطة الجوهرية: يجب أن تكون أول 56 بايت تجزئة SHA224 سداسية عشرية صالحة، متبوعة بـ \r في الموضع 56. يتم التحقق من هذا قبل تحليل الترويسة بالكامل.

آلية التراجع

عندما يكون التراجع مُهيّأ، يتم تحويل الاتصالات غير الصالحة بشفافية إلى خادم آخر. يدعم نظام التراجع ثلاثة مستويات من المطابقة:

  1. SNI (name): يُطابق مع ServerName لـ TLS
  2. ALPN (alpn): يُطابق مع بروتوكول ALPN المتفاوض عليه
  3. المسار (path): يُطابق مع مسار طلب HTTP (المستخرج من البايتات الأولى)
go
type Fallback struct {
    Name string  // SNI match
    Alpn string  // ALPN match
    Path string  // HTTP path match
    Dest string  // Destination address (e.g., "127.0.0.1:8080")
    Type string  // Network type ("tcp" or "unix")
    Xver uint64  // PROXY protocol version (0=none, 1=v1, 2=v2)
}

بنية بيانات التراجع هي خريطة متداخلة من 3 مستويات: map[name]map[alpn]map[path]*Fallback

المصدر: proxy/trojan/server.go:66-113

يدعم معالج التراجع أيضاً بروتوكول PROXY (النص v1 والثنائي v2) لتمرير عنوان IP الحقيقي للعميل إلى الخادم الخلفي:

go
// PROXY protocol v1
"PROXY TCP4 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n"

// PROXY protocol v2
"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"  // signature
"\x21\x11\x00\x0C"                                      // v2 + PROXY + AF_INET + STREAM + 12 bytes

المصدر: proxy/trojan/server.go:493-522

المعالج الصادر (العميل)

الملف: proxy/trojan/client.go

يقوم معالج العميل بما يلي:

  1. الاتصال بالخادم عبر النقل المُهيّأ (عادةً TLS)
  2. إنشاء ConnWriter الذي يكتب الترويسة بشكل كسول عند أول استدعاء Write()
  3. لـ UDP، يُغلّف بـ PacketWriter لتأطير كل حزمة
  4. إرسال الحمولة الأولى مع الترويسة لتحسين 0-RTT
go
connWriter := &ConnWriter{
    Writer:  bufferWriter,
    Target:  destination,
    Account: account,
}
// First write triggers header send
buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100)

المصدر: proxy/trojan/client.go:99-128

تُجمّع ConnWriter.writeHeader():

go
buffer.Write(c.Account.Key)   // 56-byte SHA224 hex
buffer.Write(crlf)            // \r\n
buffer.WriteByte(command)     // 0x01 or 0x03
addrParser.WriteAddressPort(&buffer, c.Target.Address, c.Target.Port)
buffer.Write(crlf)            // \r\n

المصدر: proxy/trojan/protocol.go:64-95

ملاحظات التنفيذ

  1. متطلب TLS: لا يوفر Trojan أي تشفير بنفسه. بدون TLS، تُنقل تجزئة كلمة المرور وجميع حركة المرور بنص واضح. البروتوكول مصمّم للاستخدام دائماً مع نقل TLS أو REALITY.

  2. التراجع حيوي للتخفي: عندما لا يُعثر على ترويسة Trojan صالحة، يجب على الخادم تحويل الاتصال إلى خادم ويب حقيقي. هذا يعني أن فحص المنافذ والاستقصاء لا يمكنهما تمييز الخادم عن موقع HTTPS عادي.

  3. بدون ترويسة استجابة: بخلاف VMess، لا توجد ترويسة من الخادم إلى العميل على الإطلاق. هذا يُبسّط التنفيذ لكنه يعني أن العميل لا يستطيع كشف أخطاء الخادم على مستوى البروتوكول.

  4. حد أقصى لحمولة UDP: كل إطار UDP محدود بـ 8192 بايت (maxLength = 8192). تُرفض الحزم التي تتجاوز هذا الحد.

  5. ConnReader ذو حالة: يُستدعى ConnReader.ParseHeader() مرة واحدة فقط. بعد ذلك، يمرّر Read() مباشرة إلى القارئ الأساسي. يمنع علم headerParsed إعادة التحليل.

  6. دعم الشبكة: يدعم الخادم net.Network_TCP وnet.Network_UNIX، لكن ليس UDP الخام. يتم نقل حركة UDP كبيانات مُؤطّرة داخل اتصال TCP/TLS.

المصدر: proxy/trojan/server.go:144-146

تحليل تقني لأغراض إعادة التنفيذ.