猿人学第一届比赛平台刷题记录

2.js混淆_动态cookie1

首先使用hook cookie的方式定位到加密位置:
在这里插入图片描述在这里插入图片描述

采用“手动翻译”的方式,把每个函数都拿出来运行一下,简化一下这句加密代码,可见_0x313b78是要逆向的目标,加下来就是扣代码的过程了,其中有几个格式化检测,扣下来太长了就不放了。
在这里插入图片描述

function GET_SDK(){
var document = {};
var navigator = {'vendorSub':'',}
//这部分是扣下来的加密代码
document.cookie =  _0x313b78(Date.parse(new Date()))+ "|"+ Date.parse(new Date())
return document.cookie
}
def get_m():    
    filename = '2.js'
    ctx = execjs.compile(open(filename,'r',encoding = 'utf8').read())
    m = ctx.eval('GET_SDK()')
    return m

headers = { 'cookie': f'sessionid=24v7hqkro91o8xm6fppg5nib133zl4yf;m={get_m()}',
           'User-Agent': 'yuanrenxue.project'}

sum_all = 0
for i in range(1,6):
    url = 'https://match.yuanrenxue.com/api/match/2?page=%d'%i
    res = requests.get(url = url,headers = headers,verify = False)
    print(res.json())
    for value_dic in res.json()['data']:
        sum_all += int(value_dic['value'])
print('和为:',sum_all)

3.访问逻辑-推心置腹

反爬点:请求头的顺序,session的保持
经观察可发现,每次请求数据的时候需要先请求一次jssm这个文件,查看网页的源码也可以得到验证:
在这里插入图片描述
在这里插入图片描述
解决方案:
session保持、使用fiddler查看真实请求头。

import requests

import pandas as pd 

from requests.packages import urllib3
urllib3.disable_warnings()

session = requests.Session()
session.headers = {'Host': 'match.yuanrenxue.com',
'Connection': 'keep-alive',
'Content-Length': '0',
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"',
'sec-ch-ua-mobile': '?0',
'User-Agent': 'yuanrenxue.project',
'sec-ch-ua-platform': '"Windows"',
'Accept': '*/*',
'Origin': 'https://match.yuanrenxue.com',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Dest': 'empty',
'Referer': 'https://match.yuanrenxue.com/match/3',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cookie': 'sessionid=t1eoazv6q9q95n4v4ma4nwe9v5k1gpcj'
}
list_all = []
url_jssm = 'https://match.yuanrenxue.com/jssm'
for i in range(1,6):
    url_api = 'https://match.yuanrenxue.com/api/match/3?page=%d'%i
    session.post(url = url_jssm,verify = False)
    res = session.get(url = url_api,verify = False)
    for value_dic in res.json()['data']:
        list_all.append(int(value_dic['value']))

result = pd.value_counts(list_all)
print(result)

4.雪碧图、样式干扰

反爬点:css偏移
经观察可以发现返回数据是这样的:
在这里插入图片描述

import re
import base64,hashlib
import requests
from bs4 import BeautifulSoup
from requests.packages import urllib3
urllib3.disable_warnings()

class Match_5:
    
    def __init__(self) -> None:
        #数字的映射字典
        self.cast_num_dic = {
            '放入对应的值':0,
            '放入对应的值':1,
            '放入对应的值':2,
            '放入对应的值':3,
            '放入对应的值':4,
            '放入对应的值':5,
            '放入对应的值':6,
            '放入对应的值':7,
            '放入对应的值':8,
            '放入对应的值':9
        }
        self.headers = {
            'Cookie': 'sessionid=1khdr163qlqejo6c6uwj4xbvkoxiwwvi',
            'User-Agent': 'yuanrenxue.project'
        }
        
    def __parse_json(self):
        sum_all = 0
        for i in range(1,6):
            print('当前请求第%d页'%i)
            url_api = 'https://match.yuanrenxue.com/api/match/4?page=%d'%i
            res = requests.get(url = url_api,headers = self.headers,verify = False)
            key = res.json()['key']
            value = res.json()['value']
            info = res.json()['info']         
            display_none = self.__get_display(key,value)
            soup = BeautifulSoup(info,'lxml')
            for td in soup.select('td'):
                actual_num = {}
                imgs = [img for img in td.select('img') if display_none not in img['class']]
                for index,img in enumerate(imgs):
                    left_css = float(re.search('left:(.*?)px',img['style']).group(1)) / 11.5
                    img_num = self.cast_num_dic[img['src']]
                    actual_num[int(index + left_css)] = img_num
                sum_all += int(''.join(str(actual_num[i]) for i in range(len(actual_num))))
        return sum_all
   
    def __get_display(self,key,value):
        b64_input = key + value
        bytesStr = b64_input.encode(encoding='utf-8')
        b64str = base64.b64encode(bytesStr)
        b64_out = b64str.decode(encoding = 'utf-8') .replace('=', '')
        md5_input = b64_out.encode(encoding = 'utf-8')
        display_none = hashlib.md5(md5_input).hexdigest()
        return display_none

    def go(self):
        ans = self.__parse_json()
        print('总和为:', ans)

match_5 = Match_5()
match_5.go()

5.js混淆-乱码增强

进来发现cookie中有m和RM4hZBv0dDon443M两个加密参数,用油猴脚本Hook一下,发现m输出了5次,前四次的m在1716行生成,第五次的m在866行生成 ,RM4hZBv0dDon443M在978行生成。在这里插入图片描述
第五次生成的m参数也是发送请求的m参数。_$yw也是url中的m,所以对于m这个参数来说,目标就是扣_0x474032这个函数。
在这里插入图片描述

接下来就是扣代码:

在这里插入图片描述
在扣代码的过程中,遇到b64pad未定义,此时可以进入浏览器调试,发现b64pad的值没有改变,可以写死为1。
接下来依然是依照缺啥补啥的原则扣代码。直到_0x474032函数可以被运行时,与浏览器生成的m进行一个验证,看看扣的代码对不对。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

此时会发现虽然_0x474032函数成功运行了,但是依然没有得到一致的输出,说明某个地方出错了。
对hook进行一下修改:

(function() {
    'use strict';
        var pre = "";
    Object.defineProperty(document, 'cookie', {
        get: function() {
            console.log('Getting cookie');
            return pre
        },
        set: function(val) {
            console.log('Setting cookie====>', val);
            console.log('_$6_为--->' + _$6_ + ',_$tT为--->' + _$tT + ',_$Jy为--->' + _$Jy);
            debugger ;
            pre = val;
        }
    })
})();

此时可以发现前四个m都是在1716行生成的,生成规律与_$6_,_$tT,_$Jy的关系如下图所示:
在这里插入图片描述

在这里插入图片描述
第五个m在866行生成,生成规律与_$6_,_$tT,_$Jy的关系如下图所示:
在这里插入图片描述

第五个m
结合以上分析可以知道,前四次的m生成中,_$6_都是8821003647,_$tT依次是-172015004、-717253467(三次),_$Jy为461512024、时间戳(三次)。
可以hook一下_$Jy与_$tT,可以看是怎么生成的,可以发现_$tT虽然在1721行进行了赋值,但并没有使用这里的值,所以_$tT搞清楚了。再看_$Jy,可以得知第一次的_$Jy是在274行生成,后三次的_$Jy是在1720行生成的时间戳。
在这里插入图片描述
第五次m的这三个值是写死的,分别为-389564586、-660478335、-405537848,此时已经可以将这几个值直接写成第五次的值就行了,我这里为了尽量还原它的算法以达到练习js逆向的效果,将它还原了出来:在这里插入图片描述
这其中有几个坑需要注意,我刚开始的时候踩坑踩得很难受,像下图中的_$Jy与_$tT在扣代码的过程中不会报错,但是值为undefined,需要注意第273行与274行的这俩行代码,是这里给第一次m进行了赋值。
在这里插入图片描述在这里插入图片描述

再者是_0x11a7a2这个函数,一定要注意它是如何运行的。可以多次进行浏览器调试之后,将其中几个值写死,也可以像我一样在脚本的开头补一下它的环境和指纹。我是补了它的$_zw这个数组。

在这里插入图片描述

m参数的加密函数_0x474032扣下来之后,RM4hZBv0dDon443M参数就好办了,可以发现它是来自于window的_$ss,hook一下_$ss就知道是1229行来的,观察后发现是一个标准的AES,require(“crypto-js”)即可。在这里插入图片描述在这里插入图片描述

扣下来的主流程如下:

function GET_SDK(){
	//这部分是扣下来的代码,省略。
	//这部分是扣下来的代码,省略。
	//这部分是扣下来的代码,省略。
    for (i = 1; i <= 4; i++) {
        console.log('*****************第' + i + '个m生成中*****************');
        _$Wa = Date.parse(new Date());
        _0x4e96b4['_$pr']['push'](_0x474032(_$Wa));
        delete _0x4e96b4['_$Jy'];
        delete _0x4e96b4['_$tT'];
        _0x4e96b4['_$Jy'] = _0x2d5f5b();
        // _0x4e96b4['_$tT'] = _0x2d5f5b() - _0x12eaf3();
        _0x4e96b4['_$tT'] = -0x2ac06b5b; //2057
    }
    delete _0x4e96b4['_$tT'];
    delete _0x4e96b4['_$Jy'];
    _0x4e96b4['_$6_'] = -0x173848aa;
    _0x4e96b4['_$tT'] = -0x275e197f;
    _0x4e96b4['_$Jy'] = -0x182c0438;
    console.log('*****************第5个m生成中*****************');
    _$yw = new _0x35bb1d()['valueOf']()['toString']();
    cookie_m = _0x474032(_$yw);
    _0x4e96b4['_$pr']['push'](_0x474032(_$yw));
    _0x4e96b4['_$is'] = _$yw;
    _$Ww = _$Tk['enc']['Utf8']['parse'](window['_$pr']['toString']());
    _0x4e96b4['_$qF'] = CryptoJS['enc']['Utf8']['parse'](_0x4e96b4['btoa'](_$yw)['slice'](0x0, 0x10));
    _0x29dd83 = _$Tk['AES']['encrypt'](_$Ww, _0x4e96b4['_$qF'], {
        'mode': _$Tk['mode']['ECB'],
        'padding': _$Tk['pad']['Pkcs7']
    });
    cookie_RM4hZBv0dDon443M = _0x29dd83['toString']();
    return {
        'cookie': 'm=' + cookie_m + ';RM4hZBv0dDon443M=' + cookie_RM4hZBv0dDon443M,
        'm': window._$is,
        'f': window.$_zw[23]
    }
    }

总结:一定要注意window = global、window = {}以及window = this的区别,这决定了你扣的代码该如何修改细节,刚开始的时候因为这个地方错了很多次。

7.动态字体,随风漂移

反爬点:动态字体
首先打开F12,观察抓包结果:
在这里插入图片描述
可以看到,数字2342对应xf789、xb925、xb642、xf789,先记住这个结果,再看下一页的抓包:
在这里插入图片描述
可以看到,数字5553对应xb458、xb458、xb458、xe178。
我们在上一页的结果中,数字3对应了xb925;但在这一页的结果中,数字3对应了xe178。可以得知这个数字的映射关系是在改变的。我们再看下面这段代码:
在这里插入图片描述
从这段代码可以看出,我们抓包得到的数据中,woff中的数据被获取并用以下载一个字体包,woff中的数据是一段base64编码后的二进制数据。
在这里插入图片描述
我们分别把刚才抓到的两个包所对应的字体文件下载下来,后缀名改为woff,再保存一份ttf格式的字体文件,用在线字体编辑器打开:

在这里插入图片描述

在这里插入图片描述
使用python中的fontTools可以将上面两个包转为xml文件,方便对比查看:

# 加载字体文件:
font = TTFont('字体文件.woff')
# 保存为xml文件:
font.saveXML('字体文件.xml')

fontTool包的具体使用可以查看十一大佬的文章
经过上面一番操作,我们有以下几个文件:
在这里插入图片描述
对比一下两个字体:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
经过对比,我们可以找到两个字体文件的共同点:
两个同样的数字1,其name不同,但坐标on的数据是一致的
利用上面的这个特性,我们可以建立字体的映射关系:
在这里插入图片描述
经过以上分析,我们下面可以开始写代码了:
在这里插入图片描述
在这里插入图片描述

def parse_woff():
    word_dic = {映射字典}
    res_dic = {}
    font = TTFont('./7_woff.woff')
    for uni in font.getGlyphNames()[1:]:  #依次获取name标签的值,不要第一个,并for循环遍历
        on_data = font['glyf'][uni].flags   #此处返回的是对应的name标签的bytearray
        on_key = ''.join([str(n) for n in on_data])  #获得对应的name标签的on坐标值,如1001101111
        corresponding_num = uni.replace('uni','&#x')   #替换一下,用以和网页中响应的数据进行映射
        res_dic.update({corresponding_num:word_dic[on_key]})
    
    return res_dic

此函数每次返回的res_dic即为每次解析好的字体对应关系,如下图所示:
在这里插入图片描述

dic_ans = {}
for page in range(1,6):
        yyq = 1
        print('当前请求第%d页'%page)
        url = f'https://match.yuanrenxue.com/api/match/7?page={page}'
        res = requests.get(url = url,headers = headers)
        data_list = res.json()['data']
        woff_content = base64.b64decode(res.json()['woff'])    #获取的是woff文件的二进制数据,直接在下面以二进制写入文件即可
        with open('7_woff.woff','wb') as fp:
            fp.write(woff_content)
        res_dic = parse_woff()    #解析,并返回对应的字体映射关系
        for i in range(0,len(data_list)):
            data_list2 = data_list[i]['value'].split(' ')[:-1]
            data = ''.join([res_dic[num] for num in data_list2])
            name = player_name_list[yyq + (page-1)*10 ]    #根据网页中的名字计算逻辑,此处略过
            dic_ans.update({name:int(data)})
            yyq+=1      
print('所有玩家及其对应的胜点字典为:\n',dic_ans,'\n')
print('胜点最高的玩家为:\n',max(dic_ans, key=lambda x: dic_ans[x]))

结果:
在这里插入图片描述

15.备周则意怠,常见则不疑(wasm)

在这里插入图片描述
打开F12,观察找到数据所在的包,发现有两个参数,一个是加密的m值,另一个是页码page值。目标就是逆向m值。
很容易发现m加密的位置:
在这里插入图片描述
在这里插入图片描述
此时发现q函数是native code,是通过wasm文件定义的。
于是接下来的流程就是调用第三方python库pywasm来调用encode这个函数了:

import math
import random
import time
import pywasm
import requests

def get_m():
    t = int(time.time())
    t1 = int(t / 2)
    t2 = int(t / 2 - math.floor(random.random() * 50 + 1))
    vm = pywasm.load("main.wasm")
    r = vm.exec("encode", [t1, t2])
    m = f"{r}|{t1}|{t2}"
    return m 

sums = 0
headers = {'cookie': 'sessionid=9a2irg2q2x66wrkfbm685cu6fpkb23rt;',
           'User-Agent': 'yuanrenxue.project'}
for i in range(1, 6):
    url = f"https://match.yuanrenxue.com/api/match/15?page={i}&m={get_m()}"
    print(url)
    response = requests.get(url = url,headers = headers).json()
    for each in response["data"]:
        sums += each["value"]
print('总和为:',sums)

其中有一个巨坑,在刚开始的时候我下载这个wasm的方式是点入sources里面,右键这个wasm文件来下载,但发现这样的wasm文件python会报错:【magic header not detected】,后来尝试了在network里面下载(open in new tab)才解决。可能是因为在sources里面看到的wasm文件已经是编译过的代码导致的。在这里插入图片描述
在这里插入图片描述

16.js逆向-window蜜罐(webpack)

反爬点:
①:node环境与浏览器环境不同,进行“投毒”
②:webpack的扣取方法
③:格式化检测
主要流程:
首先打开F12查看抓包,很容易就能定位到加密点:
在这里插入图片描述
然后按照缺啥补啥的方式去扣代码即可,以这种扣代码的方式可以算作解法一。需要注意在try-catch或if-else中的投毒,看看在浏览器里是什么。
在这里插入图片描述
也可以把f、_0x4c28、_0x34e7写死,我这里选择尽量还原代码的方式,没有写死。
在这里插入图片描述
除了这种硬扣的方法以外,还可以根据webpack的特性去改写它:
在这里插入图片描述
webpack的一般逻辑:

!function (allModule) {
  function useModule(whichModule) {
      allModule[whichModule].call(null, "hello world!");
  }
  useModule(0)   //导入的allModule是数组形式,这里以这样的方式去引用
}([
  function module0(param) {console.log("module0: " + param)},
  function module1(param) {console.log("module1: " + param)}
]);

!function (allModule) {
  function useModule(whichModule) {
      allModule[whichModule].call(null, "hello world!");
  }
  useModule('module1')   //导入的allModule是对象形式,这里以这样的方式去引用
}({
  module0: function (param) {console.log("module0: " + param)},
  module1: function (param) {console.log("module1: " + param)}
});

webpack的五步改写流程:
①找到加载器(加载模块的方法):function n(r){}即为加载器
②找到调用的模块:依次调用了127、58、732
③构造一个自执行方法
④导出加密方法
⑤编写自定义方法,按照流程加密

如下即为解法二,改写webpack,127、58、732里无用的代码要注意删除,不然会影响代码运行:
在这里插入图片描述

18.jsvmp-洞察先机

插桩打日志直接出秘钥的解决方法

打一下xhr断点可以很容易跟到如图所示的位置,发现XMLHttpRequest.prototype.open方法被改写了,那么跟进去看一看
在这里插入图片描述
我先试着用解决抖音的vmp的办法去插桩:找到最大坨的那个函数插桩
在这里插入图片描述

如图,打一下这个_[2]里面的东西,至于为什么要打它,当然是调试出来的,里面有一些我们可能需要的关键字符串
在这里插入图片描述
在这里插入图片描述
此时结合上面两张图已经解出这道题了:AES-CBC加密,填充方式为Pkcs7
但也可以看到
“key”:{“words”:[909402422,845427045,909402422,845427045],“sigBytes”:16}
“iv”:{“words”:[909402422,845427045,909402422,845427045],“sigBytes”:16}
这还需要我们还原为它们本来的样子才行,可以用这个方法:
先另开一个空白网页,参考我的这篇文章,把AES源码导进来:
在这里插入图片描述
这里我给出字符串与32位整数数组互转的办法:

// 将字符串转化为字节数组
var text = "64a62d1e64a62d1e";
var encoder = new TextEncoder();
var data = encoder.encode(text); // data为Uint8Array类型的字节数组

// 将字节数组转化为32位整数数组
var blockCount = Math.ceil(data.length / 4);
var blocks = [];
for (var i = 0; i < blockCount; i++) {
  var word = 0;
  for (var j = 0; j < 4; j++) {
    var index = i * 4 + j;
    if (index < data.length) {
      word |= data[index] << (8 * (3 - j));
    }
  }
  blocks.push(word);
}
console.log(blocks)   // 输出结果:[909402422, 845427045, 909402422, 845427045]
// 将32位整数数组转化为字节数组
var encryptedBlocks = [909402422, 845427045, 909402422, 845427045];
var words = CryptoJS.lib.WordArray.create(encryptedBlocks);
var encrypted = CryptoJS.enc.Hex.parse(words.toString());

var text = CryptoJS.enc.Utf8.stringify(encrypted);
console.log(text); // 输出结果:64a62d1e64a62d1e

根据这个方法,我们可以得到key和iv,同时也能得到加密前的原文,也在日志里打出来了。
不过可以发现key、iv是64a62d1e64a62d1e,加密前原文是"2|80m696,78m701,77m702,76m703,76d703,76u703"这种形式,接下来就研究它们是怎么生成的。
通过搜索key和iv,我们找到日志中它出现的第一个地方:
在这里插入图片描述
要怎么定位到最后一个时间戳的断点呢?可以通过条件断点解决,我们搜索一下6 '(即日志开头的字符串),发现有几十个,数一下这个关键位置是第几个:
在这里插入图片描述
那么定义一个window.Mycount = 0;在这里打上条件断点:
在这里插入图片描述
这样当第42次进入__U===6的逻辑时,会停在此处,之后单步慢慢调试就可以跟到这里,可以看出来这就是一个十进制转十六进制而已:
在这里插入图片描述

在这里插入图片描述
之后经过这里的一大坨三元表达式把时间戳拼起来了:
在这里插入图片描述
至于加密的原字符串2|80m696,78m701,77m702,76m703,76d703,76u703,2一看就是页码,后面的一坨可以直接固定写死。
请求就完事了:

from Crypto.Cipher import AES
import base64
from Crypto.Util.Padding import pad
import requests
import time
import math

headers = {
    "user-agent": "yuanrenxue.project",
    "cookie":"sessionid="
}

# 加密
def AES_encrypt(text,key,iv):
    key = key.encode('utf-8')
    iv = iv.encode('utf-8')
    padding_text = pad(text.encode(), AES.block_size, style='pkcs7')

    cryptos = AES.new(key, AES.MODE_CBC, iv)
    cipher_text = cryptos.encrypt(padding_text)
    return base64.b64encode(cipher_text).decode()

def get_data(page):
    timestamp = math.floor(time.time())
    enc_text = f"{page}|80m696,78m701,77m702,76m703,76d703,76u703"
    key_and_iv = str(hex(timestamp)[2:]) + str(hex(timestamp)[2:])
    request_url = f"https://match.yuanrenxue.cn/match/18data"
    params = {
        "page": page,
        "t": timestamp,
        "v": AES_encrypt(enc_text,key_and_iv,key_and_iv)
    }
    res = requests.post(request_url,headers=headers,params=params)

    return res.json()

sum_all = 0

for page in range(1,6):
    print(f"请求第{page}页:")
    data = get_data(page)
    print(data)
    for v in data["data"]:
        value = v["value"]
        sum_all += value

print("答案为",sum_all)

在这里插入图片描述

对于您提到的错误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 ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值