Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mKCP加密设计不当可导致流量被精准识别(附PoC) #2530

Closed
p4gefau1t opened this issue Jun 2, 2020 · 18 comments
Closed

mKCP加密设计不当可导致流量被精准识别(附PoC) #2530

p4gefau1t opened this issue Jun 2, 2020 · 18 comments

Comments

@p4gefau1t
Copy link

p4gefau1t commented Jun 2, 2020

先说结论:mKCP的加密方案(其实应该称作混淆)引入的特征,可导致mKCP流量,以极低的开销被精准识别

目前没有找到除了重新设计mkcp加密以外的缓解措施。我个人的建议是,用标准的加密系统替代当前的混淆方式。

PoC比较简单,下面是核心部分,完整代码放在最后

func IsMKCPPacket(packet []byte) bool {
	auth := NewSimpleAuthenticator()
	_, err := auth.Open(packet, nil, nil, nil)
	if err != nil {
		return false
	}
	return true
}

其中的NewSimpleAuthenticator是core/transport/kcp中的函数

下面是分析和PoC


mKCP被识别不是一天两天的事情了,下面这个只是一种可能的思路。

mKCP的加密实现了一个AEAD接口,但是nonce和tag都没有被使用,仅仅使用了一个不需要密钥的xor进行混淆(汇编实现),然后使用附在末尾的校验值来判定包的完整性。

type SimpleAuthenticator struct{}
// NewSimpleAuthenticator creates a new SimpleAuthenticator
func NewSimpleAuthenticator() cipher.AEAD {
return &SimpleAuthenticator{}
}
// NonceSize implements cipher.AEAD.NonceSize().
func (*SimpleAuthenticator) NonceSize() int {
return 0
}
// Overhead implements cipher.AEAD.NonceSize().
func (*SimpleAuthenticator) Overhead() int {
return 6
}
// Seal implements cipher.AEAD.Seal().
func (a *SimpleAuthenticator) Seal(dst, nonce, plain, extra []byte) []byte {
dst = append(dst, 0, 0, 0, 0, 0, 0) // 4 bytes for hash, and then 2 bytes for length
binary.BigEndian.PutUint16(dst[4:], uint16(len(plain)))
dst = append(dst, plain...)
fnvHash := fnv.New32a()
common.Must2(fnvHash.Write(dst[4:]))
fnvHash.Sum(dst[:0])
dstLen := len(dst)
xtra := 4 - dstLen%4
if xtra != 4 {
dst = append(dst, make([]byte, xtra)...)
}
xorfwd(dst)
if xtra != 4 {
dst = dst[:dstLen]
}
return dst
}
// Open implements cipher.AEAD.Open().
func (a *SimpleAuthenticator) Open(dst, nonce, cipherText, extra []byte) ([]byte, error) {
dst = append(dst, cipherText...)
dstLen := len(dst)
xtra := 4 - dstLen%4
if xtra != 4 {
dst = append(dst, make([]byte, xtra)...)
}
xorbkd(dst)
if xtra != 4 {
dst = dst[:dstLen]
}
fnvHash := fnv.New32a()
common.Must2(fnvHash.Write(dst[4:]))
if binary.BigEndian.Uint32(dst[:4]) != fnvHash.Sum32() {
return nil, newError("invalid auth")
}
length := binary.BigEndian.Uint16(dst[4:6])
if len(dst)-6 != int(length) {
return nil, newError("invalid auth")
}
return dst[6:], nil
}

虽然注释写的是legacy,但是在mkcp的设置中的确被使用了

// GetSecurity returns the security settings.
func (*Config) GetSecurity() (cipher.AEAD, error) {
return NewSimpleAuthenticator(), nil
}

混淆和校验本身没有问题,问题在于这种混淆太过特殊,而且存在一个校验和判断内容是否正确,并且加解密开销很低

所以检测的思路就是:对一个未知的UDP包,简单地用这个算法解密,然后判断校验和是否正确,如果正确,为mKCP。否则不是mKCP。

我猜测设计者应该是想使用简单的xor替换常规加密方式,降低开销,提升计算速度。但是,加密的开销越低,检测的开销就越低。因此使得大规模部署和检测成为可能。考虑到mKCP暴力发包的特性,防火墙只需要抽取其中几个包进行抽样测试即可准确判定。在我本地,mtu为1350的情况下(默认的最大的mKCP包),可以每秒检测超过20k个包。

建议使用标准的密码系统,或者直接明文传输,带来的特征都会比使用这种加密算法少。

完整PoC代码

package main

import (
	"flag"
	"log"
	"net"

	"v2ray.com/core/transport/internet/kcp"
)

var listenAddr = flag.String("from", "127.0.0.1:4444", "listen address")
var verbose = flag.Bool("verbose", false, "enable detailed output")

func init() {
	flag.Parse()
}

func isMKCPPacket(packet []byte) bool {
	auth := kcp.NewSimpleAuthenticator()
	_, err := auth.Open(packet, nil, nil, nil)
	if err != nil {
		return false
	}
	return true
}

func main() {
	udpAddr, _ := net.ResolveUDPAddr("udp4", *listenAddr)
	l, _ := net.ListenUDP("udp", udpAddr)

	log.Printf("mKCP PoC opened at %s...", *listenAddr)
	for {
		buf := make([]byte, 2048)
		nRecv, _, e := l.ReadFromUDP(buf)
		if e == nil {
			result := isMKCPPacket(buf[:nRecv])
			if *verbose {
				log.Printf("detected=%v, size=%d, data=%x", result, nRecv, buf[:nRecv])
			} else {
				log.Printf("detected=%v, size=%d", result, nRecv)
			}
		}
	}
}

这个程序监听本地4444端口,开启一个v2ray客户端向4444端口发送mkcp数据包即可识别。发送其他udp包,如dns,quic等不会误报。

@DuckSoft
Copy link
Contributor

DuckSoft commented Jun 2, 2020

mKCP 被封是很早之前的事情了……不过这次是把原因挖出来了。
不管怎么说,建议立刻停止侥幸,停用 mKCP
实在有需求的,可以考虑使用 gost 提供的 kcptun,可以避免这个问题。

没了,瞬间识别:
图片

@betaxab
Copy link

betaxab commented Jun 2, 2020

mKCP 活不过一天的原因找到了。

@nicholascw
Copy link
Contributor

就KCP这个拥塞控制的机制本身就没啥好抗识别的把。。。

@vcptr
Copy link
Contributor

vcptr commented Jun 2, 2020

混淆是为了得到isp 优待的QoS级别,不是用来让GFW不识别的。WontFix

@p4gefau1t
Copy link
Author

混淆是为了得到isp 优待的QoS级别,不是用来让GFW不识别的。WontFix

那这样等于宣告mKCP不能用于翻越长城了

其实改进措施也不难,替换aead的实现,增加解密的开销

@p4gefau1t
Copy link
Author

混淆是为了得到isp 优待的QoS级别,不是用来让GFW不识别的。WontFix

而且最主要的问题是,运营商部署这一识别,比防火墙更容易

如此还能得到ISP的优待吗?

@nicholascw
Copy link
Contributor

不能就不用,提供了配置选项就是让用户自己选的,本身作为一个网络工具就不只有一种应用场景。

@DuckSoft
Copy link
Contributor

DuckSoft commented Jun 2, 2020

混淆是为了得到isp 优待的QoS级别,不是用来让GFW不识别的。WontFix

一个很轻易就能识别出来的流量为何会受到 ISP 的优待……不明……
那还是不用 mKCP 吧……

@ghost
Copy link

ghost commented Jun 2, 2020

不能就不用,提供了配置选项就是让用户自己选的,本身作为一个网络工具就不只有一种应用场景。

你或许应该瞧瞧用户们实际上是怎么用mKCP的,issue里面往上翻就行。至少请在文档里标明此类手段不适用于此场景。

@p4gefau1t
Copy link
Author

不能就不用,提供了配置选项就是让用户自己选的,本身作为一个网络工具就不只有一种应用场景。

我的提议是,使用aes等加密方式替换当前的简单xor混淆,哪怕是硬编码的都比当前方案优。这是开销的问题。

gfw不可能所有包都做一次aes来校验,但是可以所有包都做一次xor校验,因为xor的开销太低了。

@p4gefau1t
Copy link
Author

不能就不用,提供了配置选项就是让用户自己选的,本身作为一个网络工具就不只有一种应用场景。

你或许应该瞧瞧用户们实际上是怎么用mKCP的,issue里面往上翻就行。至少请在文档里标明此类手段不适用于此场景。

同意,如果不打算修复或者改进,至少应该提醒用户不能用来翻墙。

就我自己而言,我最初使用mKCP的唯一目的就是翻墙,直到几个小时后服务器IP被封禁,反复几次。最终才决定使用其他方案。

我不希望更多的用户与我有相同的经历,浪费大量的时间,精力和财力在无用功上。

@devyujie
Copy link

devyujie commented Jun 2, 2020

不能就不用,提供了配置选项就是让用户自己选的,本身作为一个网络工具就不只有一种应用场景。

你或许应该瞧瞧用户们实际上是怎么用mKCP的,issue里面往上翻就行。至少请在文档里标明此类手段不适用于此场景。

同意,如果不打算修复或者改进,至少应该提醒用户不能用来翻墙。

就我自己而言,我最初使用mKCP的唯一目的就是翻墙,直到几个小时后服务器IP被封禁,反复几次。最终才决定使用其他方案。

我不希望更多的用户与我有相同的经历,浪费大量的时间,精力和财力在无用功上。

支持,一个和谐的社区不应该只有一种声音

@1265578519
Copy link

kcp不能过墙的问题先不说,,,早几年开发者解释过,这个协议不做加密,只是针对运营商QOS限速突破用的。混淆模拟伪装成BT下载之类的流量。
那么,有谁能提交下修复代码解决游戏不能玩的问题?延迟999+
v2ray/discussion#558 (comment)

@wltc2005
Copy link

膜拜,识别原因终于找到了

@wltc2005
Copy link

kcp不能过墙的问题先不说,,,早几年开发者解释过,这个协议不做加密,只是针对运营商QOS限速突破用的。混淆模拟伪装成BT下载之类的流量。
那么,有谁能提交下修复代码解决游戏不能玩的问题?延迟999+
v2ray/discussion#558 (comment)

改ss白名单秒解

@fbion

This comment has been minimized.

@flyingOrange
Copy link

我最近用这kcp挺好用的,比其他协议都快,只不过需要seed

@zhudashi
Copy link

难怪……我看的太晚了……

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

12 participants