逆向 Virustotal 搜尋介面 X-VT-Anti-Abuse-Header

顾平安發表於2024-10-08

搜尋示例

搜尋 123,網頁地址為:https://www.virustotal.com/gui/search/123/comments

截圖_20241008183426

請求介面

GET /ui/search?limit=20&relationships%5Bcomment%5D=author%2Citem&query=123 HTTP/1.1
Accept-Encoding: gzip, deflate, br, zstd
Accept-Ianguage: en-US,en;q=0.9,es;q=0.8
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Cookie: _gid=GA1.2.1662779803.1728383656; _ga=GA1.2.686372046.1728383655; _gat=1; _ga_BLNDV9X2JR=GS1.1.1728383655.1.1.1728383759.0.0.0
DNT: 1
Host: www.virustotal.com
Pragma: no-cache
Referer: https://www.virustotal.com/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
X-Tool: vt-ui-main
X-VT-Anti-Abuse-Header: MTgwNjgyNDI1ODItWkc5dWRDQmlaU0JsZG1scy0xNzI4MzgzNzYxLjMxMg==
accept: application/json
content-type: application/json
sec-ch-ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
x-app-version: v1x304x0

注意觀察,其中發現引數:X-VT-Anti-Abuse-Header 需要逆向,目測是 base64 加密,這個引數的含義也很明確——“反濫用頭”。在編寫爬蟲的程式碼層的實現時,儘量將 User-AgentX-Toolx-app-version 補全,因為它們是網站特有的亦或者常見的反爬識別引數。

值得注意的是,X-Toolx-app-version 是固定值,x-app-version 需要定期去官網檢視一下更新,不更新可能也行,自行測試。

也就是說,目前我們得到如下 Headers:

{
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
    'X-Tool': 'vt-ui-main',
    'x-app-version': 'v1x304x0',
}

開始逆向

接下來,逆向 X-VT-Anti-Abuse-Header,總體沒啥難度,不過這個網站具備反除錯措施(無法斷點除錯、無法從請求的啟動器回溯),這個反除錯我沒有去解決,而是透過直接查詢。

搜尋 X-VT-Anti-Abuse-Header,可得到:

截圖_20241008190340

直接進入該 js 檔案中:

截圖_20241008190408

可以看到,我們的目標引數由方法 r.computeAntiAbuseHeader() 計算而來。全域性搜尋 computeAntiAbuseHeader

截圖_20241008190551

序號1為我們所需的,也就是函式的實現,序號2為該函式的呼叫,也就是上方的 X-VT-Anti-Abuse-Header 來源。進入 1 所在的 js 檔案。

截圖_20241008190849

可以看到,這個方法真的非常簡單,不需要任何回溯和斷點,可以直接的手搓出來。這個網站典型的將反爬中的防君子不防小人做得很好。言歸正傳,這個方法:

  1. 首先獲取當前時間的秒級時間戳
  2. 然後生成一個介於 1e10 和 5e14 之間略大的隨機數,如果生成的數小於 50,則返回 "-1",否則返回該數的整數部分
  3. 最後將生成的隨機數、固定字串 "ZG9udCBiZSBldmls"(意為 "不要作弊")和當前時間戳拼接並進行 base64 加密

有趣的一點是:

> atob('ZG9udCBiZSBldmls ')
< 'dont be evil'

固定的字串告訴我們不要作惡……這尼瑪絕了,哈哈哈哈

加密實現

# header.py
import base64
import random
import time


def computeAntiAbuseHeader():
    e = time.time()
    n = 1e10 * (1 + random.random() % 5e4)
    raw = f'{n:.0f}-ZG9udCBiZSBldmls-{e:.3f}'
    res = base64.b64encode(raw.encode())
    return res.decode()


if __name__ == '__main__':
    print(computeAntiAbuseHeader())

結束了嗎?

看到這裡你以為結束了?oh~不,還差一點點,儘管你有了上述的分析和實現,你發現你怎麼請求都沒用!資料還是不給你,為啥?

再次將你的視線挪移到請求介面中:

Accept-Ianguage: en-US,en;q=0.9,es;q=0.8
Accept-Language: zh-CN,zh;q=0.9

此處有個容易忽略的老6請求頭:Accept-Ianguage,好了,到此才算結束了,看一下下方完整的程式碼示例吧。

"""
翻頁採集實現
2024年9月27日 solved
https://www.virustotal.com/gui/
"""
import time
import requests
import header
from urllib.parse import urlencode, urlparse

base_url = "https://www.virustotal.com/ui/search"
initial_params = {
    "limit": 20,
    "relationships[comment]": "author,item",
    "query": "baidu"
}

proxies = {
    'http': None,
    'https': None
}


def build_url(url, params):  # ☆
    return urlparse(url)._replace(query=urlencode(params)).geturl()


def get_headers():
    return {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
        'X-Tool': 'vt-ui-main',
        'X-VT-Anti-Abuse-Header': header.computeAntiAbuseHeader(),
        'x-app-version': 'v1x304x0',
        'accept-ianguage': 'en-US,en;q=0.9,es;q=0.8'
    }


def fetch_data(url):
    response = requests.get(url, headers=get_headers(), proxies=proxies)
    return response.json()


def process_data(data):
    for item in data['data']:
        print(f"ID: {item['id']}, Type: {item['type']}")


# 主迴圈
next_url = build_url(base_url, initial_params)
while next_url:
    print(f"Fetching: {next_url}")
    json_data = fetch_data(next_url)

    # 檢查是否有資料
    if not json_data.get('data'):
        print("No more data.")
        break

    # 處理當前頁面的資料
    process_data(json_data)

    # 獲取下一頁的 URL
    next_url = json_data.get('links', {}).get('next')

    if not next_url:
        print("No more pages.")
        break

    time.sleep(1)

print("Finished fetching all pages.")

相關文章