前言
到底什么是ja3
简介
git:https://github.com/salesforce/ja3
JA3 是一种创建 SSL/TLS 客户端指纹的方法,它应该易于在任何平台上生成,并且可以轻松共享以用于威胁情报
更权威的介绍文章:https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967
JA3 是一种对传输层安全应用程序进行指纹识别的方法。它于 2017 年 6 月首次发布在 GitHub 上,是 Salesforce 研究人员 John Althouse、Jeff Atkinson 和 Josh Atkins 的作品。创建的 JA3 TLS/SSL 指纹可以在应用程序之间重叠,但仍然是一个很好的妥协指标 (IoC)。指纹识别是通过创建客户端问候消息的 5 个十进制字段的哈希来实现的,该消息在 TLS/SSL 会话的初始阶段发送。
这.....,2017年就有了,我是2021年才通过阅读青南大佬的文章知道有这么个东西。
然后我猜,ja3名字的由来,是因为有3个研究人员,且他们的姓名缩写都是ja
ja3的由来
其实,John Althouse 自己说过,官方解释:对这个 TLS 客户端进行指纹识别的主要概念来自 Lee Brotherston 2015 年的研究(https://blog.squarelemon.com/tls-fingerprinting/)和他的 DerbyCon 演讲(https://www.youtube.com/watch?v=XX0FRAy2Mec)。如果不是 Lee ,就不会有JA3的出现
ja3如何工作的
TLS 及其前身 SSL 用于为常见应用程序和恶意软件加密通信,以确保数据安全,因此可以隐藏在噪音中。要启动 TLS 会话,客户端将在 TCP 3 次握手之后发送 TLS 客户端 Hello 数据包。此数据包及其生成方式取决于构建客户端应用程序时使用的包和方法。服务器如果接受 TLS 连接,将使用基于服务器端库和配置以及 Client Hello 中的详细信息制定的 TLS Server Hello 数据包进行响应。由于 TLS 协商以明文形式传输,因此可以使用 TLS Client Hello 数据包中的详细信息来指纹和识别客户端应用程序
更多更专业的,如果很感兴趣,劳烦移步John Althouse发表的文章:https://engineering.salesforce.com/open-sourcing-ja3-92c9e53c3c41
感觉看了上面的话,是不是有点似懂非懂的感觉,没事,听我慢慢说,来个图吧,要来图的话,先来个三次握手的吧,老图了,相信各位朋友也都看烦了,我也看烦了,因为这图都传包浆了
详细的三次握手流程解释我就不说了,网上能查到的太多了,我就不献丑解释了,本篇文章的重点也不是它
那么按照ja开发者自述,是在三次握手之后,客户端向服务端发起client hello包,这个包里带了客户端这边的一些特征发给服务端,服务端拿来解析数据包,然后回发一个hello给客户端,之后再进行ssl数据交互,下面这个图,就是John Althouse自己画的
更多原文解释,请看这里:https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967John Althouse有关ja3的ppt讲义,上面的动图就是ppt文件里的,费了老大劲搞到的:链接: https://pan.baidu.com/s/1pfQwRwg3tbOT2Y2nQZYoAA 提取码: ptj6 (链接失效了可以联系我)
识别原理
1.JA3 不是简单地查看使用的证书,而是解析在 SSL 握手期间发送的 TLS 客户端 hello 数据包中设置的多个字段。然后可以使用生成的指纹来识别、记录、警报和/或阻止特定流量。
2.JA3 在 SSL 握手中查看客户端 hello 数据包以收集 SSL 版本和支持的密码列表。如果客户端支持,它还将使用所有支持的 SSL 扩展、所有支持的椭圆曲线,最后是椭圆曲线点格式。这些字段以逗号分隔,多个值用短划线分隔(例如,每个支持的密码将在它们之间用短划线列出)
3. JA3 方法用于收集 Client Hello 数据包中以下字段的字节的十进制值:版本、接受的密码、扩展列表、椭圆曲线和椭圆曲线格式。然后按顺序将这些值连接在一起,使用“,”分隔每个字段,使用“-”分隔每个字段中的每个值
其中第一条,也解释了我前一篇请求时尝试提交一个ssl证书为啥没有用的,第二条,服务端会在3次握手之后,收到客户端过来的hello包,然后解包,收集版本、接受的密码、扩展列表、椭圆曲线和椭圆曲线格式,在这时候就可以拿着ja3指纹去比对,哪些是限制了,哪些没有限制的,当确实有在限制名单里,就针对处理,当没有在限制名单里也返回一个hello,接着再继续ssl
ja3已收录指纹/黑名单
https://sslbl.abuse.ch/blacklist/sslblacklist.csv 这个没有更新了只有上百条
https://github.com/salesforce/ja3/blob/master/lists/osx-nix-ja3.csv 这个不是很全,只有上百条
https://ja3er.com/getAllUasJson 这个一直在更新,十几万条
https://ja3er.com/getAllHashesJson 同上,只是给定标注字段不同
我猜测,ja3er.com里的十几万ja3指纹,就是所有人访问过该网站的客户端(浏览器或者语言请求库)的指纹,有一条算一条的收集
案例解释
前面说了一堆理论概念,我们做开发的,如果只有概念没有实操或者例子解释是不够的,所以,来个案例,还是猿人学19题,同时继续祭出wireshark方便分析,先浏览器访问网站,拿到ip,提示一下,你们可别拿着ip去干人网站哈,挺好的一个爬虫练习平台,否则后果自负
此时你会看到很多的数据数据包,过滤一下,直接在过滤器位置输入ip.,就会有很多指令提示和历史记录
全部的命令是啥意思这里就不多说了,更多的可以百度,只介绍几个这里会用到的命令:
ip.dst_host就是目标地址,这里你可以理解为就是访问网站的服务端地址
ip.src_host就是源地址,这里你可以理解为就是你正在操作的电脑的ip,这个ip大多是局域网ip
ip.host 跟上面一样,但是不会区分是src还是dst,只要有指定地址的都会过滤出来
给定过滤指令,回车,此时有可能你输入过滤命令之后,看不到有任何数据包,小问题,可能你打开wireshark完了,没抓到包,重新刷新下网页就行了,此时就看到如下的数据:
注意看,最开始的6个数据包就是上面说的流程,前三个是三次握手,第四个开始到第6个就是ja3的hello数据包,个人感觉这段过程也很像三次握手,再后续的数据包就是实质的ssl数据交互了,
有了这个,看看ja3指纹到底是啥样的,双击这个client hello,然后服务端返回的sever hello就不看了,此时不需要看,忘掉它吧
771,43690-4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,64250-0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513-51914-21,51914-29-23-24,0
TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
以【,】分割,第一个数,771,根据官方解释,就是ssl/tls版本,也就是这个:
python终端跑一下,0x0303就是771,对上了:
ja3图1
细心的你发现了,我截图这里是没有第四个数相关的套件, 但是ja自己的文章里是有第四个数EllipticCurves相关的:
其实不是没有的,是现在第四个参数由EllipticCurves改成了supported_groups
ja3图2
注:ja3图1是公司电脑,ja3图2是家里电脑,所以看着ja3字段有点不一样,tls版本也不一样
说到版本,纠正下我前一篇给的说明,ja3指纹不是一定在tls1.3上支持的,上面的截图各位朋友应该也看得到,tls1.2也支持的,不过也确实是需要wireshark的最新版才能看到ja3指纹有关第三个数Extensions扩展列表,感兴趣的可以看看更详细的解释:https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
如何突破ja3
完全突破tls,我知道的有两种,一种是hello包hook改写,一种是自编译openssl重新改默认的指纹
python半突破
在上一篇的解决方案里,修改cipher里的加密算法即可,也就是TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats里的【Ciphers】,有关ciphers相关套接字的,可以看看openssl的官方解释:https://www.openssl.org/docs/man1.1.1/man1/ciphers.html,,并且华为官网的waf介绍里也有不同tls版本对应的套接字:https://support.huaweicloud.com/bestpractice-waf/waf_06_0012.html
也就是说,我们要想改,可以直接复制上面的套件覆盖下即可,例如:
urllib3.util.ssl_.DEFAULT_CIPHERS = 'EECDH+AESGCMEDH+AESGCM'
但是,相信对这个tls有过相关经验的大佬来说,其实在调试requests请求时,调试到这个http/client.py文件库里时,就能看到,在开始connect 时 http版本直接写死成了1.0,还有这个建立连接的tunnel_headers,代码给了个空值
如果你再配上fiddler或者charles抓包看的时候,明显能看到,在python发送实际请求前的CONNECT请求如下:
而浏览器的这个CONNECT请求是http1.1,且headers是有值的,如下:
而且根据我多方查阅,加上咨询了各位大佬之后,python目前只能改Ciphers里面的算法套件,来生成非默认的ja3指纹,然后可以骗过检测不是太高的反爬机制。但是其他的Extensions,EllipticCurves,EllipticCurvePointFormats是没法改的,原因是 python跟openssl没有很直接的联系,python发https请求最后还是借助openssl库暴露出来的方法,也就是的ssl_.py里的方法create_urllib3_context,因为openssl库对外提供的方法或者接口是没办法这么高度自定义的,Ciphers部分也最多能改改算法,都不能给个自己定义的算法进去的,而chrome可以访问是因为chrome有自己的ssl,且chrome肯定是不会被禁止的(闹呢,禁止了浏览器正常用户怎么访问?)也就是说这是python自身的缺陷了?所以我之前测试时不管用requests,httpx,还是aiohttp都不行,因为这三个库底层都借助了openssl库发请求。假如这种反爬手段满天飞的时候,python层面还没有成型的方案解决怎么办?WTF?python的业务方向中引以为傲的爬虫居然有缺陷?而且这个问题非常致命啊,感觉被降维打击了,伤心的是目前还没法改变局面,你说焦虑不焦虑?那有朋友估计会想问,“那为啥之前猿人学19题可以过?” ,那是因为19题检测的不严,只要ja3指纹长度小于等于浏览器的指纹长度都可以过,但其实还有很多特征的可以检测到的,来,看图,还是上次的图,看最后一个数有很大区别的,这个我在上一篇的结尾里也把这个问题抛出来了
这么明显的特征,如果是一个实际的网站案例,你觉得他会放过你吗?所以目前python针对tls指纹的有两个缺陷,第一个发起CONNECT请求时http1.0被写死,headers为空(当然这个可以改源码临时解决),第二个指纹没法完全自定义,有很多特征被识别
说到这里,有朋友可能不信,口说无凭,这次来个实际的案例,一个大佬给我的某网站浏览器打开(抱歉,我必须马赛克马到位),正常访问是这样的
python代码,这里我用之前介绍的简写形式了,只加这两行,其他地方不用改就行的
运行时发现程序卡住且一直没有响应的状态,测试得知原因是这个网站强制验证了http2.0的问题,requests暂不支持http2被该服务器识别一直不响应结果导致卡住。那换成httpx,(有关httpx详细的代码如何突破ja3的,可以看我之前写的这篇:python爬虫- requests、httpx、aiohttp、scrapy突破ja3指纹识别 )
怎么改都不成功的,这个就是实际例子说明了,这是python语言层面的问题啊,难道python真的有缺陷?
golang 之ja3transport库突破ja3
核心就是,代码里加了第三方库,ja3transport,这个库可以直接伪造ja3,刺不刺激?
一执行,一看,就发现真的改变了ja3指纹,惊呆!激动!刺激!兴奋!
大概的看了下ja3transport的介绍,它在发起请求的时候,会将请求的client hello数据包里的ja3指纹修改为我们自己的给定的,这样就达到了修改 JA3指纹的目的,妙啊
ja3transport简介
JA3 的问题在于它仅比基于用户代理字符串的指纹识别客户端好一点。到目前为止,用户代理字符串比 TLS ClientHello 数据包更容易更改。JA3 签名的参数仍由 TLS 客户端控制,因此不能作为可信的信息来源。在 Jeff 和 John 的 ShmooCon 谈话中,他们提到 JA3 不是灵丹妙药。他们提出的颠覆 JA3 检测的方法是使用操作系统的 HTTPS 客户端绕过 TLS 客户端特定的 JA3 签名。我们提出了另一种通过制作与其他 TLS 客户端(如浏览器)匹配的 ClientHello 数据包来破坏 JA3 检测的方法
ja3transport突破原理
要想颠覆JA3,需要修改5个JA3参数,可以在Refraction Networking的utls项目ClientHelloSpec提供的struct中修改。该包允许用户构建和执行 ClientHello 握手。第一个参数,TLS 版本,可以用和成员修改。第二个参数,可用密码套件,可以通过更新成员来更改。第三个参数 TLS 扩展可以通过更新成员来更改。参数的一个问题是所有参数都必须遵循 TLSVersionMinTLSVersionMaxCipherSuitesExtensionsExtensionsTLSExtension界面。第四个和第五个参数,椭圆曲线和椭圆曲线点格式,分别是 TLS 扩展SupportedCurvesExtension和 的一部分SupportedPointsExtension
我们不是根据 JA3 字符串创建客户端,而是可以生成与 Web 浏览器等良性签名匹配的 JA3 签名。有一些预设允许 JA3 签名匹配 Chrome 或 Firefox,甚至更多。我们仅限于屏蔽使用 Go 可用的相同扩展的应用程序。例如,如果 Chrome 使用 Go 不支持的扩展程序,我们无法屏蔽它。屏蔽密码套件比较棘手,因为任何密码套件都可以进行广告,即使它没有实现。如果执行握手的服务器接受客户端通告但实际上并不支持的密码套件,则会出现问题。只要服务器接受实际支持的密码套件,虚假宣传比可用密码套件多的密码套件就不是问题
Go 的net/http库有一个名为Transport. 传输结构负责编写如何将数据包发送到目标服务器。由于 JA3 的签名是基于 ClientHello 数据包的,我们可以进行 TLS 握手,让 Go 完成剩下的工作。该Transport对象是一个参数http.Client结构其中大部分进入开发人员都很熟悉。通过生成Transport结构体,我们的库应该可以与任何现有的 Go 项目无缝协作
浓缩下梗概,意思就是在go构建请求,三次握手之后,到实际要发起client hello包之前,ja3transport把数据包拦截了,即上面说的hello包hook的方法,然后把原来的ja3指纹修改成了自传递的ja3字段发出client hello,服务端就认了,然后就通过了。太牛逼了,他不是直接修改的tls里的那五个数组套件,因为ja3transport作者自己也说了,要想突破ja3,他认为的方法和选择的方法也是避开了直接改5个JA3参数,而是在5个JA3参数创建好之后进行拦截替换那么也就是说,其实golang跟python比也并没有在语言上占有很大优势,唯一就是golang多了这么一群人提前就研究并写好了这个第三方库,而python没法解决只是还没有人写出能够拦截数据包并替换ja3指纹的库,也就是这个并不是python的缺陷啊。当然,如果你急于求得结果,我建议你还是学一下golang来解决问题。
更多的解释请看原文:https://medium.com/cu-cyber/impersonating-ja3-fingerprints-b9f555880e42
ja3transport后续问题
作者也说了,如果你随意地伪造ja3,假如服务端通过一些方式得知你客户端访问进程跟实际的ja3不匹配,那应该也会屏蔽你我们能想到的最简单的检测改进是将 JA3 签名与生成 TLS ClientHello 数据包的进程映像配对。如果有客户端生成与 Firefox 匹配的 JA3 签名,但该进程不是 Firefox,则可能会发生一些奇怪的事情
ja3transport缺陷
但是,用这个库ja3transport执行的刚才那个案例网站的时候发现报了如下错,提示就是说这个服务器不支持版本304的协议
tls: server selected unsupported protocol version 304
一搜这个报错,就看到有个大佬说是因为不支持http2.0导致的:
对啊,这个网站上面它强制http2了,那也就是说,ja3transport不支持http2.0,这就是他的缺陷啊
golang之CycleTLS库http2.0+ja3指纹突破
那只要能解决http2这个问题,是不是就破了这个站呢?再看上面这个大佬发的日期:
7 May,那现在已经过去这么久了,大胆猜测一下,是不是这个大佬已经解决了呢?此时估计有朋友应该回想,“你这个人怎么这么不严谨,什么都靠猜吗?上一篇解决猿人学19题删一大片cipher加密算法解决问题也是猜的”哈哈哈,是啊,因为我个人觉得,爬虫跟前后端开发还是有点区别的,前后端开发,按照我的理解,讲究的逻辑严密,事先考虑各个层面的问题,尽量少bug然后服务能够长期稳定运行,但是爬虫的话,有时候没思路的情况下真的是大胆猜测出来的,我现在的思维已经转向这个方面了,虽然以前也干过后端开发,但是思维已经回不去了。我开始尝试了这个库:git地址:https://github.com/Danny-Dasilva/CycleTLS,真的就是抱着试一试的心态,执行,结果真的跟浏览器访问一样的,搞定,牛逼!
所以,也就是说,之前用的库github.com/CUCyber/ja3transport,不够适合当下场景 http2+ja3双重验证
这辈子没想到搞了几年爬虫逆向开发会为了解决ja3问题去学golang
nodejs
还是用上面大佬Danny-Dasilva的CycleTLS库即可,他也开发了node的库:https://github.com/Danny-Dasilva/CycleTLS#example-cycletls-request-jsts直接npm install cycletls,然后照着案例的代码来就行了,可能对于大部分爬虫开发来说,node相对golang来说会更熟悉一点的。
验证python指纹
既然已经可以随意改了对吧,那我改成python默认的指纹访问下这个网站看看呢?我下面只改了ja3指纹,看运行结果,果然是被拉黑了,哈哈哈哈
换接口验证
为了验证真的成功了,哈哈,该严谨的地方确实得严谨一点,还是用的golang,我换了另一个接口地址执行,确实有结果了。
用wireshark做最后的验证
那边go程序在执行,还是这个被马死的目标,这边wireshark抓包看结果,是的,相信你也看出猫腻了,它居然发了两次client hello包
而之前我们看到的,比如猿人学19题,内部题22,29题的那个,实际只会发一次的,这里发两次就很奇怪,然后,这两次发出去的ja3指纹还不一样:
哪里不一样,以逗号作为分割,除了第一个数和最后一个数,其他的字段的开头一个数组都不一样(以【-】作为分割)
然后,此时此刻,我们先去ja3官网看看浏览器本来的指纹呢?
chrome访问ja3官网返回得到的:
771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0
chrome访问目标网站用wireshark抓包得到:
第一个:
771,43690-4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,60138-0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513-51914-41,14906-29-23-24,0
第二个:
771,10794-4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,14906-0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513-2570-21,60138-29-23-24,0
去掉上面标记出来的,其实第一个和第二个是一样的,仅仅是这个被我马赛克马死的平台是这样,我猜应该是这个平台自己多加了一层握手流程,所以会有2个client hello,具体为啥有两个不纠结了,我也不是该平台的开发,也没法得知具体原因,不重要了,能获取数据就行了。不过等等,突然有个疑问?为啥wireshark的ja3指纹,同一个golang脚本啊,就算两个client hello,也应该是两个一样的ja3指纹啊,为了进一步分析,先用浏览器访问ja3后,同时看看wireshark抓包的是啥,这?我该信哪个?怎么wireshark多了点其他的东西
ja3到底存在吗?
按理来说,应该信ja3官方,因为这玩意儿就是别人搞出来的,那既然如此,是wireshark在搞事咯?(此时此刻突然想起了学生时代的一个数学逻辑题,假如你是侦探,现在有几个嫌疑人,已知里面有个人说了谎,从几个嫌疑人的供词里找出真凶。。。扯远了)为了进一步确认这个问题,根据青南大佬给文章的方案,可以伪造ja3指纹,那此时此刻,试下吧,现在的逻辑就是,我用一个我已知的指纹去请求ja3,然后看看ja3官方的返回,再看wireshark的ja3指纹对比,如果wireshark里的ja3不全等于我们自定义的ja3,那就确认是wireshark在他喵的搞事了首先澄清,没作弊哈,我用的第一个,跟我浏览器里的ja3是不一样的
这里我用的ja3transport库了,因为ja3官网没有强制HTTP2了,运行下面的代码,发现返回的确实改变了的,且值正是我们给定的
相信有朋友会问,这就奇怪了,为啥这里又一样了?难道wireshark没有骗我们?啊这。。。,不是吧,忘了吗?ja3transport这个库会在在三次握手后,即将发起client hello数据包时,拦截这个包,然后把自定义的ja3指纹替换原有的啊,这是ja3transport库的原理,所以,这里wireshark能跟我们给定的ja3指纹对应上那么,也就是说,wireshark有一套的自己的ja3指纹解析套件解析并显示了,但是这个解析套件跟ja3官网是不一样的,所以显示不一样。当然这是我经过多次分析推理得出来的结论,查了wireshark的文档,暂时没找到相关的介绍和解释,姑且这么理解吧,肯定信ja3官方不能信wireshark啊,毕竟这ja3是别人搞出来的。
而且仔细看下面这个图,我鼠标放到ja3上面的时候,下面的进制数据并不会对应显示
用微信好友chao的话,"所有真实的信息都有二进制的数据,而wireshark的ja3都没有对应的二进制数据"所以我跟chao讨论之后,认为ja3其实并不存在(上一次这么醍醐灌顶还是读到《三体》里的那句台词"物理学不存在",不好意思又扯远了。。。),或者说ja3并不是实质性存在的字符,而是通过TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats这五个tls组件根据自己的加密算法另类存在的,因为wireshark都能通过自己的解析逻辑解析显示啊。以上推论仅代表个人观点,有误请指正
需要注意的点
1.有没有发现,其实ja3在2017年就有了,据我了解,有很多公司的开发正在研究中。 2.有了一个ja3,那我觉得后续肯定会出现升级版或者替代版了,红蓝对抗,反爬与反反爬,一直在对抗中进步,是好是坏,只能用时间来判定了。很多新东西靠自己一个人摸索是没法走到更远的,好比这个ja3,如果一开始没有Lee Brotherston大佬在2015公开讲解,也就不会有这么牛逼的ja3指纹出现了,很喜欢微信好友Regionover在一篇文章里的一句话:【故事要留给过去,但成长要用于分享】3.另外,希望国内外能有大神可以仿照这个ja3transport库或者CycleTLS库写一个python的库出来,唉,我自己也想写出来啊,过去这么久了,我也在看原理,还得花时间研究啊。
后续怎么继续学习tls指纹
练习题:猿人学外部题19题,内部题22,29,32题,32题是app版的tls指纹校验,强的一批,网洛者练习平台第9题(这个题正常的浏览器都会返回假数据,作者看了一会儿我的文章一晚上就搞了这道题出来,强的一批),只提示下,有几页假数据。
志远大佬的最新课程里有tls指纹的,感兴趣可以整一个。他的方法就是自编译openssl
感兴趣可以读下openssl源码
之前说的tls指纹研究怎么样了
说来惭愧,搞了这么久,也没搞出个所以然来,我越去了解,就越觉得这个东西不是那么容易的,资料太少,全靠摸索,只能慢慢来了,因为我想实现的是完全自定义tls指纹,这个想法说实话,越来越觉得难实现了,不过一有空就在研究的。不仅研究突破tls,也在研究怎么利用它更好实现反爬,欢迎有兴趣的朋友一起讨论。
结语
1.真诚感谢一路下来认识的大佬们。以上都是个人见解,如果有误还望指正,有任何问题,我很乐意跟各位大佬们交流,我微信id:geekbyte,备注来意
2.另外应有些朋友的建议建了个群,里面很多大佬,安全渗透,爬虫,web+app逆向,前后端开发,ios开发的大佬都有,已满200人,想进群的可以加我微信
3.推荐下蔡老板的星球,web逆向必须学的ast技术。他虽然不在爬虫圈,但是在爬虫圈一直有他的传说,我能有今天,能认识这么多大佬,很大一部分原因是因为他。真的很感谢蔡老板。