本帖最后由 buluo533 于 2025-6-21 09:16 编辑
接单遇到一个网站,发现登陆有个中等难度的滑块,话不多说。直接干:aHR0cHM6Ly93d3cubG9mdGVyLmNvbS9mcm9udC9sb2dpbi8=
食用前提:
(1)python基础(python复现代码)
(2)js基础(看懂js加密逻辑)
(3)密码学基础(涉及sha256,aes,rsa,base64)
密码学学习文章:密码学常用基本数据类型和运算在不同语言当中的体现------aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvdFg2V3NFdHNic2FvOUtiUlFwZXJIQQ==
sha256:一文读懂SHA-2-------aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvUG5ScHJNc01vM3VxUG5JQ25KQlUzZw==
base64:一文读懂Base64-------aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvSVpjOVJIakxSZmJzQzhibDNUR3FIUQ==
aes:分析crypto-js当中AES的实现----aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvRlBjUzlvRUQ5UHFDOV9nLXpmTEhqUQ==
rsa: 加密的诗篇:RSA与Padding技术的艺术融合------aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvaU0xS1lldy13R1VORXBwWk1YTG5vdw==
RSA当中整数的表示方法 ------aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvMWhQN090RUNOV2VqN2Vpd3RReExMQQ==
经历过一次滑块(如果没有可以看我之前的文章),我们简单先整理一下处理滑块的思路
(1)一般的滑块最开始肯定要先加载图片的的地址,拿到图片信息用于后续的距离或者轨迹,同时可能包含后续会用的加密参数
(2)分析滑块的加密逻辑,根据上一次的经验,加密参数请求会返回值用于登录
(3) 整理返回的加密信息,完成登录
一、流程分析
我们直接输入好账号密码,可以看到返回两个接口信息,第一个是登录的验证,第二个就是返回的图片信息(大胆假设,小心求证),我们先看一下,因为没有下断点,图片信息肯定也加载出来了,对比一下
大致看一下,大概是一个是底图,一个是滑块的图片,有一个id,一个type(大概就是类似版本号的东西),
很明显发现了端倪,返回值确实是图片地址,可以看出协议的信息data:image/png;base64, 说明可以通过base64编码读取图片信息,从而保存底图和滑块,id可以暂时留着,没看出来用处。
这个的滑块和之前的有点差别,点击的的登录后加载的。
可以看到我们的密码被加密了一次,同时呢,返回值是提示要求验证码进行校验,可以猜一下,大概是通过登录拿到cookie再去请求图片信息,这样再去通过滑块的验证,保险起见,可以测试,所以我们要先处理密码的加密方式
接下来我们滑动一下看看情况.
我们滑动测试一下发现,有一个校验的接口,接下来因为错误就重新加载了图片信息,看一下校验接口是怎么校验的信息。
我勒个豆,还是挺多的三个加密点要处理,应该就是直接从这里返回值看看有没有成功,但是都是加密后的结果
二、加密定位与还原
1、登录接口,密码加密
对密码的调用栈直接下断点,重新输入账号密码跟栈(上一篇文章有详细的跟栈思路)
到这里我们可以明显地看到密码的加密逻辑,我们直接用标准的来进行测试然后用python还原
还是很明显的一致,接下来用python还原登录
[Python] 纯文本查看 复制代码 from hashlib import sha256
def sha256_password(text):
return sha256(text.encode('utf-8')).hexdigest()
接下来实现登录的逻辑
[Python] 纯文本查看 复制代码 import time
def log_in(pass_word):
time_temp = int(time.time() * 1000)
passport = sha256_password(pass_word)
cookies = {
}
headers = {
}
params = {
'product': 'lofter-pc',
'_': time_temp,
}
data = {
'phone': '脱敏处理',
'passport': passport,
'type': '0',
'clientType': '0',
'deviceType': '3',
'Target': 'www.lofter.com',
}
response = session.post('脱敏处理', params=params, cookies=cookies, headers=headers,
data=data).json()
2、图片请求地址与距离识别
[Python] 纯文本查看 复制代码 def get_pic():
cookies = {}
headers = {}
params = {
'type': 'jigsaw',
}
response = session.get('脱敏处理', params=params, cookies=cookies,
headers=headers).json()
pic_id = response['data']['id']
pic_gap = response['data']['front']
pic_bg = response['data']['bg']
gap_pic = base64.b64decode(pic_gap)
bg_pic = base64.b64decode(pic_bg)
with open('gap.jpg', 'wb') as f:
f.write(gap_pic)
with open('bg.jpg', 'wb') as f:
f.write(bg_pic)
det = ddddocr.DdddOcr(det=False, ocr=True, show_ad=False)
with open('gap.jpg', 'rb') as f:
target_bytes = f.read()
with open('bg.jpg', 'rb') as f:
background_bytes = f.read()
res = det.slide_match(target_bytes, background_bytes, simple_target=True)
value = res['target'][0]
print('识别到距离', value)
return pic_id, value
这一部分逻辑就很简单了,主要就是要理解他是怎么产生的,看懂协议,用base64编码后直接读取保存下来
提取出图片之后直接用ddddocr识别到距离。
3、滑块加密逻辑还原
还是一样的
这个跟栈也是非常的简单,核心是他的加密算法逻辑,命名非常的直接,我们明显看出的是aes和base64,这就要大佬们读读密码学的文章,方便后续的分析
直接进到aes里面,先看一下传入的参数有哪些,很明显,我们之前请求剩下的id有用了,然后就是一个距离,我们通过ddddocr已经搞定了,这样的话我们就知道这个接口的请求体就是一个图片接口返回的id和一个距离
同时我们可以看到iv,padding,key这些非常关键的参数,但是这个又有些不同寻常,我们经常看到的加密一般是这样的写的(举个例子)
[JavaScript] 纯文本查看 复制代码 let key = crypto.enc.Utf8.parse('key');
let iv = crypto.enc.Utf8.parse('iv' );
function aesEncrypt(plaintext) {
let encrypted = crypto.AES.encrypt(
crypto.enc.Utf8.parse(plaintext),
key,
{
iv: iv,
mode: crypto.mode.CBC,
padding: crypto.pad.Pkcs7
}
);
return encrypted.toString();
}
但是这次很明显有一些不同,我们继续跟进encrprt的逻辑中进行分析
继续进去
这里我们能看到大概的逻辑了,就是一个非常明确的aes加密的逻辑,这里是一些初始化的操作。然后回到梦开始的地方,因为现在函数里面的内容都是在最开始传进来的
其实我们稍微往上看一点就看到了key和iv随机生成的一个位置,随机生成的代码也清晰可见了
[JavaScript] 纯文本查看 复制代码 getRandomString = function(t) {
for (var e = "", n = 0; n < t; n++)
e += Math.round(Math.random()) ? Math.floor(9 * Math.random()) : String.fromCharCode(Math.floor(25 * Math.random() + 97));
return e
}
我们直接python还原一手,暂时没看到其他参数值的赋值,先写成16位
[Python] 纯文本查看 复制代码
import random
def getRandomString(t=16):
s = ""
for _ in range(t):
if random.randint(0, 1):
s += str(int(9 * random.random()))
else:
s += chr(int(25 * random.random()) + 97)
return s
接下来就是看看padding和加密模式,常见的有iv的就是CBC(不懂的还是建议补一补密码学基础),我们直接猜一下,也可以翻翻源码,也有惊喜的发现(我刚开始确实不是很熟悉这个,所以也是这样找到的,半猜半蒙)
这样有了padding和加密模式,这样我们先用js还原一下测试一下,再用python来复现
非常完美!!!一模一样,接下是python的还原,众所周知,crypto.enc.Utf8.parse(iv)在python中等价于iv.encode('utf-8')(不懂的还是密码学基础)
这样我们继续还原
[Python] 纯文本查看 复制代码 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modesfrom cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.padding import PKCS7
def aesEncrypt(plaintext, aes_iv, aes_key):
if isinstance(aes_iv, str):
aes_iv = aes_iv.encode('utf-8')
if isinstance(aes_key, str):
aes_key = aes_key.encode('utf-8')
cipher = Cipher(algorithms.AES(aes_key), modes.CBC(aes_iv), backend=default_backend())
encryptor = cipher.encryptor()
padder = padding.PKCS7(128).padder()
padded_data = padder.update(plaintext.encode('utf-8')) + padder.finalize()
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
return base64.b64encode(ciphertext).decode('utf-8')
试试加密结果对照
非常ok的搞定了,接下来就是base64编码的操作了
至于怎么看出来的一个是js中的atob和btoa,以及密码学常用基本数据类型和运算在不同语言当中的体现(q佬文章),下一个就是对请求头参数的处理
4、请求头加密参数处理
还是老规矩,跟进去看看
这个还是很明显,一个公钥,加密内容是aes的key拼接一个-和iv,接下可以简单看一下rsa算法(求助过q佬,rsa的魔改点很少),可以简单结合文章和源码了解一下rsa
很标准的rsa算法,大数组,我们找一下公钥,然后用python还原
经常加密的大佬应该知道rsa以pem的格式存储公钥
[Python] 纯文本查看 复制代码 from Crypto.PublicKey import RSAfrom Crypto.Cipher import PKCS1_v1_5
def x_encseckey_get(rsa_iv, rsa_key):
with open('public.pem', 'r') as f:
public_key = f.read()
datas = key + "-" + iv
text = datas.encode('utf-8')
pk = RSA.importKey(public_key)
cipher = PKCS1_v1_5.new(pk)
cipher_text = cipher.encrypt(text)
cipher_text_base64 = base64.b64encode(cipher_text).decode('utf-8')
return cipher_text_base64
我们的生成是没有问题的,这个可能不好检测,但是可以通过生成公私钥对来测试加密情况,方便检验rsa的效果
5.返回值参数解密算法分析
我们可以看到,在对接口请求之后,就开始处理返回值的信息了,熟悉js的伙伴都知道,js请求都是异步操作,这也加大了还原的难度还有一个默认的arrayBuffer()函数,我们可以去js官方文档查查成分
官方网址:https://developer.mozilla.org/zh-CN/docs/Learn_web_development/Getting_started/Your_first_website/Adding_interactivity
也就是将返回值转化为二进制
将断点到异步执行后后查看t的值
这部分对应python的实现还是可以参考文章内容
[Python] 纯文本查看 复制代码 def array_buffer_to_base64(buffer):
if isinstance(buffer, list):
data = bytes(buffer)
elif isinstance(buffer, bytearray):
data = bytes(buffer)
else:
# 直接使用 bytes 对象
data = buffer.encode('utf-8')
return base64.b64encode(data).decode('utf-8')
def base64_to_array_buffer(base64_str):
decoded_data = base64.b64decode(base64_str)
return decoded_data
接下来就是跟进去对t的一个处理逻辑
嘎嘎熟悉的一个base64操作,btoa
接下来就是通过aes进行的一个解密操作
这里需要主义的就是n.parse和之前的s.parse不一样,我们可以跟进去看一下
还是一个base64的运算操作,然后就是很常规的解密操作,key和iv都是之前生成的,以及s.parse的操作分析,接下里是一个base64decoed的函数操作,依旧跟进去看看
我们可以看到w函数的位置,以及整个的调用逻辑(不清楚w是函数的可以看看js基础语法,箭头函数),这个能看出来或者借助ai工具能分析出来是一个base64的操作
我们可以看到最后结果应该是这样子的 ,整体来说返回值是一个base64+aes解密过程,之前代码和思路很清晰了,大佬们可以尝试一下还原,评论区晒晒成果
三、总结
这段时间狠狠地研究和回顾了一下学习的过程,刚开始看到这个网站的时候以为很简单,后面越做越难受,发现还是对密码学理解太浅,在拷打ai和q佬的指导下也算是搞定了,狠狠地补了一下密码学。这一个滑块总的来说还是很有研究的价值,很多js的语法特性与密码学的结合,以及不同语言之前的特性糅合,适合进阶的小伙伴食用。 我也可以安心去准备期末考试了,我还要肝下一篇ast
|