漁滒 发表于 2021-1-19 21:27

利用AST对抗某网站的javascript抽取型混淆

因为网站比较敏感,所以省略的一部分内容,主要讲逻辑部分
python中处理js代码需要用到slimit这个库,使用pip install slimit即可安装
首先对网站源代码进行分析,,发现需要的js代码在script标签中,并且用flashvars开头的变量储存

首先将这段js代码拿出来
    requests = requests_html.HTMLSession()
    response = requests.get(shareurl, headers=headers)
    for script in response.html.xpath('//script[@type="text/javascript"]'):
      script = script.xpath('//text()')
      if 'flashvars' in script:
            # 此时可以获取script内所有的js代码
这里都比较简单,就不多说了,拿到这段js代码后,格式化先看一下

这里可以看到,所有的视频地址都被抽取出来了,继续往后面看

经过一段拼接后,重新形成正确的视频地址,接下来就是要使用ast来还原这个地址
            # 转化为ast结构树
            tree = Parser().parse(script)
通过Parser类可以将js代码转换为ast结构树
获取到结构树后,需要自己通过继承ASTVisitor类来编写自定义访问者来遍历节点
这里我首先还原被抽取的mediaDefinitions列表
flashvars = []

class VarStatement_Visitor(ASTVisitor):
    # 自定义访问者,重写VarStatement节点访问逻辑
    def visit_VarStatement(self, node):
      Identifier, Object = node.children().children()
      # 获取flashvars定义的节点
      if 'flashvars' in Identifier.value:
            for each in Object.properties:
                left, right = each.children()
                # 找到mediaDefinitions数组
                if left.value == '"mediaDefinitions"':
                  # 还原每一个字典
                  for item in right.items:
                        data = {}
                        for key in item.properties:
                            keyleft, keyright = key.children()
                            if isinstance(keyright, ast.Array):
                              datalist =
                              data] = datalist
                            else:
                              if keyright.value == '"defaultQuality"':
                                    data] = keyright.value
                              else:
                                    data] = keyright.value
                        flashvars.append(data)
其中类名可以自定义,必须继承于ASTVisitor,然后重写【visit_+类型】这个方法,来指定在什么类型的节点进入函数
例如我这里定义的是visit_VarStatement方法,那就是访问所有的VarStatement节点,运行代码后可以得到还原的mediaDefinitions数组

接着就是继续编写访问者,来还原视频地址
class media_Visitor(ASTVisitor):

    def __init__(self, i, *args, **kwargs):
      # 视频所在的序号
      self.i = i
      # 用于添加映射关系
      self.identifier = {}
      # 用于添加映射顺序
      self.identifiers = []
      super(*args, **kwargs)

    # 递归获取映射顺序
    def get_Identifier(self, node, identifierlist):
      left, right = node.children()
      identifierlist.append(self.identifier)
      if isinstance(left, ast.BinOp):
            self.get_Identifier(left, identifierlist)
      else:
            identifierlist.append(self.identifier)

    def visit_VarStatement(self, node):
      Identifier, BinOp = node.children().children()
      # 函数地址的映射顺序
      if 'media_'+str(self.i) == Identifier.value:
            # 计算真实视频地址
            self.get_Identifier(BinOp, self.identifiers)
            # 填充视频地址
            flashvars['videoUrl'] = ''.join(self.identifiers[::-1])
      # 映射的定义
      elif isinstance(BinOp, ast.String) or (len(BinOp.children()) == 2 and isinstance(BinOp.children(), ast.String) and isinstance(BinOp.children(), ast.String)):
            if isinstance(BinOp, ast.String):
                self.identifier = BinOp.value
            else:
                self.identifier = ''.join( for i in BinOp.children()])
这里进入的节点依然是VarStatement
因为视频地址的公式是由多个变量拼接得到的,我们并不知道会有多少个变量,所以定义了递归方法get_Identifier来获取完整的拼接公式
运行后可以看到,所有被抽取的视频地址都已经还原回去(图片就不放了)
下面是完整代码

import requests_html
# 将js代码转换为ast结构树
from slimit.parser import Parser
# 用于创建自定义访问者
from slimit.visitors.nodevisitor import ASTVisitor
from slimit import ast

flashvars = []

class VarStatement_Visitor(ASTVisitor):
    # 自定义访问者,重写VarStatement节点访问逻辑
    def visit_VarStatement(self, node):
      Identifier, Object = node.children().children()
      # 获取flashvars定义的节点
      if 'flashvars' in Identifier.value:
            for each in Object.properties:
                left, right = each.children()
                # 找到mediaDefinitions数组
                if left.value == '"mediaDefinitions"':
                  # 还原每一个字典
                  for item in right.items:
                        data = {}
                        for key in item.properties:
                            keyleft, keyright = key.children()
                            if isinstance(keyright, ast.Array):
                              datalist =
                              data] = datalist
                            else:
                              if keyright.value == '"defaultQuality"':
                                    data] = keyright.value
                              else:
                                    data] = keyright.value
                        flashvars.append(data)

class media_Visitor(ASTVisitor):

    def __init__(self, i, *args, **kwargs):
      # 视频所在的序号
      self.i = i
      # 用于添加映射关系
      self.identifier = {}
      # 用于添加映射顺序
      self.identifiers = []
      super(*args, **kwargs)

    # 递归获取映射顺序
    def get_Identifier(self, node, identifierlist):
      left, right = node.children()
      identifierlist.append(self.identifier)
      if isinstance(left, ast.BinOp):
            self.get_Identifier(left, identifierlist)
      else:
            identifierlist.append(self.identifier)

    def visit_VarStatement(self, node):
      Identifier, BinOp = node.children().children()
      # 函数地址的映射顺序
      if 'media_'+str(self.i) == Identifier.value:
            # 计算真实视频地址
            self.get_Identifier(BinOp, self.identifiers)
            # 填充视频地址
            flashvars['videoUrl'] = ''.join(self.identifiers[::-1])
      # 映射的定义
      elif isinstance(BinOp, ast.String) or (len(BinOp.children()) == 2 and isinstance(BinOp.children(), ast.String) and isinstance(BinOp.children(), ast.String)):
            if isinstance(BinOp, ast.String):
                self.identifier = BinOp.value
            else:
                self.identifier = ''.join( for i in BinOp.children()])

def geturl(shareurl, headers):
    requests = requests_html.HTMLSession()
    response = requests.get(shareurl, headers=headers)
    for script in response.html.xpath('//script[@type="text/javascript"]'):
      script = script.xpath('//text()')
      if 'flashvars' in script:
            # 转化为ast结构树
            tree = Parser().parse(script)
            # 自定义访问者,访问VarStatement节点
            VarStatement_Visitor().visit(tree)
            for i in range(len(flashvars)):
                media_Visitor(i).visit(tree)
            break
    print(flashvars)


xixicoco 发表于 2021-1-19 21:35

牛逼,顶你

lipinghao 发表于 2021-1-19 21:36

渔歌大佬越来越厉害了:lol

netspirit 发表于 2021-1-20 14:48

渔哥厉害~~~~~~~~~~~

gaofeng_pj 发表于 2021-1-24 15:56

大佬就是大佬,还是要js基础较好的才能看得懂

安安20152015 发表于 2021-5-30 07:03

有解密工具吗

JPK 发表于 2021-7-30 09:36

写的很细,学习到了。

libaiddufu 发表于 2021-8-4 02:08

看到好多这个AST,就是不会,还没有入门呢
页: [1]
查看完整版本: 利用AST对抗某网站的javascript抽取型混淆