• js逆向爬虫


    js逆向


    js常见的加密方式

    • 加密在前端开发和爬虫中是经常遇见的。掌握了加密算法且可以将加密的密文进行解密破解的,也是你从一个编程小白到大神级别质的一个飞跃。且加密算法的熟练和剖析也是很有助于帮助我们实现高效的js逆向。下述只把我们常用的加密方法进行总结。不去深究加密的具体实现方式。

    • 常见的加密算法基本分为这几类,

      • 线性散列算法(签名算法)MD5
      • 对称性加密算法 AES DES
      • 非对称性加密算法 RSA

    Md5加密

    • MD5是一种被广泛使用的线性散列算法,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整的一致性。且MD5加密之后产生的是一个固定长度(32位或16位)的数据。
    • 解密:
      • 常规讲MD5是不存在解密的。但是理论上MD5是可以进行反向暴力破解的。暴力破解的大致原理就是用很多不同的数据进行加密后跟已有的加密数据进行对比,由此来寻找规律。理论上只要数据量足够庞大MD5是可以被破解的。但是要注意,破解MD5是需要考虑破解的成本(时间和机器性能)。假设破解当前的MD5密码需要目前计算能力最优秀的计算机工作100年才能破解完成。那么当前的MD5密码就是安全的。
    • 增加破解成本的方法(方法很多,这里只说我常用的)。
      • 使用一段无意义且随机的私匙进行MD5加密会生成一个加密串,我们暂且称之为串1
      • 将要加密的的数据跟串1拼接,再进行一次MD5,这时会生成串2
      • 将串2再次进行MD5加密,这时生成的串3就是我们加密后的数据。
    • 我们在注册账号时的密码一般都是用的MD5加密。

    In [ ]:

    <html>
    	<script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.js"></script>
        <script type="text/javascript">
    		var hashCode = md5("i am bobo!"); 
    		alert(hashCode)
    	</script>
    		
    </html>
    

    DES/AES加密

    • DES全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的算法。该加密算法是一种对称加密方式,其加密运算、解密运算需要使用的是同样的密钥(一组字符串)即可。
    • 注意:
      • 现在用AES这个标准来替代原先的DES。
      • AES和DES的区别:
        • 加密后密文长度的不同:
          • DES加密后密文长度是8的整数倍
          • AES加密后密文长度是16的整数倍
        • 应用场景的不同:
          • 企业级开发使用DES足够安全
          • 如果要求高使用AES
        • DES和AES切换只需要修改 CryptoJS.AES <=> CryptoJS.DES
    • 使用DES/AES进行数据交互时要求双方都拥有相同的私匙
    • 破解方法:
      • 暴力破解。
      • DES如果使用 56 位的密钥,则可能的密钥数量是 2 的 56 次方个。只要计算足够强大是可以被破解的
    • DES算法的入口参数有三个:
      • Key、Data、Mode,padding。
        • Key为7个字节共56位,是DES算法的工作密钥;
        • Data为8个字节64位,是要被加密或被解密的数据;
        • Mode为DES的工作方式。
        • padding为填充模式,如果加密后密文长度如果达不到指定整数倍(8个字节、16个字节),填充对应字符
          • padding的赋值固定为CryptoJS.pad.Pkcs7即可

    In [ ]:

    <html>
    	<script src="https://cdn.bootcss.com/crypto-js/3.1.9-1/crypto-js.js"></script>
        <script type="text/javascript">
    		  var aseKey = "12345678"     //定制秘钥,长度必须为:8/16/32位
    		  var message = "i am bobo,who are you ?";
    		  //加密 DES/AES切换只需要修改 CryptoJS.AES <=> CryptoJS.DES
    		  var encrypt = CryptoJS.DES.encrypt(message, CryptoJS.enc.Utf8.parse(aseKey), {
    			mode: CryptoJS.mode.ECB,
    			padding: CryptoJS.pad.Pkcs7
    		  }).toString();
    
    		  alert(encrypt); // 0Gh9NGnwOpgmB525QS0JhVJlsn5Ev9cHbABgypzhGnM
    
    		  //解密
    		  var decrypt = CryptoJS.DES.decrypt(encrypt, CryptoJS.enc.Utf8.parse(aseKey), {
    			mode: CryptoJS.mode.ECB,
    			padding: CryptoJS.pad.Pkcs7
    		  }).toString(CryptoJS.enc.Utf8);
    
    		  alert(decrypt); // 我是一个密码
    	</script>
    		
    </html>
    

    RSA加密

    • RSA加密:
      • RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用。
    • 非对称加密算法:
      • 非对称加密算法需要两个密钥:
        • 公开密钥(publickey:简称公钥)
        • 私有密钥(privatekey:简称私钥)
        • 公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
    • 注意:
      • 使用时都是使用公匙加密使用私匙解密。公匙可以公开,私匙自己保留。
      • 算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。
    • 使用流程和场景介绍
      • 通过公匙加密,使用私匙解密。私匙是通过公匙计算生成的。假设ABC三方之间相互要进行加密通信。大家相互之间使用公匙进行信息加密,信息读取时使用各自对应的私匙进行信息解密
      • 用户输入的支付密码会通过RSA加密
    • 公钥私钥生成方式:

    In [ ]:

    <html>
    	<script src="https://cdn.bootcss.com/jsencrypt/3.0.0-beta.1/jsencrypt.js"></script>
        <script type="text/javascript">
            //公钥
            var PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALyBJ6kZ/VFJYTV3vOC07jqWIqgyvHulv6us/8wzlSBqQ2+eOTX7s5zKfXY40yZWDoCaIGk+tP/sc0D6dQzjaxECAwEAAQ==-----END PUBLIC KEY-----';
            //私钥
    		var PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvIEnqRn9UUlhNXe84LTuOpYiqDK8e6W/q6z/zDOVIGpDb545NfuznMp9djjTJlYOgJogaT60/+xzQPp1DONrEQIDAQABAkEAu7DFsqQEDDnKJpiwYfUE9ySiIWNTNLJWZDN/Bu2dYIV4DO2A5aHZfMe48rga5BkoWq2LALlY3tqsOFTe3M6yoQIhAOSfSAU3H6jIOnlEiZabUrVGqiFLCb5Ut3Jz9NN+5p59AiEA0xQDMrxWBBJ9BYq6RRY4pXwa/MthX/8Hy+3GnvNw/yUCIG/3Ee578KVYakq5pih8KSVeVjO37C2qj60d3Ok3XPqBAiEAqGPvxTsAuBDz0kcBIPqASGzArumljkrLsoHHkakOfU0CIDuhxKQwHlXFDO79ppYAPcVO3bph672qGD84YUaHF+pQ-----END PRIVATE KEY-----';
    		//使用公钥加密
            var encrypt = new JSEncrypt();//实例化加密对象
            encrypt.setPublicKey(PUBLIC_KEY);//设置公钥
            var encrypted = encrypt.encrypt('hello bobo!');//对指定数据进行加密
    		alert(encrypted)
            //使用私钥解密
            var decrypt = new JSEncrypt();
            decrypt.setPrivateKey(PRIVATE_KEY);//设置私钥
            var uncrypted = decrypt.decrypt(encrypted);//解密
    		alert(uncrypted);
        </script>
    </html>
    

    base64伪加密

    • Base64是一种用64个字符来表示任意二进制数据的方法。base64是一种编码方式而不是加密算法。只是看上去像是加密而已。
    • Base64使用A--Z,a--z,0--9,+,/ 这64个字符实现对数据进行加密。

    In [ ]:

    <html>
        <script type="text/javascript">
    		// 创建Base64对象
    		var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9+/=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/rn/g,"n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}
    		 
    		// 定义字符串
    		var string = 'i am bobo!';
    		 
    		// 加密
    		var encodedString = Base64.encode(string);
    		alert(encodedString); 
    		 
    		// 解密
    		var decodedString = Base64.decode(encodedString);
    		alert(decodedString); 
    	</script>
    		
    </html>
    

    拓展:https加密

    • https是基于http和SSL/TLS实现的一个协议,他可以保证在网络上传输的数据都是加密的,从而保证数据安全。

    • 接下来我们从http协议开始,提出想法并逐步进行分析,最终实现Https。

      1. http协议是不安全的。
        • 在https诞生之前,所有网站都使用http协议,而http协议在数据传输的过程中都是明文,所以可能存在数据泄露和篡改。
        • image-20220331142728826
      1. 使用对称秘钥进行数据加密
        • 为了防止数据泄露和篡改,我们对数据进行加密,如:生成一个对称密码【DKUFHNAF897123F】,将对称秘钥分别交给浏览器和服务器端,他们之间传输的数据都使用对称秘钥进行加密和解密。
        • image-20220331142718097
    • 请求和响应流程如下:

      • 客户端使用对称秘钥对请求进行加密,并发送给服务端。
      • 服务端接收到密文之后,使用对称秘钥对密文进行解密,然后处理请求。 最后再使用对称秘钥把要返回的内容再次加密,返回给客户端。
      • 客户端接收到密文之后,使用对称秘钥进行解密,并获取最终的响应内容。
    • 如此一来,数据传输都是密文,解决了明文传输数据的问题。但是,这么干有bug。

      • 浏览器如何获取对称秘钥?
      • 每个客户端的对称秘钥相同,浏览器能拿到对称秘钥,那么黑客也可以拿到,所以,数据加密也就没有意义了。
      1. 动态对称秘钥和非对称秘钥
        • 为了解决对称秘钥动态性以及让客户端和服务端安全的获取对称秘钥,可以引入非对称秘钥机制。image-20220331142705701
    • 如此一来,解决了 动态对称秘钥 和 数据加密的问题,因为每个用户的对称秘钥都是随机生成且传输的过程中都使用公钥加密(公钥加密的数据只有私钥能解密),所有黑客无法截获对称秘钥。而数据传输是通过对称秘钥加密过的,所以黑客即使能获取数据也无法去解密看到真实的内容。 看似无懈可击,但是,这么干还是又bug。

    • 如果黑客在上图 【步骤2】劫持,黑客把自己的公钥返回给客客户端,那么客户端会使用黑客的公钥来加密对称秘钥,黑客在【步骤6】截获请求,使用自己的私钥获取对称秘钥,后面过程全都会完蛋...

      1. CA证书的应用
        • 使用 ca 证书可以解决黑客劫持的问题。
        • image-20220331142637995
    • 如此一来,就解决了黑客劫持的问题,因为即使黑客劫持后的给浏览器即使返回了证书也无法通过校验,同时浏览器也会提示错误信息。

    • 以上就是Https的实现原理,https可以保证数据安全,但由过程需要反复加密解密所有访问速度会有所下降(鱼和熊掌不能兼得)。

    In [ ]:

     
    

    实践1:微信公众平台逆向

    • js调试工具
      • 发条js调试工具
    • PyExecJs
      • 实现使用python执行js代码
      • 环境安装:
        • 1.nodejs开发环境
        • 2.pip install PyExecJs
    • js算法改写初探
      • 打断点
      • 代码调试时,如果发现了相关变量的缺失,一般给其定义成空字典即可。
    打开微信公众平台,用账号登录,打开F12

    账号123456789@163.com

    密码123456

    1643440392040

    发送请求,找到有账号密码的请求

    1643440861547

    用全局搜索 ctrl+shift+f 搜索pwd
    1643440999989
    在pwd这行打断点,搜索pwd

    1643441065873

    经过断点调试,找到生成pwd的js代码
    1643441188798
    把这段js代码复制到js生成工具中做逆向解析
    1643441356091
    发现n未定义,于是定义n
    代码加载成功;
    改写代码,
    function getPwd(e, t, n) {
    return t ? n ? s(t, e) : o(s(t, e)) : n ? i(e) : o(i(e))
    }
    
    import execjs
    #1.实例化一个node对象
    node = execjs.get()
    
    #2.js源文件编译
    ctx = node.compile(open('./wechat.js',encoding='utf-8').read())
    
    #3.执行js函数
    funcName = 'getPwd("{0}")'.format('123456')
    pwd = ctx.eval(funcName)
    print(pwd)
    

    e10adc3949ba59abbe56e057f20f883e
    

    实践2:STEAM平台逆向

    在python中进行逆向
    #获取秘钥
    import requests
    import execjs
    
    url = 'https://store.steampowered.com/login/getrsakey/'
    data = {
        'donotcache': '1602552732596',
        'username': '123@qq.com',
    }
    headers = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
    }
    response_json = requests.post(url=url,headers=headers,data=data).json()
    mod = response_json['publickey_mod']
    exp = response_json['publickey_exp']
    
    
    #进行密码逆向
    node = execjs.get()
    ctx = node.compile(open('./steam.js',encoding='utf-8').read())
    funcName = 'getPwd("{0}","{1}","{2}")'.format('123456',mod,exp)
    pwd = ctx.eval(funcName)
    print(pwd)
    

    Av0nQtvcBdyOZfSR5YBzsP9NIWhKOuzo6SPRjH2UPyvJtoyroy6u/eQIX4RStD6eMOg0hMTfvBNQip5th9IslLoDiKdCCshKyPmSpQVGObsm9PPsJ88duH9B5nqnWnUQ/zUADDUxWyrWswj8CPYw8HwdZQCIJ+yItYLS3Gmt4yD4Nk+c/2LenT54NUi0GEqGlNEmO1hGVIxDgfGfi8nsPVa8KlKL+ELjB9EolWWStnAfpuqPckBfb9bmY8jjHc8xDwY+RdsN6Xmfmtz6KN5cTiJ6VgJ4x9hkh+ThrfqNHatH5qHIibXi4UL7FJ2xCdejLWu/GVGLpC2PByyRN9hv2A==
    

    实践3:凡科网逆向

    • url:https://i.fkw.com/
    • 注意:如果需要逆向的js函数的实现是出现在一个闭包中,那么直接将闭包的整个代码块拷贝出进行调试即可。

    1644195472860

    1644195535976

    import execjs
    node = execjs.get()
    ctx = node.compile(open('./fanke.js',encoding='utf-8').read())
    funcName = "md5('{0}')".format('654321')
    pwd = ctx.eval(funcName)
    print(pwd)
    

    c33367701511b4f6020ec61ded352059
    

    实践4:完美世界逆向

    1644210030405

    1644210072117

    1644210125994

    1644210703478

    在发条工具中调试

    1644217879243

    需要通过·xpath拿到公钥解密1644217964787
    import requests
    from lxml import etree
    import execjs
    headers = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
    }
    #发起请求获取公钥
    url = 'https://passport.wanmei.com/sso/login?service=passport&isiframe=1&location=2f736166652f'
    page_text = requests.get(url=url,headers=headers).text
    tree = etree.HTML(page_text)
    key = tree.xpath('//input[@id="e"]/@value')[0]
    
    #加密的逆向
    node = execjs.get()
    ctx = node.compile(open('./wanmei.js',encoding='utf-8').read())
    funcName = 'getPwd("{0}","{1}")'.format('123456',key)
    pwd = ctx.eval(funcName)
    print(pwd)
    

    JNTXRKgg9uuNfFhaJYrQTGEVf5kQWxFC2yGx14ywmCdgDiSn8JDUvV2PXzrP7n9C7VvbwXNEkwCNw3KGCToBi0tYpQUaVQ3QcGGF9sFfISo6UsB8YqSJ8gxFWYbWWKwCzYLsSa1HLREEQUJrQ1sEiwGZIWMi7vbrSD7mydAW+bw=
    

    实践5:试客联盟逆向

    • url:http://login.shikee.com/
    • serializeArray():js函数的作用使用来实现序列化(对登录页面的表达中的值进行序列化,序列化成一个数组,数组元素就是表单中的数据【用户名和密码】)
    • key表示的是公钥,公钥的生成需要用rsa_n,rsa_n是什么鬼目前还不知道。后续对其做处理。
      • 基于抓包工具对rsa_n进行全局搜索。

    1644224779460

    1644224858370

    发现有一个rsa_n,要找到rsa_n,通过全局搜索,1644224942333

    通过栈跟踪,找到加密password的代码,全部拷贝下来

    1644225069492

    放到发条工具中调试

    }
    if (result.charCodeAt(result.length - 1) == 0) {
        result = result.substring(0, result.length - 1);
    }
    return (result);
    }
    
    function getPwd(p) {
    setMaxDigits(131);
    var rsa_n = 'DC1683EEAA2B709F97743773E18F53E3C9A15D12465CE82227A6E447E6EC590D0B569876BB631B0AB4D67881E7EC874066D6E022E2978B4C6EAA8903EC1774AAE040A3BEAF9C2B48730ADD46BEF5F0C8109DB6FCEFED0F4A84CCD7AFFDB4FB4214DA0D0FF1A8E2831E81FA4D7C2F4346184EEC87CE42230FC320B2B4E392ECDF'
    var key = new RSAKeyPair("10001", '', rsa_n);
    return encryptedString(key,p);
    }
    

    1644225375092

    再放到python中动态获取rsa_n

    #捕获rsa_n的值
    import requests
    import re
    import execjs
    headers = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
    }
    url = 'http://login.shikee.com/getkey?v=4305e873fa87109730b17c5138e85cd8'
    page_text = requests.get(url=url,headers=headers).text
    ex = 'var rsa_n = "(.*?)";'
    rsa_n = re.findall(ex,page_text)[0]
    
    #实现密码加密逆向操作
    node = execjs.get()
     
    pwd = '123456'
    file = 'shike.js'
    ctx = node.compile(open(file,encoding='utf-8').read())
    funcName = 'getPwd("{0}","{1}")'.format(pwd,rsa_n)
    passwd = ctx.eval(funcName)
    print(passwd)
    

    5787e6d0807f6ae002b7d2703f49f94c84bc76fb146c0970e6964821276790cf4ead90e64085d195b9fe10980898b7b02c82e5b14f260937118f8857348623476be1ea239546f3cb67629f43d98267133349d2058de625116b856efef004456600513c664fa08eb056a4fc5f1286f5c5ee7e0e2fdf64f4a644b158f10bfac23c
    

    实践6:空中网逆向

    1.搜索password

    2.找到生成加密password的代码

    3.发现是js混淆

    • js混淆:

      • 将js核心的相关代码进行变相的加密,加密后的数据就是js混淆之后的结果。
    • js反混淆:

      • 反混淆的线上工具(不理想)
      • 浏览器自带的反混淆工具设置(推荐)
        • 开发者工具的Sourse-》settings-》Sources-》第一项点上对勾
        • 进行关键字的全局搜索-》VMxx(就是反混淆后的代码)
    • 发现了一个data['dc']不知道是什么,后续处理:

      • 全局搜索,直接搜索data,结果太多不便于定位

    1644230993115

    • 1644231072455

      1644231317293

      4.打断点找encrypt

      5.把生成password的代码全部拷贝到发条

      1644231393408

      6.调试

      1644231415671

      7.放到python中逆向

    #捕获dc
    import requests
    import execjs
    import re
    import json
    headers = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
        'Referer': 'https://passport.kongzhong.com/'
    }
    url = 'https://sso.kongzhong.com/ajaxLogin?j=j&jsonp=j&service=https://passport.kongzhong.com/&_=1602643754066'
    data = requests.get(url,headers=headers).text
    ex = "KZLoginHandler.jsonpCallbackKongZ\((.*)\)"
    dc = re.findall(ex,data)[0]
    dc = json.loads(dc)['dc']
    
    node = execjs.get()
    pwd = '123456'
    file = 'kongzhong.js'
    ctx = node.compile(open(file,encoding='utf-8').read())
    funcName = 'getPwd("{0}","{1}")'.format(pwd,dc)
    passwd = ctx.eval(funcName)
    print(passwd)
    

    317109ccdbf60337663b
    

    实践7:厂房网逆向

    import requests
    import execjs
    node = execjs.get()
    pwd = '123456'
    file = 'changfang.js'
    ctx = node.compile(open(file,encoding='utf-8').read())
    funcName = 'getPwd("{0}")'.format(pwd)
    passwd = ctx.eval(funcName)
    print(passwd)
    

    䐵匠䴵LJHlwmWkl2aBSI2v8TOJ2A==
    

    实践8:有道翻译逆向

    • url:http://fanyi.youdao.com/

    • 通过抓包工具抓取不同单词对应的数据包进行请求参数的比对,发现只有如下三个参数需要动态处理:

      • salt
      • lts
      • sign
        • sign的值是经过md加密,加密的时候使用到了两个变量:
          • e:需要翻译的单词
          • i:字符串形式的js时间戳+1位数的随机整数,i是一个字符串类型的数据
            • 备注:python的时间戳*1000 = js时间戳
    import requests
    import execjs
    import random
    import time
    
    #获取sign
    e = input('enter a English word:')
    r = str(int(time.time() * 1000))
    i = r +str(int(random.random()*10))
    node = execjs.get()
    ctx = node.compile(open('./youdao.js',encoding='utf-8').read())
    funcName = "getSign('{0}','{1}')".format(e,i)
    sign = ctx.eval(funcName)
    
    url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
    headers = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
        'Referer': 'http://fanyi.youdao.com/',
        'Cookie':'OUTFOX_SEARCH_USER_ID=-86392255@10.108.160.19; OUTFOX_SEARCH_USER_ID_NCOO=1443501431.8073304; _ntes_nnid=313a624ab6f59f6f08cda47bce09570d,1602471473739; JSESSIONID=aaaIo9UjLVhPsfnx9UPux; ___rl__test__cookies=1602729224478'
    }
    data = {
        'i': e,
        'from': 'AUTO',
        'to': 'AUTO',
        'smartresult': 'dict',
        'client': 'fanyideskweb',
        'salt': i,
        'sign': sign,
        'lts': r,
        'bv': '725c1757d01ecab5adcab61daeb9d360',
        'doctype': 'json',
        'version': '2.1',
        'keyfrom': 'fanyi.web',
        'action': 'FY_BY_REALTlME',
    }
    result = requests.post(url,headers=headers,data=data).json()
    print(result)
    

    enter a English word:cat
    {'translateResult': [[{'tgt': '猫', 'src': 'cat'}]], 'errorCode': 0, 'type': 'en2zh-CHS', 'smartResult': {'entries': ['', 'n. 猫,猫科动物\r\n'], 'type': 1}}
    

    实践9:空气质量平台

    js加密,js混淆,js逆向机制

    • url:https://www.aqistudy.cn/html/city_detail.html

    • 分析:

      • 1.当我们修改了页面的查询条件后,点击查询按钮,在抓包工具中会捕获到两个一样的数据包,我们只要能破解其中一个数据包的内容剩下一个也可以同理破解。
        • 请求的url:https://www.aqistudy.cn/apinew/aqistudyapi.php
        • 请求方式:post
        • 请求参数:d:一组动态变化且加密的数据值
        • 返回的响应数据就是我们想要捕获的空气指标数据
          • 响应数据也被加密,是一组被加密的密文数据。我们必须将密文数据解密成原文数据才可以被我们使用。
      • 2.我们在点击查询按钮后,发现页面没有全局刷新,则表示点击查询按钮后发起的是ajax请求。
        • 发起的ajax请求对应的数据包就是我们在step1表示的数据包。
        • 该ajax请求发送的时候请求的:
        • 请求回来的数据是一组密文数据,但是在前台页面中显示的为原文数据?
          • 原因在于前台将响应回来的密文数据进行解密。
            • 价值的信息:在该网站的相关数据包中是存在解密方案,我们只需要将其解密方法获取,对密文进行解密即可。
      • 3.点击查询按钮发起的是ajax请求,该请求在该网站中一定是用js或者jQuery写的,在ajax请求的代码中,可以看到那些具体的操作:
        • 请求的url
        • 请求的方式
          • 请求的url和请求方式可以从抓包工具中捕获
        • 携带的请求参数
          • 可以从ajax的代码中获取
          • 为什么请求参数不可以从抓包工具中获取?
            • 应为该参数是动态变化且加密
        • 请求回来的数据对应的回调函数
          • 函数的参数就是请求到的响应数据(加密的密文数据),该函数会对拿到的密文数据进行解密,将解密后的原文动态显示在前台页面中。
        • 重点:只要能够找到点击查询按钮对应的ajax请求代码后,就可以知道动态变化且加密的请求参数如何生成。那么我们就可以使用该方式动态生成一组请求参数,然后基于指定的url进行请求发送,获取加密的响应数据。获取的密文数据就可以通过ajax请求代码中的回调/匿名函数的操作中获取解密方案,使用该方案对加密的响应数据解密即可。【大功告成】
    • 操作:找寻ajax请求的代码

      • 方案:获取搜索按钮对应的点击事件绑定的函数即可。
        • 最好使用火狐浏览器找寻点击事件的函数代码。

    image-20220331142229337

    • 点击搜索按钮对应的点击事件函数名叫做getData,接下来就进入该函数内部找寻ajax请求代码。

      • 找到了getData函数的实现:
        • 没有发现ajax代码实现
        • 发现函数内部调用了另外的两个函数 getAQIData();getWeatherData();
        • ajax代码的实现一定是在上述的两个函数内部
        • 有价值的信息:
          • type=="HOUR":查询时间是以小时为单位
            • 进入到getAQIData();getWeatherData()函数内部找寻ajax代码实现:
            • 在这两个函数内部没有发现ajax代码
            • 调用了一个叫做getServerData()函数,那么ajax代码一定存在getServerData函数中。
            • getServerData的参数:
              • method:GETDETAIL字符串
              • param:字典,有四组键值对
                • - param.city = city; - param.type = type; - param.startTime = startTime; - param.endTime = endTime;
              • 回调函数
              • 0.5
      • 进入到getServerData函数内部找寻ajax代码:
        • 函数实现的找寻,需要在抓包工具中进行全局搜索。
        • 找到的结果看上去像是密文,看不懂。
        • js混淆:
          • 在网站后台,关键的重要的js函数的实现为了保密,一般会对这些js函数代码进行混淆(加密),所以我们需要对js混淆的代码进行反混淆,将加密的js函数进行解密。
        • 反混淆:
    • getServerData反混淆后的实现:

    function getServerData(method, object, callback, period) {
            const key = hex_md5(method + JSON.stringify(object));
            const data = getDataFromLocalStorage(key, period);
            if (!data) {
                var param = getParam(method, object);
                $.ajax({
                    url: '../apinew/aqistudyapi.php',
                    data: {
                        d: param#加密且动态变化的请求参数
                    },
                    type: "post",
                    #data请求到的加密的响应数据
                    success: function (data) {
                        #decodeData就是对加密的响应数据进行解密
                        data = decodeData(data);
                        obj = JSON.parse(data);
                        if (obj.success) {
                            if (period > 0) {
                                obj.result.time = new Date().getTime();
                                localStorageUtil.save(key, obj.result)
                            }
                            callback(obj.result)
                        } else {
                            console.log(obj.errcode, obj.errmsg)
                        }
                    }
                })
            } else {
                callback(data)
            }
        }
    
    • 分析getServerData函数实现中的ajax代码:

      • 动态变化且加密的请求参数
        • getParam(method, object)
          • method:GETDETAIL
          • object:
            • param:字典,有四组键值对
              • param.city = city;
              • param.type = type;
              • param.startTime = startTime;
              • param.endTime = endTime;
      • decodeData(data):将data这个加密的响应数据进行解密
        • 是需要调用该函数就可以对加密的响应数据进行解密
      • 总结:
        • 调用getParam后就可以获取加密变化的请求参数
        • 调用decodeData就可以将响应数据进行解密
      • 问题:
        • 上述的两个函数是js函数,我们现在写的爬虫程序是python,python如何调用js函数呢?
        • python如何调用js函数
          • 方式1:
            • 可以将js函数改写为python函数
          • 方式2:
            • 使用相关模块进行js逆向
            • PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。我们需要pip install PyExecJS对其进行环境安装。除此之外还需要安装nodejs的开发环境。
              • js逆向:
              • 将js函数改写为python函数
    • PyExecJS的使用

      • 1.需要将待执行的js函数的全部定义存储到一个js源文件中
      • 2.模拟执行js源文件中的js函数
        • 1.加载js源文件中的代码
        • 2.将js源文件中的代码进行编译
    #使用模块进行js逆向,模拟调用js源文件(test.js)中的getParams函数获取动态变化加密的请求参数
    import execjs
    #实例化一个对象
    node = execjs.get()
    # Params
    method = 'GETDETAIL'
    city = '北京'
    type = 'HOUR'
    start_time = '2018-01-25 00:00:00'
    end_time = '2018-01-25 23:00:00'
     
    # Compile javascript
    file = 'test.js'#待加载编译的js源文件
    #加载编译js源文件中的js代码
    ctx = node.compile(open(file,encoding='utf-8').read())
     
    # Get params
    js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
    #eval表示模拟执行编译好的js函数
    params = ctx.eval(js)
    print(params)#返回是加密变化的请求参数
    

    tdgHOYxwKdDSgYXe+RLPzYCgLvrddahasI5XXklB4gVLYqab+XRPpMD/oSqnJ/aEmFwzVEUhLnPzRy03+X1BIzLvxQKwu4A3YsqR3OemYgNnHqPdBwvJlbxia99YeK+xfgEPMfQ8z6n7Ztqd19fYky9NG2tISsGzZYlOuNWFekTywpTbzjA6pajfMz6X9BKKJOJNxwImjmT7z70O5l3nWcdylkn/wPjSbS9Xloz77/xzWwIyk5QlPmxlJw1rU+EeePA78mb5U7G3LQga46sQVjFHwviqZ/nUtjaJgvnE4USD6xOyhIcFRZD7VWC44QCUFfwE4YTt3CXWuEp6leCBZ3cAaZtvUBDZqmpf3QbUO9EbDMkvQaldUD1eP9v9JJUVyCq3RviKaPNVHJLsXXBYy4x6Q9oaB0VBMaO5nv467qgVkhBcBIO9xUUZsAv2rfBZMhSuxsdBeXvJKyyTN3MA1A==
    
    • 可以对ajax进行请求发送
    import execjs
    import requests
    
    node = execjs.get()
    # Params
    method = 'GETDETAIL'
    city = '北京'
    type = 'HOUR'
    start_time = '2018-01-25 00:00:00'
    end_time = '2018-01-25 23:00:00'
     
    # Compile javascript
    file = 'test.js'#待加载编译的js源文件
    #加载编译js源文件中的js代码
    ctx = node.compile(open(file,encoding='utf-8').read())
     
    # Get params
    js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
    #eval表示模拟执行编译好的js函数
    params = ctx.eval(js)
    
    #发起post请求
    url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
    response_text = requests.post(url, data={'d': params}).text
    print(response_text)#返回加密的响应数据
    

    n8Un3OpswjBlUIgnbmQ+Npphg68NTuGGzboCX28bX4NaHfiL5LH6CmmfDxyY74+E7OD4v/JpTW7vrVHQDi9Pev5HYg9Fcuso1ZVkZhd910MsBJkkjnJuAZomwI+NTEgwhmDiZkYK6YuIWq3X0qgUrQ==
    
    • 加密的响应数据获取了,接下来,模拟执行decodeData的js函数进行加密响应数据的解密
    import execjs
    import requests
    
    node = execjs.get()
    # Params
    method = 'GETDETAIL'
    city = '北京'
    type = 'HOUR'
    start_time = '2018-01-25 00:00:00'
    end_time = '2018-01-25 23:00:00'
     
    # Compile javascript
    file = 'test.js'#待加载编译的js源文件
    #加载编译js源文件中的js代码
    ctx = node.compile(open(file,encoding='utf-8').read())
     
    # Get params
    js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
    #eval表示模拟执行编译好的js函数
    params = ctx.eval(js)
    
    #发起post请求
    url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
    #返回加密的响应数据
    response_text = requests.post(url, data={'d': params}).text
    
    
    #模拟执行decodeData来对密文数据进行解密
    js = 'decodeData("{0}")'.format(response_text)
    decrypted_data = ctx.eval(js)
    print(decrypted_data)
    
    • 返回的原文形式的响应数据: {"success":true,"errcode":0,"errmsg":"success","result":{"success":true,"data":{"total":24,"rows":[{"time":"2018-01-25  00:00:00","temp":"-7","humi":"35","wse":"1","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25  01:00:00","temp":"-9","humi":"38","wse":"1","wd":"\u897f\u98ce","tq":"\u6674"},{"time":"2018-01-25  02:00:00","temp":"-10","humi":"40","wse":"1","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25  03:00:00","temp":"-8","humi":"27","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25  04:00:00","temp":"-8","humi":"26","wse":"2","wd":"\u4e1c\u98ce","tq":"\u6674"},{"time":"2018-01-25  05:00:00","temp":"-8","humi":"23","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25  06:00:00","temp":"-9","humi":"27","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25  07:00:00","temp":"-9","humi":"24","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25  08:00:00","temp":"-9","humi":"25","wse":"2","wd":"\u4e1c\u98ce","tq":"\u6674\u8f6c\u591a\u4e91\u8f6c\u591a\u4e91\u95f4\u6674"},{"time":"2018-01-25  09:00:00","temp":"-8","humi":"21","wse":"3","wd":"\u4e1c\u5317\u98ce","tq":"\u6674\u8f6c\u591a\u4e91\u8f6c\u591a\u4e91\u95f4\u6674"},{"time":"2018-01-25  10:00:00","temp":"-7","humi":"19","wse":"3","wd":"\u4e1c\u5317\u98ce","tq":"\u6674\u8f6c\u591a\u4e91\u8f6c\u591a\u4e91\u95f4\u6674"},{"time":"2018-01-25  11:00:00","temp":"-6","humi":"18","wse":"3","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25  12:00:00","temp":"-6","humi":"17","wse":"3","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25  13:00:00","temp":"-5","humi":"17","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25  14:00:00","temp":"-5","humi":"16","wse":"2","wd":"\u4e1c\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25  15:00:00","temp":"-5","humi":"15","wse":"2","wd":"\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25  16:00:00","temp":"-5","humi":"16","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25  17:00:00","temp":"-5","humi":"16","wse":"2","wd":"\u4e1c\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25  18:00:00","temp":"-6","humi":"18","wse":"2","wd":"\u4e1c\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25  19:00:00","temp":"-7","humi":"19","wse":"2","wd":"\u4e1c\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25  20:00:00","temp":"-7","humi":"19","wse":"1","wd":"\u4e1c\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25  21:00:00","temp":"-7","humi":"19","wse":"0","wd":"\u5357\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25  22:00:00","temp":"-7","humi":"22","wse":"1","wd":"\u4e1c\u5317\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25  23:00:00","temp":"-9","humi":"27","wse":"1","wd":"\u897f\u5357\u98ce","tq":"\u6674\u95f4\u591a\u4e91"}]}}}
      

    09.移动端数据爬取

    前言

    • 随着移动市场的火热,各大平台都陆陆续续的推出了自己的移动端APP来拉拢吸引和便捷其广大的用户。那么在移动端的平台当时势必会出现大量有价值的信息和数据,那这些数据我们是否可以去享用一下呢?那么接下来就进入我们的移动端APP数据的爬虫中来吧。

    今日概要

    • fiddler简介
    • 手机APP抓包设置
      • fiddler设置
      • 安装证书下载
      • 安全证书安装
      • 局域网设置
      • fiddler手机抓包测试

    今日详情

    1 什么是Fiddler?
    Fiddler是位于客户端和服务器端的HTTP代理,也是目前最常用的http抓包工具之一 。 它能够记录客户端和服务器之间的所有 HTTP请求,可以针对特定的HTTP请求,分析请求数据、设置断点、调试web应用、修改请求的数据,甚至可以修改服务器返回的数据,功能非常强大,是web调试的利器。

    既然是代理,也就是说:客户端的所有请求都要先经过Fiddler,然后转发到相应的服务器,反之,服务器端的所有响应,也都会先经过Fiddler然后发送到客户端,基于这个原因,Fiddler支持所有可以设置http代理为127.0.0.1:8888的浏览器和应用程序。使用了Fiddler之后,web客户端和服务器的请求如下所示:img

    利用可以设置代理的这个特点,我们就可以对手机APP进行抓包了。怎么设置?不急不急,让我先把Fiddler安装上吧!

    Fiddler下载地址:https://www.telerik.com/fiddler

    傻瓜式安装,一键到底。Fiddler软件界面如图所示:img

    2 手机APP抓包设置

    a. Fiddler设置

    打开Fiddler软件,打开工具的设置。(Fiddler软件菜单栏:Tools->Options)

    在HTTPS中设置如下:

    img

    在Connections中设置如下,这里使用默认8888端口,当然也可以自己更改,但是注意不要与已经使用的端口冲突:

    Allow remote computers to connect:允许别的机器把请求发送到fiddler上来

    img

    b. 安全证书下载
    在电脑浏览器中输入地址:http://localhost:8888/,点击FiddlerRoot certificate,下载安全证书:

    img
    c. 安全证书安装
    证书是需要在手机上进行安装的,这样在电脑Fiddler软件抓包的时候,手机使用电脑的网卡上网才不会报错。

    Android手机安装:把证书放入手机的内置或外置存储卡上,然后通过手机的"系统安全-》从存储设备安装"菜单安装证书。

    ​ 然后找到拷贝的FiddlerRoot.cer进行安装即可。安装好之后,可以在信任的凭证中找到我们已经安装好的安全证书。img

    苹果手机安装:

    ​ - 保证手机网络和fiddler所在机器网络是同一个网段下的

    ​ - 在safari中访问http://fiddle机器ip:fiddler端口,进行证书下载。然后进行安装证书操作。

    ​ - 在手机中的设置-》通用-》关于本机-》证书信任设置-》开启fiddler证书信任

    d. 局域网设置
    想要使用Fiddler进行手机抓包,首先要确保手机和电脑的网络在一个内网中,可以使用让电脑和手机都连接同一个路由器。当然,也可以让电脑开放WIFI热点,手机连入。这里,我使用的方法是,让手机和电脑同时连入一个路由器中。最后,让手机使用电脑的代理IP进行上网。
    在手机上,点击连接的WIFI进行网络修改,添加代理。进行手动设置,ip和端口号都是fiddler机器的ip和fiddler上设置的端口号。

    e. Fiddler手机抓包测试

    上述步骤都设置完成之后,用手机浏览器打开百度首页,我们就可以顺利抓包了

  • 相关阅读:
    hive 调优
    nohup
    安装ElasticSearch 6.1.1 head插件
    101. Symmetric Tree
    67. Add Binary
    70. Climbing Stairs
    896. Monotonic Array
    66. Plus One
    27. Remove Element
    Apache Tomcat文件包含漏洞风险大,威胁全球约8万台服务器
  • 原文地址:https://www.cnblogs.com/abldh12/p/15848060.html
Copyright © 2020-2023  润新知