本文為看雪論壇優(yōu)秀文章
看雪論壇作者ID:
記錄下學(xué)習(xí)Quic協(xié)議的過程,測試工程是在之前發(fā)的帖子中提到的測試app:
某視頻app(V15.7)及web分析記錄|某視頻app(V15.7-V18.4)的學(xué)習(xí)記錄|軟硬件環(huán)境:某音 (應(yīng)用寶渠道)IDA 7..2.- 17.1 ( 10)小米8
了解到Quic也是從某音開始的,之前看過某音相關(guān)帖子提到Quic,說用就是走Quic協(xié)議,現(xiàn)在看感覺有概念問題,是協(xié)議棧組件,支持http,ftp 等很多種協(xié)議的,Quic也支持,用并不表示會走Quic協(xié)議。使用某音.so訪問相同鏈接,是否開quic協(xié)議的返回數(shù)據(jù)對比,如下面2張調(diào)試截圖:
開quic后是 http/2+quic/43;關(guān)quic后是 h2。
最開始看到quic時候一臉懵,查了些資料:網(wǎng)絡(luò)協(xié)議選擇之HTTP2與QUIC的競速_嗶哩嗶哩探索(二):編譯第一個QUIC工程 - 簡書 ()[翻譯]QUIC 與 HTTP/3:太過龐大而致失敗?-- 論導(dǎo)致 QUIC 失敗的因素-外文翻譯-看雪論壇-安全社區(qū)|安全招聘|
受前面提到的一個某音帖子影響dns可以采用的傳輸層協(xié)議是,以為手機上協(xié)議在傳輸層都是走quic的,直接截了下某音數(shù)據(jù)包,做了下常用操作(刷視頻,點贊,關(guān)注等),但是沒發(fā)現(xiàn)有quic相關(guān)的數(shù)據(jù)包,仍然還是TLS1.2的,這樣搞得完全不知道怎么回事。
后來想到直接用測試app調(diào)用.so連本地服務(wù)器看看,考慮到quic強制要求走加密的,這里要使用https連接(連本地服務(wù)器,需要屏蔽掉java層和層的ssl證書校驗,so層的這個論壇有帖子說過了,java層的要是服務(wù)器是正式證書也不需要修改):
UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder(
"https://10.0.117.217/about", new MyUrlRequestCallback(), executor);
結(jié)果發(fā)現(xiàn)還是TLS1.2的,就說還是走的TCP:
這里提下指紋JA3:
tls.handshake.ja3_full:
771,64250-4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,2570-0-23-65281-10-11-35-16-5-13-18-51-45-43-27-10794-21,2570-29-23-24,0
[JA3: ca6385bf726b3d5f2b4db30186da0fec]
這個JA3,其實就是md5(ja3_full)。
逗號隔開5個部分,分別對應(yīng)如下:
771: Version: TLS 1.2 (0x0303)
64250-4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53:
2570-0-23-65281-10-11-35-16-5-13-18-51-45-43-27-10794-21:
2570-29-23-24:
Supported Groups (4 groups)
Supported Group: Reserved (GREASE) (0x0a0a)
Supported Group: x25519 (0x001d)
Supported Group: secp256r1 (0x0017)
Supported Group: secp384r1 (0x0018)
這里最后一部分是0:猜測對應(yīng): 后來看到下面這張圖:
想到會不會是因為沒走TLS1.3的緣故,打算本地用TLS1.3請求看看有什么返回,Node請求要設(shè)置如下,需要v14.18.2以上版本:
: . | . | .
這樣看起來就是TLSv1.3的協(xié)議了,但是也沒看到有quic相關(guān)的信息。后來看一個資料提到()已經(jīng)支持quic訪問了,測試了下:
確實有頭部字段Alt-Svc,下面文章有提到
這種訪問QUIC服務(wù)的奇怪方式,要求終端事先知道訪問一個特定網(wǎng)站所用的協(xié)議及服務(wù)的端口。這樣是非常不靈活的,因而它主要用于缺乏適當?shù)膮f(xié)議協(xié)商機制的情況下,用于QUIC協(xié)議的實驗階段。不久前,有一個稱為 替代服務(wù)( ) 的HTTP機制標準化了,其標準規(guī)范文檔為 。
這種機制允許對一個HTTP資源的訪問,被重定向到另外的一個網(wǎng)絡(luò)位置,甚至是以一種不同的協(xié)議配置來訪問。具體而言,這種機制為HTTP新增了一個頭部字段Alt-Svc,HTTP服務(wù)器可以通過這個頭部字段,告知客戶端服務(wù)器被重定向到的服務(wù)的信息,包括協(xié)議,端口號等。通過手機瀏覽器訪問,截包也能看到有quic相關(guān)協(xié)議:
不過這里發(fā)現(xiàn)有的是GQUIC,查了下資料:gGUIC與 iQUIC
由 創(chuàng)建并以 QUIC 的名稱提交給 IETF 的協(xié)議與隨后在 IETF 中創(chuàng)建的 QUIC 完全不同(盡管名稱相同)。最初的 QUIC(也稱為 gQUIC)嚴格來說是通過加密 UDP 發(fā)送 HTTP/2 幀的協(xié)議,而 IETF 創(chuàng)建的 QUIC 是通用傳輸協(xié)議,也就是說 HTTP 以外的其他協(xié)議(如 SMTP、DNS、SSH、、NTP)也可以使用它。
重要的是要注意并記住其差異。自 2012 年以來, 在其服務(wù)及 中使用的 QUIC 版本(直到 2019 年 2 月)為 QUIC。隨著時間的推移,它正在逐漸變得類似于 IETF QUIC(也稱為 iQUIC)。知道可以使用quic協(xié)議訪問后,就想在測試app中使用來訪問,需要設(shè)置為true,及調(diào)用添加quic使用的域名:
public CronetEngineBuilderImpl(Context context) {
this.mApplicationContext = context.getApplicationContext();
enableQuic(true); //這里可以設(shè)置打開Quic
enableHttp2(true);
enableBrotli(false);
("",443,443);//這里如果不設(shè)置,就會走tlsv1.3協(xié)議,設(shè)置后才會走QUIC協(xié)議。
某音中途有個版本升到: 2021-09-15,后來又降到了: 2021-08-12,最新19.5版本的quic也還是這個版本。
這里也能通過:.("/data/local/tmp/.json",true);
輸出日志,然后使用下面地址可以分析這個日志:#
這個其實也可以用在某音里面截包:
Java.choose("org.chromium.CronetClient", {
onMatch: function (instance) {
instance.getCronetEngine().startNetLogToFile("/data/local/tmp/cronet_log.json", true);
},
onComplete: function () { }
});
到這里后,已經(jīng)能夠調(diào)用.so走quic協(xié)議了,就想回過頭來繼續(xù)訪問某音協(xié)議,從頭分析了下數(shù)據(jù),在某音啟動時候發(fā)現(xiàn)了quic數(shù)據(jù)包:
看到了這個域名。
這里提一下,Quic是帶競速模式的,同時也會發(fā)送tcp鏈接,如遇網(wǎng)絡(luò)或服務(wù)器不支持quic/udp,客戶端標記quic為,后面會嘗試重試quic,一旦再次成功采用quic并把標記取消:
后來發(fā)現(xiàn)了這個url,正好跟前面發(fā)現(xiàn)的域名一致的:這里還有個字段=false,更加說明這個協(xié)議涉及到的相關(guān)功能會用到quic(后來發(fā)現(xiàn)是視頻上傳相關(guān)的),雖然這個url請求本身不一定會走quic,但是這個域名跟之前截到的quic數(shù)據(jù)包中的一致,可以拿來測試。
使用app測試訪問這個域名,也能正常走quic協(xié)議。
到這里后,就要繼續(xù)看看這個協(xié)議格式及模塊中的quic請求流程了,調(diào)試時候斷在能看到quic數(shù)據(jù)包:
這里碰到了知識盲區(qū),發(fā)現(xiàn)調(diào)用的時候沒有設(shè)置目標地址參數(shù),一下懵了,這怎么知道數(shù)據(jù)發(fā)哪里去的,也嘗試斷在,發(fā)現(xiàn)不是,查資料后發(fā)現(xiàn):
UDP也可以有連接
若()函數(shù)和()函數(shù)向已連接的進程中發(fā)送消息,則忽略參數(shù)、和msg結(jié)構(gòu)體中用于傳遞地址的成員。此時若參數(shù)和不為NULL,則可能會返回錯誤或0;連接套接字是需要一定開銷的,比如需要查找路由表信息。
所以,UDP 客戶端程序通過 可以獲得一定的性能提升。斷在后,發(fā)現(xiàn)了目標IP和端口:
調(diào)試分析流程過程中,發(fā)現(xiàn)要是有個本地服務(wù)器會更方便,看網(wǎng)上有提到node支持quic,打算用node.js架設(shè)一個支持quic的服務(wù)器。直接下了個V16+的,結(jié)果發(fā)現(xiàn)不支持,后來查了下提交log,發(fā)現(xiàn)有次提交,去掉了這個quic支持:
quic: quic by · Pull #37067 · /node ()
需要拿15.X的代碼編譯,帶上 clone git 在本地架好TCP UDP服務(wù)器后測試,也驗證了開啟quic請求url確實會同時發(fā)送tcp和udp的請求:
在調(diào)試協(xié)議過程中,發(fā)現(xiàn)走不通,翻了下代碼,找到相關(guān),查了下/: is an to IETF QUIC ()
項目是實現(xiàn)IETF QUIC的協(xié)議,那跟某音這個還不同,協(xié)議幀最小數(shù)據(jù)長度也不一致,實際用這個測試需要基于某音版本修改。
現(xiàn)在這個quic還沒統(tǒng)一,很多資料不是gquic的,就算是也可能是不同版本的。
頭部格式目前就有幾種:at:{,,,};想著還是直接用版本的,把拖下來了:;。這里面的協(xié)議就是跟某音quic協(xié)議一致的了,這里提下Pulic Flags的bit3:
0x08: Bit3被置上就表示中包含8字節(jié)的 id 這個標志位在所有的包中都應(yīng)該被設(shè)置
代碼中的注釋:
// All versions of Google QUIC up to and including Q043 set
// FLAGS_DEMULTIPLEXING_BIT to one on all client-to-server packets. Q044
// and Q045 were never default-enabled in production. All subsequent
// versions of Google QUIC (starting with Q046) require FLAGS_FIXED_BIT to
// be set to one on all packets. All versions of IETF QUIC (since
// draft-ietf-quic-transport-17 which was earlier than the first IETF QUIC
// version that was deployed in production by any implementation) also
// require FLAGS_FIXED_BIT to be set to one on all packets. If a packet
// has the FLAGS_LONG_HEADER bit set to one, it could be a first flight
// from an unknown future version that allows the other two bits to be set
// to zero. Based on this, packets that have all three of those bits set
// to zero are known to be invalid.
看資料說去掉了win上的編譯支持,就打算改下,希望能用VS編譯,方便調(diào)試,后來越改感覺工作量越多,放棄了,搜索發(fā)現(xiàn)了,還是c實現(xiàn)的,拿代碼下來編譯了個版本(基本按照文檔走,需要先編譯,perl,go都需要安裝,perl裝好后要手動加入環(huán)境變量,之前就卡這里了):/: QUIC and HTTP/3 ()
文檔: — 3.0.4
開啟自帶的測試程序中的服務(wù)器后,app上發(fā)起連接,服務(wù)器正常收到了請求,要記得設(shè)置證書啟動,不然處理CHLO Frame數(shù)據(jù)會異常:.exe -r ./ -s 10.0.117.217:443 -c 10.0.117.217,file.crt,.pem
再鏈接發(fā)現(xiàn)有交互數(shù)據(jù)了:
但是隨后就被客戶端斷了,發(fā)現(xiàn)是證書校驗錯誤,我這個測試證書是自己生成的:
之前已經(jīng)是修改了證書校驗的(java層和so的都修改了),能夠連接我本地的https服務(wù)器,但是連quic服務(wù)器不行,就算設(shè)置 屬性也不行,說明走quic時候還有一個校驗,根據(jù)字符串找到下面地方:
在代碼中也找到對應(yīng)的:
根據(jù)代碼找到修改點:
這個時候服務(wù)器沒收到客戶端因為校驗斷開的包了,截包發(fā)現(xiàn)認證也過了,不會像之前那樣斷開了:
到這里后,基本上有了完整的調(diào)試環(huán)境,后面需要分析quic協(xié)議算法就可以很方便的帶源碼調(diào)試了。目前看起來某音并沒有大規(guī)模使用quic,從測試url看是上傳視頻可能會用到,但是我的測試環(huán)境也沒發(fā)現(xiàn)走quic,最近的幾個版本看見有協(xié)議中增加了下面信息:
current_network_quality_info=
{"http_rtt":27,"tcp_rtt":15,"quic_rtt":15,"downstream_throughput_kbps":1600,"net_effective_connection_type":7,"video_download_speed":3311,"quic_receive_loss_rate":-1,"quic_send_loss_rate":-1}
看起來是quic相關(guān)的,不知道是不是在收集用戶環(huán)境信息,評估用戶網(wǎng)絡(luò)環(huán)境,然后再決定是否部署quic。就算后面一些功能http協(xié)議都轉(zhuǎn)向quic,那也有很多切入點:1、對于走的,那還是可以在JAVA層對應(yīng)jni函數(shù)及回調(diào)函數(shù)拿到請求數(shù)據(jù)。
2、最終還是走層的,在so層收發(fā)數(shù)據(jù)對應(yīng)函數(shù)也可以拿到數(shù)據(jù)。
3、用上面提到的.直接寫網(wǎng)絡(luò)日志(這個實際測試發(fā)現(xiàn)不全,還不確定是不是參數(shù)設(shè)置問題)。
4、直接在quic實現(xiàn)部分的收發(fā)函數(shù)拿數(shù)據(jù)。
5、了解quic協(xié)議過程后dns可以采用的傳輸層協(xié)議是,還可以把各個通訊層級加密key記錄下來,直接解析udp截包數(shù)據(jù)。
6、可以像上面測試那樣,搭建本地quic服務(wù)器,把請求轉(zhuǎn)到本地服務(wù)器來。
看雪ID:
*本文由看雪論壇 原創(chuàng),轉(zhuǎn)載請注明來自看雪社區(qū)
#往期推薦
1.
2.
3.
4.
5.
6.