From 22581018a32ec1552b4bb76fd8ae876c2a9b5aeb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=B0=8F=E7=99=BD=E7=8C=BF?= <1083092844@qq.com>
Date: Thu, 17 Aug 2023 20:36:57 +0800
Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=94=AF=E6=8C=81=E7=9B=91?=
=?UTF-8?q?=E5=90=AC=E8=BD=AE=E8=AF=A2=E5=BC=B9=E5=B9=95;=E5=AE=8C?=
=?UTF-8?q?=E5=96=84=E7=95=8C=E9=9D=A2=E6=97=A0=E6=93=8D=E4=BD=9C=E6=A3=80?=
=?UTF-8?q?=E6=B5=8B=E7=BB=95=E8=BF=87=E9=80=BB=E8=BE=91;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Proxy/EventArgs/HttpResponseEventArgs.cs | 2 +-
.../Proxy/EventArgs/WsMessageEventArgs.cs | 5 +
BarrageGrab/Proxy/ISystemProxy.cs | 11 +-
BarrageGrab/Proxy/SystemProxy.cs | 27 +-
BarrageGrab/Proxy/TitaniumProxy.cs | 261 +++++++++++++-----
5 files changed, 220 insertions(+), 86 deletions(-)
diff --git a/BarrageGrab/Proxy/EventArgs/HttpResponseEventArgs.cs b/BarrageGrab/Proxy/EventArgs/HttpResponseEventArgs.cs
index 8f11a6e..2df1ada 100644
--- a/BarrageGrab/Proxy/EventArgs/HttpResponseEventArgs.cs
+++ b/BarrageGrab/Proxy/EventArgs/HttpResponseEventArgs.cs
@@ -30,6 +30,6 @@ namespace BarrageGrab.Proxy.ProxyEventArgs
///
/// 域名
///
- public string HostName { get; set; }
+ public string HostName { get; set; }
}
}
diff --git a/BarrageGrab/Proxy/EventArgs/WsMessageEventArgs.cs b/BarrageGrab/Proxy/EventArgs/WsMessageEventArgs.cs
index 2d24c41..a41f195 100644
--- a/BarrageGrab/Proxy/EventArgs/WsMessageEventArgs.cs
+++ b/BarrageGrab/Proxy/EventArgs/WsMessageEventArgs.cs
@@ -27,5 +27,10 @@ namespace BarrageGrab.Proxy.ProxyEventArgs
/// 域名
///
public string HostName { get; set; }
+
+ ///
+ /// 是否需要解压缩
+ ///
+ public bool NeedDecompress { get; set; } = true;
}
}
diff --git a/BarrageGrab/Proxy/ISystemProxy.cs b/BarrageGrab/Proxy/ISystemProxy.cs
index e65f898..51fa5ff 100644
--- a/BarrageGrab/Proxy/ISystemProxy.cs
+++ b/BarrageGrab/Proxy/ISystemProxy.cs
@@ -9,18 +9,11 @@ namespace BarrageGrab.Proxy
{
event EventHandler OnWebSocketData;
- event EventHandler OnResponse;
-
- ///
- /// 域名过滤器
- ///
- Func HostNameFilter { get; set; }
+ event EventHandler OnFetchResponse;
///
/// 开始监听
///
void Start();
- }
-
-
+ }
}
\ No newline at end of file
diff --git a/BarrageGrab/Proxy/SystemProxy.cs b/BarrageGrab/Proxy/SystemProxy.cs
index 3bdd2e8..a734d01 100644
--- a/BarrageGrab/Proxy/SystemProxy.cs
+++ b/BarrageGrab/Proxy/SystemProxy.cs
@@ -14,11 +14,6 @@ namespace BarrageGrab.Proxy
{
internal abstract class SystemProxy : ISystemProxy
{
- ///
- /// 域名过滤器
- ///
- public Func HostNameFilter { get; set; }
-
///
/// 接收到websocket消息事件
///
@@ -27,7 +22,7 @@ namespace BarrageGrab.Proxy
///
/// 接收到http响应事件
///
- public event EventHandler OnResponse;
+ public event EventHandler OnFetchResponse;
///
/// 代理端口
@@ -45,13 +40,17 @@ namespace BarrageGrab.Proxy
///
///
///
- protected bool CheckHost(string host)
- {
- var result = true;
+ protected virtual bool CheckHost(string host)
+ {
+ host = host.Trim().ToLower();
- if (HostNameFilter != null) return HostNameFilter(host);
+ if (!Appsetting.Current.FilterHostName) return true;
+
+ if (host.StartsWith("webcast")) return true;
- return result;
+ if (Appsetting.Current.HostNameFilter.Any(a => a.Trim().ToLower() == host)) return true;
+
+ return false;
}
///
@@ -64,12 +63,12 @@ namespace BarrageGrab.Proxy
}
///
- /// 触发http响应事件
+ /// 触发弹幕http弹幕事件
///
///
- protected void FireOnResponse(HttpResponseEventArgs args)
+ protected void FireOnFetchResponse(HttpResponseEventArgs args)
{
- OnResponse?.Invoke(this, args);
+ OnFetchResponse?.Invoke(this, args);
}
///
diff --git a/BarrageGrab/Proxy/TitaniumProxy.cs b/BarrageGrab/Proxy/TitaniumProxy.cs
index 689bb65..7231e90 100644
--- a/BarrageGrab/Proxy/TitaniumProxy.cs
+++ b/BarrageGrab/Proxy/TitaniumProxy.cs
@@ -4,14 +4,11 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
-using System.Runtime.InteropServices;
-using System.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using BarrageGrab.Modles;
using BarrageGrab.Proxy.ProxyEventArgs;
-using Microsoft.Win32;
+using ColorConsole;
using Titanium.Web.Proxy;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Http;
@@ -25,6 +22,14 @@ namespace BarrageGrab.Proxy
ProxyServer proxyServer = null;
ExplicitProxyEndPoint explicitEndPoint = null;
+ const string SCRIPT_HOST = "lf-cdn-tos.bytescm.com";
+ const string LIVE_HOST = "live.douyin.com";
+ const string DOUYIN_HOST = "www.douyin.com";
+ const string USER_INFO_PATH = "/webcast/user/me/";
+ const string BARRAGE_POOL_PATH = "/webcast/im/fetch";
+ static ConsoleWriter console = new ConsoleWriter();
+
+
public TitaniumProxy()
{
//注册系统代理
@@ -43,95 +48,220 @@ namespace BarrageGrab.Proxy
proxyServer.CertificateManager.CreateRootCertificate();
}
- proxyServer.ServerCertificateValidationCallback += ProxyServer_ServerCertificateValidationCallback; ;
+ proxyServer.ServerCertificateValidationCallback += ProxyServer_ServerCertificateValidationCallback;
proxyServer.BeforeResponse += ProxyServer_BeforeResponse;
- proxyServer.AfterResponse += ProxyServer_AfterResponse;
+ //proxyServer.AfterResponse += ProxyServer_AfterResponse;
+
explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, ProxyPort, true);
- explicitEndPoint.BeforeTunnelConnectRequest += ExplicitEndPoint_BeforeTunnelConnectRequest;
+ explicitEndPoint.BeforeTunnelConnectRequest += ExplicitEndPoint_BeforeTunnelConnectRequest;
proxyServer.AddEndPoint(explicitEndPoint);
}
- private Task ProxyServer_AfterResponse(object sender, SessionEventArgs e)
+
+ private async Task ProxyServer_BeforeResponse(object sender, SessionEventArgs e)
{
+ string uri = e.HttpClient.Request.RequestUri.ToString();
string hostname = e.HttpClient.Request.RequestUri.Host;
var processid = e.HttpClient.ProcessId.Value;
+ var processName = base.GetProcessName(processid);
+ var contentType = e.HttpClient.Response.ContentType ?? "";
- this.FireOnResponse(new HttpResponseEventArgs()
+ //处理弹幕
+ await HookBarrage(e);
+
+ //处理JS注入
+ await HookPageAsync(e);
+
+ //处理脚本拦截修改
+ await HookScriptAsync(e);
+ }
+
+ private async Task HookBarrage(SessionEventArgs e)
+ {
+ string uri = e.HttpClient.Request.RequestUri.ToString();
+ string hostname = e.HttpClient.Request.RequestUri.Host;
+ var processid = e.HttpClient.ProcessId.Value;
+ var processName = base.GetProcessName(processid);
+ var contentType = e.HttpClient.Response.ContentType ?? "";
+
+ //ws 方式
+ if (
+ e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Websocket &&
+ hostname.StartsWith("webcast")
+ )
{
- ProcessID = processid,
- HostName = hostname,
- ProcessName = base.GetProcessName(processid),
- HttpClient = e.HttpClient
- });
-
- return Task.CompletedTask;
+ e.DataReceived += WebSocket_DataReceived;
+ }
+ //轮询方式(当抖音ws连接断开后,客户端也会降级使用轮询模式获取弹幕)
+ if (uri.Contains(BARRAGE_POOL_PATH) && contentType.Contains("application/protobuffer"))
+ {
+ var payload = await e.GetResponseBody();
+ base.FireOnFetchResponse(new HttpResponseEventArgs()
+ {
+ HttpClient = e.HttpClient,
+ ProcessID = processid,
+ HostName = hostname,
+ ProcessName = base.GetProcessName(processid),
+ });
+ }
}
+ ///
+ /// 获取配置的注入的js
+ ///
+ ///
+ ///
+ private string GetInjectScript(string name)
+ {
+ //获取exe所在目录路径
+ var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Scripts", "inject", name + ".js");
+ if (File.Exists(path))
+ {
+ return File.ReadAllText(path);
+ }
+ return null;
+ }
+
+ private async Task HookPageAsync(SessionEventArgs e)
+ {
+ string uri = e.HttpClient.Request.RequestUri.ToString();
+ string hostname = e.HttpClient.Request.RequestUri.Host;
+ string url = e.HttpClient.Request.Url;
+ var urlNoQuery = url.Split('?')[0];
+ var processid = e.HttpClient.ProcessId.Value;
+ var processName = base.GetProcessName(processid);
+ var isLiveRoom = Regex.IsMatch(uri.Trim(), @".*:\/\/live.douyin\.com\/\d+");
+ var contentType = e.HttpClient.Response.ContentType ?? "";
+
+ //检测是否为dom页,用于脚本注入
+ if (contentType.Contains("text/html"))
+ {
+ //如果响应头含有 CSP(https://blog.csdn.net/qq_30436011/article/details/127485927 会阻止内嵌脚本执行) 则删除
+ var csp = e.HttpClient.Response.Headers.GetFirstHeader("Content-Security-Policy");
+ if (csp != null)
+ {
+ e.HttpClient.Response.Headers.RemoveHeader("Content-Security-Policy");
+ }
+
+ //获取 content-type
+ if (isLiveRoom)
+ {
+ //获取直播页注入js
+ string liveRoomInjectScript = GetInjectScript("livePage");
+
+ //注入上下文变量;
+ var scriptContext = $"const PROCESS_NAME = '{processName}';\n";
+ liveRoomInjectScript = scriptContext + liveRoomInjectScript;
+
+ if (!liveRoomInjectScript.IsNullOrWhiteSpace())
+ {
+ //利用 HtmlAgilityPack 在尾部注入script 标签
+ var html = await e.GetResponseBodyAsString();
+ try
+ {
+ var doc = new HtmlAgilityPack.HtmlDocument();
+ doc.LoadHtml(html);
+ //找到body标签,在尾部注入script标签
+ var body = doc.DocumentNode.SelectSingleNode("//body");
+ if (body != null)
+ {
+ var script = doc.CreateElement("script");
+ script.InnerHtml = liveRoomInjectScript;
+ body.AppendChild(script);
+ var newHtml = doc.DocumentNode.OuterHtml;
+ e.SetResponseBodyString(newHtml);
+ console.WriteLine($"直播页{urlNoQuery},用户脚本已成功注入!\n", ConsoleColor.Green);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, $"直播页{url},用户脚本注入异常");
+ }
+ }
+ }
+ }
+ }
+
+ private async Task HookScriptAsync(SessionEventArgs e)
+ {
+ string uri = e.HttpClient.Request.RequestUri.ToString();
+ string hostname = e.HttpClient.Request.RequestUri.Host;
+ string url = e.HttpClient.Request.Url;
+ var urlNoQuery = url.Split('?')[0];
+ var processid = e.HttpClient.ProcessId.Value;
+ var processName = base.GetProcessName(processid);
+ var contentType = e.HttpClient.Response.ContentType ?? "";
+
+ //判断响应内容是否为js application/javascript
+ if (contentType != null &&
+ contentType.Trim().ToLower().Contains("application/javascript") &&
+ hostname == SCRIPT_HOST
+ && e.HttpClient.Response.StatusCode == 200
+ //这俩个进程不需要注入
+ && processName != "直播伴侣"
+ && processName != "douyin"
+ )
+ {
+ var js = await e.GetResponseBodyAsString();
+ //var reg2 = new Regex(@"if\(!N.DJ\(\)&&(?\S).current\)\{"); //版本1,已过时
+ //if(!(0,k.DJ)()&&_.current){
+
+ var reg2 = new Regex(@"if\s*\(\s*\!\s*(?[\s\S]+\s*\.\s*DJ\s*\))\s*\(\s*\)\s*&\s*&\s*(?\S)\s*.\s*current\s*\)\s*\{");
+ var match = reg2.Match(js);
+ if (match.Success)
+ {
+ js = reg2.Replace(js, "if(!${v1}()&&${v2}.current){return;");
+ e.SetResponseBodyString(js);
+ console.WriteLine($"已成功绕过JS页面无操作检测 {urlNoQuery}\n", ConsoleColor.Green);
+ return;
+ }
+ }
+ }
+
+
private Task ProxyServer_ServerCertificateValidationCallback(object sender, CertificateValidationEventArgs e)
{
// set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == SslPolicyErrors.None)
{
e.IsValid = true;
- }
+ }
return Task.CompletedTask;
}
- private async Task ProxyServer_BeforeResponse(object sender, SessionEventArgs e)
- {
- string uri = e.HttpClient.Request.RequestUri.ToString();
- string hostname = e.HttpClient.Request.RequestUri.Host;
- if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Websocket)
- {
- e.DataReceived += WebSocket_DataReceived;
- }
-
- //判断响应内容是否为js application/javascript
- if (e.HttpClient.Response.ContentType != null &&
- e.HttpClient.Response.ContentType.Trim().ToLower().Contains("application/javascript") &&
- hostname == "lf-cdn-tos.bytescm.com"
- )
- {
- var js = await e.GetResponseBodyAsString();
-
- //修改js,绕过页面无操作检测
- var reg1 = new Regex(@"start\(\)\{Dt\.enable\(\),this\.tracker&&this\.tracker\.enable\(\)\}");
- var match = reg1.Match(js);
- if (match.Success)
- {
- js = reg1.Replace(js, "start(){return;Dt.enable(),this.tracker&&this.tracker.enable()}");
- e.SetResponseBodyString(js);
- return;
- }
-
- var reg2 = new Regex(@"if\(!N.DJ\(\)&&(?\S).current\)\{");
- match = reg2.Match(js);
- if (match.Success)
- {
- js = reg2.Replace(js, "if(!N.DJ()&&${variable}.current){return;");
- e.SetResponseBodyString(js);
- return;
- }
- }
- }
-
private async Task ExplicitEndPoint_BeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string url = e.HttpClient.Request.RequestUri.ToString();
string hostname = e.HttpClient.Request.RequestUri.Host;
- if (hostname == "lf-cdn-tos.bytescm.com")
+
+ //用于检测修改js脚本
+ if (hostname == SCRIPT_HOST)
{
e.DecryptSsl = true;
return;
}
-
+
+ //用于采集当前用户信息数据,供本地调用
+ if (hostname == LIVE_HOST)
+ {
+ e.DecryptSsl = true;
+ return;
+ }
+
if (!CheckHost(hostname))
{
e.DecryptSsl = false;
}
}
+ protected override bool CheckHost(string host)
+ {
+ host = host.Trim().ToLower();
+ var succ = base.CheckHost(host);
+ return succ || host == SCRIPT_HOST || host == LIVE_HOST;
+ }
+
private async void WebSocket_DataReceived(object sender, DataEventArgs e)
{
var args = (SessionEventArgs)sender;
@@ -153,6 +283,7 @@ namespace BarrageGrab.Proxy
}
else
{
+ //读取完毕
byte[] payload;
if (messageData.Count > 0)
{
@@ -194,7 +325,10 @@ namespace BarrageGrab.Proxy
{
proxyServer.Stop();
proxyServer.Dispose();
- CloseSystemProxy();
+ if (Appsetting.Current.UsedProxy)
+ {
+ CloseSystemProxy();
+ }
}
///
@@ -202,9 +336,12 @@ namespace BarrageGrab.Proxy
///
override public void Start()
{
- proxyServer.Start();
- proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
- proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
+ proxyServer.Start(Appsetting.Current.UsedProxy);
+ if (Appsetting.Current.UsedProxy)
+ {
+ proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
+ proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
+ }
}
}
}