逆向从网络下载视频的方法

逆向的思路
https://www.aynakeya.com/articles/ctf/wechat-video-encryption-reverse-engineer/
逆向的实战
https://github.com/xuncv/WechatVideoSniffer
尝试仿照的实现:

import os
import random
import string
import shutil
import threading
import time
import sys
import signal
import asyncio
from mitmproxy import proxy, options
from mitmproxy.tools.dump import DumpMaster
from mitmproxy.addons import core
import requests
from pathlib import Path
import re
import subprocess

# 全局变量和状态管理
class AppState:
    def __init__(self):
        self.video_url = None
        self.download_running = False
        self.decrypt_table = {}  # 存储视频URL和解密数组的对应关系
        self.download_queue = []
        self.lock = threading.Lock()
        
app_state = AppState()

# 创建必要的目录
def init_dirs():
    os.makedirs("cache", exist_ok=True)
    os.makedirs("视频", exist_ok=True)

# 生成随机字符串
def random_string(length=6):
    return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))

# mitmproxy的HTTP请求拦截处理
class VideoInterceptor:
    def request(self, flow):
        # 只处理HTTPS请求
        if not flow.request.scheme == "https":
            return
        
        # 拦截worker_release.js请求
        if flow.request.method == "GET" and flow.request.url.endswith("/worker_release.js"):
            # 移除压缩,确保我们可以修改响应内容
            if "Accept-Encoding" in flow.request.headers:
                del flow.request.headers["Accept-Encoding"]
        
        # 接收从注入脚本发来的解密数组
        if flow.request.method == "POST" and "httpbin.org" in flow.request.host:
            if app_state.video_url:
                print(f"[+] 拦截到视频下载链接和解密数组: {app_state.video_url}")
                dec_array = flow.request.content
                
                # 保存解密数组到文件
                save_path = os.path.join("cache", random_string(16) + ".bin")
                with open(save_path, "wb") as f:
                    f.write(dec_array)
                
                with app_state.lock:
                    # 存储URL和解密数组的对应关系
                    app_state.decrypt_table[app_state.video_url] = save_path
                    # 添加到下载队列
                    app_state.download_queue.append(app_state.video_url)
                    app_state.video_url = None
                
                # 设置响应头以避免CORS问题
                flow.response = requests.Response()
                flow.response.status_code = 200
                flow.response.headers = {
                    "Access-Control-Allow-Origin": "*",
                    "Access-Control-Allow-Headers": "*",
                    "Access-Control-Allow-Methods": "OPTIONS,POST,GET",
                    "Content-Type": "text/html"
                }
                flow.response.content = b"<html><body>success!</body></html>"

    def response(self, flow):
        if not flow.request.scheme == "https":
            return
            
        # 修改worker_release.js响应
        if flow.request.method == "GET" and flow.request.url.endswith("/worker_release.js"):
            # 注入代码获取解密数组
            inject_script = "var rr=\\2.reverse();fetch('https://www.httpbin.org/post',{method:'POST',headers:{'Content-Type':'application/octet-stream',},body:rr,}).then(response=>{console.log(response.ok,response.body)});\\1.decryptor_array.set(rr);"
            content = flow.response.text
            modified_content = re.sub(r'(\w)\.decryptor_array\.set\((\w)\.reverse\(\)\)', inject_script, content)
            flow.response.text = modified_content
        
        # 拦截视频URL
        if "finder.video.qq.com/251/20302" in flow.request.url:
            if flow.request.method == "HEAD":
                app_state.video_url = flow.request.url
                
            # 添加CORS相关头
            flow.response.headers["Access-Control-Allow-Origin"] = "*"
            flow.response.headers["Access-Control-Allow-Headers"] = "*"
            flow.response.headers["Access-Control-Allow-Methods"] = "OPTIONS,POST,GET"

# 下载和解密视频
def download_and_decrypt_video(url):
    try:
        # 从存储中获取对应的解密数组路径
        with app_state.lock:
            dec_array_path = app_state.decrypt_table.get(url)
        
        if not dec_array_path:
            print(f"[-] 获取链接解密数组失败: {url}")
            return
            
        if not os.path.exists(dec_array_path):
            print("[-] 解密数组路径获取失败!")
            return
            
        # 读取解密数组
        with open(dec_array_path, "rb") as f:
            dec_array = f.read()
        
        # 删除临时文件
        os.remove(dec_array_path)
        
        # 生成随机文件名
        name = random_string()
        filename = os.path.join("cache", f"{name}_raw.dec")
        
        # 处理可能的URL参数
        clean_url = re.sub(r"\&X-snsvideoflag=(\w+)", "", url)
        print(f"[+] 开始通过链接下载视频, 链接: {clean_url}")
        
        # 下载视频
        response = requests.get(clean_url, stream=True, timeout=30)
        response.raise_for_status()
        
        with open(filename, "wb") as f:
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
        print("[+] 视频下载完成。")
        
        # 解密视频
        dec_size = len(dec_array)  # 通常是131072
        with open(filename, "rb+") as fw:
            enc_video_bin = fw.read(dec_size)
            print(f"[+] 开始解密视频,加密块大小: {len(enc_video_bin)}, {dec_size}")
            
            # 进行XOR解密
            dec_video_array = bytearray()
            for i in range(min(len(enc_video_bin), dec_size)):
                dec_video_array.append(enc_video_bin[i] ^ dec_array[i])
            
            # 写入解密后的数据
            fw.seek(0)
            fw.write(dec_video_array)
            # 保留原数据的其余部分
            if len(enc_video_bin) > dec_size:
                fw.write(enc_video_bin[dec_size:])
        
        # 移动到视频文件夹
        video_filename = os.path.join("视频", f"{name}.mp4")
        shutil.move(filename, video_filename)
        
        print(f"[+] 解密完成,已保存视频到路径: {video_filename}")
        
        # 清理记录
        with app_state.lock:
            if url in app_state.decrypt_table:
                del app_state.decrypt_table[url]
                
    except Exception as e:
        print(f"[-] 下载或解密过程出错: {e}")

# 下载管理线程
def download_manager():
    while app_state.download_running:
        try:
            url_to_download = None
            with app_state.lock:
                if app_state.download_queue:
                    url_to_download = app_state.download_queue.pop(0)
            
            if url_to_download:
                print(f"[*] 正在处理下载队列中的视频: {url_to_download}")
                download_and_decrypt_video(url_to_download)
            else:
                time.sleep(1)
        except Exception as e:
            print(f"[-] 下载管理器出错: {e}")
            time.sleep(1)

# 安装证书
def install_certificate():
    try:
        # 生成证书
        cert_dir = os.path.expanduser("~/.mitmproxy")
        os.makedirs(cert_dir, exist_ok=True)
        
        print("[*] 正在安装证书...")
        if sys.platform == "win32":
            # Windows平台
            subprocess.run(["mitmdump", "--set", "confdir=~/.mitmproxy", "--set", "ssl_insecure=true"], 
                          stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=10)
            print("[*] 将在浏览器中打开证书安装页面...")
            subprocess.run(["start", "http://mitm.it"], shell=True)
        else:
            # Linux/macOS平台
            subprocess.run(["mitmdump", "--set", "confdir=~/.mitmproxy", "--set", "ssl_insecure=true"], 
                          stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=1)
            print("[*] 请访问 http://mitm.it 安装证书")
            
        print("[+] 证书安装完成后,请按回车继续...")
        input()
    except Exception as e:
        print(f"[-] 证书安装失败: {e}")
        sys.exit(1)

def main():
    print("=" * 50)
    print("微信视频号下载助手 Python版")
    print("=" * 50)
    
    # 初始化目录
    init_dirs()
    print("[+] 初始化完成")
    
    # 安装证书
    install_certificate()
    
    try:
        # 启动mitmproxy
        print("[*] 正在启动代理服务器...")
        
        opts = options.Options(
            listen_host="127.0.0.1", 
            listen_port=8080,
            ssl_insecure=True,
            confdir="~/.mitmproxy"
        )
        
        from mitmproxy import master
        dump_master = master.Master(opts=opts, event_loop=asyncio.get_event_loop())
        
        # 初始化代理服务器 - 旧版本兼容
        master = DumpMaster(dump_master)
        master.addons.add(VideoInterceptor())
        
        # 启动下载管理器
        app_state.download_running = True
        download_thread = threading.Thread(target=download_manager)
        download_thread.daemon = True
        download_thread.start()
        
        print("[+] 代理服务器启动成功,监听端口: 8080")
        print("[*] 请设置微信代理为 127.0.0.1:8080")
        print("[*] 按 Ctrl+C 停止程序")
        
        # 运行代理服务器
        master.run()
    except KeyboardInterrupt:
        print("\n[*] 正在关闭程序...")
    finally:
        # 清理资源
        app_state.download_running = False
        if 'master' in locals():
            master.shutdown()
        print("[+] 程序已关闭")

if __name__ == "__main__":
    main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值