helian147 发表于 2021-5-24 16:25

【js逆向】Qfang网的解析与爬取

本帖最后由 helian147 于 2021-5-24 17:13 编辑

一、链接的请求逻辑

先用F12进入浏览器控制台看看,直接被debugger糊一脸。还是Fiddler来试试,不能这也不行吧。

看前几个请求,在第三个请求发现了返回的网页数据,往上回溯:

第一个get请求的response返回set-cookie、且返回一个js文件;
第二个get请求为302重定向,URL带参数,response返回新的set-cookie;
第三个get请求,返回HTML内容。

显然,解析js文件,获得第二个请求的URL参数是关键。
各个请求的headers, cookie可以通过requests.Session来保持、传递。








二、js逆向
分析js文件的目的就是获得第二个get请求的URL参数生成逻辑。
【'jsjiami.com.v6'   这是js文件直接提示的加密版本号】

从前面第一步获得js文件保存,格式化,是ob混淆后的一堆s山,要分析出参数生成逻辑不得头秃?
1、先清除浏览器cookie、缓存等数据,进浏览器控制台,有无限debugger挡着,Deactivate Breakpoints先统统禁止掉,此时页面已加载完毕。
debugger被禁止了,自己也没法用插件下debugger了,只能手撕了:

二分法,将各个明显的function都打下断点,然后逐步迫近。首先得下断点在无限debugger之前,多打几个断点,不要钱的。
重新加载页面,被第一个断点断下。
之后,被断下,跳转到_0x305110,执行完,返回;
再次进入_0x305110,执行其内的_x7fc51d(0x0),再次返回;
再次进入_0x305110,执行其内的_x7fc51d(),然后就是无限debugger。

查看884行附近if-else语句,不仅有_x7fc51d(0x0),还有一句_x7fc51d(‘0’)语句未被执行到,这可能是正常运行时的语句。
这个‘0’是哪里赋值的,只要向_x7fc51d输入参数‘0’,就可让其去想去的地方,而不是无限debugger卡着。
_0x459edd,很明显,而_0x459edd就在if语句上头呢: _0x459edd = f _0x7fc51d(_0x3cff02)
验证一下:
console控制台直接输入 _0x3cff02,获得其值正是整数0
console控制台将其值修改为‘0’字符,输入 _0x3cff02 = ‘0’
然后让断点下一步,来到新地方,接着直接跳出第二个get请求。












2、很明显了,重新来过:
先在上一步跳转的附近多下几个断点;
清除浏览器cookie、缓存,重新加载页面;
为避免反复被debugger,在try语句附近查看_0x3cff02值,为整数0,直接修改赋值为字符‘0’;
1023行附近,_0x21f02d变量生成了URL参数,目的达到。






3、回溯_0x21f02d变量的生成过程
直接往上回溯即可,有两个坑:

var_0x14e579 , var _0x351708   这个2个变量,每次get获得js文件中的值都不同;


var _0x4ce3 变量存储了大量变量和函数,一般js逆向时,找到参数的生成过程后,直接扒拉下来相关的js代码,运行即可,包括node,浏览器等都可以运行js代码,这个_0x4ce3扒下来后运行直接是node崩溃、浏览器控制台崩溃,吐血......替换掉_0x4ce3[]对应的值。

function _0xcff1b8(_0x358fd9) {
    var _0x179d92 = {
      'TXsUM': function (_0x2383e2, _0x1de425) {
            return _0x2383e2(_0x1de425);
      },
      'HQeHK': "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
      'ZUcpH': function (_0x248b52, _0x5ed497) {
            return _0x248b52 < _0x5ed497;
      },
      'FbBTx': function (_0x36946b, _0x3a2f11) {
            return _0x36946b & _0x3a2f11;
      },
      'jhkwT': function (_0x4e9119, _0xa070d6) {
            return _0x4e9119 == _0xa070d6;
      },
      'WJmPx': function (_0x51583c, _0x5367ea) {
            return _0x51583c !== _0x5367ea;
      },
      'NiLhy': "BVUeB",
      'FjkCX': function (_0x3ddcc5, _0x2b6867) {
            return _0x3ddcc5 >> _0x2b6867;
      },
      'mBHDt': function (_0x2a91d8, _0x228d4f) {
            return _0x2a91d8 << _0x228d4f;
      },
      'aCQPW': function (_0x1306f3, _0x598fc9) {
            return _0x1306f3 & _0x598fc9;
      },
      'Walbv': function (_0x793957, _0x39b61b) {
            return _0x793957 >> _0x39b61b;
      },
      'NWBHv': function (_0x4586db, _0x3a8868) {
            return _0x4586db | _0x3a8868;
      },
      'fBZaG': function (_0x68d10e, _0x3db0c3) {
            return _0x68d10e >> _0x3db0c3;
      },
      'pOsdD': function (_0x1d641f, _0x4f2573) {
            return _0x1d641f & _0x4f2573;
      },
      'atQDi': function (_0x1b239c, _0x21e5f5) {
            return _0x1b239c | _0x21e5f5;
      },
      'WtLrH': function (_0x31fc35, _0x3b7945) {
            return _0x31fc35 << _0x3b7945;
      },
      'pyPRI': function (_0x328d78, _0x36b47f) {
            return _0x328d78 >> _0x36b47f;
      },
      'LVufl': function (_0x1bdb11, _0x337636) {
            return _0x1bdb11 << _0x337636;
      },
      'NNOIu': function (_0x4d3d01, _0x17a933) {
            return _0x4d3d01 & _0x17a933;
      },
      'OIfGa': function (_0xf3dd18, _0x7f485e) {
            return _0xf3dd18 & _0x7f485e;
      }
    };
    var _0xfed051 = _0x179d92['HQeHK'];
    var _0x2139d5 = _0x358fd9["length"]; // _0x4ce3
    var _0x10071f = '';
    for (var _0x23e584 = 0x0; _0x179d92['ZUcpH'](_0x23e584, _0x2139d5); ) {
      var _0x2fa93b = _0x179d92['FbBTx'](_0x358fd9["charCodeAt"](_0x23e584++), 0xff);
      if (_0x179d92["jhkwT"](_0x23e584, _0x2139d5)) {
            if (_0x179d92["WJmPx"](_0x179d92["NiLhy"], _0x179d92["NiLhy"])) {
                _0x179d92["TXsUM"](result, '0');
            } else {
                _0x10071f += _0xfed051["charAt"](_0x179d92["FjkCX"](_0x2fa93b, 0x2));
                _0x10071f += _0xfed051["charAt"](_0x179d92["mBHDt"](_0x179d92["aCQPW"](_0x2fa93b, 0x3), 0x4));
                _0x10071f += '==';
                break;
            }
      }
      var _0x3a4809 = _0x358fd9["charCodeAt"](_0x23e584++);      
      if (_0x179d92["jhkwT"](_0x23e584, _0x2139d5)) {         
            _0x10071f += _0xfed051["charAt"](_0x179d92["Walbv"](_0x2fa93b, 0x2));            
            _0x10071f += _0xfed051["charAt"](_0x179d92["NWBHv"](_0x179d92["mBHDt"](_0x179d92["aCQPW"](_0x2fa93b, 0x3), 0x4), _0x179d92["fBZaG"](_0x179d92["aCQPW"](_0x3a4809, 0xf0), 0x4)));            
            _0x10071f += _0xfed051["charAt"](_0x179d92["mBHDt"](_0x179d92["pOsdD"](_0x3a4809, 0xf), 0x2));            
            _0x10071f += '=';
            break;
      }
      var _0x3e2d13 = _0x358fd9["charCodeAt"](_0x23e584++);
      _0x10071f += _0xfed051["charAt"](_0x179d92["fBZaG"](_0x2fa93b, 0x2));
      _0x10071f += _0xfed051["charAt"](_0x179d92["atQDi"](_0x179d92["WtLrH"](_0x179d92["pOsdD"](_0x2fa93b, 0x3), 0x4), _0x179d92["pyPRI"](_0x179d92["pOsdD"](_0x3a4809, 0xf0), 0x4)));
      _0x10071f += _0xfed051["charAt"](_0x179d92["atQDi"](_0x179d92["LVufl"](_0x179d92["NNOIu"](_0x3a4809, 0xf), 0x2), _0x179d92["pyPRI"](_0x179d92["NNOIu"](_0x3e2d13, 0xc0), 0x6)));
      _0x10071f += _0xfed051["charAt"](_0x179d92["OIfGa"](_0x3e2d13, 0x3f));
    }
    return _0x10071f;
}


var args = process.argv.splice(2);
console.log(_0xcff1b8(args));


三、python爬取数据
解析后,python代码就好办了。
import requests
from lxml import etree
import time
import re
from subprocess import check_output

headers = {
    "User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
    'Host':'shenzhen.qfang.com',
    'Connection': 'keep-alive'
    }


def c_0x25d6be(a, b):
    j = 0
    for i in range(0, len(a)):
      j += ord(a)
    j *= int(b)
    j += 0x1b207
    str_wzws = "WZWS_CONFIRM_PREFIX_LABEL" + str(j)
    print(str_wzws)
    return str_wzws


# 由于有302重定向,以Session保持、传递response的set-cookie
s = requests.Session()

# 第一次get请求:js文件中的2个变量值提取、计算得出第二次get的url参数
url = 'https://shenzhen.qfang.com/sale'
rsp_1 = s.get(url=url, headers=headers)

rsp_1_0x14e579 = re.findall(r"_0x14e579='(.*?)';" ,rsp_1.text)
rps_1_0x351708 = re.findall(r"_0x351708='(.*?)';" ,rsp_1.text)
str_wzws = c_0x25d6be(rsp_1_0x14e579, rps_1_0x351708)

# process模块的check_output, 以命令行运行node
js_open = check_output(['node', r'd:\new-3.js', str_wzws], timeout=100)
url_path = js_open.decode('utf8').strip()
print(url_path)
time.sleep(1)


# 第二次get请求:
headers.update({'Referer':'https://shenzhen.qfang.com/sale'})

url_2 = 'https://shenzhen.qfang.com/WZWSREL3NhbGU=?wzwschallenge={0}'.format(url_path)
rsp_2 = s.get(url=url_2, headers=headers)
time.sleep(1)


# 第三次get请求:解析response获得数据
rsp_3 = s.get(url=url, headers=headers)
selector = etree.HTML(rsp_3.text)
x = selector.xpath('/html/body/div/div/div/div/ul/li/div/div/a/text()')
print(x)


⊙⌒⊙ 发表于 2021-5-25 10:05

get 换成了post,立即就出来了,这是为什么呀???为什么呀???求解原理!

涛之雨 发表于 2021-5-24 21:09

var _0x500dd8 = '/WZWSREL3NhbGU=';
var _0x14e579 = '#IPP)&93(;Q]V!Yaz';
var _0x351708 = '4231';
var _0x41f35b = 'WZWS_METHOD';
var _0x349042 = 'WZWS_PARAMS';

function _0xcff1b8(_0x358fd9) {
var _0xfed051 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var _0x2139d5 = _0x358fd9.length;
var _0x10071f = '';

for (var _0x23e584 = 0; _0x23e584 < _0x2139d5;) {
    var _0x2fa93b = _0x358fd9.charCodeAt(_0x23e584++) & 255;

    if (_0x23e584 == _0x2139d5) {
      _0x10071f += _0xfed051.charAt(_0x2fa93b >> 2);
      _0x10071f += _0xfed051.charAt((_0x2fa93b & 3) << 4);
      _0x10071f += '==';
      break;
    }

    var _0x3a4809 = _0x358fd9.charCodeAt(_0x23e584++);

    if (_0x23e584 == _0x2139d5) {
      _0x10071f += _0xfed051.charAt(_0x2fa93b >> 2);
      _0x10071f += _0xfed051.charAt((_0x2fa93b & 3) << 4 | (_0x3a4809 & 240) >> 4);
      _0x10071f += _0xfed051.charAt((_0x3a4809 & 15) << 2);
      _0x10071f += '=';
      break;
    }

    var _0x3e2d13 = _0x358fd9.charCodeAt(_0x23e584++);

    _0x10071f += _0xfed051.charAt(_0x2fa93b >> 2);
    _0x10071f += _0xfed051.charAt((_0x2fa93b & 3) << 4 | (_0x3a4809 & 240) >> 4);
    _0x10071f += _0xfed051.charAt((_0x3a4809 & 15) << 2 | (_0x3e2d13 & 192) >> 6);
    _0x10071f += _0xfed051.charAt(_0x3e2d13 & 63);
}

return _0x10071f;
}

function _0x2a8db4(_0x589677) {
var _0x98cb2b = new Array(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1);

var _0x5cf96d = _0x589677.length;
var _0xc6e80d = '';

for (var _0x145031 = 0; _0x145031 < _0x5cf96d;) {
    var _0x3f81b5, _0x1ac542, _0x3f8231, _0x3661aa;

    do {
      _0x3f81b5 = _0x98cb2b;
    } while (_0x145031 < _0x5cf96d && _0x3f81b5 == -1);

    if (_0x3f81b5 == -1) break;

    do {
      _0x1ac542 = _0x98cb2b;
    } while (_0x145031 < _0x5cf96d && _0x1ac542 == -1);

    if (_0x1ac542 == -1) break;
    _0xc6e80d += String.fromCharCode(_0x3f81b5 << 2 | (_0x1ac542 & 48) >> 4);

    do {
      _0x3f8231 = _0x589677.charCodeAt(_0x145031++) & 255;
      if (_0x3f8231 == 61) return _0xc6e80d;
      _0x3f8231 = _0x98cb2b;
    } while (_0x145031 < _0x5cf96d && _0x3f8231 == -1);

    if (_0x3f8231 == -1) break;
    _0xc6e80d += String.fromCharCode((_0x1ac542 & 15) << 4 | (_0x3f8231 & 60) >> 2);

    do {
      _0x3661aa = _0x589677.charCodeAt(_0x145031++) & 255;
      if (_0x3661aa == 61) return _0xc6e80d;
      _0x3661aa = _0x98cb2b;
    } while (_0x145031 < _0x5cf96d && _0x3661aa == -1);

    if (_0x3661aa == -1) break;
    _0xc6e80d += String.fromCharCode((_0x3f8231 & 3) << 6 | _0x3661aa);
}

return _0xc6e80d;
}

function _0x13698a() {
var _0x338d15 = 0;
var _0xbe152f = 0;

for (_0xbe152f = 0; _0xbe152f < _0x14e579.length; _0xbe152f++) {
    _0x338d15 += _0x14e579.charCodeAt(_0xbe152f);
}

_0x338d15 *= _0x351708;
_0x338d15 += 111111;
return "WZWS_CONFIRM_PREFIX_LABEL" + _0x338d15;
}

function _0x1eb786(_0x180717, _0x349042) {
var _0x49b27c = document.createElement("form");

_0x49b27c.action = _0x180717;
_0x49b27c.method = "post";
_0x49b27c.style.display = "none";

var _0x48c71a = _0x2a8db4(_0x349042);

if (_0x48c71a.search('=') != -1) {
    var _0xce49ba = _0x48c71a.split('&');

    for (var _0x125e40 = 0; _0x125e40 < _0xce49ba.length; _0x125e40++) {
      var _0x45f660 = document.createElement("textarea");

      var _0x2409cd = _0xce49ba;

      var _0x25642e = _0x2409cd.split('=');

      _0x45f660.name = _0x25642e;
      _0x45f660.value = _0x25642e;

      _0x49b27c.appendChild(_0x45f660);
    }
}

document.body.appendChild(_0x49b27c);

_0x49b27c.submit();

return _0x49b27c;
}

!function submit_answer() {
var _0x59fc72 = _0x13698a();

var _0x25d6be = _0xcff1b8(_0x59fc72.toString());

var _0x21f02d = _0x500dd8 + "?wzwschallenge=" + _0x25d6be;

if (_0x41f35b == "post") {
    _0x1eb786(_0x21f02d, _0x349042);
} else {
    window.location = _0x21f02d;
}
}();

源码大概是这样,可以对照看一下

986244073 发表于 2021-5-24 21:08

看了之后 学到了 好的思路

Nemoris丶 发表于 2021-5-24 21:36

{:1_918:}针对这个解迷,你其实可以试试用post请求看看,你会发现新天地。好像这种加密的,跟换了post请求后全部都失效了

helian147 发表于 2021-5-24 21:44

感谢大佬!

这种以代码生成的无限debuger,能有比较简单方便的方法绕过吗?
如能过了这个debuger,利用CC11001100的下断点来追踪URL参数,就会很快定位参数的生成位置了吧
【https://github.com/CC11001100/crawler-js-hook-framework-public】

helian147 发表于 2021-5-24 21:52

本帖最后由 helian147 于 2021-5-24 21:54 编辑

Nemoris丶 发表于 2021-5-24 21:36
针对这个解迷,你其实可以试试用post请求看看,你会发现新天地。好像这种加密的,跟换了post请求 ...
我......发现了什么???

import requests
from lxml import etree

headers = {
    "User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
    'Host':'shenzhen.qfang.com',
    'Connection': 'keep-alive'
    }

url = 'https://shenzhen.qfang.com/sale'
rsp_3 = requests.post(url=url, headers=headers)
selector = etree.HTML(rsp_3.text)
x = selector.xpath('/html/body/div/div/div/div/ul/li/div/div/a/text()')
print(x)




太神奇了,服务器那边肿了么? 感谢感谢,哈哈哈

8814202 发表于 2021-5-25 00:10

Q房网员工路过

xixicoco 发表于 2021-5-25 01:43

呵呵,感谢各位大佬出手干饭

鸭子咯咯哒~ 发表于 2021-5-25 06:11

helian147 发表于 2021-5-24 21:52
我......发现了什么???

import requests


请求头什么参数都能设置吗

helian147 发表于 2021-5-25 08:02

鸭子咯咯哒~ 发表于 2021-5-25 06:11
请求头什么参数都能设置吗

URL参数, 请求头里的参数, POST请求的data表单参数,代{过}{滤}理设置proxyies,证书验证verify,这些都有自己的设置。

请求头headers,服务器需要验证request的哪些参数,就在headers里面设置哪些参数,具体会有哪些参数,可以用浏览器的控制台、postman、fiddler等工具查看、验证。

所以你的疑问太笼统。
如果为了让服务器返回我们需要的response数据,那要按服务器的request要求设置。
如果是试探服务器漏洞,那随便设置,只要请求数据能发出去。
页: [1] 2
查看完整版本: 【js逆向】Qfang网的解析与爬取