继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

【JS逆向百例】某盾 Blackbox 算法逆向分析

K哥爬虫
关注TA
已关注
手记 147
粉丝 28
获赞 43

7Wkl9Q.png

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

逆向目标

目标:某盾 Blackbox 算法逆向分析

网站:aHR0cHM6Ly93d3cuanVuZXlhb2Fpci5jb20v

本文只对某盾 Blackbox 的其中一种算法进行逆向分析,不涉及指纹风控、环境部分。

逆向分析

7VSMVQ.png

通过搜索 blackBox 即可定位到如下位置:

7VSfJf.png

往上跟栈,发现是由此函数生成的:

7VSsTc.png

进入到该函数内,重新下断点刷新网站,单步往下跟,定位到最后 return 处:

7VS2h3.png

发现 Blackbox 值是由 5WPH173561408225WrZh6Cf 经过 QOoooO 函数生成的 ,搜索发现该值是

profile.json 接口返回的:

7VS6Yj.png

7VSFd5.png

我们的重点就是 profile.json 接口的这些请求参数,非常之多,但是不要怕,慢慢来。

查看堆栈,发现和 Blackbox 值最终生成的 js 文件一样,都是 fm.js 文件,也就是 Blackbox 值生成的核心文件,点进去发现有一堆 oQ0Qo0 大数组混淆,定位到 ooQGOO 的生成位置 oQ0Qo0 = ooQGOO(oQ0Qo0);

7VSdwm.png

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 工具进行替换,写好规则保存勾选就好了:

7VSeO4.png

刷新网站没有任何问题,然后我们再从头再来:

先解决 QOoooO 函数,其传入接口返回的 tokenId 参数,生成最终的 Blackbox 值,进入该函数,逻辑非常清晰:

7VSoih.png

可以直接扣下来,或者分析还原,代码如下:

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:搜索即可定位到:

7VUk79.png

7VUxsw.png

由 partner、时间戳、随机值组成:

import time
import random

token_id = (
partner

  • “-”
  • str(int(time.time() * 1000))
  • “-”
  • “”.join(random.choices(“0123456789abcdef”, k=12))
    )

剩下的不太好搜索定位,我们用 idf 搜索来定位,发现有五处生成位置,我们全部打上断点:

7VUBVY.png

断到如上位置,可以发现:

  • v:是 version 在 js 文件中是写死的:

7VUTXH.png

  • idf:目前生成的明显不是最终请求的参数;

  • w:通过 O0QOOQ 函数 对版本 v 进行 加密生成,我们跟进去:

7VUpTZ.png

然后再跟进到 QOoo0Q 函数中,单步走,就定位到如下 return 的位置,发现关键字 TripleDES

7VULhU.png

这个可能有些小伙伴不太熟悉,直接百度或者 ai 就会发现是 3DES 加密:

7VU0rq.png

  • 浏览器生成:

7VU3ds.png

  • 标准:

7VU7wa.png

发现浏览器生成的,和标准的不太一样,不过又非常相似,我们其实也能看到一些特征,比如大小写互换,然后我们可以自己处理成一样的:

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)

keyQ0oQ0o["timestamp"]["substring"](0, 24)

通过搜索,可以定位到 Q0oQ0o["timestamp"] 的生成位置:

7VUES7.png

也是由时间戳、随机数组成,可以重新刷新,我们看看还有什么参数是走的这个算法。

断住后,往上跟值,可以发现是哪个参数:

7VUQiI.png

7VUGSP.png

发现:

  • abcdgfct 都是由这个算法生成,明文就是加密的值,key 都是同一个值如上;

idf

然后跳断点,又来到了我们断 idf 的地方:

7VUuWL.png

Q0oQ0o["timestamp"] 就是我们截取生成 key 的初始值,上面已经分析了。又发现关键字 setPublicKey

可能猜到是 RSA 加密,PublicKey 搜索发现 在 js 文件中是写死的,经过测试是标准的 RSA 加密:

7VUwXJ.png

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 多个位置,不过非常容易定位到其生成的位置:

7VUHmG.png

Q0o0o0 函数生成,进入后发现也是随机生成的:

7VUK8B.png

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 的下面:

7VUbrt.png

将前面的请求参数,拼接成字符串,然后再进行 O00O0o["hash128"] 函数的加密:

query_string = “?” + urllib.parse.urlencode(params)
h = oo0OOQ.hash128(query_string)

hash128 可以直接扣 js 代码,不多,或者直接用 python 还原:

7VUgeb.png

python 算法的源码,会分享到知识星球当中,需要的小伙伴自取,仅供学习交流。

结果验证

图片描述

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP