Aparecium - Detect ShadowTLS v3 & REALITY TLS Camouflage
Aparecium is a proof-of-concept tool that detects TLS camouflage protocols like ShadowTLS v3 and REALITY, which disguise encrypted traffic as legitimate TLS 1.3 to evade DPI and censorship. By exploiting their failure to handle post-handshake messages (e.g., NewSessionTicket), Aparecium identifies these protocols with high accuracy, especially against OpenSSL-based servers. ShadowTLS’s HMAC tainting (adding 4 bytes) makes it particularly vulnerable. Built in Go, it uses utls.Fingerprinter for ClientHello fingerprinting and analyzes message lengths to expose discrepancies.
是的,经过确认它似乎确实在reality上存在,经典的长度特征((( 我们在握手完成后发回一个空 application data 并填充到指定长度。好消息是这是非 break change. 但这可能需要一些时间,我们可以暂时警告用户避开选择这类dest @RPRX @Yuhan6665
Yes, after confirmation, it seems to actually exist in reality. The classic length feature ((( After the handshake is complete, we send back an empty application data and fill it to the specified length. The good news is that this is not a break change. However, this may take some time, so we can temporarily warn users to avoid selecting this type of destination.
短期来讲应该问题不大 因为握手后的消息有一定多样性 长期来讲 seed 应该可以解决
我认为 seed 要解决两大(握手后)问题 一是消息长度 二是消息发送的时序 众所周知 vless 为了减低服务端第一个回包特征 会把第一包和代理回覆粘包一起发 但是之前有实验发现这个代理回覆比本机回包慢一点(比如这里提到OpenSSL new session ticket) 所以我之前发的草稿已经修改这块 让seed具备不等代理数据直接回覆空padding的能力
至于具体的时序和消息长度 我跟R佬讨论过 我有一个想法是reality不是刚好有跟dest的全文代理转发吗 我们可以做一个dest的消息往来记录 这样我们模仿真实的时序 不需要硬编码任何信息 也不需要用户做任何配置 而且还能使得各人dest不同而多样化
In the short term, there should be no major issues because the messages after the handshake have a certain degree of diversity. In the long term, seed should be able to resolve this.
I believe that seed needs to address two major issues (after the handshake): message length and message transmission timing. As is well known, Vless reduces the distinctive characteristics of the server's first response by combining the first packet with the proxy response into a single packet. However, previous experiments have shown that the proxy response is slightly slower than the local response (e.g., as mentioned in the OpenSSL new session ticket). Therefore, I have modified the draft I previously sent to enable the seed to respond with empty padding directly without waiting for the proxy data.
Regarding the specific timing and message length, I discussed this with R. I have an idea: since reality already has full proxy forwarding with the destination, we can create a message exchange record for the destination. This way, we can mimic real-world timing without hard-coding any information or requiring user configuration, while also allowing for diversity based on different destinations for each user.
又一次充分说明化身cloudflare反代器的必要性?
Another clear example of the necessity of using cloudflare as a reverse proxy?
Another clear example of the necessity of becoming a cloudflare reverse proxy?
@wkrp Translation.
Not affected if the fronting domain was on Cloudflare? Just a coincidence.
Another clear example of the necessity of becoming a cloudflare reverse proxy?
@wkrp Translation.
Not affected if the fronting domain was on Cloudflare? Just a coincidence.
这东西说白了就是握完手之后,收到客户端 change cipher spec 后, openssl 服务端会发送一个极其明显TCP数据包,包含两段等长的 tls record(两个NewSessionTicket) 根据服务端TLS实现这会有不同的特征,除开 openssl 这种,还有其他行为。比如go TLS服务器会跟着server hello那次flight回来 这种没问题。再比如cloudflare的实现,它没有 openssl 那么明显,但是不代表没有,它会夹在正常载荷中一起返回,但是表现为一个TCP数据包承载了两个record,其中一个record偏小,大概五六十字节。这在某种程度上也是独特的,因为大多数情况下,一个来自cloudflare的TCP数据包只会包含一个record。又比如作者提到的谷歌,压根没有 NewSessionTicket 那就没这个问题
The thing is that after the handshake, after receiving the client's change cipher spec, the openssl server will send an extremely obvious TCP packet containing two equal-length tls records (two NewSessionTickets). Depending on the server's implementation of TLS, this will have different characteristics. In addition to the openssl kind, there are other behaviors. For example, the go TLS server will follow the server hello flight back, which is fine. The cloudflare implementation, for example, is not as obvious as openssl, but that doesn't mean it's not there. It will return with the normal load, but it will behave as if a TCP packet carried two records, one of which is on the small side, about 50 or 60 bytes. This is also unique in a way, because in most cases a TCP packet from cloudflare will only contain one record, and Google, for example, as mentioned by the author, doesn't have a NewSessionTicket, so it doesn't have this problem.
Please note that responses manually edited in emails will not be displayed. To make our discussion more accessible to contributors from different countries, I’d appreciate it if we could primarily use English, as I don’t speak Chinese and AI translation seems more efficient for broader participation.
On the technical side, I have new progress: I tested websites using Cloudflare CDN, and their session tickets have distinct characteristics, returning only one record instead of two. Any thoughts or feedback on this finding?
我没说过它在Cloudflare不存在,只是比openssl稍微好一点点。反正就是在同一个TCP包中包含两个record,而与此同时大多数TCP包只包含一个导致的问题 这些东西可以被归类为握手后指纹,大多数这类TLS伪装协议并未考虑过它,只考虑过握手过程的指纹
I didn't say it doesn't exist on Cloudflare, it's just slightly better than OpenSSL. Anyway, it's the problem of containing multiple records in one TCP packet, while most TCP packets only contain one at the same time These things can be classified as fingerprints after handshake, and most TLS spoofing protocols have not considered it, only the fingerprints during the handshake
https://github.com/XTLS/Xray-core/issues/4778#issuecomment-2939912442
@yuhan6665 我觉得虽然 Vision Seed 有这个能力,但是交给它做不合适,并不是所有 REALITY 都是 Vision,第一层有可能是 XHTTP
总之 REALITY 自身需要更新代码来解决该问题,这将会是 REALITY 缓存 target 特征机制的开端
I think that although Vision Seed has this capability, it is not appropriate to entrust it with this task. Not all REALITY is Vision; the first layer may be XHTTP.
In short, REALITY itself needs to update its code to resolve this issue, which will mark the beginning of REALITY's cache target feature mechanism.
@Fangliding 估计是都被 Go 的 TLS 实现带偏了,不过服务端一次性把话说完挺好的,等 ClientFinished 后再发剩下的有点没必要
I guess we've all been influenced by Go's TLS implementation, but it's good that the server says everything at once. Waiting for ClientFinished before sending the rest seems unnecessary.
https://github.com/XTLS/Xray-core/issues/4778#issuecomment-2939912442
@yuhan6665 我觉得虽然 Vision Seed 有这个能力,但是交给它做不合适,并不是所有 REALITY 都是 Vision,第一层有可能是 XHTTP
总之 REALITY 自身需要更新代码来解决该问题,这将会是 REALITY 缓存 target 特征机制的开端
交给哪个层面处理是一个问题。不过目前的seed草稿是在vless底层 也就是说 无流控 有传输层也(应该)能用
Which level should handle this is a question. However, the current seed draft is at the vless layer, which means that there is no flow control, but the transport layer should also work.
https://github.com/XTLS/Xray-core/issues/4778#issuecomment-2939977944 3xy 3xy 125 应该是比较典型的 OpenSSL 行为,我们先探测出这个长度并发个版,然后再研究 CF
3xy 3xy 125 should be a typical OpenSSL behavior. We first detect this length and create a version, then study CF.
@yuhan6665 XHTTP 传输层是在 Vision 流控外面的,所以有传输层时 Vision 就控制不了整体流量特征了
Anyway,你现在有空写个简单的代码去探测这个 3xy 3xy 125 吗,大概就是用 Go 的 TLS 请求一下,外面套个 conn,等 C 发消息,等 S 发消息,等 C 发消息,然后开始记录 S 的 TLS record 长度,等个五秒,如果第三个 record 是 66+5,就记下前两个的长度
The XHTTP transport layer is outside Vision flow control, so when the transport layer is present, Vision can no longer control the overall traffic characteristics.
Anyway, could you write a simple code snippet to test this 3xy 3xy 125 setup? Basically, use a Go TLS request wrapped in a conn, wait for C to send a message, wait for S to send a message, wait for C to send a message, then start recording the TLS record length of S. Wait for five seconds, and if the third record is 66+5, record the lengths of the first two.
确实 想了一下xhttp 还有上下行分离 那么可能放在reality,传输层面比较好点
另我最近比较忙 但是可能可以帮忙
Indeed, after considering xhttp and separating upstream and downstream, it might be better to place it in reality, at the transmission layer.
I've been quite busy lately, but I might be able to help.
@yuhan6665 有空的话把上面那个写成一个函数 PR 到 REALITY 仓库的 tls.go,应该很简单,剩下的我来,明天就可以发个版
~~我就说当初写 REALITY 时咋发现有些网站不发 NewSessionTicket,原来是放 ClientFinished 后面了~~
If you have time, please write the above as a function and PR it to the tls.go file in the REALITY repository. It should be pretty simple. I'll take care of the rest, and we can release a new version tomorrow.
~~I was wondering why some websites didn't send NewSessionTicket when I was writing REALITY, and it turns out it was placed after ClientFinished.~~
I think about Xray's development detail, we can discuss in Xray's own repo(((
感谢 @ban6cat6 的发现,~~直到有人在 Xray-core 发 issue 我才看到这个~~,Xray-core v25.6.7 已经初步解决了该问题
此外我们还发现了不同 CH 指纹会触发不同长度的 NewSessionTicket(尚未彻底解决),以及很多服务器会提前发 h2 settings 帧(已经可以模拟,但尚未解决长度变化的问题),详细讨论见 https://github.com/XTLS/Xray-core/issues/4778 ,下一步方向是 https://github.com/XTLS/Xray-core/issues/4788
Thanks to @ban6cat6 for discovering this. ~~I didn't see this until someone posted an issue on Xray-core.~~ Xray-core v25.6.7 has preliminarily resolved this issue.
Additionally, we discovered that different CH fingerprints trigger NewSessionTicket frames of varying lengths (not yet fully resolved), and many servers are sending h2 settings frames prematurely (this can be simulated, but the issue with varying frame lengths remains unresolved). For detailed discussions, please refer to https://github.com/XTLS/Xray-core/issues/4778. The next steps are outlined in https://github.com/XTLS/Xray-core/issues/4788
感谢 @ban6cat6 的发现,~直到有人在 Xray-core 发 issue 我才看到这个~,Xray-core v25.6.7 已经初步解决了该问题
此外我们还发现了不同 CH 指纹会触发不同长度的
NewSessionTicket(尚未彻底解决),以及很多服务器会提前发 h2 settings 帧(已经可以模拟,但尚未解决长度变化的问题),详细讨论见 XTLS/Xray-core#4778 ,下一步方向是 XTLS/Xray-core#4788Thanks to @ban6cat6 for discovering this. ~I didn't see this until someone posted an issue on Xray-core.~ Xray-core v25.6.7 has preliminarily resolved this issue.
Additionally, we discovered that different CH fingerprints trigger
NewSessionTicketframes of varying lengths (not yet fully resolved), and many servers are sending h2 settings frames prematurely (this can be simulated, but the issue with varying frame lengths remains unresolved). For detailed discussions, please refer to XTLS/Xray-core#4778. The next steps are outlined in XTLS/Xray-core#4788
To be honest, incorporating excessive application-layer logic into a camouflage protocol at the TLS layer is a dreadfully poor idea, as such a design only serves to complicate the protocol further. The HTTP2 Settings frame you referenced is, in fact, part of nginx's logic following the TLS handshake, and this imitation ought to be managed by the application layer. Refer to https://github.com/nginx/nginx/blob/5b8a5c08ce28639e788734b2528faad70baa113c/src/http/ngx_http_request.c#L845.
REALITY 被设计于可以搭配任何内层协议,而绝大多数内层协议并没有处理流量特征这种功能,尤其是人们将 REALITY 用于非代理用途时(非 VLESS 等代理协议),所以对于客户端简单握手就能引发 server 发的几个包,特征过于明显,REALITY 服务端应当模仿,目前的想法是如果内层是 XHTTP 这种时可以把 h2 settings 帧 padding 一下,长远来看的话借助 TLSv1.3 的 padding 功能,在 REALITY 层面控制整体的流量特征以符合 target server 的典型流量特征也是可以做的事情,从而无需内层协议关心这件事
REALITY is designed to be compatible with any underlying protocol, and most underlying protocols do not have the functionality to handle traffic characteristics, especially when REALITY is used for non-proxy purposes (such as non-VLESS proxy protocols). Therefore, for simple client handshakes that trigger the server to send a few packets, the characteristics are too obvious.The REALITY server should emulate this behavior. The current idea is that if the inner layer is XHTTP, the h2 settings frame can be padded. In the long term, leveraging the padding feature of TLSv1.3, it is also feasible to control the overall traffic characteristics at the REALITY layer to align with the typical traffic characteristics of the target server, thereby eliminating the need for the inner layer protocol to handle this.