首页
论坛
课程
招聘
[原创] 通过抖音Cronet模块学习Quic协议
2022-2-20 17:38 26851

[原创] 通过抖音Cronet模块学习Quic协议

2022-2-20 17:38
26851

[原创]通过抖音Cronet模块学习Quic协议


 

    记录下学习Quic协议的过程,测试工程是在之前发的帖子中提到的测试app:

    某视频app(V15.7)及web分析记录|bbs.pediy.com

    某视频app(V15.7-V18.4)的学习记录|bbs.pediy.com

 

软硬件环境:

 

抖音android (应用宝渠道)

IDA 7.5

Frida 14.2.2

Gda

JEB

jadx-gui

unidbg

Wireshark

LineageOs 17.1 (android 10)

小米8


 

流水账

 

了解到Quic也是从抖音开始的,之前看过抖音相关帖子提到Quic,说用Cronet就是走Quic协议,现在看感觉有概念问题,Cronet是协议栈组件,

支持http,ftp 等很多种协议的,Quic也支持,用Cronet并不表示会走Quic协议。

 

     使用抖音libsscronet.so访问相同链接,是否开quic协议的返回数据对比,如下面2张调试截图:

     开quic后mNegotiatedProtocol是 http/2+quic/43

     关quic后是 h2

 

 

 

最开始看到quic时候一脸懵,查了些资料:

Cronet网络协议选择之HTTP2与QUIC的竞速_哔哩哔哩_bilibili

QUIC探索(二):编译第一个QUIC工程 - 简书 (jianshu.com)

[翻译]QUIC 与 HTTP/3:太过庞大而致失败?-- 论导致 QUIC 失败的因素-外文翻译-看雪论坛-安全社区|安全招聘|bbs.pediy.com

实战 | QUIC 协议助力腾讯业务提速 30% (qq.com)

实战 | QUIC 协议在蚂蚁集团落地 (qq.com)

 

 

受前面提到的一个抖音帖子影响,以为手机上协议在传输层都是走quic的,直接截了下抖音数据包,做了下常用操作(刷视频,点赞,关注等),但是没发现有quic相关的数据包,仍然还是TLS1.2的,这样搞得完全不知道怎么回事。

后来想到直接用测试app调用libsscronet.so连本地服务器看看,考虑到quic强制要求走加密的,这里要使用https连接(连本地服务器,需要屏蔽掉java层和native层的ssl证书校验,so层的这个论坛有帖子说过了,java层的要是服务器是正式证书也不需要修改):

 

UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder(
       
"https://10.0.117.217/about", new MyUrlRequestCallback(), executor);

 

结果发现还是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个部分,分别对应如下:

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:

猜测对应Extension: padding

 

后来看到下面这张图:

想到会不会是因为没走TLS1.3的缘故,打算本地用TLS1.3请求aweme.snssdk.com看看有什么返回,

Node请求option要设置如下,需要v14.18.2以上版本:

secureOptions: m_constants.SSL_OP_NO_TLSv1 | m_constants.SSL_OP_NO_TLSv1_1 | m_constants.SSL_OP_NO_TLSv1_2

 

 

这样看起来就是TLSv1.3的协议了,但是也没看到有quic相关的信息。

 

后来看一个资料提到youtube(https://www.youtube.com)已经支持quic访问了,测试了下:

 

确实有头部字段Alt-Svc,下面文章有提到

https://blog.csdn.net/wangyiyungw/article/details/81746901

这种访问QUIC服务的奇怪方式,要求终端事先知道访问一个特定网站所用的协议及服务的端口。这样是非常不灵活的,因而它主要用于缺乏适当的协议协商机制的情况下,用于QUIC协议的实验阶段。不久前,有一个称为 替代服务(Alternative Services) 的HTTP机制标准化了,其标准规范文档为 RFC7838。这种机制允许对一个HTTP资源的访问,被重定向到另外的一个网络位置,甚至是以一种不同的协议配置来访问。具体而言,这种机制为HTTP新增了一个头部字段Alt-Svc,HTTP服务器可以通过这个头部字段,告知客户端服务器被重定向到的服务的信息,包括协议,端口号等。

 

通过手机浏览器访问youtube,截包也能看到有quic相关协议:

 

不过这里发现有的是GQUIC,查了下资料:

 

gGUIC 与 iQUIC

由 Google 创建并以 QUIC 的名称提交给 IETF 的协议与随后在 IETF 中创建的 QUIC 完全不同(尽管名称相同)。 最初的 Google QUIC(也称为 gQUIC)严格来说是通过加密 UDP 发送 HTTP/2 帧的协议,而 IETF 创建的 QUIC 是通用传输协议,也就是说 HTTP 以外的其他协议(如 SMTP、DNS、SSH、Telnet、NTP)也可以使用它。重要的是要注意并记住其差异。 自 2012 年以来,Google 在其服务及 Chrome 中使用的 QUIC 版本(直到 2019 年 2 月)为 Google QUIC。随着时间的推移,它正在逐渐变得类似于 IETF QUIC(也称为 iQUIC)。

 

知道可以使用quic协议访问youbube后,就想在测试app中使用cronet来访问,需要设置enableQuic为true,及调用addQuicHint添加quic使用的域名:

 

public CronetEngineBuilderImpl(Context context) {
   
this.mApplicationContext = context.getApplicationContext();
    enableQuic(
true);             //这里可以设置打开Quic
    enableHttp2(
true);
    enableBrotli(
false);


addQuicHint("m.youtube.com",443,443);   //这里如果不设置,就会走tlsv1.3协议,设置后才会走QUIC协议


 

 

抖音中途有个版本升到QuicVersion:c9f35932 2021-09-15,后来又降到了QuicVersion:68cae75d 2021-08-12,最新19.5版本的quic也还是这个版本。

这里也能通过:

cronetEngine.startNetLogToFile("/data/local/tmp/cronet_log.json",true);

输出日志,然后使用下面地址可以分析这个日志:
 https://netlog-viewer.appspot.com/#events


 

这个其实也可以用在抖音里面截包:

Java.choose("org.chromium.CronetClient", {                    
       onMatch: function (instance) {                             
                    instance.getCronetEngine().startNetLogToFile("/data/local/tmp/cronet_log.json", true);
                    },
       onComplete: function () { }
});


 

到这里后,已经能够调用libsscronet.so走quic协议了,就想回过头来继续访问抖音协议,从头分析了下数据,在抖音启动时候发现了quic数据包:

看到了这个域名api5-core-c-lf.amemv.com。

 

这里提一下,Quic是带竞速模式的,同时也会发送tcp链接,如遇网络或服务器不支持quic/udp,客户端标记quic为broken,后面会尝试重试quic,一旦再次成功采用quic并把broken标记取消:

 

后来发现了这个url,正好跟前面发现的域名一致的:

https://api5-core-c-lf.amemv.com/top/v1?Action=ApplyUploadInner&Region=CN&SpaceName=aweme&UseQuic=false&Version=

 

这里还有个字段UseQuic=false,更加说明这个协议涉及到的相关功能会用到quic(后来发现是视频上传相关的),虽然这个url请求本身不一定会走quic,但是这个域名跟之前截到的quic数据包中的一致,可以拿来测试。

使用app测试访问这个域名,也能正常走quic协议。

 

 

到这里后,就要继续看看这个协议格式及Cronet模块中的quic请求流程了.

调试时候断在sendto能看到quic数据包:

 

这里碰到了知识盲区,发现sendto调用的时候没有设置目标地址参数,一下懵了,这怎么知道数据发哪里去的,也尝试断在sendmsg,发现不是,查资料后发现:

UDP也可以有connect连接

若sendto()函数和sendmsg()函数向已连接的进程中发送消息,则忽略参数dest_addr、addrlen和msg结构体中用于传递地址的成员。此时若参数dest_addr和addrlen不为NULL,则可能会返回错误EISCONN或0;

连接套接字是需要一定开销的,比如需要查找路由表信息。所以,UDP 客户端程序通过 connect 可以获得一定的性能提升。

 

断在connect后,发现了目标IP和端口:

调试分析流程过程中,发现要是有个本地服务器会更方便,看网上有提到node支持quic,打算用node.js架设一个支持quic的服务器。

直接下了个V16+的,结果发现不支持,后来查了下提交log,发现有次提交,去掉了这个quic支持:


quic: remove experimental quic by jasnell · Pull Request #37067 · nodejs/node (github.com)

需要拿15.X的代码编译,带上experimental_quic

git clone  https://github.com/nodejs/node

git checkout c4cab1f408

 

在本地架好TCP UDP服务器后测试,也验证了开启quic请求url确实会同时发送tcp和udp的请求:

 

在调试协议过程中,发现走不通,翻了下代码,找到ngtcp2相关,查了下

ngtcp2/ngtcp2: ngtcp2 project is an effort to implement IETF QUIC protocol (github.com)

ngtcp2项目是实现IETF QUIC的协议,那跟抖音这个还不同,协议帧最小数据长度也不一致,实际用这个测试需要基于抖音版本修改。

现在这个quic还没统一,很多资料不是gquic的,就算是也可能是不同版本的。


头部格式目前就有几种:

enum PacketHeaderFormat : uint8_t {

  IETF_QUIC_LONG_HEADER_PACKET,

  IETF_QUIC_SHORT_HEADER_PACKET,

  GOOGLE_QUIC_PACKET,

};

 

想着还是直接用google版本的,把quiche拖下来了:

https://chromium.googlesource.com/chromium/src

https://quiche.googlesource.com/quiche

 

这里面的协议就是跟抖音quic协议一致的了,这里提下Pulic Flags的bit3:

 

 

0x08: Bit3被置上就表示header中包含8字节的connection id 这个标志位在所有的包中都应该被设置

(上面这句是网上资料,下面是google代码中的注释)

   // 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.

 

看资料说google去掉了win上的编译支持,就打算改下,希望能用VS编译,方便调试,后来越改感觉工作量越多,放弃了,搜索发现了lsquic,还是c实现的,拿代码下来编译了个版本(基本按照文档走,需要先编译boringssl,perl,go都需要安装,perl装好后要手动加入环境变量,之前就卡这里了):

 

litespeedtech/lsquic: LiteSpeed QUIC and HTTP/3 Library (github.com)

lsquic文档:

Tutorial — lsquic 3.0.4 documentation

 

 

开启自带的测试程序中的服务器后,app上发起连接,服务器正常收到了请求,要记得设置证书启动,不然处理CHLO Frame数据会异常:

http_server.exe -r ./ -s 10.0.117.217:443 -c 10.0.117.217,file.crt,private.pem


再链接发现有交互数据了:

但是随后就被客户端断了,发现是证书校验错误,我这个测试证书是自己生成的:

 

之前已经是修改了证书校验的(java层和so的都修改了),能够连接我本地的https服务器,但是连quic服务器不行,就算设置enablePublicKeyPinningBypassForLocalTrustAnchors 属性也不行,说明走quic时候还有一个校验,根据字符串找到下面地方:


在google代码中也找到对应的:

 

根据代码找到修改点:

 

 

这个时候服务器没收到客户端因为校验断开的包了,截包发现认证也过了,不会像之前那样断开了:

到这里后,基本上有了完整的调试环境,后面需要分析quic协议算法就可以很方便的带源码调试了。

 

目前看起来抖音并没有大规模使用quic,从测试url看是上传视频可能会用到,但是我的测试环境也没发现走quic,最近的几个版本看见有协议中增加了下面信息:

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相关的,不知道是不是在收集用户环境信息,评估用户网络环境,然后再决定是否部署quic。

 

就算后面一些功能http协议都转向quic,那也有很多切入点:

1.  对于走Cronet的,那还是可以在JAVA层对应jni函数及Cronet回调函数拿到请求数据。

2.  Cronet最终还是走native层的,在so层收发数据对应函数也可以拿到数据。

3.  用上面提到的cronetEngine.startNetLogToFile直接写网络日志(这个实际测试发现不全,还不确定是不是参数设置问题)。

4.  直接在quic实现部分的收发函数拿数据。

5.  了解quic协议过程后,还可以把各个通讯层级加密key记录下来,直接解析udp截包数据。

6.  可以像上面测试那样,搭建本地quic服务器,把请求转到本地服务器来。

 



【看雪培训】《Adroid高级研修班》2022年夏季班招生中!

收藏
点赞1
打赏
分享
打赏 + 80.00雪花
打赏次数 1 雪花 + 80.00
 
赞赏  Editor   +80.00 2022/03/13 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (6)
雪    币: 2690
活跃值: 活跃值 (1850)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2022-2-21 09:06
2
0
很好的文章。支持下
雪    币: 4521
活跃值: 活跃值 (2136)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
LeadroyaL 活跃值 1 2022-2-21 09:43
3
0
学习了
雪    币: 1282
活跃值: 活跃值 (1330)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
StriveMario 活跃值 2022-2-21 09:58
4
0
很强
雪    币: 3828
活跃值: 活跃值 (2630)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
mb_aoooaosd 活跃值 2022-2-21 11:38
5
0
哥 学习了
雪    币: 46
活跃值: 活跃值 (409)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
hrpirip 活跃值 1 2022-2-21 17:40
6
0
很好的文章。支持下
雪    币: 266
活跃值: 活跃值 (70)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
奋斗小菜鸟 活跃值 2022-2-25 11:18
7
0
大佬很稳
游客
登录 | 注册 方可回帖
返回