using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Security; using System.Text.RegularExpressions; using System.Threading.Tasks; using BarrageGrab.Modles; using BarrageGrab.Proxy.ProxyEventArgs; using ColorConsole; using Org.BouncyCastle.Ocsp; using Titanium.Web.Proxy; using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.StreamExtended.Network; namespace BarrageGrab.Proxy { internal class TitaniumProxy : SystemProxy { ProxyServer proxyServer = null; ExplicitProxyEndPoint explicitEndPoint = null; ExternalProxy upStreamProxy = 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() { //注册系统代理 //RegisterSystemProxy(); proxyServer = new ProxyServer(); proxyServer.ReuseSocket = false; proxyServer.EnableConnectionPool = true; proxyServer.ForwardToUpstreamGateway = true; proxyServer.CertificateManager.SaveFakeCertificates = true; proxyServer.CertificateManager.RootCertificate = proxyServer.CertificateManager.LoadRootCertificate(); if (proxyServer.CertificateManager.RootCertificate == null) { Console.WriteLine("正在进行证书安装,需要安装证书才可进行https解密,若有提示请确定"); proxyServer.CertificateManager.CreateRootCertificate(); } //设置上游代理地址 var upstreamProxyAddr = Appsetting.Current.UpstreamProxy; if (!upstreamProxyAddr.IsNullOrWhiteSpace()) { upStreamProxy = new ExternalProxy() { HostName = upstreamProxyAddr.Split(':')[0], Port = int.Parse(upstreamProxyAddr.Split(':')[1]), ProxyType = ExternalProxyType.Http }; proxyServer.UpStreamHttpProxy = upStreamProxy; proxyServer.UpStreamHttpsProxy = upStreamProxy; } proxyServer.ServerCertificateValidationCallback += ProxyServer_ServerCertificateValidationCallback; proxyServer.BeforeResponse += ProxyServer_BeforeResponse; //proxyServer.AfterResponse += ProxyServer_AfterResponse; explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, ProxyPort, true); explicitEndPoint.BeforeTunnelConnectRequest += ExplicitEndPoint_BeforeTunnelConnectRequest; proxyServer.AddEndPoint(explicitEndPoint); } 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 ?? ""; //处理弹幕 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") ) { 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 liveRoomMactch = Regex.Match(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 (liveRoomMactch.Success) { string webrid = liveRoomMactch.Groups[1].Value; //获取直播页注入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(); RoomInfo roominfo; var tup = RoomInfo.TryParseRoomPageHtml(html, out roominfo); int code = tup.Item1; string msg = tup.Item2; if (code == 0) { Logger.LogInfo($"直播页{webrid}房间信息已采集到缓存"); roominfo.WebRoomId = webrid; roominfo.LiveUrl = url; AppRuntime.RoomCaches.AddRoomInfoCache(roominfo); } else { roominfo = new RoomInfo(); roominfo.WebRoomId = webrid; roominfo.LiveUrl = url; //正则匹配主播标题 //
和平精英小夜y
var match = Regex.Match(html, @"(?<=live-room-nickname""\>).+(?=<\/div>)"); if (match.Success) { roominfo.Owner = new RoomInfo.RoomAnchor() { Nickname = match.Value, UserId = "-1" }; } AppRuntime.RoomCaches.AddRoomInfoCache(roominfo); } 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 ExplicitEndPoint_BeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e) { string url = e.HttpClient.Request.RequestUri.ToString(); string hostname = e.HttpClient.Request.RequestUri.Host; //用于检测修改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; string hostname = args.HttpClient.Request.RequestUri.Host; var processid = args.HttpClient.ProcessId.Value; List messageData = new List(); try { foreach (var frame in args.WebSocketDecoderReceive.Decode(e.Buffer, e.Offset, e.Count)) { if (frame.OpCode == WebsocketOpCode.Continuation) { messageData.AddRange(frame.Data.ToArray()); continue; } else { //读取完毕 byte[] payload; if (messageData.Count > 0) { messageData.AddRange(frame.Data.ToArray()); payload = messageData.ToArray(); messageData.Clear(); } else { payload = frame.Data.ToArray(); } base.FireWsEvent(new WsMessageEventArgs() { ProcessID = processid, HostName = hostname, Payload = payload, ProcessName = base.GetProcessName(processid) }); } } } catch (Exception ex) { Console.WriteLine("解析某个WebSocket包出错:" + ex.Message); } if (messageData.Count > 0) { // 没有收到 WebSocket 帧的结束帧,抛出异常或者进行处理 } } /// /// 释放资源,关闭系统代理 /// override public void Dispose() { proxyServer.Stop(); proxyServer.Dispose(); if (Appsetting.Current.UsedProxy) { CloseSystemProxy(); } } /// /// 启动监听 /// override public void Start() { proxyServer.Start(Appsetting.Current.UsedProxy); if (Appsetting.Current.UsedProxy) { proxyServer.SetAsSystemHttpProxy(explicitEndPoint); proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); } } } }