声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!
逆向目标
目标:某盾 Blackbox 算法逆向分析
网站:aHR0cHM6Ly93d3cuanVuZXlhb2Fpci5jb20v
本文只对某盾 Blackbox 的其中一种算法进行逆向分析,不涉及指纹风控、环境部分。
逆向分析
通过搜索 blackBox
即可定位到如下位置:
往上跟栈,发现是由此函数生成的:
进入到该函数内,重新下断点刷新网站,单步往下跟,定位到最后 return
处:
发现 Blackbox
值是由 5WPH173561408225WrZh6Cf
经过 QOoooO
函数生成的 ,搜索发现该值是
profile.json
接口返回的:
我们的重点就是 profile.json
接口的这些请求参数,非常之多,但是不要怕,慢慢来。
查看堆栈,发现和 Blackbox
值最终生成的 js 文件一样,都是 fm.js
文件,也就是 Blackbox
值生成的核心文件,点进去发现有一堆 oQ0Qo0
大数组混淆,定位到 ooQGOO
的生成位置 oQ0Qo0 = ooQGOO(oQ0Qo0);
:
oQ0Qo0
就是传入的长字符串, 通过 ooQGOO
函数就生成了 oQ0Qo0
的大数组,可以把 ooQGOO
给扣下来,非常容易,也可以分析 ooQGOO
函数,对这类混淆熟悉的可以直接秒,基本都是一套逻辑:
// 传入的长字符串
en_txt = ‘’;
// ooQGOO 函数部分
oQ0Qo0 = decodeURIComponent(atob(en_txt))‘split’;
traverse(ast, {
MemberExpression(path){
let {object, property} = path.node;
if (!t.isIdentifier(object) && !t.isNumericLiteral(property)) return;
if (object.name !== “oQ0Qo0”) return;
let _v = oQ0Qo0[property.value]
path.replaceWith(t.valueToNode(_v))
}
});
然后再通过工具替换代码,这边选用 ReRes
工具进行替换,写好规则保存勾选就好了:
刷新网站没有任何问题,然后我们再从头再来:
先解决 QOoooO
函数,其传入接口返回的 tokenId
参数,生成最终的 Blackbox
值,进入该函数,逻辑非常清晰:
可以直接扣下来,或者分析还原,代码如下:
import random
def get_blackbox(token_id):
chars = "ghijklmnopqrstuv"
all_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
return chars[int(token_id[0], 16)] + token_id[1:4] + random.choice(all_chars) + token_id[4:14] + random.choice(all_chars) + token_id[14:22] + random.choice(all_chars) + token_id[22]
然后就是 profile.json
接口的这些请求参数分析:
-
partner
:写死; -
app_name
:写死; -
token_id
:搜索即可定位到:
由 partner、时间戳、随机值组成:
import time
import random
token_id = (
partner
- “-”
- str(int(time.time() * 1000))
- “-”
- “”.join(random.choices(“0123456789abcdef”, k=12))
)
剩下的不太好搜索定位,我们用 idf
搜索来定位,发现有五处生成位置,我们全部打上断点:
断到如上位置,可以发现:
- v:是 version 在 js 文件中是写死的:
-
idf:目前生成的明显不是最终请求的参数;
-
w:通过
O0QOOQ
函数 对版本 v 进行 加密生成,我们跟进去:
然后再跟进到 QOoo0Q
函数中,单步走,就定位到如下 return
的位置,发现关键字 TripleDES
:
这个可能有些小伙伴不太熟悉,直接百度或者 ai
就会发现是 3DES
加密:
- 浏览器生成:
- 标准:
发现浏览器生成的,和标准的不太一样,不过又非常相似,我们其实也能看到一些特征,比如大小写互换,然后我们可以自己处理成一样的:
import base64
from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad, unpad
def replace_characters(input_str):
return input_str.replace(’+’, ‘~’)
def swap_characters_3DES2(s):
使用字典映射实现字符互换
swap_map = {
‘i’: ‘j’,
‘j’: ‘i’,
‘I’: ‘J’,
‘J’: ‘I’
}
使用列表推导式逐个字符替换
swapped_s = ‘’.join([swap_map.get(char, char) for char in s])
return swapped_s
def encrypt_3des_cbc(data, key):
iv = ‘12345678’ # 3DES 使用8字节IV
block_size = DES3.block_size
对数据进行填充
data = pad(data.encode(‘latin-1’), block_size)
cipher = DES3.new(key.encode(‘latin-1’), DES3.MODE_CBC, iv.encode(‘latin-1’))
encrypted = cipher.encrypt(data)
return replace_characters(swap_characters_3DES2(base64.b64encode(encrypted).decode(‘latin-1’).swapcase()))
if name == ‘main’:
data = 'JZuFK8iZfzhZG+BaqcUjAgNuPh8lFrtHCX3Ev7uGAGTj9gLkI0nL5bb/QS7zhKew’
key = '1735627982523-1142600494’
encrypted_data = encrypt_3des_cbc(data, key)
print(encrypted_data)
key
:Q0oQ0o["timestamp"]["substring"](0, 24)
。
通过搜索,可以定位到 Q0oQ0o["timestamp"]
的生成位置:
也是由时间戳、随机数组成,可以重新刷新,我们看看还有什么参数是走的这个算法。
断住后,往上跟值,可以发现是哪个参数:
发现:
a
、b
、c
、d
、g
、f
、ct
都是由这个算法生成,明文就是加密的值,key 都是同一个值如上;
idf
然后跳断点,又来到了我们断 idf
的地方:
Q0oQ0o["timestamp"]
就是我们截取生成 key 的初始值,上面已经分析了。又发现关键字 setPublicKey
可能猜到是 RSA
加密,PublicKey
搜索发现 在 js 文件中是写死的,经过测试是标准的 RSA
加密:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
def RSA_Public_Encrypt(plain_text):
RSA 公钥
public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCuR3+MuPOVYuAKOS6O+J/ds+JAesgyFforFupDiDBBMTItdXyMrG6gUPFxj/pT/9uQSq8Zxl7BrdiKdi0G2ppEn4Nym+VRLTv2+lNa3kvlrj25Lop7wDZkVRecK5oDvdaQHrm4KKiF7jZNbHEreWGsINLpGvzBMRNztRtOJ6+XEQIDAQAB"
加载公钥
key = RSA.import_key(base64.b64decode(public_key))
使用 PKCS1_v1_5 进行加密
cipher = PKCS1_v1_5.new(key)
encrypted_data = cipher.encrypt(plain_text.encode(‘utf-8’))
返回 Base64 编码的加密结果
return base64.b64encode(encrypted_data).decode(‘utf-8’)
e
直接暴力搜索 ["e"]
,发现有 20 多个位置,不过非常容易定位到其生成的位置:
由 Q0o0o0
函数生成,进入后发现也是随机生成的:
import random
def generate_random_string():
字符集
characters = “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789”
随机生成 127 长度的字符串
random_string = “”.join(random.choice(characters) for _ in range(108))
在随机位置插入一个反斜杠
insert_position = random.randint(0, len(random_string))
random_string = random_string[:insert_position] + “\” + random_string[insert_position:]
return random_string
h
搜索 _callback
或者 h=
可以定位到目标位置, 就在生成 idf
的下面:
将前面的请求参数,拼接成字符串,然后再进行 O00O0o["hash128"]
函数的加密:
query_string = “?” + urllib.parse.urlencode(params)
h = oo0OOQ.hash128(query_string)
hash128
可以直接扣 js 代码,不多,或者直接用 python
还原:
纯 python
算法的源码,会分享到知识星球当中,需要的小伙伴自取,仅供学习交流。
结果验证
共同学习,写下你的评论
暂无评论
作者其他优质文章