数据抓取实战(一)
概述
在web开发,测试以及性能优化,数据抓包时,常会借助一此工具,帮助我们更好地理解和控制网络通信过程。
本文提供两个工具FiddlerCore
和TitaniumProxy
的实现方式以及示例代码,分享几个数据抓包的实际案例,供大家参考。
FiddlerCore
简介
FiddlerCore是一个强大的库,允许你使用C#拦截、修改HTTP(Https)和WebSocket流量。
它主要有以下一些特点和功能:
-
网络抓包:可以捕获浏览器与服务器之间的 HTTP/HTTPS 请求和响应。
-
请求分析:详细查看请求的各种信息,如请求头、请求体、响应头、响应体等。
-
模拟请求:能够手动创建和发送自定义请求。
-
调试会话:方便对网络交互进行故障排查和调试。
-
篡改请求和响应:在测试和开发中可临时修改数据。
Fiddlercore拦截Https的原理是自己创建一个Https的证书,重新对网站的链接数据进行加密传输,所以,我们要通过代码创建一个https证书给Fiddlercore。
https的处理过程需要一个自签名证书:
创建Https证书有两种方式,一种是通过MakeCert.exe ,另一种是通过 CertMaker.dll及BCMakeCert.dll来创建。
在官网说明中,明确的说了两点:
1、MakeCert.exe使用Windows API生成存储在用户的\Personal\Certificates存储中的证书。这些证书与iOS设备不兼容,后者需要证书中未由MakeCert.exe设置的特定字段。
2、CertMaker.dll使用BouncyCastle C#库(BCMakeCert.dll)从头开始生成新证书。这些证书仅存储在内存中,并与iOS设备兼容。
我们 在Fiddlercore编程的时候,通常采用dll方式创建证书,创建后的证书放在内存中。所以,仅当程序第一次启动的时候,我们才需要创建证书,以后再启动程序,就不需要创建证书了,这个过程持续到电脑重启为止。
FiddlerCoreStartupSettingsBuilder
的配置方法如下:
常用设置包括:
·ListenOnPort(int):指定FiddlerCore将侦听的端口。若设置为0,则表示随机分配端口。
·AllowRemoteClients():允许FiddlerCore接受来自当前机器外部的请求,例如远程计算机和设备。在允许远程客户端连接到FiddlerCore时需谨慎,因为攻击者可能通过该FiddlerCore实例代理其流量,从而绕过IPSec流量规则和内部网防火墙。系统代理设置方面,有多种可能需要修改以适应合适代理设置的系统和连接类型,在此只处理最常见场景。对于更高级的代理配置,请参阅注册为系统代理文章。
RegisterAsSystemProxy()可修改本地局域网连接的代理设置,使其指向FiddlerCore在本地主机上监听的端口。
MonitorAllConnections()可修改所有系统连接的代理设置,使其指向FiddlerCore在本地主机上侦听的端口。
CaptureFTP()可修改系统中与ftp相关联的代理设置,使其指向FiddlerCore在本地主机上侦听的端口。HookUsingPACFile()可修改当前使用PAC文件配置方式进行网络连接时所使用到的代理设置。同时,FiddlerCore还提供了一个PAC文件用于调整网络连接,在服务默认PAC文件时可以通过更改“fiddler.proxy.pacfile”进行配置,默认情况下它包含了FindProxyForURL(url, host)函数体等内容:"
FiddlerCore
是一个 .NET 类库,没有界面,您可以将其集成到您的 .NET 应用程序中。它提供 .NETCore、.NET Framework版本,官方收费,我提供了一个免费版本5.0.2,直接在项目中使用。
以下以.netcore版本为例,FiddlerCore5.0.2
版本 项目中引用FiddlerCore
库
安装证书
//加v runsoft1024提供源码
static bool InstallCertificate()
{
FiddlerApplication.Log.LogString($"安装证书,为了监听https请求");
if (!CertMaker.rootCertExists())
{
if (!CertMaker.createRootCert())
return false;
if (!CertMaker.trustRootCert())
return false;
}
return true;
}
卸载证书
static bool UninstallCertificate()
{
if (CertMaker.rootCertExists())
{
if (!CertMaker.removeFiddlerGeneratedCerts(true))
return false;
}
return true;
}
创建代理
static void StartupFiddler()
{
// Attach to events of interest:
FiddlerApplication.AfterSessionComplete += session => Console.WriteLine(session.fullUrl);
//Build startup settings:
var settings = new FiddlerCoreStartupSettingsBuilder()
.ListenOnPort(9898)
.RegisterAsSystemProxy()
.DecryptSSL()
.OptimizeThreadPool() //启用多线程
.AllowRemoteClients() //websocket open
.Build();
CONFIG.EnableIPv6 = true; //websocket open
CONFIG.IgnoreServerCertErrors = true;
// Start:
FiddlerApplication.Startup(settings);
FiddlerApplication.Log.LogString($"Created endpoint listening on port {CONFIG.ListenPort}");
}
关闭代理
static void UninstallFiddler()
{
if (FiddlerApplication.IsStarted())
{
FiddlerApplication.Shutdown();
}
}
处理事件
//拦截请求与响应事件
static void AttachListening()
{
//FiddlerApplication.OnNotification += (o, nea) => Console.WriteLine($"** NotifyUser: {nea.NotifyString}");
FiddlerApplication.Log.OnLogString += (o, lea) => Console.WriteLine($"** LogString: {lea.LogString}");
FiddlerApplication.OnWebSocketMessage += FiddlerApplication_OnWebSocketMessage;
FiddlerApplication.BeforeRequest += FiddlerApplication_BeforeRequest;
FiddlerApplication.BeforeResponse += FiddlerApplication_BeforeResponse;
}
websocket处理
FiddlerApplication.OnWebSocketMessage += FiddlerApplication_OnWebSocketMessage;
static void FiddlerApplication_OnWebSocketMessage(object? sender, WebSocketMessageEventArgs e)
{
//编写业务逻辑
var payload = e.oWSM.PayloadAsBytes();
var txt = Utilities.ByteArrayToString(payload);
var s = e.oWSM.PayloadAsString();
e.oWSM.SetPayload(payload);
}
http请求事件
FiddlerApplication.BeforeRequest += FiddlerApplication_BeforeRequest;
static void FiddlerApplication_BeforeRequest(Session oSession)
{
//为了启用响应篡改,缓冲模式必须被启用;
//这允许FiddlerCore允许修改BeforeResponse处理程序中的响应,而不是流式传输响应进来时对客户端的响应。
oSession.bBufferResponse = false;
//只监听目标网站
if(!oSession.fullUrl.Contains("rscode.cn"))
{
return;
}
// 如果您希望FiddlerCore通过以下方式自动进行身份验证,请设置此属性
// answering Digest/Negotiate/NTLM/Kerberos challenges itself
//session["X-AutoAuth"] = "(default)";
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(String.Format("{0} {1}", oSession.RequestMethod, oSession.fullUrl));
//header
Console.ForegroundColor = ConsoleColor.White;
for (int i = 0; i < oSession.RequestHeaders.Count(); i++)
{
var s=String.Format("{0,-20}{1}", oSession.RequestHeaders[i].Name, oSession.RequestHeaders[i].Value);
FiddlerApplication.Log.LogString($" {s}");
}
//body
if(oSession.RequestBody!=null)
{
// var s= Encoding.UTF8.GetString(oSession.RequestBody);
var s=oSession.GetRequestBodyAsString();
FiddlerApplication.Log.LogString($" {s}");
}
}
http响应事件
FiddlerApplication.BeforeResponse += FiddlerApplication_BeforeResponse;
/// <summary>拦截请求返回Response信息</summary>
static void FiddlerApplication_BeforeResponse(Session oSession)
{
//只监听目标网站
if (!oSession.oRequest.host.Contains("rscode.cn"))
{
return;
}
if(oSession.ResponseHeaders.Count()>0)
{
Console.ForegroundColor = ConsoleColor.White;
for (int i = 0; i < oSession.ResponseHeaders.Count(); i++)
{
var s=String.Format("{0,-20}{1}", oSession.ResponseHeaders[i].Name, oSession.ResponseHeaders[i].Value);
FiddlerApplication.Log.LogString($" {s}");
}
}
if (oSession.ResponseBody != null)
{
Console.ForegroundColor = ConsoleColor.White;
var s = oSession.GetResponseBodyAsString();
FiddlerApplication.Log.LogString($" {s}");
}
}
运行Fiddler
void FiddlerStart()
{
FiddlerApplication.Log.LogString($"启动程序...");
//安装证书
InstallCertificate();
//拦截http请求信息事件
AttachListening();
//启动Fiddler
StartupFiddler();
Console.WriteLine("按任意键结束本地代理监听..." + Environment.NewLine);
Console.Read();
//卸载证书
UninstallCertificate();
//关闭Fiddler
UninstallFiddler();
}
以上代码展示了从创建证书,启动fiddler,监听请求与响应,关闭fiddler的全过程,不明白还可以查阅FiddlerCore官方文档
TitaniumProxy
简介
TitaniumProxy 是一个跨平台、轻量级、低内存、高性能的HTTP(S)代理服务器,开发语言为C#,常用于抓包、模拟低带宽、修改请求等场景。
功能特性
- 支持HTTP(S)与HTTP 1.1的大部分功能
- 支持redirect/block/update 请求
- 支持更新Response
- 支持HTTP承载的WebSocket
- Support mutual SSL authentication
- 完全异步的代理
- 支持代理授权与自动代理检测
- Kerberos/NTLM authentication over HTTP protocols for windows domain
实现步骤
以下是使用Titanium.Web.Proxy
实现HTTP代理服务器的基本步骤:
- 安装
Titanium.Web.Proxy
库; - 创建代理服务器,并设置基本配置;
- 响应事件处理,可以根据需要进行自定义;
- 添加终结点;
- 启动代理服务器,并设置为系统代理。
如果你在使用Titanium.Web.Proxy过程中遇到问题,可以参考相关文档或社区帖子,也可以尝试搜索其他解决方案。
创建代理
using System.Net;
using Titanium.Web.Proxy;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
var proxyServer = new ProxyServer();
创建证书
// 此代理使用的本地信任根证书
//proxyServer.CertificateManager.TrustRootCertificate = true;
//proxyServer.CertificateManager.TrustRootCertificate(true);
//使用BouncyCastle库来生成证书
proxyServer.CertificateManager.CertificateEngine = Titanium.Web.Proxy.Network.CertificateEngine.DefaultWindows;
proxyServer.CertificateManager.EnsureRootCertificate();
//在Mono之下,只有BouncyCastle将得到支持
//proxyServer.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle;
proxyServer.CertificateManager.SaveFakeCertificates = true;
proxyServer.CertificateManager.RootCertificate = proxyServer.CertificateManager.LoadRootCertificate();
if (proxyServer.CertificateManager.RootCertificate == null)
{
Console.WriteLine("正在进行证书安装,需要安装证书才可进行https解密,若有提示请确定");
proxyServer.CertificateManager.CreateRootCertificate();
}
添加端点
显式端点
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
// 在所有https请求上使用自颁发的通用证书
// 通过不为每个启用https的域创建证书来优化性能
// 当代理客户端不需要证书信任时非常有用
//GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
// 显式端点是客户端知道代理存在的地方,因此,客户端以代理友好的方式发送请求
proxyServer.AddEndPoint(explicitEndPoint);
透明端点
透明endpoint 对于反向代理很有用(客户端不知道代理的存在); 透明endpoint 通常需要一个网络路由器端口来转发HTTP(S)包或DNS发送数据到此endpoint
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
// 客户端禁用SNI时要使用的通用证书主机名
GenericCertificateName = "google.com"
};
proxyServer.AddEndPoint(transparentEndPoint);
处理事件
添加事件
proxyServer.ServerCertificateValidationCallback += ProxyServer_ServerCertificateValidationCallback;
proxyServer.BeforeRequest += ProxyServer_BeforeRequest;
proxyServer.BeforeResponse += ProxyServer_BeforeResponse;
explicitEndPoint.BeforeTunnelConnectRequest += ExplicitEndPoint_BeforeTunnelConnectRequest;
explicitEndPoint.BeforeTunnelConnectResponse += ExplicitEndPoint_BeforeTunnelConnectResponse;
proxyServer.AddEndPoint(explicitEndPoint);
处理事件
async Task<bool> OnBeforeTunnelConnect(string hostname)
{
if (hostname.Contains("rscode.cn"))
{
//排除rscode.cn被解密,而是通过安全的TCP隧道中继
return await Task.FromResult(true);
}
else
{
return await Task.FromResult(false);
}
}
async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
await Task.Run(() =>
{
string hostname = e.HttpClient.Request.RequestUri.Host;
if (hostname.Contains("rscode.cn"))
{
// 排除您不想代理的Https地址
// 对于使用证书固定的客户端很有用
// 例如本例 dropbox.com
e.DecryptSsl = false;
}
});
}
async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.HttpClient.Request.Url);
// read request headers
var requestHeaders = e.HttpClient.Request.Headers;
var method = e.HttpClient.Request.Method.ToUpper();
if ((method == "POST" || method == "PUT" || method == "PATCH"))
{
// Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody();
e.SetRequestBody(bodyBytes);
// Get/Set request body as string
string bodyString = await e.GetRequestBodyAsString();
e.SetRequestBodyString(bodyString);
// store request
// 这样你就能从响应处理器中找到它
e.UserData = e.HttpClient.Request;
}
// 取消带有自定义HTML内容的请求
// Filter URL
if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("rscode.cn"))
{
e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
// Redirect example
if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
e.Redirect("https://www.paypal.com");
}
}
// Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
// read response headers
var responseHeaders = e.HttpClient.Response.Headers;
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.HttpClient.Request.Method == "GET" || e.HttpClient.Request.Method == "POST")
{
if (e.HttpClient.Response.StatusCode == 200)
{
if (e.HttpClient.Response.ContentType != null && e.HttpClient.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = await e.GetResponseBody();
e.SetResponseBody(bodyBytes);
string body = await e.GetResponseBodyAsString();
e.SetResponseBodyString(body);
}
}
}
if (e.UserData != null)
{
// 从存储在RequestHandler中的UserData属性的访问请求
var request = (Request)e.UserData;
}
}
// 允许重写默认的证书验证逻辑
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
// 根据证书错误,设置IsValid为真/假
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
return Task.CompletedTask;
}
// 允许在相互身份验证期间重写默认客户端证书选择逻辑
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
// set e.clientCertificate to override
return Task.CompletedTask;
}
websocket处理
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true);
//收到CONNECT请求时触发
explicitEndPoint.BeforeTunnelConnectRequest += ExplicitEndPoint_BeforeTunnelConnectRequest;
explicitEndPoint.BeforeTunnelConnectResponse += ExplicitEndPoint_BeforeTunnelConnectResponse;
private async Task ExplicitEndPoint_BeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.HttpClient.Request.RequestUri.Host;
if (!barrageWsHostNames.Contains(hostname))
{
e.DecryptSsl = false;
}
}
private Task ExplicitEndPoint_BeforeTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.HttpClient.Request.RequestUri.Host;
if (!barrageWsHostNames.Contains(hostname))
{
e.DecryptSsl = false;
}
Console.WriteLine($"ExplicitEndPoint_BeforeTunnelConnectResponse url={hostname}");
if (e.UserData != null)
{
}
return Task.CompletedTask;
}
private async void WebSocket_DataReceived(object sender, DataEventArgs e)
{
var args = (SessionEventArgs)sender;
string hostname = args.HttpClient.Request.RequestUri.Host;
var processid = args.HttpClient.ProcessId.Value;
var frames = args.WebSocketDecoderReceive.Decode(e.Buffer, e.Offset, e.Count).ToList();
foreach (var frame in frames)
{
base.SendWebSocketData(new WsMessageEventArgs()
{
ProcessID = processid,
HostName = hostname,
Payload = frame.Data.ToArray(),
ProcessName = base.GetProcessName(processid)
});
}
}
设为系统代理
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
// 只有显式代理可以设置为系统代理!
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
打开代理
Console.WriteLine("打开代理");
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
关闭代理
Console.WriteLine("关闭代理");
proxyServer.ServerCertificateValidationCallback -= ProxyServer_ServerCertificateValidationCallback;
proxyServer.BeforeRequest -= ProxyServer_BeforeRequest;
proxyServer.BeforeResponse -= ProxyServer_BeforeResponse;
explicitEndPoint.BeforeTunnelConnectRequest -= ExplicitEndPoint_BeforeTunnelConnectRequest;
explicitEndPoint.BeforeTunnelConnectResponse -= ExplicitEndPoint_BeforeTunnelConnectResponse;
proxyServer.Stop();
proxyServer.Dispose();
总结
TitaniumProxy 和 FiddlerCore 是两个不同的 HTTP(S) 代理服务器,它们有以下一些区别:
- 设计和架构:TitaniumProxy 是一个重新实现的 FiddlerCore,具有更合理的框架设计和易于扩展的特点。FiddlerCore 可能在某些方面存在设计上的不足,如 API 命名不规范、属性/字段混用等。
- 性能和资源使用:TitaniumProxy 被宣传为具有低内存和高性能的特点。具体的性能差异可能因实际使用情况而异,但在一些情况下,TitaniumProxy 可能更适合对性能要求较高的场景。
- 功能和特性:两者的功能可能会有所不同。具体取决于它们的版本和配置。一些可能的差异包括对协议的支持、代理规则的灵活性、数据过滤和修改的能力等。
- 社区和支持:FiddlerCore 是由 Telerik 开发的,可能有更广泛的社区和文档支持。TitaniumProxy 可能是一个相对较新的项目,社区和支持可能相对较小。
在选择使用哪个代理服务器时,你可以考虑以下因素:
- 具体需求:根据你的具体需求确定所需的功能和特性。
- 性能要求:如果对性能有较高要求,可以评估两者在实际使用中的性能表现。
- 开发和集成:考虑与你的开发环境和项目的集成难易程度。
- 社区和支持:一个活跃的社区和良好的文档支持可以帮助你解决问题和获取更多资源。
记得无论用哪种方式创建代理,程序结束时都要关闭代理,否则电脑为断网;解决断网的方法就是重新开应用,再正常关闭应用即可。
下一篇将用两个实际案例:美元汇率监控和直播间弹幕抓取项目,结合FiddlerCore和TitaniumProxy写代码。