爬虫逆向实战(二十四)--某鸟记录中心

一、数据接口分析

主页地址:某鸟记录中心

1、抓包

通过抓包可以发现数据接口是front/record/search/page
爬虫逆向实战(二十四)--某鸟记录中心_第1张图片

2、判断是否有加密参数

  1. 请求参数是否加密?
    通过查看“载荷”模块可以发现,请求参数是加密的
    爬虫逆向实战(二十四)--某鸟记录中心_第2张图片
  2. 请求头是否加密?
    通过查看“请求标头”可以发现RequestidSign是加密参数,并且有一个Timestamp时间戳
    爬虫逆向实战(二十四)--某鸟记录中心_第3张图片
  3. 响应是否加密?
    通过查看“响应”模块可以发现,响应中的数据是加密数据
    爬虫逆向实战(二十四)--某鸟记录中心_第4张图片
  4. cookie是否加密?

二、加密位置定位

1、加密参数以及请求头加密

(1)看启动器

查看启动器发现里面有一个pullData的调用堆栈,点进去查看
爬虫逆向实战(二十四)--某鸟记录中心_第5张图片
点进去后,可以看出,此处是发送ajax请求的位置,在此处下断点,再次翻页获取数据,发现可以断住,但是此处是明文数据,所以加密位置不在这里。
爬虫逆向实战(二十四)--某鸟记录中心_第6张图片

(2)搜索关键字

因为“载荷”是一整个密文,没有关键字,所以无法搜索

(3)hook

因为“载荷”是一整个密文,所以网站大概率会使用JSON.stringify将数据转换为json字符串再进行加密,所以我们可以hookJSON.stringify,hook代码:

var my_stringify = JSON.stringify;
JSON.stringify = function (params) {
    debugger
    console.log("json_stringify params:",params);
    return my_stringify(params);
};

运行hook代码,再次获取数据,发现可以断住
爬虫逆向实战(二十四)--某鸟记录中心_第7张图片
继续调试执行,可以发现,网站是使用ajaxSetup设置了ajax请求的全局配置,请求头和加密参数都是在此处进行赋值的。
爬虫逆向实战(二十四)--某鸟记录中心_第8张图片

2、响应数据

(1)hook

因为响应加密数据一般都是json数据加密,所以解密后会使用JSON.parse进行解密,所以我们可以对JSON.parse进行hook
hook代码段:

var my_parse = JSON.parse;
JSON.parse = function (params) {
    debugger
    console.log("json_parse params:",params);
    return my_parse(params);
};

运行hook代码,再次获取数据,发现可以断住明文
爬虫逆向实战(二十四)--某鸟记录中心_第9张图片
接着调试执行,就可以找到解密位置
爬虫逆向实战(二十四)--某鸟记录中心_第10张图片

三、扣js代码

1、加密参数及请求头

将定位到的加密位置的代码扣出,可以发现网站的数据加密是使用的JSEncrypt模块进行的RAS加密,所以我们可以直接使用标准模块进行加密,但是网站使用的encrypt.encryptUnicodeLong方法,node.js中是没有的,所以我们还需要将这个方法扣出来。下方请求头加密使用的md5,我们同样可以使用标准模块进行加密。
爬虫逆向实战(二十四)--某鸟记录中心_第11张图片

2、响应数据

将定位到的解密位置的代码扣出,进入到网站的解密方法BIRDREPORT_APIJS.decode中,可以发现,网站是使用的AES解密的,所以我们可以使用标准的AES模块进行解密。
爬虫逆向实战(二十四)--某鸟记录中心_第12张图片
JavaScript源码:

const JSEncrypt = require('jsencrypt');

var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var b64pad = "=";

function hex2b64(h) {
    var i;
    var c;
    var ret = "";
    for (i = 0; i + 3 <= h.length; i += 3) {
        c = parseInt(h.substring(i, i + 3), 16);
        ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63);
    }
    if (i + 1 == h.length) {
        c = parseInt(h.substring(i, i + 1), 16);
        ret += b64map.charAt(c << 2);
    } else if (i + 2 == h.length) {
        c = parseInt(h.substring(i, i + 2), 16);
        ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4);
    }
    while ((ret.length & 3) > 0) {
        ret += b64pad;
    }
    return ret;
}

JSEncrypt.prototype.encryptUnicodeLong = function (string) {
    var k = this.getKey();
    //根据key所能编码的最大长度来定分段长度。key size - 11:11字节随机padding使每次加密结果都不同。
    var maxLength = ((k.n.bitLength() + 7) >> 3) - 11;
        var subStr = "", encryptedString = "";
        var subStart = 0, subEnd = 0;
        var bitLen = 0, tmpPoint = 0;
        for (var i = 0, len = string.length; i < len; i++) {
            //js 是使用 Unicode 编码的,每个字符所占用的字节数不同
            var charCode = string.charCodeAt(i);
            if (charCode <= 0x007f) {
                bitLen += 1;
            } else if (charCode <= 0x07ff) {
                bitLen += 2;
            } else if (charCode <= 0xffff) {
                bitLen += 3;
            } else {
                bitLen += 4;
            }
            //字节数到达上限,获取子字符串加密并追加到总字符串后。更新下一个字符串起始位置及字节计算。
            if (bitLen > maxLength) {
                subStr = string.substring(subStart, subEnd)
                encryptedString += k.encrypt(subStr);
                subStart = subEnd;
                bitLen = bitLen - tmpPoint;
            } else {
                subEnd = i;
                tmpPoint = bitLen;
            }
        }
        subStr = string.substring(subStart, len)
        encryptedString += k.encrypt(subStr);
        return hex2b64(encryptedString);
};

const CryptoJS = require('crypto-js')

function getUuid() {
    var s = [];
    var a = "0123456789abcdef";
    for (var i = 0; i < 32; i++) {
        s[i] = a.substr(Math.floor(Math.random() * 0x10), 1)
    }
    s[14] = "4";
    s[19] = a.substr((s[19] & 0x3) | 0x8, 1);
    s[8] = s[13] = s[18] = s[23];
    var b = s.join("");
    return b
}

function sort_ASCII(a) {
    var b = new Array();
    var c = 0;
    for (var i in a) {
        b[c] = i;
        c++
    }
    var d = b.sort();
    var e = {};
    for (var i in d) {
        e[d[i]] = a[d[i]]
    }
    return e
}

function url2json(a) {
    var b = /^[^\?]+\?([\w\W]+)$/
        , reg_para = /([^&=]+)=([\w\W]*?)(&|$|#)/g
        , arr_url = b.exec(a)
        , ret = {};
    if (arr_url && arr_url[1]) {
        var c = arr_url[1], result;
        while ((result = reg_para.exec(c)) != null) {
            ret[result[1]] = result[2]
        }
    }
    return ret
}

function dataTojson(a) {
    var b = [];
    var c = {};
    b = a.split('&');
    for (var i = 0; i < b.length; i++) {
        if (b[i].indexOf('=') != -1) {
            var d = b[i].split('=');
            if (d.length == 2) {
                c[d[0]] = d[1]
            } else {
                c[d[0]] = ""
            }
        } else {
            c[b[i]] = ''
        }
    }
    return c
}

const serialize = function (a) {
    var b = [];
    for (var p in a)
        if (a.hasOwnProperty(p) && a[p]) {
            b.push(encodeURIComponent(p) + '=' + encodeURIComponent(a[p]))
        }
    return b.join('&')
};
var paramPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvxXa98E1uWXnBzXkS2yHUfnBM6n3PCwLdfIox03T91joBvjtoDqiQ5x3tTOfpHs3LtiqMMEafls6b0YWtgB1dse1W5m+FpeusVkCOkQxB4SZDH6tuerIknnmB/Hsq5wgEkIvO5Pff9biig6AyoAkdWpSek/1/B7zYIepYY0lxKQIDAQAB";
var encrypt = new JSEncrypt();
encrypt.setPublicKey(paramPublicKey);


function get_params() {
    var b_data = 'page=1&limit=20&taxonid=&startTime=&endTime=&province=&city=&district=&pointname=%E6%B2%B3%E5%8D%97&username=&serial_id=&ctime=&taxonname=&state=&mode=0&outside_type=0'
    var c = Date.parse(new Date());
    var d = getUuid();
    var e = JSON.stringify(sort_ASCII(dataTojson(b_data || '{}')));
    b_data = encrypt.encryptUnicodeLong(e);
    var f = CryptoJS.MD5(e + d + c).toString();
    return [{
        timestamp: c.toString(),
        requestId: d.toString(),
        sign: f.toString()
    }, b_data]
}

var key = '3583ec0257e2f4c8195eec7410ff1619';
var iv = 'd93c0d5ec6352f20'
apijs_decode = function(a) {
        var b = CryptoJS.enc.Utf8.parse(key);
        var c = CryptoJS.enc.Utf8.parse(iv);
        var d = CryptoJS.AES.decrypt(a, b, {
            iv: c,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        });
        return d.toString(CryptoJS.enc.Utf8)
    }

function get_data(r_data) {
    var decode_str = apijs_decode(r_data);
    return JSON.parse(decode_str)
}

你可能感兴趣的:(爬虫逆向实战,爬虫)