http_https分析
http/https网络协议分析#
http与https的区别#
http#
http是一种应用层协议位于传输层协议tcp之上,其不会对数据进行任何的加密,只是按照一定的格式对数据进行封装(http报文格式),在数据封装完之后直接使用tcp的socket进行数据的发送。传输层tcp是一种长连接,http基于tcp实现短连接,所以每一次http会话都需要经过tcp三次握手--->http数据传输---->tcp四次挥手(为了提高网络传输效率,默认开启keep-alive支持了http的长连接)。
https#
https是http + ssl,其通过ssl加密套件实现了数据在传输过程中加密。SSL也是通过tcp的socket进行数据的传输,只不过在传输数据前会先对数据进行加解密。https数据传输过程为tcp三次握手--->ssl握手--->http数据传输--->tcp四次挥手。
libcurl网络库分析#
libcurl(http)#
对于http连接而言,libcurl通过http协议的格式进行封包后调用Curl_send_plain函数发送http数据包,Curl_send_plain函数会直接调用tcp的socket函数send。
接着调用Curl_recv_plain获取http返回的数据包,Curl_recv_plain会调用tcp的socket函数recv。
libcurl会解析返回的http数据包,并判断Content-Length:长度的值,如果判断此次http请求返回的数据还没有读取完会继续调用Curl_recv_plain从socket中读取数据。直到读取到的http报文主体的长度等于Content-Length才返回。
libcurl(https)#
libcurl利用openssl实现https,libcurl通过http协议的格式进行封包后调用ossl_send函数发送http数据包,ossl_send会使用openssl的接口SSL_Write发送http数据(在openssl中,SSL_Write会先通过会话密钥对http数据进行加密,然后调用socket的send函数进行发送)。
接着调用ossl_recv函数获取http返回的数据包,ossl_recv函数调用openssl的接口SSL_Read(SSL_Read会调用socket的recv函数并通过会话密钥对数据进行解密)。因此到达ossl_recv后的数据是已经解密后的。
接下来处理和libcurl对http的处理一样,libcurl会解析返回的http数据包,并判断Content-Length:长度的值,如果此次http请求返回的数据还未读取完就继续调用SSL_Read。
//开启ssl验证
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, TRUE);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, TRUE);
//指定根证书
curl_easy_setopt(curl, CURLOPT_CAINFO, pCAFile);
//指定客户端证书
curl_easy_setopt(curl, CURLOPT_SSLCERT, pCertFile);
libcurl通过开启ssl验证并指定根证书来与https服务器进行ssl握手,一般的web服务器只要求单项认证(客户端验证服务端),如果需要进行双向认证(服务端也验证客户端)就需要指定客户端的证书。
cpp-lib网络库分析#
cpp-lib通过调用SSLSocketStream::Write发送http数据包,此函数会调用openssl的SSL_Write。
同样cpp-lib通过调用SSLSocketStream::Read--->SSL_Read获取http请求返回的reponse数据包。
libcef的net模块网络库分析#
libcef基于开源的chromium,使用的是chromium的net模块进行http连接。分析chromium的net模块源码发现http/https最后会调用HttpStreamParser::DoLoop进行http请求数据的发送和返回数据的读取。
以DoSendHeader为例,其最后会调用stream_socket_->Write发送http数据包
对于http连接而言stream_socket_就是SocketStream,其会调用send和recv进行http数据的传输。
对于https连接而言stream_socket_就是SSLSocketStream,其会调用SSL_Write和SSL_Read进行http数据的传输。
其中获取http请求返回的reponse数据时需要注意,DoReadHeaders可能会读取部分boby数据,所以在调用DoReadBoby时需要把之前已经读取的boby数据给整合,最后boby数据全部放在user_read_buf_中。
https服务端#
https服务端主要涉及到SSL的初始化,SSL握手,使用SSL_Write/SSL_Read与客户端进行数据通讯
SSL初始化#
SSL初始化会加载必要的库,设置服务端证书(包含公钥)以及私钥,验证证书中的公钥与私钥是否匹配。
//加载服务端证书(包含公钥)
if (SSL_CTX_use_certificate_file(ctx, SERVER_CERT_FILE, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(0);
}
//加载服务端私钥
if (SSL_CTX_use_PrivateKey_file(ctx, SERVER_KEY_FILE, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(0);
}
// 判定服务端证书(包含公钥)与私钥是否匹配
if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stderr);
exit(0);
}
默认是SSL_VERIFY_NONE进行单项认证,即客户端验证服务端的真伪。可以通过设置SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT 并调用SSL_get_verify_result来实现双向认证(执行SSL_get_verify_result才进行真正的验证过程),即服务端也需要验证客户端的真伪。
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, TRUE);
SSL_get_verify_result(ssl);
SSL握手#
当服务端通过socket监听到有https客户端连接时accept函数就会返回,这时候已经完成了TCP的三次握手。然后https服务端需要进行SSL握手,通过调用SSL_accept与https客户端进行SSL握手的过程。
SSL_Write/SSL_Read#
服务端使用openssl的接口SSL_Write和SSL_Read进行数据的传输,函数会对http的数据进行加密后在调用send/recv进行传输
http/https抓包#
软件不支持设置代理如何抓包#
fiddler是通过设置系统来实现http/https的抓包的,如何程序使用WinInet库(默认使用系统代理)或者使用的第三方网络库默认设置使用系统代理则可以通过fiddler来进行抓包。当然如果第三方程序可以设置代理,然后将代理设置为fiddler也是可以进行抓包的。
但是如果第三方程序不支持设置代理就无法使用fiddler进行抓包了,这时候可以使用Proxifier或者SocksCap64强制为程序设置代理。SocksCap64的原理是利用API hook,通过hook socket的api强制进行tcp流量的转发(不稳定),Proxifier则是利用windows的接口通过设置LSP模块过滤和转发tcp流量(稳定)。通过Proxifier设置代理服务器为fiddler监听的地址和端口即可,Proxifier还支持配置规则来代理指定应用程序。
例如在使用libcurl库时,没有调用curl_easy_setopt(curl, CURLOPT_PROXY, "127.0.0.1:8888");
设置代理,也没有通过读取windows注册表设置代理为系统代理,这样fiddler是无法抓到这个应用的http/https数据的。通过proxifier+fiddler就可以强制为其设置代理并捕获http数据流量了。
软件默认不使用windows系统预制的根证书#
SSL握手过程中如果是只是单项认证则需要服务端将证书发送给客户端,客户端会利用收到的服务端证书与信任的根证书进行验证。对于一般的浏览器应用其会默认信任windows系统中预制的CA根证书,而fiddler就是通过将自己伪造的证书设置在windows预制的信任根证书列表中来实现中间人抓包。但是有的应用程序并不使用windows预制的根证书,而是使用自身携带的一个信任证书列表,这样自然也不会信任fiddler伪造的根证书。例如libcurl就默认不使用windows预制的根证书,需要调用curl_easy_setopt(curl, CURLOPT_CAINFO, "E:\\ca.crt");
设置信任的根证书列表。例如使用libcurl进行https连接,设置代理为fiddler并通过fiddler进行抓包。SSL握手会返回证书验证错误。
这个原因就是因为fiddler抓包时会返回自己伪造的服务端证书给libcurl客户端,而libcurl客户端只信任自己设置的根证书列表(E:\ca.crt),并不信任windows系统中的根证书列表(包含fiddler伪造的根证书),所以libcurl客户端获取到fiddler伪造的服务端证书后会验证失败。
这个时候如果需要对此应用程序进行http/https抓包的话就需要分析其使用的具体框架,通过注入和hook禁用掉SSL验证,或者通过将fiddler伪造的根证书加入到其内存信任的根证书列表中。例如libcurl是通过设置CURLOPT_SSL_VERIFYPEER和 CURLOPT_SSL_VERIFYHOST为TRUE开启SSL验证的,要想禁用SSL代理就需要设置verfpeer和verifyhost为FALSE。
例如这里先通过将fiddler伪造的CA根证书从计算机中导出
然后再libcurl源代码中设置代理为fiddler,并将导出的fiddler的根证书添加到程序信任列表中。
这样fiddler返回自己伪造的证书libcurl客户端就可以验证通过了(因为libcurl客户端信任的根证书列表中已经包含了fiddler的根证书),fiddler可以成功抓包。
双向认证的抓包#
如果https服务器要求进行双向认证,需要客户端提供客户端证书。需要在fiddler默认不发送客户端证书,首先获取到客户端证书后需要通过fiddler脚本设置。通过charles也可以设置客户端证书,成功抓取取到https包
fiddler设置代理#
http/https代理和sock4/sock5代理的区别是前者在应用层,后者是在传输层。在通过fiddler设置了http代理后可以继续设置fiddler的下一级代理为sock代理服务器(vpn),这样就可以抓取一些需要使用vpn的应用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2021-11-14 Duilib消息处理