12-xx儿

day12 xx儿

需求:

  • 逆向滑块请求
  • 发送短信登录

image-20231207221058606

地址:https://user.qunar.com/passport/login.jsp

1.必备知识点

1.1 页面滑动

image-20231207224252569

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div onmousedown="doDown(event)">武沛齐爬虫开发教程</div>

<script>
    function doDown(e) {
        console.log("doDown", e);
        document.addEventListener("mousemove", doMove);
        document.addEventListener("mouseup", doUp)

    }
    function doMove(e) {
        console.log("doMove", e)
    }
    function doUp(e) {
        console.log("doUp", e)
        document.removeEventListener("mousemove", doMove);
        document.removeEventListener("mouseup", doUp);
    }
</script>
</body>
</html>

1.2 滑动轨迹

image-20231207231116649

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div onmousedown="doDown(event)">武沛齐爬虫开发教程</div>

<script>
    t = {
        state: {},
        sliderInfo: {
            track: [],
            deviceMotion:[]
        }
    }

    function doDown(e) {
        // n = e   n.clientX是点击的x坐标
        var n = (e.changedTouches || e.touches || [e])[0];
        t.state.downX = n.clientX;
        console.log("开始x坐标:", n.clientX);

        console.log("doDown", e);
        document.addEventListener("mousemove", doMove);
        document.addEventListener("mouseup", doUp)

    }

    function doMove(e) {
        var o = (e.changedTouches || e.touches || [e])[0];
        var n = t.state.downX;
        var u = (e = e || window.event, o.clientX - n);
		var n = u;
        
        var i = Date.now() % 1e5
          , s = (e.changedTouches || e.touches || [e])[0]
          , o = s.clientX.toFixed(2)
          , u = s.clientY.toFixed(2)
          , a = n.toFixed(2)
          , f = "".concat(i, ";").concat(o, ";").concat(u, ";").concat(a);

        t.sliderInfo.track.push(f);
		window.addEventListener("deviceorientation", function(e) {
            t.sliderInfo.deviceMotion.push(e)
        }, !1)
        
        console.log("滑动过程u:", u)
        console.log("doMove", e)
    }

    function doUp(e) {
        console.log("doUp", e)
        document.removeEventListener("mousemove", doMove);
        document.removeEventListener("mouseup", doUp);

        console.log(t.sliderInfo.track)
        console.log(t.sliderInfo.deviceMotion)
    }
</script>
</body>
</html>

1.3 浏览器环境

在使用pyexecjs执行JavaScript代码时,如果存在读取浏览器环境,会失败。例如:

image-20231208113808884

import execjs

js_string = """
function func(arg) {
    return arg + '666' + document.location.hostname + window.navigator.userAgent;
}
"""
JS = execjs.compile(js_string)

sign = JS.call("func", "wupeiqi")
print(sign)  

此时,就需要创造浏览器环境然后再执行JavaScript代码。

npm config set registry https://registry.npm.taobao.org
npm install -g jsdom       【主要】
npm install -g node-gyp 
npm install -g canvas --canvas_binary_host_mirror=https://npm.taobao.org/mirrors/canvas/

安装后就可以补浏览器环境:

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import execjs

js_string = """
const jsdom = require("jsdom");
const {JSDOM} = jsdom;

const html = `<!DOCTYPE html><p>Hello world</p>`;
const dom = new JSDOM(html, {
    url: "https://www.toutiao.com",
    referrer: "https://example.com/",
    contentType: "text/html"
});
document = dom.window.document;

window = global;
Object.assign(global, {
    location: {
        hash: "",
        host: "user.qunar.com",
        hostname: "user.qunar.com",
        href: "https://user.qunar.com/passport/login.jsp",
        origin: "https://user.qunar.com",
        pathname: "/",
        port: "",
        protocol: "https:",
        search: "",
    },
    navigator: {
        appCodeName: "Mozilla",
        appName: "Netscape",
        appVersion: "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
        cookieEnabled: true,
        deviceMemory: 8,
        doNotTrack: null,
        hardwareConcurrency: 4,
        language: "zh-CN",
        languages: ["zh-CN", "zh"],
        maxTouchPoints: 0,
        onLine: true,
        platform: "MacIntel",
        product: "Gecko",
        productSub: "20030107",
        userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
        vendor: "Google Inc.",
        vendorSub: "",
        webdriver: false
    }
});

function func(arg) {
    return arg + '666' + document.location.hostname + window.navigator.userAgent;
}
"""
JS = execjs.compile(js_string)

sign = JS.call("func", "wupeiqi")
print(sign) 

1.4 关于补环境

上述示例中只是补充了浏览器中常见的window、document对象,有些算法中还有其他的值和变量,此时就需要根据环境提示来进行补环境。

  • 分析提示错误信息
  • 补充 & 测试

image-20231208114915922

image-20231208115002696

根据提示补环境并运行:

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import execjs

js_string = """
const jsdom = require("jsdom");
const {JSDOM} = jsdom;

const html = `<!DOCTYPE html><p>Hello world</p>`;
const dom = new JSDOM(html, {
    url: "https://user.qunar.com/passport/login.jsp",
    referrer: "https://www.qunar.com/",
    contentType: "text/html"
});
document = dom.window.document;

window = global;
Object.assign(global, {
    location: {
        hash: "",
        host: "user.qunar.com",
        hostname: "user.qunar.com",
        href: "https://user.qunar.com/passport/login.jsp",
        origin: "https://user.qunar.com",
        pathname: "/passport/login.jsp",
        port: "",
        protocol: "https:",
        search: "",
    },
    navigator: {
        appCodeName: "Mozilla",
        appName: "Netscape",
        appVersion: "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
        cookieEnabled: true,
        deviceMemory: 8,
        doNotTrack: null,
        hardwareConcurrency: 4,
        language: "zh-CN",
        languages: ["zh-CN", "zh"],
        maxTouchPoints: 0,
        onLine: true,
        platform: "MacIntel",
        product: "Gecko",
        productSub: "20030107",
        userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
        vendor: "Google Inc.",
        vendorSub: "",
        webdriver: false
    }
});

XMLHttpRequest = function(){};
function func(arg) {
    var xhr = new XMLHttpRequest();
    return arg + '666' + document.location.hostname + window.navigator.userAgent;
}
"""
JS = execjs.compile(js_string)

sign = JS.call("func", "wupeiqi")
print(sign)

image-20231208115053077

1.5 关于XMLHttpRequest

xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if(xhr.readyState == 4){
        // 已经接收到全部响应数据,执行以下操作
        var data = xhr.responseText;
        console.log(data);
    }
};

xhr.open('POST', "/test/", true);

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');

xhr.send('n1=1;n2=2;');

参考:https://www.cnblogs.com/wupeiqi/articles/5703697.html

2.逆向轨迹snapshot

2.1 请求分析

分析请求:搞定 data 就行了。

image-20231207231801456

image-20231207231932609

image-20231207232321521

e.data = {
	appCode: "register_pc",
	cs: "pc",
	data: "/rbk3gF/w9E6nxeN9/KAwvVH5vtVuJBW3+TBsSJY5ZH9nURD0....",
	orca: 2
}

image-20231207232450923

image-20231207232519594

image-20231208150258149

此处的 a 就是我们要的数据,此数据是由:var a = t.encryption() 执行获取,内部:

  • 序列化 this.sliderInfo 【滑动轨迹】

    "{"openTime":1701958250076,"startTime":1701958331349,"endTime":1701958331492,"userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36","uid":"0000ff0025405942b0d0087b","track":["31364;136.00;342.00;1.00","31384;150.00;342.00;15.00","31404;231.00;347.00;96.00","31425;315.00;347.00;180.00","31447;457.00;349.00;322.00","31468;505.00;349.00;370.00","31489;555.00;349.00;420.00"],"acc":[],"ori":[],"deviceMotion":[{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true}]}"
    
  • 对轨迹进行AES加密

最终生成的就是data数据(其实就是模拟滑块的滑动)。

2.2 轨迹

image-20231207232610470

image-20231207232728499

image-20231207232803420

t.sliderInfo = {
    openTime: 0,
    startTime: 0,
    endTime: 0,
    userAgent: window.navigator.userAgent,
    uid: h("QN1"), #  读取Cookie中的"QN1"
    track: [],
    acc: [],
    ori: [],
    deviceMotion: []
}
var r = U(t)
, i = Date.now() % 1e5
, s = (e.changedTouches || e.touches || [e])[0]  # e事件
, o = s.clientX.toFixed(2)  # 横坐标,小数点后2位
, u = s.clientY.toFixed(2)  # 纵坐标,小数点后2位
, a = n.toFixed(2)          # 当前移动的横坐标 - 开始时的横坐标,数据:1/2/3....(移动了多远的距离)
, f = "".concat(i, ";").concat(o, ";").concat(u, ";").concat(a);

t.sliderInfo.track.push(f);

r.sliderInfo.deviceMotion.push(e)

基于Python来构建轨迹信息:

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import random
import time


def get_slider_info():
    slider_list = []
    
    client_x = 300
    client_y = 500
    
    start_time = int(int(time.time() * 1000) % 1e5)
    
    width = random.randint(419, 431)
    
    for slice_distance in range(3, width, 26):
        if width - slice_distance <= 26:
            slice_distance = width
        start_time += random.randint(10, 1000)
        i = start_time
        o = f"{client_x + slice_distance}.00"
        u = f"{client_y + random.randint(-5, 5)}.00"
        a = f"{slice_distance}.00"
        f = f"{i};{o};{u};{a}"
        slider_list.append(f)
    return slider_list


def run():
    slider_list = get_slider_info()
    print(slider_list)


if __name__ == '__main__':
    run()

2.3 AES加密

pip install pycryptodome

image-20231208154625404

e = '{"openTime":1702021534202,"startTime":1702021535985,"endTime":1702021536303,"userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36","uid":"0000f800254059475ae0ab8d","track":["36040;58.00;273.00;1.00","36061;112.00;277.00;55.00","36085;222.00;291.00;165.00","36108;309.00;294.00;252.00","36128;385.00;294.00;328.00","36150;405.00;294.00;348.00","36172;421.00;294.00;364.00","36192;428.00;294.00;371.00","36212;433.00;294.00;376.00","36233;443.00;294.00;386.00","36256;457.00;294.00;400.00","36276;468.00;294.00;411.00","36296;486.00;297.00;429.00"],"acc":[],"ori":[],"deviceMotion":[{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true}]}'
key = 227V2xYeHTARSh1R   或 32323756327859654854415253683152
iv = 无
AES加密后="/rbk3gF/w9E6nxeN9/KAwkfV/Suaadocu+lITcBeF8Otqk1I7U5YdIehRxzb3zQmP3nuWBUTC3eYj2iITgNXxqUEwqHFnIehVq4aj2POYtbbwIYdS4ceo1kLfu+9Hxv4Pf1bQK7GBdkHOn+pynXZ5rf/Cry06pPfR/Hf7BrFSk1rvLNqcTBvq6730+O1VcEkBJTDTx/lnSA+jwK9ypQ44HRjonU347dLl4qbCximrSMU4Ffa8UWz5/7Za6IUpI8bGJBsICcW13u/6VrdJ1k9i4R0Prk8WdJuRpPDmIzzHtIGj0jWVcoGykitY334e/I3HBU0J/9zF4IVyilOvRoCHlNCX33K/W7o8ZfhTuyEHeSmfUDXII+y60so3sgjyL6Jx2mPbqAUf4i3BN0Nf8+Ehmop4nOd/4umofKpm8l3CNKBmsyKr+Lqd4PM0ePZnNsy8z6K4fZYN/Z/3cK8fD6Ud3uaIIBmoAkNOBwOYs/a2Qp4U4qa57YtADX/SpDbBGlX+P2knhf7c4WU7qMJW6UzFA/1woeCPGNBbxwxhh/7fC0E/tmOnvNZWeohdzc86US7Ic0MrdHeX9bsQ3dGBPy86XZ6j2fJSYNwxfB3m4ExpeEDtg09manx7bkh8qXv4LpLMDfnzhAz/wJ9ikXYq93ImkAlu5PWFWsKycWz2scjfhg48P+aP/OHkbKV2th/b97Np3iHNye/HQA4zTadhYs0EIKu5YTCp5l1xLDKxuz3P/xFW6BtATFuF5MpH0mljqahfBpV9puaOaCyDP3zIgGTIWqniLuY3HqA3yLZ6IUCorv94ueBMYHq1MwAIzL32OjfviDgo+7B9qh9zVQukaR2gwSmia+CRDJ2uwyZUETpm3NOzhR356QqA+IvQ2aHc7zKnKk3M7fJBrZNDi6sELSRVOVsdO+7uxrqQu7c0h9/NxNeNiBRN5LaB5R+ocEji3KeR6IZbrOAtcIS9RNkOHu0P/G2yNNJc372MPQtfjnzRwKFf8VcRH/zsFhQuA/KZSDqYQBU/4L4Drj8BGNSF38kUPU2GkEFTTigl5RrL1qEBX7xXK+6PIjrKcYC0HlDR0UvmjMMi9JKMMJAPR4FHBDh6hsZmjmMayZVBwgTUQ9DubuLOzB9+B4/Dn6dYM1rxJNlZMzHuAkhnb1Qm3ewUWA3+S6m49NpM1GTYTjP4G9I5s2+BkingUHSxSD5qBIHRUwamypxs71L3mUXtScON+PWa9K+9Ruib9Jjr6ZkXN5jv9mTAQt7vEKXxQBdivxADUY9Ts4Ud+ekKgPiL0Nmh3O8ypypNzO3yQa2TQ4urBC0kVTlbHTvu7sa6kLu3NIffzcTXjYgUTeS2geUfqHBI4tynkeiGW6zgLXCEvUTZDh7tD/xtsjTSXN+9jD0LX4580cChX/FXER/87BYULgPymUg6mEAVP+C+A64/ARjUhd/JFD1NhpBBU04oJeUay9ahAV+8VyvujyI6ynGAtB5Q0dFL5ozDIvSSjDCQD0eBRwQ4eobGZo5jGsmVQcIE1EPQ7m7izswffgePw5+nWDNa8STZWTMx7gJIZ29UJt3sFFgN/kupuPTaTNRk2E4z+BvSObNvgZIp4FB0sUg+agSB0VMGpsqcbO9S95lF7UnDjfj1mvSvvUbom/SY6+mZFzeY7/ZkwELe7xCl8UAXYr8QA1GPU7OFHfnpCoD4i9DZodzvMqcqTczt8kGtk0OLqwQtJFU5Wx077u7GupC7tzSH383E142IFE3ktoHlH6hwSOLcp5Hohlus4C1whL1E2Q4e7Q/8bbI00lzfvYw9C1+OfNHAoV/xVxEf/OwWFC4D8plIOphAFT/gvgOuPwEY1IXfyRQ9TYaQQVNOKCXlGsvWoQFfvFcr7o8iOspxgLQeUNHRS+aMwyL0kowwkA9HgUcEOHqYHfDji5duSuitXuWLO57/A=="

image-20231208160519503

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import base64
import binascii
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

data_string = '{"openTime":1702021534202,"startTime":1702021535985,"endTime":1702021536303,"userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36","uid":"0000f800254059475ae0ab8d","track":["36040;58.00;273.00;1.00","36061;112.00;277.00;55.00","36085;222.00;291.00;165.00","36108;309.00;294.00;252.00","36128;385.00;294.00;328.00","36150;405.00;294.00;348.00","36172;421.00;294.00;364.00","36192;428.00;294.00;371.00","36212;433.00;294.00;376.00","36233;443.00;294.00;386.00","36256;457.00;294.00;400.00","36276;468.00;294.00;411.00","36296;486.00;297.00;429.00"],"acc":[],"ori":[],"deviceMotion":[{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true},{"isTrusted":true}]}'
# key = "227V2xYeHTARSh1R".encode('utf-8')
key_string = "32323756327859654854415253683152"
key = binascii.a2b_hex(key_string)


aes = AES.new(
    key=key,
    mode=AES.MODE_ECB
)
raw = pad(data_string.encode('utf-8'), 16)
aes_bytes = aes.encrypt(raw)
res = base64.b64encode(aes_bytes)
print(res)

2.4 示例代码:轨迹+加密

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import json
import random
import time
import base64
import binascii
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


def get_slider_list():
    slider_list = []
    client_x = 300
    client_y = 500
    start_time = int(int(time.time() * 1000) % 1e5)
    width = random.randint(419, 431)
    for slice_distance in range(3, width, 26):
        if width - slice_distance <= 26:
            slice_distance = width
        start_time += random.randint(10, 1000)
        i = start_time
        o = f"{client_x + slice_distance}.00"
        u = f"{client_y + random.randint(-5, 5)}.00"
        a = f"{slice_distance}.00"
        f = f"{i};{o};{u};{a}"
        slider_list.append(f)
    return slider_list


def aes_encrypt(data_string):
    # key = "227V2xYeHTARSh1R".encode('utf-8')
    key_string = "32323756327859654854415253683152"
    key = binascii.a2b_hex(key_string)

    aes = AES.new(
        key=key,
        mode=AES.MODE_ECB
    )
    raw = pad(data_string.encode('utf-8'), 16)
    aes_bytes = aes.encrypt(raw)
    res_string = base64.b64encode(aes_bytes).decode('utf-8')
    return res_string


def run():
    cookie_qn1 = "0000ff0025405942b0d0087b"
    slider_list = get_slider_list()
    slider_info = {
        "openTime": int((time.time() - random.randint(500, 3000)) * 1000),
        "startTime": int((time.time() - random.uniform(2, 4)) * 1000),
        "endTime": int((time.time() - random.uniform(0, 1)) * 1000),
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
        "uid": cookie_qn1,
        "track": slider_list,
        "acc": [],
        "ori": [],
        "deviceMotion": [{"isTrusted": True} for _ in range(random.randint(10, 100))]
    }

    data_string = json.dumps(slider_info, separators=(',', ':'))
    data = aes_encrypt(data_string)

    r = {
        "appCode": "register_pc",
        "cs": "pc",
        "data": data,
        "orca": 2
    }
    print(r)


if __name__ == '__main__':
    run()

2.5 请求

image-20231208161815090

image-20231208161944184

image-20231208162448060

image-20231208162409631

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import json
import random
import time
import base64
import binascii

import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


def get_slider_list():
    slider_list = []
    client_x = 300
    client_y = 500
    start_time = int(int(time.time() * 1000) % 1e5)
    width = random.randint(419, 431)
    for slice_distance in range(3, width, 26):
        if width - slice_distance <= 26:
            slice_distance = width
        start_time += random.randint(10, 1000)
        i = start_time
        o = f"{client_x + slice_distance}.00"
        u = f"{client_y + random.randint(-5, 5)}.00"
        a = f"{slice_distance}.00"
        f = f"{i};{o};{u};{a}"
        slider_list.append(f)
    return slider_list


def aes_encrypt(data_string):
    # key = "227V2xYeHTARSh1R".encode('utf-8')
    key_string = "32323756327859654854415253683152"
    key = binascii.a2b_hex(key_string)

    aes = AES.new(
        key=key,
        mode=AES.MODE_ECB
    )
    raw = pad(data_string.encode('utf-8'), 16)
    aes_bytes = aes.encrypt(raw)
    res_string = base64.b64encode(aes_bytes).decode('utf-8')
    return res_string


def run():
    res = requests.get("https://user.qunar.com/passport/login.jsp")
    cookie_dict = res.cookies.get_dict()
    cookie_qn1 = cookie_dict['QN1']

    slider_list = get_slider_list()
    slider_info = {
        "openTime": int((time.time() - random.randint(500, 3000)) * 1000),
        "startTime": int((time.time() - random.uniform(2, 4)) * 1000),
        "endTime": int((time.time() - random.uniform(0, 1)) * 1000),
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
        "uid": cookie_qn1,
        "track": slider_list,
        "acc": [],
        "ori": [],
        "deviceMotion": [{"isTrusted": True} for _ in range(random.randint(10, 100))]
    }

    data_string = json.dumps(slider_info, separators=(',', ':'))
    data = aes_encrypt(data_string)

    r = {
        "appCode": "register_pc",
        "cs": "pc",
        "data": data,
        "orca": 2
    }

    res = requests.post(
        url="https://vercode.qunar.com/inner/captcha/snapshot",
        json=r,
        cookies=cookie_dict
    )
    print(res.text)
    # {"ret":true,"errCode":0,"errMsg":null,"data":{"code":0,"timestamp":1702023820826,"cst":"6a1c1d7572f03ba026892ef69f98f57f","vcd":{"QN271AC":"register_pc","QN271RC":"6a1c1d7572f03ba026892ef69f98f57f","QN271SL":"6a1c1d7572f03ba026892ef69f98f57f"}}}


if __name__ == '__main__':
    run()

3.逆向提交sendLoginCode

轨迹提交后,接下来提交手机号信息。

    mobile : 手机号信息            
slideToken : 轨迹请求返回的cst字段   
     bella : 加密算法需你想         【未知】

image-20231208084041304

3.1 请求分析

image-20231208084431344

image-20231208084449697

3.2 window.Bella

看到这个 window.Bella 后就应该想到一个开发的潜规则:记载某个js文件,在内部将函数赋值给window,后续其他文件中就可以调用此方法。

3.2.1 猜想

网页加载qlogj.js文件,在内部给window.Bella赋值,以供后续PCLoginxxxx.js调用。

image-20231208164059895

image-20231208164259824

3.2.2 HTML验证

将整个js文件拷贝下来,并编写一个HTML文件去引入,然后在浏览器中打开测试。

image-20231208164804861

image-20231208164720358

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script src="sdk.js"></script>
<script>
    var res = window.Bella(
        {slideToken: "15cf502c3128593b1a3237e5c484d6c9"},
        {v: 2}
    )
    console.log(res);
</script>
</body>
</html>

3.3 浏览器补环境实现

无需逆向底层实现,用 jsdom 构造一个浏览器环境去加载js,加载成功后,就可以利用 window.Bella 调用算法。

3.3.1 基础环境

image-20231208170229802

const jsdom = require("jsdom");
const {JSDOM} = jsdom;

const html = `<!DOCTYPE html><p>Hello world</p>`;
const dom = new JSDOM(html, {
    url: "https://user.qunar.com/passport/login.jsp",
    referrer: "https://www.qunar.com/",
    contentType: "text/html"
});
document = dom.window.document;

window = global;
Object.assign(global, {
    location: {
        hash: "",
        host: "user.qunar.com",
        hostname: "user.qunar.com",
        href: "https://user.qunar.com/passport/login.jsp",
        origin: "https://user.qunar.com",
        pathname: "/passport/login.jsp",
        port: "",
        protocol: "https:",
        search: "",
    },
    navigator: {
        appCodeName: "Mozilla",
        appName: "Netscape",
        appVersion: "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
        cookieEnabled: true,
        deviceMemory: 8,
        doNotTrack: null,
        hardwareConcurrency: 4,
        language: "zh-CN",
        languages: ["zh-CN", "zh"],
        maxTouchPoints: 0,
        onLine: true,
        platform: "MacIntel",
        product: "Gecko",
        productSub: "20030107",
        userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
        vendor: "Google Inc.",
        vendorSub: "",
        webdriver: false
    }
});

3.3.2 报错

_0x3d079c = ActiveXObject('Microsoft.XMLHTTP');

image-20231208170458444

定位到代码进行分析:

image-20231208170733718

如果无脑补的话,就是创建 window.XMLHttpRequest 或者 ActiveXObject('Microsoft.XMLHTTP');,例如:

XMLHttpRequest = function () {
    return {
        open:function (){},
        send:function (){},
        onreadystatechange :function (){}
    };
};

window.XMLHttpRequest = XMLHttpRequest;

image-20231208171550433

3.3.3 报错

_0x2c4be4[_0x5a69('0x6c')](_0x5a69('0x6d'), _0x5a69('0x6e'));

image-20231208171813407

看样子,这又是在发送ajax请求,先创建 XMLHttpRequest 然后调用 send 、open 等方法。

猜想:加载js的时,难道还发送ajax请求?果然发送了,还不只一个。
image-20231208172206001

猜想:ajax请求如果和我们的window.Bella算法没关系,是不是可以把这段代码注释掉(不让他发送请求)。

image-20231208172633265

3.3.5 不报错,但卡死

这就有点恶心了。

想要解决,就只能再去分析js代码了(不到万不得已,先不这么干)。

3.3.6 再换个思路

寻找谁给 window.Bella的赋值,如果他在卡主前,那就可以早调用 ,然后主动让程序终止(不走卡主的代码)。

image-20231208174204441

var res = window.Bella(
    {slideToken: "15cf502c3128593b1a3237e5c484d6c9"},
    {v: 2}
)
console.log(res);
process.exit(0);

image-20231208174548653

3.3.7 参数化调用

var res = window.Bella(
    {slideToken: process.argv[2]},
    {v: 2}
)
console.log(res);
process.exit(0);
>>>node v1.js "15cf502c3128593b1a3237e5c484d6c9"

image-20231208174929656

3.4 代码示例(整合)

image-20231208175943309

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import json
import random
import time
import base64
import binascii

import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


def get_slider_list():
    slider_list = []
    client_x = 300
    client_y = 500
    start_time = int(int(time.time() * 1000) % 1e5)
    width = random.randint(419, 431)
    for slice_distance in range(3, width, 26):
        if width - slice_distance <= 26:
            slice_distance = width
        start_time += random.randint(10, 1000)
        i = start_time
        o = f"{client_x + slice_distance}.00"
        u = f"{client_y + random.randint(-5, 5)}.00"
        a = f"{slice_distance}.00"
        f = f"{i};{o};{u};{a}"
        slider_list.append(f)
    return slider_list


def aes_encrypt(data_string):
    # key = "227V2xYeHTARSh1R".encode('utf-8')
    key_string = "32323756327859654854415253683152"
    key = binascii.a2b_hex(key_string)

    aes = AES.new(
        key=key,
        mode=AES.MODE_ECB
    )
    raw = pad(data_string.encode('utf-8'), 16)
    aes_bytes = aes.encrypt(raw)
    res_string = base64.b64encode(aes_bytes).decode('utf-8')
    return res_string


def run():
    res = requests.get("https://user.qunar.com/passport/login.jsp")
    cookie_dict = res.cookies.get_dict()
    cookie_qn1 = cookie_dict['QN1']

    slider_list = get_slider_list()
    slider_info = {
        "openTime": int((time.time() - random.randint(500, 3000)) * 1000),
        "startTime": int((time.time() - random.uniform(2, 4)) * 1000),
        "endTime": int((time.time() - random.uniform(0, 1)) * 1000),
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
        "uid": cookie_qn1,
        "track": slider_list,
        "acc": [],
        "ori": [],
        "deviceMotion": [{"isTrusted": True} for _ in range(random.randint(10, 100))]
    }

    data_string = json.dumps(slider_info, separators=(',', ':'))
    data = aes_encrypt(data_string)

    r = {
        "appCode": "register_pc",
        "cs": "pc",
        "data": data,
        "orca": 2
    }

    res = requests.post(
        url="https://vercode.qunar.com/inner/captcha/snapshot",
        json=r,
        cookies=cookie_dict
    )
    res_dict = res.json()
    slide_token = res_dict['data']["cst"]
    cookie_dict.update(res.cookies.get_dict())

    import subprocess
    res = subprocess.check_output(f'node v1.js "{slide_token}"', shell=True)
    bella_string = res.decode('utf-8').strip()

    res = requests.post(
        url="https://user.qunar.com/weblogin/sendLoginCode",
        data={
            "usersource": "",
            "source": "",
            "ret": "",
            "ref": "",
            "business": "",
            "pid": "",
            "originChannel": "",
            "activityCode": "",
            "origin": "",
            "mobile": "自己的手机号",
            "prenum": "86",
            "loginSource": "1",
            "slideToken": slide_token,
            "smsType": "0",
            "appcode": "register_pc",
            "bella": bella_string,
            "captchaType": ""
        },
        cookies=cookie_dict
    )
    print(res.text)


if __name__ == '__main__':
    run()

4.短信登录

没啥难度,直接携带验证码去登录就行了。

image-20231208180113259

image-20231208181127776

# @课程    : 爬虫逆向实战课
# @讲师    : 武沛齐
# @课件获取 : wupeiqi666
import json
import random
import time
import base64
import binascii

import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


def get_slider_list():
    slider_list = []
    client_x = 300
    client_y = 500
    start_time = int(int(time.time() * 1000) % 1e5)
    width = random.randint(419, 431)
    for slice_distance in range(3, width, 26):
        if width - slice_distance <= 26:
            slice_distance = width
        start_time += random.randint(10, 1000)
        i = start_time
        o = f"{client_x + slice_distance}.00"
        u = f"{client_y + random.randint(-5, 5)}.00"
        a = f"{slice_distance}.00"
        f = f"{i};{o};{u};{a}"
        slider_list.append(f)
    return slider_list


def aes_encrypt(data_string):
    # key = "227V2xYeHTARSh1R".encode('utf-8')
    key_string = "32323756327859654854415253683152"
    key = binascii.a2b_hex(key_string)

    aes = AES.new(
        key=key,
        mode=AES.MODE_ECB
    )
    raw = pad(data_string.encode('utf-8'), 16)
    aes_bytes = aes.encrypt(raw)
    res_string = base64.b64encode(aes_bytes).decode('utf-8')
    return res_string


def run():
    mobile = input("请输入手机号:")

    res = requests.get("https://user.qunar.com/passport/login.jsp")
    cookie_dict = res.cookies.get_dict()
    cookie_qn1 = cookie_dict['QN1']

    slider_list = get_slider_list()
    slider_info = {
        "openTime": int((time.time() - random.randint(500, 3000)) * 1000),
        "startTime": int((time.time() - random.uniform(2, 4)) * 1000),
        "endTime": int((time.time() - random.uniform(0, 1)) * 1000),
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
        "uid": cookie_qn1,
        "track": slider_list,
        "acc": [],
        "ori": [],
        "deviceMotion": [{"isTrusted": True} for _ in range(random.randint(10, 100))]
    }

    data_string = json.dumps(slider_info, separators=(',', ':'))
    data = aes_encrypt(data_string)
    res = requests.post(
        url="https://vercode.qunar.com/inner/captcha/snapshot",
        json={
            "appCode": "register_pc",
            "cs": "pc",
            "data": data,
            "orca": 2
        },
        cookies=cookie_dict
    )
    res_dict = res.json()
    slide_token = res_dict['data']["cst"]
    cookie_dict.update(res.cookies.get_dict())

    import subprocess
    res = subprocess.check_output(f'node v1.js "{slide_token}"', shell=True)
    bella_string = res.decode('utf-8').strip()

    res = requests.post(
        url="https://user.qunar.com/weblogin/sendLoginCode",
        data={
            "usersource": "",
            "source": "",
            "ret": "",
            "ref": "",
            "business": "",
            "pid": "",
            "originChannel": "",
            "activityCode": "",
            "origin": "",
            "mobile": mobile,
            "prenum": "86",
            "loginSource": "1",
            "slideToken": slide_token,
            "smsType": "0",
            "appcode": "register_pc",
            "bella": bella_string,
            "captchaType": ""
        },
        cookies=cookie_dict
    )
    print(res.text)
    cookie_dict.update(res.cookies.get_dict())

    sms_code = input("请输入短信验证码:")
    res = requests.post(
        url="https://user.qunar.com/weblogin/verifyMobileVcode",
        json={
            "piccoloT": "login_register_pc",
            "mobile": mobile,
            "prenum": "86",
            "vcode": sms_code,
            "type": "3",
            "slideToken": slide_token,
            "appcode": "register_pc",
            "loginSource": 1,
            "captchaType": "",
            "source": "",
            "usersource": "",
            "ret": "",
            "ref": "",
            "business": "",
            "pid": "",
            "originChannel": "",
            "activityCode": ""
        }
    )
    cookie_dict.update(res.cookies.get_dict())

    print(res.text)
    print(cookie_dict)

if __name__ == '__main__':
    run()
posted @ 2024-02-11 10:00  凫弥  阅读(76)  评论(0编辑  收藏  举报