猿人学14题详解

目测重点在于cookie:mz和m

获取mz.js: https://match.yuanrenxue.com/static/match/match14/m.js

获取设置m: https://match.yuanrenxue.com/api/match/14/m

一、还原16进制

const fs = require('fs');
const parser = require('@babel/parser');
const generator = require('@babel/generator').default;

const jsCode = fs.readFileSync('./mz.js', {encoding: 'utf-8'})

let ast = parser.parse(jsCode);//parse 一下就可以了

let code = generator(ast, {minified: true, jsescOption: {minimum: true}}).code;
ast = parser.parse(code);
code = generator(ast).code;
fs.writeFile('./mz1.js', code, (err) => {
});

初步看到mz赋值位置:

 n["Nu" + "mK" + "J"] = "mz" + "=", n["ZD" + "Kb" + "v"] = ";p" + "at" + "h=" + "/", n["Dn" + "NJ" + "W"] = "/a" + "pi" + "/m" + "at" + "ch" + "/1" + "4/" + "m", n["tl" + "pB" + "r"] = function (K, Y) {

二、字符串拼接

目标:n["cW" + "QT" + "T"] -> n["cWQTT"]
import json
import os


def concat_obj_property_name(node):
    if type(node) == list:
        for item in node:
            concat_obj_property_name(item)
        return
    elif type(node) != dict:
        return
    # 捕获一个二元运算节点
    if 'type' in node and node['type'] == 'BinaryExpression':
        if not (node['left']['type'] == 'Literal' and node['right']['type'] == 'Literal'):
            concat_obj_property_name(node['left'])
            concat_obj_property_name(node['right'])
        if node['left']['type'] == 'Literal' and node['right']['type'] == 'Literal':
            # 构造新节点
            new_node = {'type': 'Literal', 'value': node['left']['value'] + node['right']['value']}
            node.clear()
            node.update(new_node)
            return
    for key in node.keys():
        concat_obj_property_name(node[key])


if __name__ == '__main__':
    with open('mz1.json', 'r', encoding='utf8') as f:
        data = json.loads(f.read())
    concat_obj_property_name(data)
    with open('mz2.json', 'w', encoding='utf8') as f:
        f.write(json.dumps(data))
    os.system('/usr/local/bin/node JsonToJs mz2.json mz2.js')

三、数值运算还原

目标:window['n'] = 7317 + -8779 + 1462; -> window['n'] = 0;

使用Python处理要考虑的情况太多。使用js更方便

const fs = require('fs');
//js转AST代码
const parser = require('@babel/parser');
//遍历ASR节点
const traverse = require('@babel/traverse').default;
//用来判断节点类型产生新的节点
const t = require('@babel/types');
//用来把AST转换成js代码
const generator = require('@babel/generator').default;
const jscode = fs.readFileSync("./mz2.js", {
  encoding: "utf-8"
});

const visitor = {
  "BinaryExpression"(path) {
    let left = path.node.left;
    let right = path.node.right;
    if (left.type == "NumericLiteral" | left.type == "UnaryExpression" && right.type == "NumericLiteral" | right.type == "UnaryExpression") {
      path.replaceWith(t.valueToNode(path.evaluate().value));
    }
  }
}

const revertUnaryExpression = {
  "UnaryExpression"(path) {
    let argument = path.node.argument;
    if (argument.type == 'NumericLiteral' | argument.type == 'UnaryExpression' && generator(path.node).code.indexOf(' -') != -1) {
      path.replaceWith(t.valueToNode(eval(generator(path.node).code)));
    }
  }
}
let ast = parser.parse(jscode);
for (let i = 0; i < 10; i++) {
  traverse(ast, visitor);
}
traverse(ast, revertUnaryExpression)

let code = generator(ast).code;
fs.writeFile('./mz3.js', code, (err) => {
});

四、对象调用还原

个人想法:这一步才是备而后动-勿使有变这个题目的精髓。

    var L = {};
    L['aKzhL'] = function (S, P) {
      return S + P;
    }
    var G = L;

    function C(S, P) {
      var F = G['aKzhL'](G['IboVR'](65535, S), G['IboVR'](65535, P));
      return G['DNzZE'](G['QpEsx'](G['aKzhL'](G['imxsL'](G['Ggivm'](S, 16), G['Ggivm'](P, 16)), G['Ggivm'](F, 16)), 16), G['IboVR'](65535, F));
    }

以L来说,在后面替换并未直接使用,而是使用var G = L;进行替换,这还只是针对一个块语句来说,若是放到全局,L不知道被污染多少次了。观察结构,可以在块语句中进行替换。

目标:还原前如上述代码。

            还原后:

    function C(S, P) {
      var F = (65535 & S) + (65535 & P);
      return (S >> 16) + (P >> 16) + (F >> 16) << 16 | 65535 & F;
    }
import copy
import json
import os

obj_name_list = []
obj_property_dict = {}


def get_property_dict(node):
    global obj_property_dict
    if type(node) == list:
        for item in node:
            get_property_dict(item)
        return
    elif type(node) != dict:
        return
    # 因为存在变量更新的情况,所以不能遍历全文,在一个块语句中就做替换
    if 'type' in node and node['type'] == 'BlockStatement':
        try:
            # 观察发现,代码中都是 赋值-表达式语句-赋值 这三种格式
            if len(node['body']) >= 3 and \
                    node['body'][0]['type'] == 'VariableDeclaration' and \
                    node['body'][1]['type'] == 'ExpressionStatement' and \
                    node['body'][2]['type'] == 'VariableDeclaration':
                obj_property_dict.clear()
                if 'init' in node['body'][2]['declarations'][0] and \
                        node['body'][2]['declarations'][0]['init']['type'] == 'Identifier':
                    obj_name = node['body'][2]['declarations'][0]['id']['name']
                    if obj_name not in obj_property_dict:
                        obj_property_dict[obj_name] = {}
                    for i in node['body'][1]['expression']['expressions']:
                        if i['type'] == 'AssignmentExpression':
                            property_name = i['left']['property']['value']
                            obj_property_dict[obj_name][property_name] = i['right']
                    obj_property_reload(node)
                    node['body'][2]['declarations'][0]['init'] = {'type': 'Literal', 'value': 'Nothing'}
                    node['body'] = node['body'][2:]
        except KeyError as e:
            print('error', e, node['body'][2]['declarations'])
    for key in node.keys():
        get_property_dict(node[key])


# 对象调用还原
def obj_property_reload(node):
    if type(node) == list:
        for item in node:
            obj_property_reload(item)
        return
    elif type(node) != dict:
        return
    try:
        if node['type'] == 'MemberExpression':
            # 处理形似:_0x5243e3['UhBgk']return '666'
            try:
                obj_name = node['object']['name']
                obj_property_name = node['property']['value']
                new_node = obj_property_dict[obj_name][obj_property_name]
                if new_node['type'] != 'FunctionExpression':
                    node.clear()
                    node.update(new_node)
            except Exception:
                pass
        # 捕获一个函数调用节点,且子节点callee的类型是一个MemberExpression
        if node['type'] == 'CallExpression' and node['callee']['type'] == 'MemberExpression':
            argument_list = node['arguments']
            for para in argument_list:
                if para['type'] == 'CallExpression':  # 递归调用
                    obj_property_reload(para)
            obj_name = node['callee']['object']['name']
            obj_property_name = node['callee']['property']['value']  # 获取需要调用的对象属性名称
            function_node = obj_property_dict[obj_name][obj_property_name]  # 获取函数定义节点,即对象的属性值(该属性值是一个函数定义)
            # 获取形参
            param_list = [item['name'] for item in function_node['params']]
            # 获取实参
            param_argument_dict = dict(zip(param_list, argument_list))
            return_node = copy.deepcopy(function_node['body']['body'][0])
            # print(return_node)
            if return_node['argument']['type'] == 'BinaryExpression' or \
                    return_node['argument']['type'] == 'LogicalExpression':
                if return_node['argument']['left']['type'] == 'Identifier':
                    return_node['argument']['left'] = param_argument_dict[return_node['argument']['left']['name']]
                if return_node['argument']['right']['type'] == 'Identifier':
                    return_node['argument']['right'] = param_argument_dict[return_node['argument']['right']['name']]
                node.clear()
                node.update(return_node['argument'])
            elif return_node['argument']['type'] == 'CallExpression':
                if return_node['argument']['callee']['type'] != 'MemberExpression':
                    function_name = return_node['argument']['callee']['name']
                    if function_name in param_argument_dict:
                        return_node['argument']['callee'] = param_argument_dict[function_name]
                    for i in range(len(return_node['argument']['arguments'])):
                        if return_node['argument']['arguments'][i]['type'] == 'Identifier':
                            argument_name = return_node['argument']['arguments'][i]['name']
                            return_node['argument']['arguments'][i] = param_argument_dict[argument_name]
                node.clear()
                node.update(return_node['argument'])
    except Exception as e:
        print('???', e)
    for key in node.keys():
        obj_property_reload(node[key])


if __name__ == '__main__':
    with open('mz3.json', 'r', encoding='utf8') as f:
        data = json.loads(f.read())
    get_property_dict(data)
    print(obj_property_dict.keys())
    with open('mz4.json', 'w', encoding='utf8') as f:
        f.write(json.dumps(data))
    os.system('/usr/local/bin/node JsonToJs mz4.json mz4.js')

五、控制流平坦化

import json
import os


def sort_code(node):
    if type(node) == list:
        try:
            if len(node) == 2 and node[1]['type'] == 'WhileStatement' and node[0]['type'] == 'VariableDeclaration':
                for i in node[0]['declarations']:
                    if i['type'] == 'VariableDeclarator' and i['init'] and \
                            i['init']['type'] == 'CallExpression':
                        sort_list = i['init']['callee']['object']['value'].split('|')
                        cases_list = node[1]['body']['body'][0]['cases']
                        result_list = [cases_list[int(i)]['consequent'][0] for i in sort_list]
                        node.clear()
                        node.extend(result_list)
        except (KeyError, TypeError):
            for item in node:
                sort_code(item)
            return
    elif type(node) == dict:
        for key in node.keys():
            sort_code(node[key])
        return
    else:
        return

    for item in node:
        sort_code(item)


if __name__ == '__main__':
    with open('mz5.json', 'r', encoding='utf8') as f:
        data = json.loads(f.read())
    sort_code(data)
    with open('mz6.json', 'w', encoding='utf8') as f:
        f.write(json.dumps(data))
    os.system('/usr/local/bin/node JsonToJs mz6.json mz6.js')

经过以上几个步骤,逻辑已经基本清晰:

  function E(K) {
    function d(h, b) {
      var D = b;
      var I = _n('jsencrypt');
      var u = new I();
      var Q = u['encode'](h, D);
      // if (m5['toString']()['indexOf']('\n') != -(1507 + -311 * -11 + -4927)) while (!![]) {
      //   console['log'](x['pSnMY'](x['pSnMY'](x['pSnMY']('生而', '为虫'), ',我'), '很抱') + '歉');
      // }
      return Q;
    }

    return result = d(K, K), result;
  }

  let a, p;
  a = Date['parse'](new Date()) * 8;
  p = E(parseInt(a / 8));  //rsa加密
  let b = Date['parse'](new Date());
  let aa = m5(p);
  let bb = m5(b);
  let d = 'Mozilla,Netscape,5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36,[object NetworkInformation],true,,[object Geolocation],8,zh-CN,zh-CN,zh,0,[object MediaCapabilities],[object MediaSession],[object MimeTypeArray],true,[object Permissions],MacIntel,[object PluginArray],Gecko,20030107,[object UserActivation],Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36,Google Inc.,,[object DeprecatedStorageQuota],[object DeprecatedStorageQuota],875,0,25,1440,30,900,[object ScreenOrientation],30,1440,[object DOMStringList],function assign() { [native code] },,match.yuanrenxue.com,match.yuanrenxue.com,https://match.yuanrenxue.com/match/14,https://match.yuanrenxue.com,/match/14,,https:,function reload() { [native code] },function replace() { [native code] },,function toString() { [native code] },function valueOf() { [native code] }'
  let b64_zw = 'TW96aWxsYSxOZXRzY2FwZSw1LjAgKE1hY2ludG9zaDsgSW50ZWwgTWFjIE9TIFggMTBfMTVfNykgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzExMC4wLjAuMCBTYWZhcmkvNTM3LjM2LFtvYmplY3QgTmV0d29ya0luZm9ybWF0aW9uXSx0cnVlLCxbb2JqZWN0IEdlb2xvY2F0aW9uXSw4LHpoLUNOLHpoLUNOLHpoLDAsW29iamVjdCBNZWRpYUNhcGFiaWxpdGllc10sW29iamVjdCBNZWRpYVNlc3Npb25dLFtvYmplY3QgTWltZVR5cGVBcnJheV0sdHJ1ZSxbb2JqZWN0IFBlcm1pc3Npb25zXSxNYWNJbnRlbCxbb2JqZWN0IFBsdWdpbkFycmF5XSxHZWNrbywyMDAzMDEwNyxbb2JqZWN0IFVzZXJBY3RpdmF0aW9uXSxNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvMTEwLjAuMC4wIFNhZmFyaS81MzcuMzYsR29vZ2xlIEluYy4sLFtvYmplY3QgRGVwcmVjYXRlZFN0b3JhZ2VRdW90YV0sW29iamVjdCBEZXByZWNhdGVkU3RvcmFnZVF1b3RhXSw4NzUsMCwyNSwxNDQwLDMwLDkwMCxbb2JqZWN0IFNjcmVlbk9yaWVudGF0aW9uXSwzMCwxNDQwLFtvYmplY3QgRE9NU3RyaW5nTGlzdF0sZnVuY3Rpb24gYXNzaWduKCkgeyBbbmF0aXZlIGNvZGVdIH0sLG1hdGNoLnl1YW5yZW54dWUuY29tLG1hdGNoLnl1YW5yZW54dWUuY29tLGh0dHBzOi8vbWF0Y2gueXVhbnJlbnh1ZS5jb20vbWF0Y2gvMTQsaHR0cHM6Ly9tYXRjaC55dWFucmVueHVlLmNvbSwvbWF0Y2gvMTQsLGh0dHBzOixmdW5jdGlvbiByZWxvYWQoKSB7IFtuYXRpdmUgY29kZV0gfSxmdW5jdGlvbiByZXBsYWNlKCkgeyBbbmF0aXZlIGNvZGVdIH0sLGZ1bmN0aW9uIHRvU3RyaW5nKCkgeyBbbmF0aXZlIGNvZGVdIH0sZnVuY3Rpb24gdmFsdWVPZigpIHsgW25hdGl2ZSBjb2RlXSB9';
  m = m5(gee(aa, bb, c, d, e, b64_zw)) + '|' + b + '|' + a + '|';
  return m

但是直接运行还是会陷入死循环,需要手动处理如:补环境,干掉格式检查,干掉本地环境检查。如以下示例:

// eval('delete document'), eval('delete window');
// bp = eval('CanvasCaptureMediaStreamTrack');
// global替换为Node环境下不存在的属性如:f**e['you']
try {
  global && (cl[cs >>> 2] &= 25 << 32 - cs % 4 * 8, cl['length'] = Math['ceil'](cs / 4));
} catch (cM) {
  cM[cs >>> 2] &= 4294967295 << 32 - cs % 4 * 8, cM['length'] = Math['ceil'](cs / 4);
}

经过以上操作,发现还缺少v14和v142参数,是在m.js文件中进行的赋值,又发现每次请求,m.js中的大数组是随机变化的。

window[$_0x3469('\x30\x78\x31\x32\x35', '\x65\x45\x30\x5a')] = '\x35\x37\x6f' + '\x38\x75\x33' + '\x70\x6b\x62' + '\x74';
window[$_0x3469('\x30\x78\x33\x34', '\x66\x56\x53\x75') + '\x32'] = $_0x3469('\x30\x78\x33\x31', '\x49\x44\x69\x5a') + '\x33\x36\x35' + $_0x3469('\x30\x78\x37\x34', '\x23\x64\x73\x53') + '\x32\x34';

 so等到需要时动态请求即可:

def get_v():
    """
    get v14 and v142
    :return:
    """
    res = session.get('https://match.yuanrenxue.com/api/match/14/m')
    v = re.findall("window\[\${0,1}.*?=(\${0,1}.*?);", res.text)
    if len(v) != 2:
        get_v()
    with open('./m.js', 'wb') as f:
        f.write(res.content)
    os.system('/usr/local/bin/node JsToJson m.js m.json')
    with open('./m.json', 'r', encoding='utf8') as f:
        node = json.loads(f.read())
    # 提取数组还原的代码及还原代码中的常量,其它代码无用
    array_revert_node = {
        'type': 'Program',
        'body': node['body'][:3],
        'sourceType': 'script'
    }
    with open('m_array_revert.json', 'w', encoding='utf8') as f:
        f.write(json.dumps(array_revert_node))
    os.system('/usr/local/bin/node JsonToJs m_array_revert.json m_array_revert.js')
    return v

完整代码:

py:

import json
import os
import re
import subprocess
import execjs
import requests

node = execjs.get()

session = requests.session()

headers = {'User-Agent': 'yuanrenxue.project'}
sum_num = 0


def login():
    data = {
        'username': 'lllll',
        'pwd': '********'
    }
    session.post('https://match.yuanrenxue.com/api/login', data=data)
    session.get('https://match.yuanrenxue.com/api/loginInfo')


def get_v():
    """
    get v14 and v142
    :return:
    """
    res = session.get('https://match.yuanrenxue.com/api/match/14/m')
    v = re.findall("window\[\${0,1}.*?=(\${0,1}.*?);", res.text)
    if len(v) != 2:
        get_v()
    with open('./m.js', 'wb') as f:
        f.write(res.content)
    os.system('/usr/local/bin/node JsToJson m.js m.json')
    with open('./m.json', 'r', encoding='utf8') as f:
        node = json.loads(f.read())
    array_revert_node = {
        'type': 'Program',
        'body': node['body'][:3],
        'sourceType': 'script'
    }
    with open('m_array_revert.json', 'w', encoding='utf8') as f:
        f.write(json.dumps(array_revert_node))
    os.system('/usr/local/bin/node JsonToJs m_array_revert.json m_array_revert.js')
    return v


def get_cookie(page_num, v):
    with open('m_array_revert.js', 'r', encoding='utf-8') as f:
        ctx = node.compile(f.read())
    v = [ctx.eval(i) for i in v]
    p = subprocess.Popen(['/usr/local/bin/node', './mz6.js', str(page_num), v[0], v[1]], stdout=subprocess.PIPE)
    m = p.stdout.read().decode('UTF-8').replace('\n', '')
    cookies = {
        'mz': 'TW96aWxsYSxOZXRzY2FwZSw1LjAgKE1hY2ludG9zaDsgSW50ZWwgTWFjIE9TIFggMTBfMTVfNykgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzExMC4wLjAuMCBTYWZhcmkvNTM3LjM2LFtvYmplY3QgTmV0d29ya0luZm9ybWF0aW9uXSx0cnVlLCxbb2JqZWN0IEdlb2xvY2F0aW9uXSw4LHpoLUNOLHpoLUNOLHpoLDAsW29iamVjdCBNZWRpYUNhcGFiaWxpdGllc10sW29iamVjdCBNZWRpYVNlc3Npb25dLFtvYmplY3QgTWltZVR5cGVBcnJheV0sdHJ1ZSxbb2JqZWN0IFBlcm1pc3Npb25zXSxNYWNJbnRlbCxbb2JqZWN0IFBsdWdpbkFycmF5XSxHZWNrbywyMDAzMDEwNyxbb2JqZWN0IFVzZXJBY3RpdmF0aW9uXSxNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvMTEwLjAuMC4wIFNhZmFyaS81MzcuMzYsR29vZ2xlIEluYy4sLFtvYmplY3QgRGVwcmVjYXRlZFN0b3JhZ2VRdW90YV0sW29iamVjdCBEZXByZWNhdGVkU3RvcmFnZVF1b3RhXSw4NzUsMCwyNSwxNDQwLDMwLDkwMCxbb2JqZWN0IFNjcmVlbk9yaWVudGF0aW9uXSwzMCwxNDQwLFtvYmplY3QgRE9NU3RyaW5nTGlzdF0sZnVuY3Rpb24gYXNzaWduKCkgeyBbbmF0aXZlIGNvZGVdIH0sLG1hdGNoLnl1YW5yZW54dWUuY29tLG1hdGNoLnl1YW5yZW54dWUuY29tLGh0dHBzOi8vbWF0Y2gueXVhbnJlbnh1ZS5jb20vbWF0Y2gvMTQsaHR0cHM6Ly9tYXRjaC55dWFucmVueHVlLmNvbSwvbWF0Y2gvMTQsLGh0dHBzOixmdW5jdGlvbiByZWxvYWQoKSB7IFtuYXRpdmUgY29kZV0gfSxmdW5jdGlvbiByZXBsYWNlKCkgeyBbbmF0aXZlIGNvZGVdIH0sLGZ1bmN0aW9uIHRvU3RyaW5nKCkgeyBbbmF0aXZlIGNvZGVdIH0sZnVuY3Rpb24gdmFsdWVPZigpIHsgW25hdGl2ZSBjb2RlXSB9',
        'm': m + str(page_num)
    }
    print(v)
    print(cookies)
    return cookies


def get_page_info():
    global sum_num
    url = 'https://match.yuanrenxue.com/api/match/14?'
    for i in range(1, 6):
        v = get_v()
        cookies = get_cookie(i, v)
        res = session.get(f'{url}page={i}', headers={'User-Agent': 'yuanrenxue.project'}, cookies=cookies).json()
        for j in res['data']:
            sum_num += j['value']


if __name__ == '__main__':
    login()
    get_page_info()
    print(sum_num)

js:

GOOD-GOOD-STUDY/mz6.js at master · tianmingbo/GOOD-GOOD-STUDY · GitHub

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
对于您提到的错误TypeError: list indices must be integers or slices, not str,这是因为您在访问列表中的元素时使用了字符串作为索引,而列表的索引必须是整数或切片类型。解决这个错误的方法是使用整数或切片来访问列表中的元素。 关于您提到的猿人js逆向的问,我需要更多的信息才能为您提供具体的答案。对于爬虫编写,您可以使用Python的各种库(如Requests、BeautifulSoup、Scrapy等)来获取网页的内容,并进一步解析和处理。您可以使用这些库发送HTTP请求获取网页内容,然后使用解析库来提取您需要的数据。 爬虫编写的一般步骤如下: 1. 导入所需的库 2. 发送HTTP请求获取网页内容 3. 使用解析库解析网页内容 4. 提取所需的数据 5. 进行数据的进一步处理和存储 您可以根据具体的需求和网站的结构进行相应的编写和调试。如果您需要更具体的帮助,请提供更多的信息。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Python:TypeError: list indices must be integers or slices, not str报错解决及原理](https://blog.csdn.net/hhd1988/article/details/128031602)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Python BeautifulSoup [解决方法] TypeError: list indices must be integers or slices, not str](https://download.csdn.net/download/weixin_38590567/14871394)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值