本帖最后由 wangjunbin345 于 2024-01-29 22:08 编辑
前分享过一次帖子,是扣JS的方法来请求接口 希望各位食客给个关注,加个评论来点更新的动力
这次直接使用C#实现了他的算法,然后在分享一下他的接口时怎么请求以及解密的 用到的工具就是Fiddler和浏览器控制台,然后写了一个C#版本的软件(随手写的有BUG哈哈) 地址是:aHR0cHM6Ly9qeC54bWZsdi5jb20v 首先打开FD,然后打开控制台抓一下包,挨个分析
这里有这样一个请求,他的请求参数有我们想看的视频并且响应是加密的 我们先分析他的请求,可以看到有个key是加密的,去控制台打一个XHR断点 可以看到成功断住,我们根据这个断点往上找加密的地方 往上找到这里的时候可以看到key=lllIIlIl,lllIIlIl=sign 先分析sign中的几个参数与结果 I111ill=时间戳,url=编码后的想看的视频的地址 可以看到时间戳拼接视频地址,然后取MD5 这个时候我们去sign中看他的逻辑即可
鼠标悬停进VM虚拟机里看sign代码是一个AES加密的,然后传过来MD5的结果 就是KEY参数的值 这个时候可以模拟请求拿到对应的响应 结果中有两个参数是加密的,分别是url和html 我们看到同时他的响应中有一个AES_KEY和AES_IV 大胆设想他是AES加密并且同时返回key和iv 一个是url,一个是html, 这个url会返回真实视频地址,两种情况一个是301,一个是直接的视频地址 html是全集和剧的详细信息 C#的代码贴在下方,diamagnetic比较烂只是能用,没有具体优化,各位佬可以根据自己需求在优化 现在他有时候接口返回的响应有一些剧的他自己网站也播放不出来,一直卡着转圈,所以软件也没办法下载,具体哪些能下哪些不能下需要自己测试了 using Newtonsoft.Json; using System; using System.IO; using System.IO.Pipes; using System.Net; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Web; using static System.Net.Mime.MediaTypeNames; using static System.Runtime.InteropServices.JavaScript.JSType; namespace xmflv { internal class Program { static async Task Main(string[] args) { Console.WriteLine("Hello, World!"); Console.Write("请输入视频URL(支持腾讯视频,爱奇艺):"); string fromUrl = Console.ReadLine(); if (string.IsNullOrEmpty(fromUrl)) { Console.WriteLine("输入url为空!"); Console.ReadLine(); } fromUrl = EncodeUrl(fromUrl); //Console.WriteLine(fromUrl); //时间戳 long timeStamp = GenerateTimestamp(); //Console.WriteLine(timeStamp); //时间戳+URL的MD5 string plaintext = CalculateMD5Hash($"{timeStamp}{UrlDecode(fromUrl)}"); //Console.WriteLine(plaintext); //请求中的key参数 string ciphertext = Sign(plaintext); //Console.WriteLine(ciphertext); HttpHelper http = new HttpHelper(); HttpItem item = new HttpItem() { URL = "接口地址的url", Method = "POST", Accept = "application/json, text/javascript, */*; q=0.01", ContentType = "application/x-www-form-urlencoded; charset=UTF-8", UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0", Postdata = $"wap=1&url={fromUrl}&time={timeStamp}&key={EncodeKey(ciphertext)}", //ProxyIp = "127.0.0.1:8888" }; item.Header.Add("Origin", "地址自己抓一下"); HttpResult result = http.GetHtml(item); if (result.StatusCode != System.Net.HttpStatusCode.OK) { Console.Write("请求失败!"); } else { string html = result.Html; //Console.WriteLine(html); var resultDynamic = JsonConvert.DeserializeObject<dynamic>(html); if (!string.IsNullOrEmpty(html) && (int)resultDynamic.code == 200) { //解密后的视频地址集合 var extm3u = DecryptAES((string)resultDynamic.url, (string)resultDynamic.aes_key, (string)resultDynamic.aes_iv); //Console.WriteLine(extm3u); //取得全集链接 var AllUrl = ExtractURLs(DecryptAES((string)resultDynamic.html, (string)resultDynamic.aes_key, (string)resultDynamic.aes_iv)); item = new HttpItem() { URL = RemoveSpecialCharacters(extm3u), Method = "GET", UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0", Accept = "*/*", //ProxyIp = "127.0.0.1:8888" }; item.Header.Add("Origin", "地址自己抓一下"); result = http.GetHtml(item); if (result.StatusCode == System.Net.HttpStatusCode.OK && !string.IsNullOrEmpty(result.Html)) { html = result.Html; //Console.WriteLine(html); string[] lines = html.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); string folderPath = AppDomain.CurrentDomain.BaseDirectory + $"{resultDynamic.name}"; int i = 0; foreach (string line in lines) { if (!line.StartsWith("#")) { //文件夹不存在则创建文件夹 if (!Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); } string fileName = $"{i}.ts"; string filePath = Path.Combine(folderPath, fileName); if (!File.Exists(filePath)) { bool bl = true; int j = 0; while (bl) { try { //WebProxy webProxy = new WebProxy("127.0.0.1", 8888); using (HttpClient client = new HttpClient()) { if (extm3u.Contains("IP地址包含就用这个")) { //先获取重定向的url item = new HttpItem() { URL = "https://服务器地址自己抓一下/" + line, Method = "GET", UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0", Accept = "*/*", //ProxyIp = "127.0.0.1:8888" }; item.Header.Add("Origin", "地址自己抓一下"); result = http.GetHtml(item); var location = result.RedirectUrl; using (HttpResponseMessage response = await client.GetAsync(location)) { response.EnsureSuccessStatusCode(); using (Stream stream = await response.Content.ReadAsStreamAsync()) { using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write)) { await stream.CopyToAsync(fileStream); // 将响应流保存到本地文件 bl = false; break; } } } } else { //client.Timeout = TimeSpan.FromSeconds(120); // 设置超时时间为 120 秒 //HttpClient.DefaultProxy = webProxy; //client.DefaultRequestHeaders.Add("origin", "地址自己抓一下"); string pattern = @"/([^/]+)$"; string match = Regex.Replace(extm3u, pattern, ""); string mp4Url = match + "/" + line; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(mp4Url); request.Method = "GET"; request.Headers.Add("sec-ch-ua", "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Microsoft Edge\";v=\"120\""); request.Headers.Add("sec-ch-ua-mobile", "?0"); request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"; request.Headers.Add("sec-ch-ua-platform", "\"Windows\""); request.Accept = "/"; request.Headers.Add("Origin", "地址自己抓一下"); request.Headers.Add("Sec-Fetch-Site", "cross-site"); request.Headers.Add("Sec-Fetch-Mode", "cors"); request.Headers.Add("Sec-Fetch-Dest", "empty"); request.Headers.Add("Accept-Encoding", "gzip, deflate, br"); request.Headers.Add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"); try { // Send the request and get the response HttpWebResponse response = (HttpWebResponse)request.GetResponse(); // Read the response content using (var stream = response.GetResponseStream()) { using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write)) { await stream.CopyToAsync(fileStream); // 将响应流保存到本地文件 bl = false; } response.Close(); break; } } catch (WebException ex) { Console.WriteLine("An error occurred: " + ex.Message); } //using (HttpResponseMessage response = await client.GetAsync(mp4Url)) //{ // if (response.StatusCode == HttpStatusCode.OK) // { // using (Stream stream = await response.Content.ReadAsStreamAsync()) // { // using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write)) // { // await stream.CopyToAsync(fileStream); // 将响应流保存到本地文件 // bl = false; // break; // } // } // } //} } } } catch (Exception ex) { j++; if (j > 3) { bl = false; Console.WriteLine($"3次请求地址自己抓一下{line}失败已经跳过!"); } } } } i++; } } CombineMP3(folderPath); } else { Console.WriteLine("请求加密接口失败!"); } } else { Console.WriteLine("请求失败!" + html); } } Console.ReadLine(); } static void CombineMP3(string folder) { string outputFilePath = $"{folder}.mp4"; string[] mp3Files = Directory.GetFiles(folder, "*.ts"); // 获取文件夹中的所有MP3文件路径 List<byte[]> mp3DataList = new List<byte[]>(); List<string> sortedFiles = mp3Files.OrderBy(f => Path.GetFileNameWithoutExtension(f), new NumericFileNameComparer()).ToList(); foreach (string filePath in sortedFiles) { byte[] mp3Data = File.ReadAllBytes(filePath); // 读取MP3文件的字节数据 mp3DataList.Add(mp3Data); } byte[] outputData = mp3DataList.SelectMany(data => data).ToArray(); // 将所有MP3文件的字节数据合并为一个字节数组 File.WriteAllBytes(outputFilePath, outputData); // 将字节数组写入输出文件 Directory.Delete(folder, true); Console.WriteLine("合并完成,输出文件路径:" + outputFilePath); } public static string RemoveSpecialCharacters(string url) { string pattern = @"[^a-zA-Z0-9.:/?=_-]"; // 匹配非法字符的正则表达式 return Regex.Replace(url, pattern, ""); // 去掉非法字符 } public class NumericFileNameComparer : IComparer<string> { public int Compare(string x, string y) { string fileNameX = Path.GetFileNameWithoutExtension(x); string fileNameY = Path.GetFileNameWithoutExtension(y); if (fileNameX == null || fileNameY == null) { return 0; // 如果文件名为空,则认为两个文件名相等 } int numericValueX, numericValueY; bool successX = int.TryParse(fileNameX, out numericValueX); bool successY = int.TryParse(fileNameY, out numericValueY); if (successX && successY) { return numericValueX.CompareTo(numericValueY); } else if (successX) { return -1; // 如果只有 x 是数字,那么 x 小于 y } else if (successY) { return 1; // 如果只有 y 是数字,那么 x 大于 y } else { return string.Compare(fileNameX, fileNameY, StringComparison.Ordinal); // 如果都不是数字,则按照字符串顺序排序 } } } /// <summary> /// 获取全集链接 /// </summary> /// <param name="text"></param> /// <returns></returns> public static List<string> ExtractURLs(string text) { List<string> urls = new List<string>(); Regex regex = new Regex(@"./\?url=(.*?)'"); MatchCollection matches = regex.Matches(text); foreach (Match match in matches) { string url = match.Groups[1].Value; urls.Add(url); } return urls; } /// <summary> /// 获取请求中的key /// </summary> /// <param name="a"></param> /// <returns></returns> public static string Sign(string a) { // 计算MD5 byte[] md5Bytes; using (var md5 = MD5.Create()) { byte[] inputBytes = Encoding.UTF8.GetBytes(a); md5Bytes = md5.ComputeHash(inputBytes); } string b = BitConverter.ToString(md5Bytes).Replace("-", "").ToLower(); // 使用MD5作为密钥 byte[] key = Encoding.UTF8.GetBytes(b); // 设置IV和Padding byte[] iv = Encoding.UTF8.GetBytes("3cccf88181408f19"); PaddingMode padding = PaddingMode.Zeros; // 加密 byte[] encryptedBytes; using (var aes = new AesCryptoServiceProvider()) { aes.Key = key; aes.IV = iv; aes.Padding = padding; aes.Mode = CipherMode.CBC; byte[] inputBytes = Encoding.UTF8.GetBytes(a); using (var encryptor = aes.CreateEncryptor()) { encryptedBytes = encryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length); } } string e = Convert.ToBase64String(encryptedBytes); return e; } /// <summary> /// 时间戳+url /// </summary> /// <param name="input"></param> /// <returns></returns> static string CalculateMD5Hash(string input) { using (MD5 md5 = MD5.Create()) { byte[] inputBytes = Encoding.UTF8.GetBytes(input); byte[] hashBytes = md5.ComputeHash(inputBytes); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < hashBytes.Length; i++) { stringBuilder.Append(hashBytes[i].ToString("x2")); } return stringBuilder.ToString(); } } /// <summary> /// 获取时间戳 /// </summary> /// <returns></returns> static long GenerateTimestamp() { DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); TimeSpan timeSpan = DateTime.UtcNow - epoch; long timestamp = (long)timeSpan.TotalMilliseconds; return timestamp; } /// <summary> /// 对网址进行url编码 /// </summary> /// <param name="url"></param> /// <returns></returns> static string EncodeUrl(string str) { string encodedUrl = Uri.EscapeDataString(str); encodedUrl = encodedUrl.Replace("%3A", "%253A").Replace("%2F", "%252F"); return encodedUrl; } static string EncodeKey(string str) { string encodedUrl = Uri.EscapeDataString(str); return encodedUrl; } public static string UrlDecode(string encodedUrl) { string decodedUrl = HttpUtility.UrlDecode(encodedUrl); return decodedUrl; } /// <summary> /// 解密 /// </summary> /// <param name="encryptedText"></param> /// <param name="key"></param> /// <param name="iv"></param> /// <returns></returns> static string DecryptAES(string encryptedText, string key, string iv) { byte[] keyBytes = Encoding.UTF8.GetBytes(key); byte[] ivBytes = Encoding.UTF8.GetBytes(iv); byte[] encryptedBytes = Convert.FromBase64String(encryptedText); using (Aes aesAlg = Aes.Create()) { aesAlg.Key = keyBytes; aesAlg.IV = ivBytes; aesAlg.Padding = PaddingMode.Zeros; aesAlg.Mode = CipherMode.CBC; ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); using (MemoryStream msDecrypt = new MemoryStream(encryptedBytes)) { using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) { using (StreamReader srDecrypt = new StreamReader(csDecrypt)) { return srDecrypt.ReadToEnd(); } } } } } } }
注:若转载请注明大神论坛来源(本贴地址)与作者信息。
|