好友
阅读权限 25
听众
最后登录 1970-1-1
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关.本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责
用产品体验网站做某天御滑块的纯算分析,难度适中,使用地址:aHR0cHM6Ly90aWFueXUuMzYwLmNuLyMvZ2xvYmFsL2RldGFpbHMvc2xpZGluZy1wdXp6bGU=
参考文章:
RSA密钥格式解析&转换 ------aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvcHcycEFEYU1QaWJpTlJvQ3dhOUlzZz9zY2VuZT0x
加密的诗篇:RSA与Padding技术的艺术融合------ aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvaU0xS1lldy13R1VORXBwWk1YTG5vdw==
【密码学】分组密码模式 ------ aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvNzZjNXNyWVZrakdIS203MmN4TU1jUQ==
代码使用了混淆加密,可以参考蔡老板文章和视频做ast还原处理,我就不献丑,只做一个简单的处理。
一、流程分析
打开控制台就是一个 debugger,直接选择永不停留简单过掉,或者hook也行。
完成一次完整的滑块滑动,发现就两个接口,一个是前置请求接口,一个是校验接口,我们进行多次滑动来分析固定值和可疑的加密参数
发现一共有七个可疑的变化参数。
我们查看前置接口的返回值,发现 captchaId, token是由前置接口返回生成的,所以不需要处理,接下来就是背景图和滑块图的信息都在接口当中
好家伙,背景图被切成了32条,重新排序的,但是他在接口中返回就是乱序,说明后期在前端也进行了重新组合处理。
所以我们需要处理的内容有:
1、前置请求变化参数 timestamp,nonce,sign
2、 背景图重新贴图处理,识别距离
3、 校验接口 report,length参数
二、参数逆向分析和还原
1、前置接口还原分析
我们直接跟进去下断点,可以简单翻看一下源码,大体上是个ob混淆,有很多十六进制数字和编码的字符串,可以简单用一个ast插件处理替换
[JavaScript] 纯文本查看 复制代码
traverse(ast, { StringLiteral({node}) {
if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
node.extra = undefined;
}
}
});
traverse(ast, {
NumericLiteral({node}) {
if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
node.extra = undefined;
}
}
});
这次滑块加密有一个特点,很多加密都是在本地作用域实现的,有看作用域习惯的大佬可能上手嘎嘎快
每次根据本地变量名搜索,很快能定位到加密生成的位置,我们下断点,重新加载滑块
先简单在控制台测试一下,发现确实是函数生成的逻辑,我们分析一下大致的流程
[JavaScript] 纯文本查看 复制代码
_0x888e9f[_0x37f37d(0x3eb, '\x30\x32\x4a\x4a')]( _0x374653,
_0x888e9f[_0x37f37d(0x534, '\x55\x35\x6a\x4d')],
)(_0x52dfa5);
这样可以看出来 _0x374653是一个函数名,传入参数是'setRequestBody',他们的返回值也是一个函数,传入的参数是_0x52dfa5,我们直接跟进去看看
进来之后很明显就看到了两个要处理的参数,我们直接下断点分析一下逻辑
[JavaScript] 纯文本查看 复制代码
timestamp: Math.round( new Date()[_0x2639ff(0x3e3, '\x69\x4a\x58\x55')](),
),
nonce: _0x1b59d6.sCtwF(
Math[_0x2639ff(0x5ac, '\x64\x6d\x6e\x5d')](
new Date()[_0x2639ff(0x183, '\x28\x67\x62\x55')](),
),
Math[_0x2639ff(0x5df, '\x74\x52\x38\x79')](
0x5f5e100 * Math[_0x2639ff(0x290, '\x79\x5d\x51\x64')](),
),
我们对混淆借助控制台进行处理后,还原代码为
[Asm] 纯文本查看 复制代码
var timestamp = Math.round(new Date()['getTime']());
var nonce = Math['floor'](100000000 * Math['random']())
这两个参数我们就处理完了,接下来就是找sign的位置和加密逻辑,我们直接在返回值位置下断点,看看整体的流程。
很明显发现了sign的赋值位置和大致的逻辑
[JavaScript] 纯文本查看 复制代码
_0x1b59d6[
_0x2639ff(0x147, '\x5e\x33\x58\x44')
](md5, _0x3ca1de))
很明显能看出加密的逻辑,第一个参数是一个函数名,对第二参数进行加密,我们可以看到的函数名是md5,传入参数是_0x3ca1de,我们需要先简单测试一下是不是标准算法
所以sign核心是一个md5加密,我们再看看_0x3ca1de的生成逻辑
大体上就是对前面的请求头的参数转化为字符串拼接,所以这一部分我们整体用python还原
[Python] 纯文本查看 复制代码
from hashlib import md5
import time
def sign_md5(en_data):
return md5(en_data.encode()).hexdigest()
def nonce_get():
t1 = round(time.time() * 1000)
t2 = random.randrange(100000000)
return t1 + t2
def first_response():
timestamp = str(int(time.time() * 1000))
nonce = nonce_get()
headers = {
}
data = {
"appId": "dc1db94ea7b3843c",
"dc": 0,
"ec": 0,
"hc": 0,
"nonce": nonce,
"os": 3,
"pc": 0,
"phone": 10000000000,
"pn": "com.web.tianyu",
"rc": 0,
"sdkName":脱敏处理,
"timestamp": timestamp,
"type": 1,
"ui": 'null',
"version": "2.0.0",
"xc": 0,
}
encode_sign = ''.join(f"{key}{value}" for key, value in data.items())
sign = sign_md5(encode_sign)
data['sign'] = sign
response = requests.post(脱敏处理, headers=headers, data=data).json()
2、校验接口还原分析
完成前置接口的加密逻辑,我们可以提取的参数有背景图地址,滑块地址, captchaId,token, 接下来就是分析校验参口的加密逻辑 report参数
还是同理跟着作用域可以很快找到加密的地址,都是明文的(说明直接搜也可以找到),然后就是来分析他的加密逻辑
[JavaScript] 纯文本查看 复制代码
_0x1b59d6[_0x34f404(0x169, '\x35\x69\x66\x73')]( _0x204684,
_0x1b59d6.sQorU,
)(
JSON[_0x34f404(0x424, '\x7a\x5b\x6d\x49')](
_0x1022c0[_0x34f404(0x4f5, '\x5a\x69\x26\x6a')],
),
),
还是一样的,我们通过控制台进行还原
可以看到前面一部分是通过 _0x204684(_0x1b59d6.sQorU)返回一个函数,后面轨迹的值就是传入的参数
很明显他的加密逻辑就是对轨迹的处理,我们跟进去看看。
这里的嵌套比较多,我们要按照代码的执行顺序来分析。
[JavaScript] 纯文本查看 复制代码
_0x145c35[_0x1426e3(0x571, '\x46\x59\x43\x6f')](
_0x1b59d6[_0x1426e3(0x348, '\x69\x4b\x4c\x39')](
atob,
_0x4070dc.vConfig.k,
),
)
这样我们就有了一个比较清晰的逻辑,还有一个大胆的猜测,这是一个rsa加密 ,setPublicKey(atob(_0x4070dc.vConfig.k))
典型的pkcs8密钥格式(不清楚的看文章,这个很重要)
接下来就是核心逻辑的处理。
[JavaScript] 纯文本查看 复制代码
_0x1b59d6[_0x1426e3(0x466, '\x55\x58\x4a\x5b')]( _0x145c35[_0x1426e3(0x5ff, '\x66\x40\x31\x4c')](_0x42739f),
_0x1b59d6[_0x1426e3(0x640, '\x4e\x4d\x56\x30')](
md5,
_0x1b59d6[_0x1426e3(0x452, '\x4b\x34\x26\x57')](
_0x4070dc[_0x1426e3(0x37e, '\x26\x50\x47\x72')][
_0x1b59d6.priWD
],
_0x4070dc[_0x1b59d6[_0x1426e3(0x4fe, '\x26\x50\x47\x72')]][
_0x1b59d6[_0x1426e3(0x141, '\x51\x57\x47\x76')]
],
),
),
)
这是整个抠出来的混淆逻辑,先进行第一步的处理
[Asm] 纯文本查看 复制代码
_0x1b59d6["PjQNt"](_0x145c35["encryptLong"](_0x42739f), _0x1b59d6["pmrAl"](md5, _0x1b59d6['vazGS'](_0x4070dc["vConfig"][_0x1b59d6.priWD], _0x4070dc["VVGww"][_0x1b59d6['FCsae']],),),)
这样我们可以得到一个比较清晰的代码,我们再将这段代码的执行逻辑给分别执行,不再嵌套(不能嫌麻烦,嫌麻烦就学ast)。
[JavaScript] 纯文本查看 复制代码
const configPriWD = '431f916e31dc956477dea02c973d4cd9'; // captchaIdconst vvgwwFCsae = '074c78f4703210e56886940734b014b6'; // token
const vazGSResult = configPriWD + vvgwwFCsae
const pmrAlResult = md5(vazGSResult)
const encryptedLong = _0x145c35["encryptLong"](_0x42739f); // 轨迹 _0x42739f rsa
const finalResult = encryptedLong + pmrAlResult
这样我们就能愉快的分析加密逻辑了(先简单写死),很明显就是一个rsa结合md5然后再拼接,这样我们 report参数整体就实现了,接下来就是python和密码学的结合了。
首先要注意的几点
(1)rsa加密的长度有限制,参考文章 分组密码模式,采用ecb模式处理
(2)pck8转化pck1格式,参考文章 RSA密钥格式解析&转换(借助python偷懒做兼容)
(3)公钥存储的换行符处理
有兴趣可以看一下文章,给一个参考代码:
[Python] 纯文本查看 复制代码
import base64from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
def get_max_chunk_size(rsa_value):
key_size_bytes = rsa_value.size_in_bytes()
return key_size_bytes - 11
def format_pem_key():
with open('public.pem', 'r') as f:
key_str = f.read()
base64_content = key_str.split('-----BEGIN PUBLIC KEY-----')[1].split('-----END PUBLIC KEY-----')[0]
formatted_base64 = "\n".join(
base64_content[i:i + 64] for i in range(0, len(base64_content), 64)
)
standard_pem = (
"-----BEGIN PUBLIC KEY-----\n" +
formatted_base64 + "\n" +
"-----END PUBLIC KEY-----"
)
with open("public.pem", "w") as pem_file:
pem_file.write(standard_pem)
def rsa_encrypt(rsa_key, enc_data):
key = base64.b64decode(rsa_key).decode('utf-8')
with open('public.pem', 'w') as f:
f.write(key)
format_pem_key()
with open('public.pem', 'r') as f:
key_value = f.read()
pk = RSA.import_key(key_value)
cipher = PKCS1_v1_5.new(pk)
max_chunk_size = get_max_chunk_size(pk)
text_bytes = enc_data.encode('utf-8')
encrypted_chunks = []
# 分段加密
for i in range(0, len(text_bytes), max_chunk_size):
chunk = text_bytes[i:i + max_chunk_size]
encrypted_chunk = cipher.encrypt(chunk)
encrypted_chunks.append(encrypted_chunk)
# 合并所有分段并 Base64 编码
encrypted_bytes = b''.join(encrypted_chunks)
encrypted_base64 = base64.b64encode(encrypted_bytes).decode('utf-8')
return encrypted_base64
3、滑块处理分析
上面还有一个轨迹和length参数没有处理都是和滑块有关系的。我们知道 背景图 返回时就是乱的,说明在前端对背景图进行了处理,我们这里借助事件侦听断点进行分析
我是菜狗用的中文的,大佬们英文界面就是canvas,然后刷新图片加载
简单分析一下里面的逻辑,经常补环境的大佬应该熟悉这个方法,创建一个画布,应该是存放滑块的地方,说明这里还没有进行一个处理,继续往下看
这后面跟着的是几个方法,我们直接都下上断点看看他的逻辑。
很有意思,先获取了_0x251234的值,又将它传入下一个方法获得了一个32位的数组,我们知道滑块的条数是32,这个会不会是滑块的正确顺序。
分析一下逻辑发现他是对一个地址进行了切片,这个还有点眼熟,好像就是滑块的背景图地址,取得格式前面的那一串字符
这一部分搞定了,我们来看数组生成的逻辑
_0x1ac65f( _0x18d30b[_0x3e1920(0x35a, '\x69\x4a\x58\x55')])(_0x251234)利用 _0x1ac65f( _0x18d30b[_0x3e1920(0x35a, '\x69\x4a\x58\x55')])函数对_0x251234也就是背景图的信息进行处理
跟进去看看逻辑。
[JavaScript] 纯文本查看 复制代码
_0x3834dc[_0x11c544(0x42c, '\x5b\x32\x33\x28')] = function( _0x121c70,
_0x10dcdd,
) {
const _0x74b07a = _0x11c544;
const _0x58b550 = {
uKQaL: _0x74b07a(0x289, '\x6e\x74\x43\x4d'),
LRljw(_0x52e5da, _0x58ed7b) {
return _0x52e5da === _0x58ed7b;
},
};
function _0x1c18e2(_0x2a7547, _0x2daf7e) {
const _0x208f76 = _0x74b07a;
if (_0x2a7547[_0x58b550[_0x208f76(0x36a, '\x4c\x41\x6f\x36')]])
return _0x2a7547[_0x58b550[_0x208f76(0x36a, '\x4c\x41\x6f\x36')]](
_0x2daf7e,
);
for (
let _0x282371 = 0x0,
_0xca62fe = _0x2a7547[_0x208f76(0x3ec, '\x66\x40\x31\x4c')];
_0x282371 < _0xca62fe;
_0x282371++
)
if (
_0x58b550[_0x208f76(0x6c9, '\x5e\x33\x58\x44')](
_0x2a7547[_0x282371],
_0x2daf7e,
)
)
return !0x0;
return !0x1;
}
for (
var _0x10dcdd = [], _0x66e9ff = 0x0;
_0x187f8f[_0x74b07a(0x30c, '\x6f\x5e\x24\x79')](
_0x66e9ff,
_0x121c70[_0x187f8f.dxGbO],
);
_0x66e9ff++
) {
let _0x285e49 = _0x121c70[
_0x187f8f[_0x74b07a(0x5bb, '\x4a\x5b\x72\x57')]
](_0x66e9ff);
if (_0x187f8f[_0x74b07a(0x5a4, '\x28\x67\x62\x55')](0x20, _0x66e9ff))
break;
for (
;
_0x1c18e2(
_0x10dcdd,
_0x187f8f[_0x74b07a(0x2ec, '\x51\x57\x47\x76')](_0x285e49, 0x20),
);
)
_0x285e49++;
_0x10dcdd.push(_0x187f8f.yMCrD(_0x285e49, 0x20));
}
return _0x10dcdd;
}
还是一段混淆的代码,老规矩,直接处理掉混淆内容
[JavaScript] 纯文本查看 复制代码
_0x3834dc = function (_0x121c70) {
function _0x1c18e2(_0x2a7547, _0x2daf7e) {
if (_0x2a7547["includes"]) {
return _0x2a7547["includes"](_0x2daf7e);
}
for (let _0x282371 = 0, _0xca62fe = _0x2a7547.length; _0x282371 < _0xca62fe; _0x282371++) {
if (_0x2a7547[_0x282371] === _0x2daf7e) {
return true;
}
}
return false;
}
var _0x10dcdd = [];
for (var _0x66e9ff = 0; _0x66e9ff < 32; _0x66e9ff++) {
let _0x285e49 = _0x121c70['charCodeAt'](_0x66e9ff);
if (32 === _0x66e9ff) {
break;
}
for (; _0x1c18e2(_0x10dcdd, _0x285e49 % 32);) {
_0x285e49++;
}
_0x10dcdd.push(_0x285e49 % 32);
}
return _0x10dcdd;
};
处理完成后我们可以简单测试一下结果是不是符合。
很好,非常符合,我们再用python简单实现一下同样的逻辑
[Python] 纯文本查看 复制代码
def canvas_list(input_string): def list_contains(lst, value):
return value in lst
result = []
for i in range(32):
if i >= len(input_string):
break
char_code = ord(input_string[i])
while list_contains(result, char_code % 32):
char_code += 1
result.append(char_code % 32)
return result
ok,搞定了,
三、总结
轨迹算法和图像还原的内容我就不献丑,纯应付处理代码,论坛里大佬有很多这样的算法,可以去===借鉴借鉴===,整个加密核心就是rsa的处理,更多的需要去看文章。
免费评分
查看全部评分