• js 解密,混淆,逆向


    1. js 解密,混淆,逆向

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

    • 分析:

      • 空气指标的数据是动态加载出来
        • 修改了搜索条件后点击搜索按钮会发起ajax请求,请求到我们想要的指标数据。
      • 从上一步定位到的数据包中提取出url,请求方式,请求参数
        • url和请求方式可以拿来直接用
        • 请求参数是动态变化且加密
      • 响应数据也是加密的密文数据
    • 找到点击搜索按钮发起的ajax请求对应的代码

      • 基于火狐浏览器的开发者工具找到搜索按钮对应点击事件绑定的js函数是哪个
        • getData(),该函数的实现
          • type=HOUR:以小时为单位进行数据的查询
          • 调用了另两个函数:getAQIData(), getWeatherData()
          • 并没有找到ajax请求对应的的代码
        • 分析getAQIData&getWeatherData:
          • 这两个函数的实现几乎一致,唯一的区别是
            • var method = 'GETDETAIL';
            • var method = 'GETCITYWEATHER';
          • 也没有找到ajax请求对应的代码,但是发现了另一个函数的调用:
            • getServerData(method, param, function(obj),0.5 )
              • method:
                • 'GETDETAIL'
                • 'GETCITYWEATHER'
              • param是一个字典,有四组键值对:
                • city;
                • type;
                • startTime;
                • endTime;
          • 分析getServerData函数的实现:
            • 基于抓包工具进行全局搜索,定位到了一个指定的数据包,出现了getServerData关键词,这个关键词对应的js代码被加密了
            • JS混淆:将js中的核心代码加密
            • JS反混淆:
              • 暴力破解:
              • 分析反混淆后的getServerData函数的实现代码:
                • 终于发现了ajax请求对应的代码:
                  • getParam(method, object)返回动态变化且加密的请求参数d的值。
                    • method == method
                    • object == param
                  • decodeData(data):接受加密的响应数据返回解密后的明文数据
                    • data:加密的响应数据
          • js逆向:
            • 自动逆向:
              • PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。
                • 一定实现在本机装好nodejs的开发环境
              • 我们需要pip install PyExecJS对其进行环境安装。

    In [3]:

    #模拟执行js函数获取动态变化且解密的请求参数d的值
    import execjs
    node = execjs.get()
     
    # Params
    method = 'GETCITYWEATHER'
    city = '北京'
    type = 'HOUR'
    start_time = '2018-01-25 00:00:00'
    end_time = '2018-01-25 23:00:00'
     
    # Compile javascript
    file = 'test.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)
    params = ctx.eval(js)#模拟执行指定的js函数
    print(params)
    tdgHOYxwKdDSgYXe+RLPzYCgLvrddahasI5XXklB4gVLYqab+XRPpMD/oSqnJ/aEmFwzVEUhLnPzRy03+X1BI4qc9EYeRPqiKrT+f1JQExGQ4ii8kKvZhGH+nPffaX/xq5iLB6vblcvBC/L8e6UxdnHlajfkXrLQf1qv5Hcg3c++RoGxPAMOgNc6HbCbQG2sE6yemJ7l8HI9CyNktTP7AwQC04bTbY+s+o7lljhqUvsyMZq88MU1VV46TFExCP7vxfmEl6YFeV892bU27lPedTCtSnYbCEfFCJDP0DfEBHe0XFOcgXs+Yl5h58efciX69k9IEvGCKenhokOJQ2tS178anRoT37sEBV5cZeLY8Uzh8UUWgxg2sH+JJsg8ARclHhK0AN/SA4wFy8XmwdBun1zHxV8LoPfn3cxqzXnNKOp/nowpNnbyuMSZtftbf41HB1dEdkm07a2LzCaJgUEpPmLZUuA7+lDlCKqTsEZVh9w=
    
    • 对ajax的url携带d请求参数进行post请求的发送可以获取加密的响应数据

    In [5]:

    import execjs
    import requests
    
    node = execjs.get()
     
    # Params
    method = 'GETCITYWEATHER'
    city = '北京'
    type = 'HOUR'
    start_time = '2018-01-25 00:00:00'
    end_time = '2018-01-25 23:00:00'
     
    # Compile javascript
    file = 'test.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)
    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)
    eAkzHZvdWqslCrP29e8XgEP22qdvyxus1TrEFB8uvsD0ChwbOTBCJErsCqVJyLQJ9wdhdK9lk3nl/SEeVqoXSY48w11ODT7v6rhQkkXuZ3Vv+VOQ7C7zXtLvbJJDIq9Nu3RRA+8rS/R0lnyMUk98IQ==
    

    In [12]:

    #将密文的响应数据进行解密:模拟调用decodeData(data)
    import execjs
    import requests
    
    node = execjs.get()
     
    # Params
    method = 'GETCITYWEATHER'
    city = '北京'
    type = 'HOUR'
    start_time = '2018-01-25 00:00:00'
    end_time = '2018-01-25 23:00:00'
     
    # Compile javascript
    file = 'test.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)
    params = ctx.eval(js)
    
    #发起post请求
    url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
    response_text = requests.post(url, data={'d': params}).text
    # #对加密的响应数据进行解密
    jss = 'decodeData("{0}")'.format(response_text)
    print(jss)
    decrypted_data = ctx.eval(jss)
    print(decrypted_data)
    decodeData("S+PG+bQmwr20q9LEnYxZb6d9kwGuj+GKqm/YqBW0N9VTTsfFzS6mR86ne1uxqNuepTIfI+opvFV/np093XWIf2IXLkXoN7yUEFNnINrJBIN9MFj2Y9rWgCXZXe4k0PtMub9YKalryHwuO7IlNJN3OA==")
    ---------------------------------------------------------------------------
    ProgramError                              Traceback (most recent call last)
    <ipython-input-12-775eb2217305> in <module>()
         26 jss = 'decodeData("{0}")'.format(response_text)
         27 print(jss)
    ---> 28 decrypted_data = ctx.eval(jss)
         29 print(decrypted_data)
    
    ~Anaconda3libsite-packagesexecjs\_abstract_runtime_context.py in eval(self, source)
         25         if not self.is_available():
         26             raise execjs.RuntimeUnavailableError
    ---> 27         return self._eval(source)
         28 
         29     def call(self, name, *args):
    
    ~Anaconda3libsite-packagesexecjs\_external_runtime.py in _eval(self, source)
         76 
         77             code = 'return eval({data})'.format(data=data)
    ---> 78             return self.exec_(code)
         79 
         80         def _exec_(self, source):
    
    ~Anaconda3libsite-packagesexecjs\_abstract_runtime_context.py in exec_(self, source)
         16         if not self.is_available():
         17             raise execjs.RuntimeUnavailableError
    ---> 18         return self._exec_(source)
         19 
         20     def eval(self, source):
    
    ~Anaconda3libsite-packagesexecjs\_external_runtime.py in _exec_(self, source)
         86             else:
         87                 output = self._exec_with_pipe(source)
    ---> 88             return self._extract_result(output)
         89 
         90         def _call(self, identifier, *args):
    
    ~Anaconda3libsite-packagesexecjs\_external_runtime.py in _extract_result(self, output)
        165                 return value
        166             else:
    --> 167                 raise ProgramError(value)
        168 
        169 
    
    ProgramError: Error: Malformed UTF-8 data
    

    selenium

    回顾

    • 单线程+多任务的异步协程

      • 特殊的函数
        • 调用后实现内部的程序语句不会被立即执行
        • 调用后返回一个协程对象
      • 协程对象
        • 协程特殊函数一组指定的操作
      • 任务对象
        • 高级的协程对象
        • 绑定回调函数
          • task.add_done_callback(func)
          • func:
            • 必须要有一个参数(当前的任务对象)
            • 参数.result()表示的就是特殊函数的返回值
        • 任务对象 == 一组指定的操作
      • 事件循环对象
        • 创建一个eventloop对象
        • 作用:
          • 必须要装载一个或者多个任务对象(任务对象是需要注册到eventloop)
          • 启动事件循环对象
            • 可以异步的执行其内部注册的每一个任务对象对应的指定操作
      • 等待await:确保eventloop一定会执行阻塞操作
      • 挂起:让当前发生阻塞的任务对象交出cpu的使用权
        • asyncio.wait(tasks)
      • 重点:
        • 特殊函数内部不可以出现不支持异步模块的代码
      • aiohttp:支持异步的网络请求模块
        • 使用使用上下文机制(with...as)
        • 实例化一个请求对象(ClientSession())
        • get/post()进行请求发送。(阻塞操作)
        • 获取响应数据(阻塞操作)
          • response.text():字符串
          • response.read():byte
    • selenium

      • 动作链
      • 无头浏览器
      • 规避检测
        • 浏览器托管
      • 12306的模拟登陆
    • 动作链

      • from selenium.webdriver import ActionChains
      • NoSuchElementException:没有定位到指定的标签
        • 定位的标签是存在于一张嵌套的子页面中,如果想定位子页面中的指定标签的话需要:
          • bro.switch_to.frame('iframe标签id的属性值'):将当前浏览器页面切换到指定的子页面的范围中
      • 针对指定的浏览器实例化一个动作链对象
        • action = ActionChains(bro)
      • action.click_and_hold(tagName)
      • move_by_offset(10,15)
      • perform()是的动作链立即执行

    In [6]:

    from selenium import webdriver
    from selenium.webdriver import ActionChains
    from time import sleep
    # 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的
    bro = webdriver.Chrome('./chromedriver.exe')
    bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
    
    #标签定位
    bro.switch_to.frame('iframeResult')
    div_tag = bro.find_element_by_id('draggable')
    
    #需要使用ActionChains定制好的行为动作
    action = ActionChains(bro)#针对当前浏览器页面实例化了一个动作链对象
    action.click_and_hold(div_tag)#点击且长按指定的标签
    
    for i in range(1,7):
        action.move_by_offset(10,15).perform()#perform()是的动作链立即执行
        sleep(0.5)
        
    bro.quit()
        
    
    
    • 无头浏览器
      • 没有可视化界面的浏览器
      • phantomJS:无头浏览器
      • 谷歌无头浏览器:
        • 就是你本机安装的谷歌浏览器,只是需要通过代码进行相关配置就可以变成无头浏览器

    In [8]:

    from selenium.webdriver.chrome.options import Options
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    
    
    
    bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options)
    bro.get('https://www.baidu.com/')
    sleep(1)
    bro.save_screenshot('./1.png')#截屏
    
    print(bro.page_source)
    

    . . .

    • selenium规避检测
      • 浏览器托管
        • 环境配置:
          • 1.本机谷歌浏览器驱动程序所在的目录的路径添加到环境变量中
          • 2.使用本机谷歌的驱动程序开启一个浏览器
            • chrome.exe --remote-debugging-port=9222 --user-data-dir="C:selenumAutomationProfile"
              • 9222:端口(任意)
              • "C:selenumAutomationProfile":已经事先存在的一个空目录
        • 使用如下代码接管目前打开的浏览器:

    In [30]:

    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
     
    chrome_options = Options()
    chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
    
    
    bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options)#代码托管代开的浏览器,不会实例化一个新的浏览器。
    bro.get('https://kyfw.12306.cn/otn/login/init')
    C:UserslaonanhaiAnaconda3libsite-packagesipykernel_launcher.py:8: DeprecationWarning: use options instead of chrome_options
      
    

    12306模拟登陆

    In [25]:

    #pip install Pillow
    from PIL import Image
    from selenium.webdriver import ActionChains
    from selenium import webdriver
    #识别验证码的函数
    def transformCode(imgPath,imgType):
        chaojiying = Chaojiying_Client('13614167787', '13614167787', '903126')
        im = open(imgPath, 'rb').read()
        return chaojiying.PostPic(im,imgType)['pic_str']
    

    In [26]:

    bro = webdriver.Chrome(executable_path='./chromedriver.exe')
    bro.get('https://kyfw.12306.cn/otn/login/init')
    sleep(2)
    bro.find_element_by_id('username').send_keys('xxxxxxx')
    bro.find_element_by_id('password').send_keys('12345465')
    
    #验证码的点击操作
    bro.save_screenshot('main.png')#将页面当做图片保存到本地
    #将单独的验证码图片从main.png中裁剪下载
    img_tag = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')#将验证码图片的标签定位到了
    location = img_tag.location
    size = img_tag.size
    # print(location,size)
    #裁剪的范围(验证码图片左下角和右上角两点坐标)
    rangle = (int(location['x']),int(location['y']),int(location['x']+size['width']),int(location['y']+size['height']))
    #使用Image类根据rangle裁剪范围进行验证码图片的裁剪
    i = Image.open('./main.png')
    frame = i.crop(rangle)#验证码对应的二进制数据
    frame.save('./code.png')
    
    result = transformCode('./code.png',9004)#99,71|120,140
    #99,71|120,140 == [[99,71],[120,140]]
    all_list = []#[[99,71],[120,140]]
    if '|' in result:
        list_1 = result.split('|')
        count_1 = len(list_1)
        for i in range(count_1):
            xy_list = []
            x = int(list_1[i].split(',')[0])
            y = int(list_1[i].split(',')[1])
            xy_list.append(x)
            xy_list.append(y)
            all_list.append(xy_list)
    else:
        x = int(result.split(',')[0])
        y = int(result.split(',')[1])
        xy_list = []
        xy_list.append(x)
        xy_list.append(y)
        all_list.append(xy_list)
        
    for data in all_list:
        x = data[0]#11
        y = data[1]#22
        ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform()
        sleep(1)
        
    sleep(2)
    bro.find_element_by_id('loginSub').click()
    
    bro.quit()
    

    In [21]:

    #下载好的示例代码
    #!/usr/bin/env python
    # coding:utf-8
    
    import requests
    from hashlib import md5
    
    class Chaojiying_Client(object):
    
        def __init__(self, username, password, soft_id):
            self.username = username
            password =  password.encode('utf8')
            self.password = md5(password).hexdigest()
            self.soft_id = soft_id
            self.base_params = {
                'user': self.username,
                'pass2': self.password,
                'softid': self.soft_id,
            }
            self.headers = {
                'Connection': 'Keep-Alive',
                'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
            }
    
        def PostPic(self, im, codetype):
            """
            im: 图片字节
            codetype: 题目类型 参考 http://www.chaojiying.com/price.html
            """
            params = {
                'codetype': codetype,
            }
            params.update(self.base_params)
            files = {'userfile': ('ccc.jpg', im)}
            r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
            return r.json()
    
        def ReportError(self, im_id):
            """
            im_id:报错题目的图片ID
            """
            params = {
                'id': im_id,
            }
            params.update(self.base_params)
            r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
            return r.json()
    
    
    # if __name__ == '__main__':
    #   chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001')  #用户中心>>软件ID 生成一个替换 96001
    #   im = open('a.jpg', 'rb').read()                                                 #本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
    #   print chaojiying.PostPic(im, 1902)                                              #1902 验证码类型  官方网站>>价格体系 3.4+版 print 后要加()
    
    
    
    python
  • 相关阅读:
    [HAOI2011] 向量
    [HNOI2004] 树的计数
    [TJOI2009] 猜数字
    Wannafly Camp 2020 Day 6K 最大权值排列
    [HAOI2012] 容易题
    [ZJOI2008] 生日聚会
    [CQOI2007] 余数求和
    [CQOI2009] 中位数
    [SDOI2012] Longge的问题
    我的Apache又挂了之apache错误:server's fully qualified domain name, using 127.0.0.1. Set the 'ServerName'
  • 原文地址:https://www.cnblogs.com/bky20061005/p/12168398.html
Copyright © 2020-2023  润新知