浅析Vmess流量与强网杯2022谍影重重

zwh_china 2022-08-02 09:41:00

前置知识

MAC

在密码学中,Message Authentication Code(消息认证码,有时也被称为tag)是用来认证消息的比较短的信息。换言之,MAC用来保证消息的数据完整性消息的数据源认证。

MAC由消息本身和一个密钥经过一系列计算产生,用于生成MAC的算法,称为MAC算法。MAC算法应能满足如下几个条件:

  • 在仅有消息本身没有密钥的情况下,无法得到该消息的MAC
  • 同一个消息在使用不同密钥的情况下,生成的MAC应当无关联
  • 在已有一系列消息以及其MAC时,给定一个新的消息,无法得到该消息的MAC。

下图摘自维基百科,可以很好的描述MAC的使用原理:

image

HMAC

HMAC (有时扩展为 英语:keyed-hash message authentication code, 密钥散列消息认证码, 或 英语:hash-based message authentication code,散列消息认证码),是一种通过特别计算方式之后产生的消息认证码(MAC),使用密码散列函数,同时结合一个加密密钥。它可以用来保证资料的完整性,同时可以用来作某个消息的身份验证。

HMAC是MAC算法中的一种,其基于加密HASH算法实现。任何加密HASH, 比如MD5、SHA256等,都可以用来实现HMAC算法,其相应的算法称为HMAC-MD5、HMAC-SHA256等。

image-20220801013501-8ic6jvh

function hmac (key, message)
    if (length(key) > blocksize) then
        key = hash(key) // keys longer than blocksize are shortened
    end if
    if (length(key) < blocksize) then
        key = key + [0x00 * (blocksize - length(key))] // keys shorter than blocksize are zero-padded (where ∥ is concatenation)
    end if

    o_key_pad = [0x5c * blocksize]  key // Where blocksize is that of the underlying hash function
    i_key_pad = [0x36 * blocksize]  key // Where ⊕ is exclusive or (XOR)

    return hash(o_key_pad + hash(i_key_pad + message)) 
end function

AEAD

认证加密(英语:Authenticated encryption,AE)和带有关联数据的认证加密(authenticated encryption with associated data,AEAD,AE的变种)是一种能够同时保证数据的保密性完整性真实性的一种加密模式。这些属性都是在一个易于使用的编程接口下提供的。

具体的实现方法

  • Encrypt-then-MAC (EtM)

EtM方法
首先对明文进行加密,然后根据得到的密文生成消息认证码(MAC)。密文和它的MAC一起发送。例如IPsec。EtM是ISO/IEC 19772:2009规定的六种认证加密方法中的一种。[5]这是唯一可以达到认证加密安全性最高定义的方法,但这只有在使用的MAC“强不可伪造”时才能实现。[8]2014年11月,EtM的传输层安全性协议(TLS)和资料包传输层安全(DTLS)扩展已经作为RFC 7366发布。各种EtM密码包也存在于SSHv2中(例如hmac-sha1-etm@openssh.com)。

image

  • Encrypt-and-MAC (E&M)

基于明文生成MAC,并且明文在没有MAC的情况下被加密。明文的MAC和密文一起发送。用于例如SSH。E&M方法本身并未被证明是“强不可伪造”的。

image

  • MAC-then-Encrypt (MtE)

基于明文生成MAC,然后将明文和MAC一起加密以基于两者生成密文。密文(包含加密的MAC)被发送。MtE方法本身并未被证明是“强不可伪造”的。用于例如SSL/TLS。[8]尽管有理论上的安全性,但对SSL/TLS进行更深入的分析将保护模型化为MAC-then-pad-then-encrypt,即明文先填充到加密函数的块大小。填充错误通常会导致接收方发现可检测到的错误,从而导致密文填塞攻击(Padding oracle attack),如Lucky Thirteen attack。

image

Vmess协议的Client(基于v2ray-core)

image

ClientSession.isAEAD

这里首先有一个是否采用AEAD的属性

在outbound中可以看到

image

isAEAD默认是false,只有在没有明确关闭AEAD模式,同时在client配置文件中没有指定了AlterID属性时才会启用

ClientSession.idHash

这个属性protocol.idHash其实是一个hmac函数,通过这个函数可以计算mac值。前面前置知识写了mac算法有很多种,hmac便是一种。这个属性一般都是传默认值,protocol.DefaultIDHash

ClientSession.requestBodyKey和ClientSession.requestBodyIV和ClientSession.responseHeader

这个比较关键,是request加密的关键参数。

image

可以看到,首先生成了33位随机的byte,然后把前16位作为BodyKey,后16位作为BodyIV。同时最后一位留作校验responseHeader。

如果client没有启用AEAD模式,则返回的BodyKey应该为requestBodyKey的md5,返回的BodyIV也为requestBodyIV的md5。

如果client启用了AEAD模式,则返回的BodyKey为requestBodyKey进行sha256后得到的前16位,返回的BodyIV同理。

protocol.MemoryUser结构体

image

protocol.Account结构体

Account结构体是MemoryUser的核心属性

image

Account结构体中对应config.json中的设置

image

我们也可以通过

image

这种方式动态生成protocol.Account

header包中的RequestHeader

image

ClientSession.EncodeRequestHeader

客户端请求认证信息部分

image

查看outbound中客户端发请求的部分,

image

其中的reuqest头部分的Version为硬编码的1,User。

image

再回到client的Encode函数中,可以看到从拿到的header中拿出了用户信息,同时判断client是否被设置成AEAD模式,如果没设置AEAD模式那就好说,拿到一个以用户的ValidID作为key的HMAC生成器。此处的ValidID是怎么来的呢,我们跟进看

image

当MemoryAccount中的中的AlterID个数为0时返回MemoryAccound中的ID,其实也就是config.json中指定的id(如果配置json中没指定,那就会随机从protocol包中的server_spec进行随机抽取)。

image

也就是说根据配置文件中的id作为hmac算法的key拿到一个hmac生成器,同时把当前的时间戳放进去,算出来一个MAC,将其写入请求流

image

也就是官方文档中的客户端请求中的16字节认证信息。

客户端请求指令部分

imageimage

指令部分此处不做详细分析。

image

image

ClientSession.EncodeRequestBody

客户端请求数据部分

查看outbound中客户端发请求的剩下部分

image

首先调用Client.EncodeRequestBody拿到一个关联了请求输出writer的加密bodyWrite,任何写进bodyWriter的数据都会直接被自动加密,接着拿着这个封装好的bodyWriter直接进行一个buf.Copy,把客户需要代理的数据直接给原封不动送进去。

接着看比较核心的ClientSession.EncodeRequestBody核心逻辑,直接先参考官方文档

image

可以看到整个的实现完全符合文档规范

image

至此,我们完成了客户端请求的发送部分。

Vmess协议的Server(基于v2ray-core)

image

ServerSession.userValidator

这个从名字就能知道肯定是验证用户的,它的类型是TimedUserValidator,从类型名也能知道这个是和时间有关系。

image

这个对应user数组也就是

image

然后再看一下这个TimedUserValidator的构造函数

image

注意这里的baseTime,取得是本地时间的时间戳,hasher基本都是和client一样的defaultIdHash,同时设置定时更新IDhash和客户端保持一致

image

image

间隔为10s一次。

ServerSession.DecodeReuqestHeader

服务端请求认证部分

看Inbound.go中的处理函数

image

image

主要就是根据Handler中的clients也就是TimedUserValidator和sessionHistory来创建ServerSession

image

然后使用创建好的ServerSession调用DecodeRequestHeader

image

这里先会读取16字节(protocol.IDBytesLen),然后尝试解密

image

image这里服务端遍历TimedUserValidator中所有user的hash,看看有没有能和HMAC匹配的hash,匹配到了则返回这个MemoryUser。

同时既然在GetAEAD这个操作中取到了对应User,那自然说明client是AEAD形式认证模式。返回true。

服务端请求指令部分

image

和客户端那边一样,对应是否存在AEAD的模式采取不同处理。

ServerSession.DecodeRequestBody

服务端请求数据部分

image

继续看inbound中处理的RequestBody,也就是请求的数据部分

image

这也是和client一样直接返回解密流writer,我们直接读取就行。

ServerSession.EncodeResponseHeader

服务端响应头部分

image

image

image

ServerSession.EncodeResponseBody

image

返回包裹好的加密流writer

强网杯2022-谍影重重解密

题目给了一个pcap包,使用wireshark打开并追踪流发现

image

捕获数据是一个socket连接传输的一个完整数据。

伪造时间戳

根据前面介绍和官方文档,IDHash的生成依赖于当前系统对应UTC时间的时间戳

image

因此我们在wireshark中观察frame时间

image

更改NewTimedUserValidator中的baseTime

image

注意 尽管我们更改了baseTime,但是每隔10s仍然会更近时间为本地时间,但是因为程序处理时间较短用不了一个updateInterval的时间,因此此处为了做题不需要考虑task.Periodic的更新

伪造vmess ServerSession

因为根据题目给的流量,客户端先发起请求,所以我们先要解密客户请求的内容,进而先构造ServserSession

结合题目给的配置文件

image

image

我们可以构造一个这样的ServerSession

伪造vmess ClientSession

image

这里我改了一下NewClientSession直接把ServerSession传入

image

目的是获取server端解密后的Request和Response加密参数,

image

对应的我也再ServerSession中添加了对应方法。

image

注意,这里我经过测试得出了请求流量的AEAD模式并未开启值为false,所以响应的BodyKey和BodyIV是md5,而非sha256

读入数据包

观察wireshark流量发现存在两段客户端发给服务端的流量

image

我们手动导入

image

然后遍历pcap包提取服务端发给客户端的数据

image

解密

image

image

image

解密得出一个html文件,html文件打开后会执行js代码下载含有恶意宏的word文件。把word上传至VT分析即可拿到c2 api解密压缩包。

image

解压后得到一个gob文件使用degob进行反序列化。

cat flag|base64|go run main.go -b64|tee dump.txt

image

包含了日期 再使用golang伪随机数unshuffle,并对图片处理得到最终flag。

完整流量解密脚本构造

直接用v2ray现成的库(https://github.com/v2ray/v2ray-core)

Route.pcapng

package encoding_test

import (
    "bytes"
    "context"
    "fmt"
    "github.com/google/gopacket"
    "github.com/google/gopacket/layers"
    "github.com/google/gopacket/pcap"
    "io"
    "os"
    "testing"
    "v2ray.com/core/common"
    "v2ray.com/core/common/buf"
    "v2ray.com/core/common/protocol"
    "v2ray.com/core/proxy/vmess"
    . "v2ray.com/core/proxy/vmess/encoding"
)

func toAccount(a *vmess.Account) protocol.Account {
    account, err := a.AsAccount()
    common.Must(err)
    return account
}

func TestEncryption(t *testing.T) {
    user := &protocol.MemoryUser{}

    account := &vmess.Account{
        Id: "b831381d-6324-4d53-ad4f-8cda48b30811"}
    user.Account = toAccount(account)

    sessionHistory := NewSessionHistory()

    defer common.Close(sessionHistory)

    userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
    userValidator.Add(user)
    defer common.Close(userValidator)

    server := NewServerSession(userValidator, sessionHistory)
    var data []byte
    data = []byte{
        0x4d, 0xd1, 0x1f, 0x9b, 0x04, 0xf2, 0xb5, 0x62,
        0xb9, 0xdb, 0x53, 0x9d, 0x93, 0x9f, 0x1d, 0x52,
        0xb4, 0x8b, 0x35, 0xbf, 0x59, 0x2c, 0x09, 0xb2,
        0x15, 0x45, 0x39, 0x2f, 0x73, 0xf6, 0xce, 0xf9,
        0x11, 0x43, 0x78, 0x64, 0x64, 0x57, 0x8c, 0x1c,
        0x36, 0x1a, 0xa7, 0x2f, 0x63, 0x8c, 0xd0, 0x13,
        0x5f, 0x25, 0x34, 0x35, 0x55, 0xf5, 0x09, 0xae,
        0xf6, 0xc7, 0x4c, 0xd2, 0xa2, 0xb8, 0x6e, 0xe0,
        0xa9, 0xeb, 0x3b, 0x93, 0xa8, 0x1a, 0x54, 0x1d,
        0xef, 0x47, 0x63, 0xcc, 0x54, 0xf9, 0x1b, 0xa0,
        0x26, 0x81, 0xad, 0xd1, 0xb8, 0x15, 0xe8, 0xc5,
        0x0e, 0x02, 0x8c, 0x76, 0xbd, 0xe0, 0xee, 0x8a,
        0x95, 0x93, 0xdb, 0x88, 0xd9, 0x01, 0x06, 0x63,
        0x05, 0xa5, 0x1a, 0x95, 0x86, 0xa9, 0xe3, 0x77,
        0xee, 0x10, 0x0e, 0x7d, 0x4d, 0x33, 0xfc, 0xfc,
        0x04, 0x53, 0xc8, 0x6b, 0x19, 0x98, 0xa9, 0x52,
        0x75, 0xcd, 0x93, 0x68, 0xa6, 0x88, 0x20, 0xc2,
        0xa6, 0xa5, 0x40, 0xb6, 0x38, 0x6c, 0x14, 0x6e,
        0xa7, 0x57, 0x9c, 0xfe, 0x87, 0xb2, 0xe4, 0x59,
        0x85, 0x67, 0x72, 0xef, 0xdc, 0xf0, 0xe4, 0xc6,
        0xab, 0x0f, 0x11, 0xd0, 0x18, 0xa1, 0x55, 0x61,
        0xcf, 0x40, 0x9c, 0xbc, 0x00, 0x49, 0x1d, 0x7f,
        0x4d, 0x22, 0xb7, 0xc4, 0x86, 0xa7, 0x6a, 0x5f,
        0x2f, 0x25, 0xfb, 0xef, 0x50, 0x35, 0x51, 0xa0,
        0xae, 0xb9, 0x0a, 0xd9, 0xdd, 0x24, 0x6a, 0x9c,
        0xc5, 0xe0, 0xd0, 0xc0, 0xb7, 0x51, 0xeb, 0x7b,
        0x54, 0xb0, 0xab, 0xbf, 0xef, 0x19, 0x8b, 0x1c,
        0x4e, 0x5e, 0x75, 0x50, 0x77, 0x46, 0x9c, 0x31,
        0x8f, 0x20, 0xf3, 0xe4, 0x18, 0xaf, 0x03, 0x54,
        0x08, 0x11, 0xab, 0x5c, 0x1e, 0xa7, 0x80, 0xc8,
        0x86, 0xea, 0x2c, 0x90, 0x3b, 0x45, 0x8a, 0x26} //client header
    var data1 []byte
    data1 = []byte{ /* Packet 92 */
        0x04, 0xf0, 0x65, 0xe0, 0xbb, 0xc3, 0x52, 0x6b,
        0xcf, 0x41, 0x14, 0xad, 0x95, 0xf8, 0x36, 0x71,
        0x9b, 0x5c, 0xf3, 0x42, 0x11, 0x12, 0xc8, 0x03,
        0x36, 0xc6, 0xc3, 0x52, 0xad, 0x5d, 0x26, 0xa5,
        0xda, 0xa9, 0xa2, 0xf5, 0x86, 0x90, 0x26, 0xe6,
        0xa1, 0x20, 0x7c, 0xd2, 0x10, 0x85, 0x1c, 0x29,
        0x34, 0x2f, 0xd5, 0x27, 0xd9, 0x4f, 0x85, 0x70,
        0x35, 0x8e, 0xc1, 0x5a, 0x79, 0xc9, 0x5d, 0xb3,
        0x1a, 0x68, 0x8b, 0xc6, 0x09, 0xa6, 0x46, 0xab,
        0x74, 0xa9, 0xd9, 0xcd, 0xce, 0xa1, 0xe8, 0x28} //client body

    var data2 []byte
    if handle, err := pcap.OpenOffline("./Route.pcapng"); err != nil {
        panic(err)
    } else {
        packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
        for packet := range packetSource.Packets() {
            if packet != nil {
                if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil && len(tcpLayer.LayerPayload()) > 0 {
                    if bytes.Compare(tcpLayer.LayerPayload(), data) != 0 && bytes.Compare(tcpLayer.LayerPayload(), data1) != 0 {
                        data2 = append(data2, tcpLayer.LayerPayload()...)
                    } else {
                        fmt.Println("Found client package")
                    }
                }

            }
        }
    }

    OutboundResponseConn := data2                    //from server
    InboudRequestConn := append((data)[:], data1...) //from client
    reader := &buf.BufferedReader{Reader: buf.NewReader(bytes.NewReader(InboudRequestConn))}
    reader1 := &buf.BufferedReader{Reader: buf.NewReader(bytes.NewReader(OutboundResponseConn))}
    request, err := server.DecodeRequestHeader(reader)
    if err != nil {
        panic(err)
    }
    fmt.Println("Received request for ", request.Destination())

    bodyReader := server.DecodeRequestBody(request, reader)
    fmt.Println(bodyReader.ReadMultiBuffer())

    client := NewClientSession(false, protocol.DefaultIDHash, context.TODO(), server)
    _, err = client.DecodeResponseHeader(reader1)

    bodyReader1 := client.DecodeResponseBody(request, reader1)

    stringData, err := bodyReader1.ReadMultiBuffer()

    f, err := os.Create("./evil.html")
    for err == nil {
        f.Write([]byte(stringData.String()))
        stringData, err = bodyReader1.ReadMultiBuffer()

    }
    if err != io.EOF {
        t.Error("nil error")
    }
}

然后改动一下server和client

  • client.go
package encoding

import (
    "bytes"
    "context"
    "crypto/aes"
    "crypto/cipher"
    "crypto/md5"
    "crypto/rand"
    "encoding/binary"
    "hash"
    "hash/fnv"
    "io"

    "golang.org/x/crypto/chacha20poly1305"

    "v2ray.com/core/common"
    "v2ray.com/core/common/bitmask"
    "v2ray.com/core/common/buf"
    "v2ray.com/core/common/crypto"
    "v2ray.com/core/common/dice"
    "v2ray.com/core/common/protocol"
    "v2ray.com/core/common/serial"
    "v2ray.com/core/proxy/vmess"
    vmessaead "v2ray.com/core/proxy/vmess/aead"
)

func hashTimestamp(h hash.Hash, t protocol.Timestamp) []byte {
    common.Must2(serial.WriteUint64(h, uint64(t)))
    common.Must2(serial.WriteUint64(h, uint64(t)))
    common.Must2(serial.WriteUint64(h, uint64(t)))
    common.Must2(serial.WriteUint64(h, uint64(t)))
    return h.Sum(nil)
}

// ClientSession stores connection session info for VMess client.
type ClientSession struct {
    isAEAD          bool
    idHash          protocol.IDHash
    requestBodyKey  [16]byte
    requestBodyIV   [16]byte
    responseBodyKey [16]byte
    responseBodyIV  [16]byte
    responseReader  io.Reader
    responseHeader  byte
}

// NewClientSession creates a new ClientSession.
func NewClientSession(isAEAD bool, idHash protocol.IDHash, ctx context.Context, sessionServer *ServerSession) *ClientSession {

    session := &ClientSession{
        isAEAD: sessionServer.isAEADRequest,
        idHash: idHash,
    }

    //randomBytes := make([]byte, 33) // 16 + 16 + 1
    //common.Must2(rand.Read(randomBytes))
    //copy(session.requestBodyKey[:], randomBytes[:16])
    //session.requestBodyIV = []uint8{}
    //copy(session.requestBodyIV[:], randomBytes[16:32])
    //session.responseHeader = randomBytes[32]
    session.requestBodyIV = sessionServer.getRequestBodyIV()
    session.responseBodyIV = sessionServer.getResponseBodyIV()
    session.requestBodyKey = sessionServer.getRequestBodyKey()
    session.responseBodyKey = sessionServer.getResponseBodyKey()
    //if !session.isAEAD {
    //  session.responseBodyKey = md5.Sum(session.requestBodyKey[:])
    //  session.responseBodyIV = md5.Sum(session.requestBodyIV[:])
    //} else {
    //  BodyKey := sha256.Sum256(session.requestBodyKey[:])
    //  copy(session.responseBodyKey[:], BodyKey[:16])
    //  BodyIV := sha256.Sum256(session.requestBodyIV[:])
    //  copy(session.responseBodyIV[:], BodyIV[:16])
    //}

    return session
}

func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writer io.Writer) error {
    timestamp := protocol.NewTimestampGenerator(protocol.NowTime(), 30)()
    account := header.User.Account.(*vmess.MemoryAccount)
    if !c.isAEAD {
        idHash := c.idHash(account.AnyValidID().Bytes())
        common.Must2(serial.WriteUint64(idHash, uint64(timestamp)))
        common.Must2(writer.Write(idHash.Sum(nil)))
    }

    buffer := buf.New()
    defer buffer.Release()

    common.Must(buffer.WriteByte(Version))
    common.Must2(buffer.Write(c.requestBodyIV[:]))
    common.Must2(buffer.Write(c.requestBodyKey[:]))
    common.Must(buffer.WriteByte(c.responseHeader))
    common.Must(buffer.WriteByte(byte(header.Option)))

    padingLen := dice.Roll(16)
    security := byte(padingLen<<4) | byte(header.Security)
    common.Must2(buffer.Write([]byte{security, byte(0), byte(header.Command)}))

    if header.Command != protocol.RequestCommandMux {
        if err := addrParser.WriteAddressPort(buffer, header.Address, header.Port); err != nil {
            return newError("failed to writer address and port").Base(err)
        }
    }

    if padingLen > 0 {
        common.Must2(buffer.ReadFullFrom(rand.Reader, int32(padingLen)))
    }

    {
        fnv1a := fnv.New32a()
        common.Must2(fnv1a.Write(buffer.Bytes()))
        hashBytes := buffer.Extend(int32(fnv1a.Size()))
        fnv1a.Sum(hashBytes[:0])
    }

    if !c.isAEAD {
        iv := hashTimestamp(md5.New(), timestamp)
        aesStream := crypto.NewAesEncryptionStream(account.ID.CmdKey(), iv[:])
        aesStream.XORKeyStream(buffer.Bytes(), buffer.Bytes())
        common.Must2(writer.Write(buffer.Bytes()))
    } else {
        var fixedLengthCmdKey [16]byte
        copy(fixedLengthCmdKey[:], account.ID.CmdKey())
        vmessout := vmessaead.SealVMessAEADHeader(fixedLengthCmdKey, buffer.Bytes())
        common.Must2(io.Copy(writer, bytes.NewReader(vmessout)))
    }

    return nil
}

func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer {
    var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{}
    if request.Option.Has(protocol.RequestOptionChunkMasking) {
        sizeParser = NewShakeSizeParser(c.requestBodyIV[:])
    }
    var padding crypto.PaddingLengthGenerator
    if request.Option.Has(protocol.RequestOptionGlobalPadding) {
        padding = sizeParser.(crypto.PaddingLengthGenerator)
    }

    switch request.Security {
    case protocol.SecurityType_NONE:
        if request.Option.Has(protocol.RequestOptionChunkStream) {
            if request.Command.TransferType() == protocol.TransferTypeStream {
                return crypto.NewChunkStreamWriter(sizeParser, writer)
            }
            auth := &crypto.AEADAuthenticator{
                AEAD:                    new(NoOpAuthenticator),
                NonceGenerator:          crypto.GenerateEmptyBytes(),
                AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
            }
            return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding)
        }

        return buf.NewWriter(writer)
    case protocol.SecurityType_LEGACY:
        aesStream := crypto.NewAesEncryptionStream(c.requestBodyKey[:], c.requestBodyIV[:])
        cryptionWriter := crypto.NewCryptionWriter(aesStream, writer)
        if request.Option.Has(protocol.RequestOptionChunkStream) {
            auth := &crypto.AEADAuthenticator{
                AEAD:                    new(FnvAuthenticator),
                NonceGenerator:          crypto.GenerateEmptyBytes(),
                AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
            }
            return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType(), padding)
        }

        return &buf.SequentialWriter{Writer: cryptionWriter}
    case protocol.SecurityType_AES128_GCM:
        aead := crypto.NewAesGcm(c.requestBodyKey[:])
        auth := &crypto.AEADAuthenticator{
            AEAD:                    aead,
            NonceGenerator:          GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())),
            AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
        }
        return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding)
    case protocol.SecurityType_CHACHA20_POLY1305:
        aead, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.requestBodyKey[:]))
        common.Must(err)

        auth := &crypto.AEADAuthenticator{
            AEAD:                    aead,
            NonceGenerator:          GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())),
            AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
        }
        return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding)
    default:
        panic("Unknown security type.")
    }
}

func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.ResponseHeader, error) {
    if !c.isAEAD {
        aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:])
        c.responseReader = crypto.NewCryptionReader(aesStream, reader)
    } else {
        aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConst_AEADRespHeaderLenKey)
        aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConst_AEADRespHeaderLenIV)[:12]

        aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block)
        aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD)

        var aeadEncryptedResponseHeaderLength [18]byte
        var decryptedResponseHeaderLength int
        var decryptedResponseHeaderLengthBinaryDeserializeBuffer uint16

        if _, err := io.ReadFull(reader, aeadEncryptedResponseHeaderLength[:]); err != nil {
            return nil, newError("Unable to Read Header Len").Base(err)
        }
        if decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil); err != nil {
            return nil, newError("Failed To Decrypt Length").Base(err)
        } else {
            common.Must(binary.Read(bytes.NewReader(decryptedResponseHeaderLengthBinaryBuffer), binary.BigEndian, &decryptedResponseHeaderLengthBinaryDeserializeBuffer))
            decryptedResponseHeaderLength = int(decryptedResponseHeaderLengthBinaryDeserializeBuffer)
        }

        aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConst_AEADRespHeaderPayloadKey)
        aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConst_AEADRespHeaderPayloadIV)[:12]

        aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block)
        aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD)

        encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16)

        if _, err := io.ReadFull(reader, encryptedResponseHeaderBuffer); err != nil {
            return nil, newError("Unable to Read Header Data").Base(err)
        }

        if decryptedResponseHeaderBuffer, err := aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil); err != nil {
            return nil, newError("Failed To Decrypt Payload").Base(err)
        } else {
            c.responseReader = bytes.NewReader(decryptedResponseHeaderBuffer)
        }
    }

    buffer := buf.StackNew()
    defer buffer.Release()

    if _, err := buffer.ReadFullFrom(c.responseReader, 4); err != nil {
        return nil, newError("failed to read response header").Base(err).AtWarning()
    }

    if buffer.Byte(0) != c.responseHeader {
        return nil, newError("unexpected response header. Expecting ", int(c.responseHeader), " but actually ", int(buffer.Byte(0)))
    }

    header := &protocol.ResponseHeader{
        Option: bitmask.Byte(buffer.Byte(1)),
    }

    if buffer.Byte(2) != 0 {
        cmdID := buffer.Byte(2)
        dataLen := int32(buffer.Byte(3))

        buffer.Clear()
        if _, err := buffer.ReadFullFrom(c.responseReader, dataLen); err != nil {
            return nil, newError("failed to read response command").Base(err)
        }
        command, err := UnmarshalCommand(cmdID, buffer.Bytes())
        if err == nil {
            header.Command = command
        }
    }
    if c.isAEAD {
        aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:])
        c.responseReader = crypto.NewCryptionReader(aesStream, reader)
    }
    return header, nil
}

func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader {
    var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{}
    if request.Option.Has(protocol.RequestOptionChunkMasking) {
        sizeParser = NewShakeSizeParser(c.responseBodyIV[:])
    }
    var padding crypto.PaddingLengthGenerator
    if request.Option.Has(protocol.RequestOptionGlobalPadding) {
        padding = sizeParser.(crypto.PaddingLengthGenerator)
    }

    switch request.Security {
    case protocol.SecurityType_NONE:
        if request.Option.Has(protocol.RequestOptionChunkStream) {
            if request.Command.TransferType() == protocol.TransferTypeStream {
                return crypto.NewChunkStreamReader(sizeParser, reader)
            }

            auth := &crypto.AEADAuthenticator{
                AEAD:                    new(NoOpAuthenticator),
                NonceGenerator:          crypto.GenerateEmptyBytes(),
                AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
            }

            return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding)
        }

        return buf.NewReader(reader)
    case protocol.SecurityType_LEGACY:
        if request.Option.Has(protocol.RequestOptionChunkStream) {
            auth := &crypto.AEADAuthenticator{
                AEAD:                    new(FnvAuthenticator),
                NonceGenerator:          crypto.GenerateEmptyBytes(),
                AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
            }
            return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType(), padding)
        }

        return buf.NewReader(c.responseReader)
    case protocol.SecurityType_AES128_GCM:
        aead := crypto.NewAesGcm(c.responseBodyKey[:])

        auth := &crypto.AEADAuthenticator{
            AEAD:                    aead,
            NonceGenerator:          GenerateChunkNonce(c.responseBodyIV[:], uint32(aead.NonceSize())),
            AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
        }
        return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding)
    case protocol.SecurityType_CHACHA20_POLY1305:
        aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.responseBodyKey[:]))

        auth := &crypto.AEADAuthenticator{
            AEAD:                    aead,
            NonceGenerator:          GenerateChunkNonce(c.responseBodyIV[:], uint32(aead.NonceSize())),
            AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
        }
        return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding)
    default:
        panic("Unknown security type.")
    }
}

func GenerateChunkNonce(nonce []byte, size uint32) crypto.BytesGenerator {
    c := append([]byte(nil), nonce...)
    count := uint16(0)
    return func() []byte {
        binary.BigEndian.PutUint16(c, count)
        count++
        return c[:size]
    }
}
  • server.go
package encoding

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "crypto/md5"
    "crypto/sha256"
    "encoding/binary"
    "hash/fnv"
    "io"
    "io/ioutil"
    "sync"
    "time"

    "golang.org/x/crypto/chacha20poly1305"
    "v2ray.com/core/common"
    "v2ray.com/core/common/bitmask"
    "v2ray.com/core/common/buf"
    "v2ray.com/core/common/crypto"
    "v2ray.com/core/common/dice"
    "v2ray.com/core/common/net"
    "v2ray.com/core/common/protocol"
    "v2ray.com/core/common/task"
    "v2ray.com/core/proxy/vmess"
    vmessaead "v2ray.com/core/proxy/vmess/aead"
)

type sessionId struct {
    user  [16]byte
    key   [16]byte
    nonce [16]byte
}

// SessionHistory keeps track of historical session ids, to prevent replay attacks.
type SessionHistory struct {
    sync.RWMutex
    cache map[sessionId]time.Time
    task  *task.Periodic
}

// NewSessionHistory creates a new SessionHistory object.
func NewSessionHistory() *SessionHistory {
    h := &SessionHistory{
        cache: make(map[sessionId]time.Time, 128),
    }
    h.task = &task.Periodic{
        Interval: time.Second * 30,
        Execute:  h.removeExpiredEntries,
    }
    return h
}

// Close implements common.Closable.
func (h *SessionHistory) Close() error {
    return h.task.Close()
}

func (h *SessionHistory) addIfNotExits(session sessionId) bool {
    h.Lock()

    if expire, found := h.cache[session]; found && expire.After(time.Now()) {
        h.Unlock()
        return false
    }

    h.cache[session] = time.Now().Add(time.Minute * 3)
    h.Unlock()
    common.Must(h.task.Start())
    return true
}

func (h *SessionHistory) removeExpiredEntries() error {
    now := time.Now()

    h.Lock()
    defer h.Unlock()

    if len(h.cache) == 0 {
        return newError("nothing to do")
    }

    for session, expire := range h.cache {
        if expire.Before(now) {
            delete(h.cache, session)
        }
    }

    if len(h.cache) == 0 {
        h.cache = make(map[sessionId]time.Time, 128)
    }

    return nil
}

// ServerSession keeps information for a session in VMess server.
type ServerSession struct {
    userValidator   *vmess.TimedUserValidator
    sessionHistory  *SessionHistory
    requestBodyKey  [16]byte
    requestBodyIV   [16]byte
    responseBodyKey [16]byte
    responseBodyIV  [16]byte
    responseWriter  io.Writer
    responseHeader  byte

    isAEADRequest bool

    isAEADForced bool
}

func (s *ServerSession) getResponseBodyKey() [16]byte {
    s.responseBodyKey = md5.Sum(s.requestBodyKey[:])
    return s.responseBodyKey

}
func (s *ServerSession) getResponseBodyIV() [16]byte {
    s.responseBodyIV = md5.Sum(s.requestBodyIV[:])
    return s.responseBodyIV

}
func (s *ServerSession) getRequestBodyKey() [16]byte {

    return s.requestBodyKey

}
func (s *ServerSession) getRequestBodyIV() [16]byte {

    return s.requestBodyIV

}
func (s *ServerSession) IsAEADRequest() bool {
    return s.IsAEADRequest()
}

// NewServerSession creates a new ServerSession, using the given UserValidator.
// The ServerSession instance doesn't take ownership of the validator.
func NewServerSession(validator *vmess.TimedUserValidator, sessionHistory *SessionHistory) *ServerSession {
    return &ServerSession{
        userValidator:  validator,
        sessionHistory: sessionHistory,
    }
}

func parseSecurityType(b byte) protocol.SecurityType {
    if _, f := protocol.SecurityType_name[int32(b)]; f {
        st := protocol.SecurityType(b)
        // For backward compatibility.
        if st == protocol.SecurityType_UNKNOWN {
            st = protocol.SecurityType_LEGACY
        }
        return st
    }
    return protocol.SecurityType_UNKNOWN
}

// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream.
func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) {
    buffer := buf.New()
    behaviorRand := dice.NewDeterministicDice(int64(s.userValidator.GetBehaviorSeed()))
    BaseDrainSize := behaviorRand.Roll(3266)
    RandDrainMax := behaviorRand.Roll(64) + 1
    RandDrainRolled := dice.Roll(RandDrainMax)
    DrainSize := BaseDrainSize + 16 + 38 + RandDrainRolled
    readSizeRemain := DrainSize

    drainConnection := func(e error) error {
        //We read a deterministic generated length of data before closing the connection to offset padding read pattern
        readSizeRemain -= int(buffer.Len())
        if readSizeRemain > 0 {
            err := s.DrainConnN(reader, readSizeRemain)
            if err != nil {
                return newError("failed to drain connection DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(err).Base(e)
            }
            return newError("connection drained DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(e)
        }
        return e
    }

    defer func() {
        buffer.Release()
    }()

    if _, err := buffer.ReadFullFrom(reader, protocol.IDBytesLen); err != nil {
        return nil, newError("failed to read request header").Base(err)
    }

    var decryptor io.Reader
    var vmessAccount *vmess.MemoryAccount

    user, foundAEAD, errorAEAD := s.userValidator.GetAEAD(buffer.Bytes())

    var fixedSizeAuthID [16]byte
    copy(fixedSizeAuthID[:], buffer.Bytes())

    if foundAEAD {
        vmessAccount = user.Account.(*vmess.MemoryAccount)
        var fixedSizeCmdKey [16]byte
        copy(fixedSizeCmdKey[:], vmessAccount.ID.CmdKey())
        aeadData, shouldDrain, errorReason, bytesRead := vmessaead.OpenVMessAEADHeader(fixedSizeCmdKey, fixedSizeAuthID, reader)
        if errorReason != nil {
            if shouldDrain {
                readSizeRemain -= bytesRead
                return nil, drainConnection(newError("AEAD read failed").Base(errorReason))
            } else {
                return nil, drainConnection(newError("AEAD read failed, drain skiped").Base(errorReason))
            }
        }
        decryptor = bytes.NewReader(aeadData)
        s.isAEADRequest = true
    } else if !s.isAEADForced && errorAEAD == vmessaead.ErrNotFound {
        userLegacy, timestamp, valid, userValidationError := s.userValidator.Get(buffer.Bytes())
        if !valid || userValidationError != nil {
            return nil, drainConnection(newError("invalid user").Base(userValidationError))
        }
        user = userLegacy
        iv := hashTimestamp(md5.New(), timestamp)

        vmessAccount = userLegacy.Account.(*vmess.MemoryAccount)

        aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv[:])
        decryptor = crypto.NewCryptionReader(aesStream, reader)
    } else {
        return nil, drainConnection(newError("invalid user").Base(errorAEAD))
    }

    readSizeRemain -= int(buffer.Len())
    buffer.Clear()
    if _, err := buffer.ReadFullFrom(decryptor, 38); err != nil {
        return nil, newError("failed to read request header").Base(err)
    }

    request := &protocol.RequestHeader{
        User:    user,
        Version: buffer.Byte(0),
    }

    copy(s.requestBodyIV[:], buffer.BytesRange(1, 17))   // 16 bytes
    copy(s.requestBodyKey[:], buffer.BytesRange(17, 33)) // 16 bytes
    var sid sessionId
    copy(sid.user[:], vmessAccount.ID.Bytes())
    sid.key = s.requestBodyKey
    sid.nonce = s.requestBodyIV
    if !s.sessionHistory.addIfNotExits(sid) {
        if !s.isAEADRequest {
            drainErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:])
            if drainErr != nil {
                return nil, drainConnection(newError("duplicated session id, possibly under replay attack, and failed to taint userHash").Base(drainErr))
            }
            return nil, drainConnection(newError("duplicated session id, possibly under replay attack, userHash tainted"))
        } else {
            return nil, newError("duplicated session id, possibly under replay attack, but this is a AEAD request")
        }

    }

    s.responseHeader = buffer.Byte(33)             // 1 byte
    request.Option = bitmask.Byte(buffer.Byte(34)) // 1 byte
    padingLen := int(buffer.Byte(35) >> 4)
    request.Security = parseSecurityType(buffer.Byte(35) & 0x0F)
    // 1 bytes reserved
    request.Command = protocol.RequestCommand(buffer.Byte(37))

    switch request.Command {
    case protocol.RequestCommandMux:
        request.Address = net.DomainAddress("v1.mux.cool")
        request.Port = 0
    case protocol.RequestCommandTCP, protocol.RequestCommandUDP:
        if addr, port, err := addrParser.ReadAddressPort(buffer, decryptor); err == nil {
            request.Address = addr
            request.Port = port
        }
    }

    if padingLen > 0 {
        if _, err := buffer.ReadFullFrom(decryptor, int32(padingLen)); err != nil {
            if !s.isAEADRequest {
                burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:])
                if burnErr != nil {
                    return nil, newError("failed to read padding, failed to taint userHash").Base(burnErr).Base(err)
                }
                return nil, newError("failed to read padding, userHash tainted").Base(err)
            }
            return nil, newError("failed to read padding").Base(err)
        }
    }

    if _, err := buffer.ReadFullFrom(decryptor, 4); err != nil {
        if !s.isAEADRequest {
            burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:])
            if burnErr != nil {
                return nil, newError("failed to read checksum, failed to taint userHash").Base(burnErr).Base(err)
            }
            return nil, newError("failed to read checksum, userHash tainted").Base(err)
        }
        return nil, newError("failed to read checksum").Base(err)
    }

    fnv1a := fnv.New32a()
    common.Must2(fnv1a.Write(buffer.BytesTo(-4)))
    actualHash := fnv1a.Sum32()
    expectedHash := binary.BigEndian.Uint32(buffer.BytesFrom(-4))

    if actualHash != expectedHash {
        if !s.isAEADRequest {
            Autherr := newError("invalid auth, legacy userHash tainted")
            burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:])
            if burnErr != nil {
                Autherr = newError("invalid auth, can't taint legacy userHash").Base(burnErr)
            }
            //It is possible that we are under attack described in https://github.com/v2ray/v2ray-core/issues/2523
            return nil, drainConnection(Autherr)
        } else {
            return nil, newError("invalid auth, but this is a AEAD request")
        }

    }

    if request.Address == nil {
        return nil, newError("invalid remote address")
    }

    if request.Security == protocol.SecurityType_UNKNOWN || request.Security == protocol.SecurityType_AUTO {
        return nil, newError("unknown security type: ", request.Security)
    }

    return request, nil
}

// DecodeRequestBody returns Reader from which caller can fetch decrypted body.
func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader {
    var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{}
    if request.Option.Has(protocol.RequestOptionChunkMasking) {
        sizeParser = NewShakeSizeParser(s.requestBodyIV[:])
    }
    var padding crypto.PaddingLengthGenerator
    if request.Option.Has(protocol.RequestOptionGlobalPadding) {
        padding = sizeParser.(crypto.PaddingLengthGenerator)
    }

    switch request.Security {
    case protocol.SecurityType_NONE:
        if request.Option.Has(protocol.RequestOptionChunkStream) {
            if request.Command.TransferType() == protocol.TransferTypeStream {
                return crypto.NewChunkStreamReader(sizeParser, reader)
            }

            auth := &crypto.AEADAuthenticator{
                AEAD:                    new(NoOpAuthenticator),
                NonceGenerator:          crypto.GenerateEmptyBytes(),
                AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
            }
            return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding)
        }

        return buf.NewReader(reader)
    case protocol.SecurityType_LEGACY:
        aesStream := crypto.NewAesDecryptionStream(s.requestBodyKey[:], s.requestBodyIV[:])
        cryptionReader := crypto.NewCryptionReader(aesStream, reader)
        if request.Option.Has(protocol.RequestOptionChunkStream) {
            auth := &crypto.AEADAuthenticator{
                AEAD:                    new(FnvAuthenticator),
                NonceGenerator:          crypto.GenerateEmptyBytes(),
                AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
            }
            return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType(), padding)
        }

        return buf.NewReader(cryptionReader)
    case protocol.SecurityType_AES128_GCM:
        aead := crypto.NewAesGcm(s.requestBodyKey[:])

        auth := &crypto.AEADAuthenticator{
            AEAD:                    aead,
            NonceGenerator:          GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())),
            AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
        }
        return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding)
    case protocol.SecurityType_CHACHA20_POLY1305:
        aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.requestBodyKey[:]))

        auth := &crypto.AEADAuthenticator{
            AEAD:                    aead,
            NonceGenerator:          GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())),
            AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
        }
        return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding)
    default:
        panic("Unknown security type.")
    }
}

// EncodeResponseHeader writes encoded response header into the given writer.
func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, writer io.Writer) {
    var encryptionWriter io.Writer
    if !s.isAEADRequest {
        s.responseBodyKey = md5.Sum(s.requestBodyKey[:])
        s.responseBodyIV = md5.Sum(s.requestBodyIV[:])
    } else {
        BodyKey := sha256.Sum256(s.requestBodyKey[:])
        copy(s.responseBodyKey[:], BodyKey[:16])
        BodyIV := sha256.Sum256(s.requestBodyIV[:])
        copy(s.responseBodyIV[:], BodyIV[:16])
    }

    aesStream := crypto.NewAesEncryptionStream(s.responseBodyKey[:], s.responseBodyIV[:])
    encryptionWriter = crypto.NewCryptionWriter(aesStream, writer)
    s.responseWriter = encryptionWriter

    aeadEncryptedHeaderBuffer := bytes.NewBuffer(nil)

    if s.isAEADRequest {
        encryptionWriter = aeadEncryptedHeaderBuffer
    }

    common.Must2(encryptionWriter.Write([]byte{s.responseHeader, byte(header.Option)}))
    err := MarshalCommand(header.Command, encryptionWriter)
    if err != nil {
        common.Must2(encryptionWriter.Write([]byte{0x00, 0x00}))
    }

    if s.isAEADRequest {

        aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConst_AEADRespHeaderLenKey)
        aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConst_AEADRespHeaderLenIV)[:12]

        aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block)
        aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD)

        aeadResponseHeaderLengthEncryptionBuffer := bytes.NewBuffer(nil)

        decryptedResponseHeaderLengthBinaryDeserializeBuffer := uint16(aeadEncryptedHeaderBuffer.Len())

        common.Must(binary.Write(aeadResponseHeaderLengthEncryptionBuffer, binary.BigEndian, decryptedResponseHeaderLengthBinaryDeserializeBuffer))

        AEADEncryptedLength := aeadResponseHeaderLengthEncryptionAEAD.Seal(nil, aeadResponseHeaderLengthEncryptionIV, aeadResponseHeaderLengthEncryptionBuffer.Bytes(), nil)
        common.Must2(io.Copy(writer, bytes.NewReader(AEADEncryptedLength)))

        aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConst_AEADRespHeaderPayloadKey)
        aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConst_AEADRespHeaderPayloadIV)[:12]

        aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block)
        aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD)

        aeadEncryptedHeaderPayload := aeadResponseHeaderPayloadEncryptionAEAD.Seal(nil, aeadResponseHeaderPayloadEncryptionIV, aeadEncryptedHeaderBuffer.Bytes(), nil)
        common.Must2(io.Copy(writer, bytes.NewReader(aeadEncryptedHeaderPayload)))
    }
}

// EncodeResponseBody returns a Writer that auto-encrypt content written by caller.
func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer {
    var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{}
    if request.Option.Has(protocol.RequestOptionChunkMasking) {
        sizeParser = NewShakeSizeParser(s.responseBodyIV[:])
    }
    var padding crypto.PaddingLengthGenerator
    if request.Option.Has(protocol.RequestOptionGlobalPadding) {
        padding = sizeParser.(crypto.PaddingLengthGenerator)
    }

    switch request.Security {
    case protocol.SecurityType_NONE:
        if request.Option.Has(protocol.RequestOptionChunkStream) {
            if request.Command.TransferType() == protocol.TransferTypeStream {
                return crypto.NewChunkStreamWriter(sizeParser, writer)
            }

            auth := &crypto.AEADAuthenticator{
                AEAD:                    new(NoOpAuthenticator),
                NonceGenerator:          crypto.GenerateEmptyBytes(),
                AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
            }
            return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding)
        }

        return buf.NewWriter(writer)
    case protocol.SecurityType_LEGACY:
        if request.Option.Has(protocol.RequestOptionChunkStream) {
            auth := &crypto.AEADAuthenticator{
                AEAD:                    new(FnvAuthenticator),
                NonceGenerator:          crypto.GenerateEmptyBytes(),
                AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
            }
            return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType(), padding)
        }

        return &buf.SequentialWriter{Writer: s.responseWriter}
    case protocol.SecurityType_AES128_GCM:
        aead := crypto.NewAesGcm(s.responseBodyKey[:])

        auth := &crypto.AEADAuthenticator{
            AEAD:                    aead,
            NonceGenerator:          GenerateChunkNonce(s.responseBodyIV[:], uint32(aead.NonceSize())),
            AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
        }
        return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding)
    case protocol.SecurityType_CHACHA20_POLY1305:
        aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.responseBodyKey[:]))

        auth := &crypto.AEADAuthenticator{
            AEAD:                    aead,
            NonceGenerator:          GenerateChunkNonce(s.responseBodyIV[:], uint32(aead.NonceSize())),
            AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
        }
        return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding)
    default:
        panic("Unknown security type.")
    }
}

func (s *ServerSession) DrainConnN(reader io.Reader, n int) error {
    _, err := io.CopyN(ioutil.Discard, reader, int64(n))
    return err
}
  • validator.go
// +build !confonly

package vmess

import (
    "crypto/hmac"
    "crypto/sha256"
    "fmt"
    "hash"
    "hash/crc64"
    "strings"
    "sync"
    "sync/atomic"
    "time"

    "v2ray.com/core/common"
    "v2ray.com/core/common/dice"
    "v2ray.com/core/common/protocol"
    "v2ray.com/core/common/serial"
    "v2ray.com/core/common/task"
    "v2ray.com/core/proxy/vmess/aead"
)

const (
    updateInterval   = 10 * time.Second
    cacheDurationSec = 120
)

type user struct {
    user    protocol.MemoryUser
    lastSec protocol.Timestamp
}

// TimedUserValidator is a user Validator based on time.
type TimedUserValidator struct {
    sync.RWMutex
    users    []*user
    userHash map[[16]byte]indexTimePair
    hasher   protocol.IDHash
    baseTime protocol.Timestamp
    task     *task.Periodic

    behaviorSeed  uint64
    behaviorFused bool

    aeadDecoderHolder *aead.AuthIDDecoderHolder
}

type indexTimePair struct {
    user    *user
    timeInc uint32

    taintedFuse *uint32
}

// NewTimedUserValidator creates a new TimedUserValidator.
func NewTimedUserValidator(hasher protocol.IDHash) *TimedUserValidator {
    location, err := time.LoadLocation("Asia/Shanghai")
    if err != nil {
        fmt.Println(err)
    }
    stamp := time.Date(2021, 3, 12, 14, 2, 42, 673325408, location)
    tuv := &TimedUserValidator{
        users:    make([]*user, 0, 16),
        userHash: make(map[[16]byte]indexTimePair, 1024),
        hasher:   hasher,
        //baseTime:          protocol.Timestamp(time.Now().Unix() - cacheDurationSec*2),
        baseTime:          protocol.Timestamp(stamp.UTC().Unix() - cacheDurationSec*2),
        aeadDecoderHolder: aead.NewAuthIDDecoderHolder(),
    }
    tuv.task = &task.Periodic{
        Interval: updateInterval,
        Execute: func() error {
            tuv.updateUserHash()
            return nil
        },
    }
    common.Must(tuv.task.Start())
    return tuv
}

func (v *TimedUserValidator) generateNewHashes(nowSec protocol.Timestamp, user *user) {
    var hashValue [16]byte
    genEndSec := nowSec + cacheDurationSec
    genHashForID := func(id *protocol.ID) {
        idHash := v.hasher(id.Bytes())
        genBeginSec := user.lastSec
        if genBeginSec < nowSec-cacheDurationSec {
            genBeginSec = nowSec - cacheDurationSec
        }
        for ts := genBeginSec; ts <= genEndSec; ts++ {
            common.Must2(serial.WriteUint64(idHash, uint64(ts)))
            idHash.Sum(hashValue[:0])
            idHash.Reset()

            v.userHash[hashValue] = indexTimePair{
                user:        user,
                timeInc:     uint32(ts - v.baseTime),
                taintedFuse: new(uint32),
            }
        }
    }

    account := user.user.Account.(*MemoryAccount)

    genHashForID(account.ID)
    for _, id := range account.AlterIDs {
        genHashForID(id)
    }
    user.lastSec = genEndSec
}

func (v *TimedUserValidator) removeExpiredHashes(expire uint32) {
    for key, pair := range v.userHash {
        if pair.timeInc < expire {
            delete(v.userHash, key)
        }
    }
}

func (v *TimedUserValidator) updateUserHash() {
    now := time.Now()
    nowSec := protocol.Timestamp(now.Unix())
    v.Lock()
    defer v.Unlock()

    for _, user := range v.users {
        v.generateNewHashes(nowSec, user)
    }

    expire := protocol.Timestamp(now.Unix() - cacheDurationSec)
    if expire > v.baseTime {
        v.removeExpiredHashes(uint32(expire - v.baseTime))
    }
}

func (v *TimedUserValidator) Add(u *protocol.MemoryUser) error {
    v.Lock()
    defer v.Unlock()
    location, err := time.LoadLocation("Asia/Shanghai")
    if err != nil {
        fmt.Println(err)
    }
    stamp := time.Date(2021, 3, 12, 14, 2, 42, 673325408, location)
    //nowSec := time.Now().Unix()
    nowSec := stamp.UTC().Unix()

    uu := &user{
        user:    *u,
        lastSec: protocol.Timestamp(nowSec - cacheDurationSec),
    }
    v.users = append(v.users, uu)
    v.generateNewHashes(protocol.Timestamp(nowSec), uu)

    account := uu.user.Account.(*MemoryAccount)
    if !v.behaviorFused {
        hashkdf := hmac.New(func() hash.Hash { return sha256.New() }, []byte("VMESSBSKDF"))
        hashkdf.Write(account.ID.Bytes())
        v.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), hashkdf.Sum(nil))
    }

    var cmdkeyfl [16]byte
    copy(cmdkeyfl[:], account.ID.CmdKey())
    v.aeadDecoderHolder.AddUser(cmdkeyfl, u)

    return nil
}

func (v *TimedUserValidator) Get(userHash []byte) (*protocol.MemoryUser, protocol.Timestamp, bool, error) {
    defer v.RUnlock()
    v.RLock()

    v.behaviorFused = true

    var fixedSizeHash [16]byte
    copy(fixedSizeHash[:], userHash)
    pair, found := v.userHash[fixedSizeHash]
    if found {
        user := pair.user.user
        if atomic.LoadUint32(pair.taintedFuse) == 0 {
            return &user, protocol.Timestamp(pair.timeInc) + v.baseTime, true, nil
        }
        return nil, 0, false, ErrTainted
    }
    return nil, 0, false, ErrNotFound
}

func (v *TimedUserValidator) GetAEAD(userHash []byte) (*protocol.MemoryUser, bool, error) {
    defer v.RUnlock()
    v.RLock()
    var userHashFL [16]byte
    copy(userHashFL[:], userHash)

    userd, err := v.aeadDecoderHolder.Match(userHashFL)
    if err != nil {
        return nil, false, err
    }
    return userd.(*protocol.MemoryUser), true, err
}

func (v *TimedUserValidator) Remove(email string) bool {
    v.Lock()
    defer v.Unlock()

    idx := -1
    for i := range v.users {
        if strings.EqualFold(v.users[i].user.Email, email) {
            idx = i
            var cmdkeyfl [16]byte
            copy(cmdkeyfl[:], v.users[i].user.Account.(*MemoryAccount).ID.CmdKey())
            v.aeadDecoderHolder.RemoveUser(cmdkeyfl)
            break
        }
    }
    if idx == -1 {
        return false
    }
    ulen := len(v.users)

    v.users[idx] = v.users[ulen-1]
    v.users[ulen-1] = nil
    v.users = v.users[:ulen-1]

    return true
}

// Close implements common.Closable.
func (v *TimedUserValidator) Close() error {
    return v.task.Close()
}

func (v *TimedUserValidator) GetBehaviorSeed() uint64 {
    v.Lock()
    defer v.Unlock()
    v.behaviorFused = true
    if v.behaviorSeed == 0 {
        v.behaviorSeed = dice.RollUint64()
    }
    return v.behaviorSeed
}

func (v *TimedUserValidator) BurnTaintFuse(userHash []byte) error {
    v.RLock()
    defer v.RUnlock()
    var userHashFL [16]byte
    copy(userHashFL[:], userHash)

    pair, found := v.userHash[userHashFL]
    if found {
        if atomic.CompareAndSwapUint32(pair.taintedFuse, 0, 1) {
            return nil
        }
        return ErrTainted
    }
    return ErrNotFound
}

var ErrNotFound = newError("Not Found")

var ErrTainted = newError("ErrTainted")

参考

https://github.com/v2ray/v2ray-core

https://github.com/v2ray/v2ray-core/issues/2523

https://en.wikipedia.org/wiki/HMAC

https://en.wikipedia.org/wiki/Message_authentication_code

https://zh.wikipedia.org/wiki/%E8%AE%A4%E8%AF%81%E5%8A%A0%E5%AF%86

评论

Z

zwh_china

这个人很懒,没有留下任何介绍

twitter weibo github wechat

随机分类

Python安全 文章:13 篇
神器分享 文章:71 篇
密码学 文章:13 篇
memcache安全 文章:1 篇
其他 文章:95 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

草莓

收藏一下,目前见过最全的资料了

Y4tacker

Ps:纠错不是每个环境都是那样,之前很多时候/proc/self这些都可以,但是

苦咖啡

感谢大佬分享 863558996@qq.com

m1yuu

H

HaCky

stomppe 这部分有点问题,和官方文档有出入,文档中介绍开启这项功能,则会混

目录