今日内容

1 今日目标

1
2
3
4
# 采集得物首页推荐信息
# 版本
-4.74.5版本
-在咱们软件中

image-20240517171722277

2 绕过强制更新

1
# 先断网----》打开app---》联网---》绕过了

image-20240517171735054

3 抓包分析

3.1 使用 SocksDroid抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1 该app不允许使用代理,我们使用SocksDroid抓包
# 2 操作步骤:
- 删除手机系统代理
- 打开app,右上角滑块打开

# 3 地址如下:https://app.dewu.com/sns-rec/v1/recommend/all/feed
-地址:https://app.dewu.com/sns-rec/v1/recommend/all/feed
-请求方式:get
-请求头:
-X-Auth-Token:必须带
-请求参数:
-newSign:这个接口不需要带,学习破解它,后续别的接口需要带

image-20240517171750686

image-20240517171758364

4 反编译破解-newSign

4.1 反编译搜索newSign

1
2
3
4
5
# 1 把app拖入 jadx中,反编译,搜索     "newSign
# 2 搜出8个,但实际上只有5个有效,5个都在同一个类中---》随便点一个进去看--》hook验证
# 3 发现它是写在拦截器中---》所有请求,都会带这个newSign
# 4 最终调用了RequestUtils.c
# 5 hook一下 RequestUtils.c, 确认有没有走

image-20240517171807865

image-20240517171819303

4.2 Hook确认位置->RequestUtils.c

image-20240517171827345

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import frida
import sys

rdev = frida.get_remote_device()
session = rdev.attach("得物(毒)")

scr = """
Java.perform(function () {
var RequestUtils = Java.use("com.shizhuang.duapp.common.utils.RequestUtils");
RequestUtils.c.implementation = function(map,j){
console.log("----------------------------------------");
console.log('1.参数字典为:',map); // 此处直接打印map,发现打印的是对象,我们需要转换一下
console.log('1.参数字典为:',JSON.stringify(map)); // 查看一下类型 :<instance: java.util.Map, $className: java.util.HashMap>,把HashMap值取出来,做个转换,如下
var Map = Java.use('java.util.HashMap');
var obj = Java.cast(map, Map);
console.log('1.参数字典为:',obj.toString());
var res = this.c(map,j);
console.log("4.newSign结果:", res);
return res;
}
});
"""
script = session.create_script(scr)


def on_message(message, data):
print(message, data)


script.on("message", on_message)

script.load()
sys.stdin.read()


'''
1.参数字典为: [object Object]
2.参数字典为,查看类型: "<instance: java.util.Map, $className: java.util.HashMap>"
3.参数字典为,字符串:
{abValue=1, deliveryProjectId=0, abRectagFengge=0, abType=social_brand_strategy_v454, limit=20, lastId=, abRecReason=0, abVideoCover=2}
4.newSign结果: 63954a47f978df83dc52b8c66e7c2f19

### 拿到newSign和 抓包抓包的newSign比较发现是一样的,确定位置
'''

# 把请求参数---》使用某种加密方式,做了加密

image-20240517171837376

image-20240517171844783

4.3 分析RequestUtils.c代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
    public static synchronized String c(Map<String, String> map, long j2) throws UnsupportedEncodingException {
synchronized (RequestUtils.class) {
PatchProxyResult proxy = PatchProxy.proxy(new Object[]{map, new Long(j2)}, null, changeQuickRedirect, true, 6612, new Class[]{Map.class, Long.TYPE}, String.class);
if (proxy.isSupported) {
return (String) proxy.result;
} else if (map == null) {
return "";
} else {
//1 刚刚hook,map不是null的,是有值的
// {abValue=1, deliveryProjectId=0, abRectagFengge=0, abType=social_brand_strategy_v454, limit=20, lastId=, abRecReason=0, abVideoCover=2}
// uuid,platform,v,loginToken,timestamp
map.put("uuid", DuHttpConfig.d.getUUID());
map.put("platform", "android");
map.put("v", DuHttpConfig.d.getAppVersion());
map.put("loginToken", DuHttpConfig.d.getLoginToken());
map.put("timestamp", String.valueOf(j2));
//2 把map转成ArrayList,做了排序 [key=value,key2=value2]
ArrayList arrayList = new ArrayList(map.entrySet());
// 排序
Collections.sort(arrayList, new Comparator<Map.Entry<String, String>>() {
public static ChangeQuickRedirect changeQuickRedirect;

@Override
public int compare(Map.Entry<String, String> entry, Map.Entry<String, String> entry2) {
PatchProxyResult proxy2 = PatchProxy.proxy(new Object[]{entry, entry2}, this, changeQuickRedirect, false, 6618, new Class[]{Map.Entry.class, Map.Entry.class}, Integer.TYPE);
return proxy2.isSupported ? ((Integer) proxy2.result).intValue() : entry.getKey().toString().compareTo(entry2.getKey());
}
});
// 3 拼接字符串---》循环arrayList--》拼接到字符串中
StringBuilder sb = new StringBuilder();
for (int i2 = 0; i2 < arrayList.size(); i2++) {
Map.Entry entry = (Map.Entry) arrayList.get(i2);
// 把key和value拼到字符串中了
// abValue1deliveryProjectId0...
sb.append(((String) entry.getKey()) + ((String) entry.getValue()));
}
// 转成字符串
String sb2 = sb.toString();
DuHttpConfig.LogConfig logConfig = DuHttpConfig.f15800h;
String str = f16243a;
logConfig.d(str, "StringToSign " + sb2);
// 4 执行了AESEncrypt.encode 把sb2,拼接后的字符串做了加密
// 5 执行了a做了加密--》md5
return a(AESEncrypt.encode(DuHttpConfig.f15796c, sb2));
}
}
}



/* 总结:
1 参数拼接
// 把uuid,platform,v,loginToken,timestamp放入map中
map.put("uuid", DuHttpConfig.d.getUUID());
map.put("platform", "android");
map.put("v", DuHttpConfig.d.getAppVersion());
map.put("loginToken", DuHttpConfig.d.getLoginToken());
map.put("timestamp", String.valueOf(j2));
2 把map转成 ArrayList并进行排序
ArrayList arrayList = new ArrayList(map.entrySet());
Collections.sort(arrayList, new Comparator<Map.Entry<String, String>>()

3 构建字符串
StringBuilder sb = new StringBuilder();
for (int i2 = 0; i2 < arrayList.size(); i2++) {
sb.append(((String) entry.getKey()) + ((String) entry.getValue()));
}

4 执行 AESEncrypt.encode 加密
AESEncrypt.encode(DuHttpConfig.f15796c, sb2)

5 把加密后的结果使用a,做运算,a是md5
a(AESEncrypt.encode(DuHttpConfig.f15796c, sb2));
*/

4.3.1 a方法就是md5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// md5 正常就是直接加密,麻烦一点的就是 加点盐,如果没有update别的就没有加盐
// 如果不放心,hook a,拿到参数,和返回,自己使用python来验证即可
public static String a(String str) {
PatchProxyResult proxy = PatchProxy.proxy(new Object[]{str}, null, changeQuickRedirect, true, 6616, new Class[]{String.class}, String.class);
if (proxy.isSupported) {
return (String) proxy.result;
}
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(str.getBytes());
byte[] digest = messageDigest.digest();
StringBuilder sb = new StringBuilder();
for (byte b2 : digest) {
String hexString = Integer.toHexString(b2 & 255);
while (hexString.length() < 2) {
hexString = "0" + hexString;
}
sb.append(hexString);
}
return sb.toString();
} catch (NoSuchAlgorithmException e2) {
e2.printStackTrace();
return "";
}
}

4.3.2 分析AESEncrypt.encode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1 AESEncrypt.encode(DuHttpConfig.f15796c, sb2)
# 2 双击点进去
public class AESEncrypt {
static {
System.loadLibrary("JNIEncrypt"); # libJNIEncrypt.so
}
public static String encode(Object obj, String str) {
# 调用getByteValues 获得字符串
# byteValues=011001
String byteValues = getByteValues();
StringBuilder sb = new StringBuilder(byteValues.length());
# 定义了一个字符串,根据byteValues的值,拼接字符串,相对于对字符串取反
for (int i2 = 0; i2 < byteValues.length(); i2++) {
if (byteValues.charAt(i2) == '0') {
sb.append('1');
} else {
sb.append('0');
}
}
# sb.toString=100110
# str.getBytes() 待加密的字符串
return encodeByte(str.getBytes(), sb.toString());
}
public static native String getByteValues(); # Native方法--》c实现的--》目前不知道
# 传入了 待加密的字符串转成byte数组,和 01010101字符串
# 用c写的
public static native String encodeByte(byte[] bArr, String str);# Native方法--》c实现的

}

4.3.3 hook–>encode确认位置是否正确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import frida
import sys

rdev = frida.get_remote_device()
session = rdev.attach("得物(毒)")


# AESEncrypt 类下有俩 encode方法,重载的方法---》参数不同---》咱们要hook的是两个参数的---》还有个一个参数的
# hook脚本,需要额外处理
# 需要写 overload(参数签名)---》记住了,以后hook重载方法,都这么多写一个 overload

scr = """
Java.perform(function () {
var AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt");
AESEncrypt.encode.overload('java.lang.Object', 'java.lang.String').implementation = function(obj,str){
console.log('传入参数:',str);
var res = this.encode(obj,str);
console.log('返回值是:',res);
return res;
}
});
"""



script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
sys.stdin.read()


'''

传入参数:
{abValue=1, deliveryProjectId=0, abRectagFengge=0, abType=social_brand_strategy_v454, limit=20, lastId=, abRecReason=0, abVideoCover=2}+uuid+logintype+timestamp

abRecReason0abRectagFengge0abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1704201024023uuidee13885e68d76ed4v4.74.5

abRecReason 0
abRectagFengge 0
abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0
lastId
limit 20
loginToken
platform android
timestamp 1704201024023
uuid ee13885e68d76ed4
v 4.74.5

返回值是: VwGEkNCFuNTmwD6w19ogqRI5VLum7q2QP6Z4TaJLKn3cvS7y81d64hsABQvdu03vdj76OpM/O2awG8h9UpcJb7mTLuTReeaHVgQgd6kvRikIdeR4xJw0+TRbBSp2lMcQMGn2O4/9BKR+RkYgwOUZd4MFCaYlia6t5gNc3rbNTJjA6aJyja63+nAcybbrLaS0yLy0ESLQSJ5zJcVwQ/vpxoOdr2wdQssTtUlmTMEAneS62ogrdRykXvjd49za9AEW

'''

4.3.4 继续分析AESEncrypt.encode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class AESEncrypt {
static {
System.loadLibrary("JNIEncrypt"); # libJNIEncrypt.so
}
public static String encode(Object obj, String str) {
// 1 调用getByteValues 获得字符串
// 假设是 byteValues=011001
String byteValues = getByteValues();
StringBuilder sb = new StringBuilder(byteValues.length());
// 2 定义了一个字符串,根据byteValues的值,拼接字符串,相对于对字符串取反
for (int i2 = 0; i2 < byteValues.length(); i2++) {
if (byteValues.charAt(i2) == '0') {
sb.append('1');
} else {
sb.append('0');
}
}
// 3 sb.toString=100110
// 4 str.getBytes() 待加密的字符串
return encodeByte(str.getBytes(), sb.toString());
}
//Native方法--》c实现的--》目前不知道
public static native String getByteValues();
//传入了 待加密的字符串转成byte数组,和 01010101字符串
//用c写的
//Native方法--》c实现的
public static native String encodeByte(byte[] bArr, String str);#
}

// 去 libJNIEncrypt.so 中读两个位置
-getByteValues:// 没有参数,直接返回了字符串,这种情况下,大规律字符串是固定的,通过hook确认值是否是固定,如果是固定,不用读了,直接用即可,如果不固定,需要去读
-encodeByte

4.3.5 hook-getByteValues查看返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import frida
import sys

rdev = frida.get_remote_device()
session = rdev.attach("得物(毒)")

scr = """
Java.perform(function () {
var AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt");
AESEncrypt.getByteValues.implementation = function(){
var res = this.getByteValues();
console.log('getByteValues返回值是:',res);
return res;
}
});
"""
script = session.create_script(scr)


def on_message(message, data):
print(message, data)


script.on("message", on_message)

script.load()
sys.stdin.read()

'''
getByteValues返回值是固定的:
101001011101110101101101111100111000110100010101010111010001000101100101010010010101110111010011101001011101110101100101001100110000110100011101010111011011001101001101011101010100001101000011
'''

4.3.6 读encodeByte–Native方法–so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 1 libJNIEncrypt.so 解压缩apk---libs中找出---》看是静态还是动态注册
-静态注册:java_包名_类名_方法名
-动态注册:JNI_OnLoad中找对应关系

# 2 找到64位的so文件,使用ida打开,发现是动态注册

# 3 如果是动态注册---》JNI_OnLoad---》找对应关系

# 4 之前咱们写的JNI_OnLoad
JNIEXPORT jint
JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
# 找到Java中的类
jclass clazz = (*env)->FindClass(env, "com/justin/s10demo03/Dynamic");
# 将类中的方法注册到JNI中 (RegisterNatives),通过它完成动态注册
# gMethods 是c语言方法---》 2 表示有两个方法要对应
int res = (*env)->RegisterNatives(env, clazz, gMethodsgMethods对应关系, 2);
}

# 5 咱们反编译出来的
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{

v4 = (*(*v6[0] + 48LL))(v6[0], "com/duapp/aesjni/AESEncrypt");
if ( v4 )
# off_15010 就是咱们写的gMethodsgMethods对应关系
(*(*v3 + 1720LL))(v3, v4, off_15010, 8LL);

return v2;
}

# 6 双击off_15010 --》找到对应关系--》encode--》双击--》按F5反编译
jstring __fastcall encode(JNIEnv_ *a1, __int64 a2, __int64 a3, __int64 a4)
{
LABEL_8:
v11[v9] = 0;
# v18是在这处理的
v18 = AES_128_ECB_PKCS5Padding_Encrypt(v11, v8);
free(v12);
a1->functions->ReleaseStringUTFChars(a1, a4, v7);
a1->functions->ReleaseByteArrayElements(a1, a3, v10, 0LL);
return a1->functions->NewStringUTF(a1, v18); # 返回字符串类型,加密后的字符串,v18才是真正字符串
}

# 7 AES_128_ECB_PKCS5Padding_Encrypt(v11, v8)
一路点进去看,看到aes字样和base64字样---》猜测使用aes加密,使用base64编码


# 8 hook--》AES_128_ECB_PKCS5Padding_Encrypt(v11, v8)--》查看参数
-之前没有学过hook-c的函数

# 9 总结:encodeByte so的代码核心逻辑是
-明文字符串传入,使用key,进行aes加密--》加密后使用base64编码后返回

image-20240517171904436

image-20240517171912306

image-20240517171919493

image-20240517171927761

image-20240517171937736

4.3.7 hook–AES_128_ECB_PKCS5Padding_Encrypt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

import frida
import sys
rdev = frida.get_remote_device()
session = rdev.attach("得物(毒)")

scr = """
Java.perform(function () {
// 返回libJNIEncrypt.so中的AES_128_ECB_PKCS5Padding_Encrypt的内存地址
var addr_func = Module.findExportByName("libJNIEncrypt.so", "AES_128_ECB_PKCS5Padding_Encrypt");
// 使用Interceptor.attach 调用,第一个参数传入内存地址,第二个参数是一个对象:onEnter 和 onLeave
Interceptor.attach(addr_func, {
onEnter: function(args){
// 所有参数都在args中,根据下标取值
console.log("--------------------------执行函数--------------------------");
console.log("参数1:", args[0].readUtf8String());
console.log("参数2:", args[1].readUtf8String());
},
onLeave: function(retValue){
console.log("返回值:", retValue.readUtf8String());
}
})

});
"""


script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
sys.stdin.read()


'''
hook--so的模版
Java.perform(function () {
var addr_func = Module.findExportByName("so文件", "函数名");
Interceptor.attach(addr_func, {
onEnter: function(args){
// 函数执行会触发
},
onLeave: function(retValue){
// 函数执行完会触发
}
})
});
'''


'''
参数1:待加密的字符串: abRecReason0abRectagFengge0abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1704204080619uuidee13885e68d76ed4v4.74.5
参数2: d245a0ba8d678a61 # aes的key是固定的
返回值: VwGEkNCFuNTmwD6w19ogqRI5VLum7q2QP6Z4TaJLKn3cvS7y81d64hsABQvdu03vdj76OpM/O2awG8h9UpcJb7mTLuTReeaHVgQgd6kvRikIdeR4xJw0+TRbBSp2lMcQMGn2O4/9BKR+RkYgwOUZd4MFCaYlia6t5gNc3rbNTJjA6aJyja63+nAcybbrLaS0Au8PDK70nRzjYPYOcSEr5YOdr2wdQssTtUlmTMEAneS62ogrdRykXvjd49za9AEW
把返回值使用md5加密后,跟抓包一致

'''

image-20240517171951012

4.3.8 python重写Native方法加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64


def aes_encrypt(data_string):
key = "d245a0ba8d678a61"
aes = AES.new(
key=key.encode('utf-8'),
mode=AES.MODE_ECB,
)
raw = pad(data_string.encode('utf-8'), 16)
return aes.encrypt(raw)


data_string = "abRecReason0abRectagFengge0abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1704204080619uuidee13885e68d76ed4v4.74.5"
res = aes_encrypt(data_string)
value = base64.encodebytes(res)
result = value.replace(b"\n", b'')
print(result)
# 运行结果: VwGEkNCFuNTmwD6w19ogqRI5VLum7q2QP6Z4TaJLKn3cvS7y81d64hsABQvdu03vdj76OpM/O2awG8h9UpcJb7mTLuTReeaHVgQgd6kvRikIdeR4xJw0+TRbBSp2lMcQMGn2O4/9BKR+RkYgwOUZd4MFCaYlia6t5gNc3rbNTJjA6aJyja63+nAcybbrLaS0Au8PDK70nRzjYPYOcSEr5YOdr2wdQssTtUlmTMEAneS62ogrdRykXvjd49za9AEW
# hook结果:VwGEkNCFuNTmwD6w19ogqRI5VLum7q2QP6Z4TaJLKn3cvS7y81d64hsABQvdu03vdj76OpM/O2awG8h9UpcJb7mTLuTReeaHVgQgd6kvRikIdeR4xJw0+TRbBSp2lMcQMGn2O4/9BKR+RkYgwOUZd4MFCaYlia6t5gNc3rbNTJjA6aJyja63+nAcybbrLaS0Au8PDK70nRzjYPYOcSEr5YOdr2wdQssTtUlmTMEAneS62ogrdRykXvjd49za9AEW

4.4 分析uuid的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#1 待加密字符串如下
abRecReason 0
abRectagFengge 0
abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0
lastId
limit 20
loginToken
platform android
timestamp 1704204080619
uuid ee13885e68d76ed4 # 这个不知道
v 4.74.5

# 2 回到原来newSign位置,找uuid
map.put("uuid", DuHttpConfig.d.getUUID());

# 3 点击进入getUUID 发现看不懂
public String getUUID() {
PatchProxyResult proxy = PatchProxy.proxy(new Object[0], this, changeQuickRedirect, false, 5097, new Class[0], String.class);
return proxy.isSupported ? (String) proxy.result : "";
}
# 4 PatchProxy.proxy,美团的热修复框架,再这里看不出uuid是什么的,不是直接调用的

# 5 搜索:X-Auth-Token 或者 hashMap.put("uuid",
-尝试,别的代码生成的uuid是否能用,如果能用就没问题

# 6 我再搜 X-Auth-Token 找到了,如下图
# 7 找到hashMap.put("uuid", HPDeviceInfo.b(BaseApplication.c()).a((Activity) null));

public String a(Activity activity) {
String str = this.f15203b;
if (str != null) {
return str;
}
if (activity != null) {
final TelephonyManager telephonyManager = (TelephonyManager) activity.getApplication().getSystemService("phone");
new RxPermissions(activity).c("android.permission.READ_PHONE_STATE").subscribe(new Consumer() { // from class: g.c.a.a.f.m
@Override // io.reactivex.functions.Consumer
public final void accept(Object obj) {
HPDeviceInfo.this.a(telephonyManager, (Boolean) obj);
}
});
} else {
this.f15203b = a();
}
return this.f15203b;
}

# 8 最终找到了:telephonyManager.getDeviceId()--》之前写过,就是手机的imei
def generate_imei2():
return "".join(random.choices('0123456789abcdef', k=15))

image-20240517172002839

image-20240517172012529

4.5 代码整合生成newSign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import time
import requests
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from urllib.parse import quote_plus
import base64
import json
import random
import copy


def create_android_id():
data_list = []
for i in range(1, 9):
part = "".join(random.sample("0123456789ABCDEF", 2))
data_list.append(part)
return "".join(data_list).lower()


def md5(data_bytes):
hash_object = hashlib.md5()
hash_object.update(data_bytes)
return hash_object.hexdigest()


def aes_encrypt(data_string):
key = "d245a0ba8d678a61"
aes = AES.new(
key=key.encode('utf-8'),
mode=AES.MODE_ECB,
)
raw = pad(data_string.encode('utf-8'), 16)
return aes.encrypt(raw)


uid = create_android_id()
ctime = str(int(time.time() * 1000))

reply_param_dict = {
"lastId": "1",
"limit": "20",
}

new_dict = copy.deepcopy(reply_param_dict)
new_dict.update(
{"loginToken": "", "platform": "android", "timestamp": str(int(time.time() * 1000)), "uuid": uid, "v": "4.74.5"}
)
ordered_string = "".join(["{}{}".format(key, new_dict[key]) for key in sorted(new_dict.keys())])

aes_string = aes_encrypt(ordered_string)
aes_string = base64.encodebytes(aes_string)
aes_string = aes_string.replace(b"\n", b"")
sign_string = md5(aes_string)
print(sign_string)

5 破解 X-Auth-Token

5.1 查看token值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#1  三段式,使用 . 分割的,典型的jwt认证中的token
#2 后端通过代码签发给我们的---》里面包含了用户的信息,三部分
-第一部分是头:加密方式。。
-第二部分是荷载:用户信息
-第三部分是签名:用来验证签名
# 3 使用base64编码了 --》反解--》第一和第二部分

Bearer
# 第一段:
eyJhbGciOiJSUzI1NiJ9.
# 第二段:
eyJpYXQiOjE3MDQxOTcyMDUsImV4cCI6MTczNTczMzIwNSwiaXNzIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInN1YiI6ImVlMTM4ODVlNjhkNzZlZDQiLCJ1dWlkIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInVzZXJJZCI6MjE0MTY4MTg1MywidXNlck5hbWUiOiLlvpfniallci1KMEQ4UzZHNSIsImlzR3Vlc3QiOnRydWV9.

# 第三段:
lPzDDzAfGvLQ8oY9-62UzLNXRdpnPRsYUB2u9kBVikwTmnyBgR0yU_zkh-pe_s5v2JUIyRIaeNqMMvUBI7eo1wmZwyG9J8ad2oTYHhmf8pR_HhHutJzYWHiV0a_AtUH7wEMZSePYWN0EOjlK7zPRz6WeCA8Pjfiyrx4ecxIrPl4hpeOHdcyKGMEoCNR2Mmfs336cwFVyTZ5P0Rg5oc45NUP38jM4hvw96vpiv-p950WNHap9JnpTt5QcQGhN3nqB8j09NxshY65pmPaxxJw-D436zC6XCkSendoguFACh9H6ppHFRG_DNRrmIV_33Pgj41Zw-JA9SzjfWBo4qt19QA


# 4 找是那个接口返回的

Bearer eyJhbGciOiJSUzI1NiJ9.

eyJpYXQiOjE3MDQyMDUzNzUsImV4cCI6MTczNTc0MTM3NSwiaXNzIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInN1YiI6ImVlMTM4ODVlNjhkNzZlZDQiLCJ1dWlkIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInVzZXJJZCI6MjE0MTY4MTg1MywiaXNHdWVzdCI6dHJ1ZX0.

gj0blSiRRZXtTmIs3hbSbIFRVREW2ZyFSk9leEcPB7wiCGwLR0RHKYM1fZq3T959G5RcWhqquXcuG8IcSKQthRmpnv6qA-TlNGwkuA--iMwdaeA-7KjDOdPoZfzah5qhur6NmVVUKiz1S1x2wQplhqH3UMAXmbUtrNc2x2pn9mHAKK_1yHhiOFezi74agdkM8A4a9XEMTrT8oPOxFtGYwBuBYJeZZCul7zPtxfvi724F8StmlOd4cUWWWtVlkTxX5hccajWfBV6LnzNrUDwEOzPDETURKXvm280XztpD8wo6yMOFCQ2d7xhZEyEEYKRmejcSTRySSghozxgJBd47uw



Bearer eyJhbGciOiJSUzI1NiJ9.

eyJpYXQiOjE3MDQyMDUzNzUsImV4cCI6MTczNTc0MTM3NSwiaXNzIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInN1YiI6ImVlMTM4ODVlNjhkNzZlZDQiLCJ1dWlkIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInVzZXJJZCI6MjE0MTY4MTg1MywiaXNHdWVzdCI6dHJ1ZX0.

gj0blSiRRZXtTmIs3hbSbIFRVREW2ZyFSk9leEcPB7wiCGwLR0RHKYM1fZq3T959G5RcWhqquXcuG8IcSKQthRmpnv6qA-TlNGwkuA--iMwdaeA-
7KjDOdPoZfzah5qhur6NmVVUKiz1S1x2wQplhqH3UMAXmbUtrNc2x2pn9mHAKK_1yHhiOFezi74agdkM8A4a9XEMTrT8oPOxFtGYwBuBYJeZZCul7zPtxfvi724F8StmlOd4cUWWWtVlkTxX5hccajWfBV6LnzNrUDwEOzPDETURKXvm280XztpD8wo6yMOFCQ2d7xhZEyEEYKRmejcSTRySSghozxgJBd47uw

image-20240517172027735

5.2 请求获取token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import time
import requests
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from urllib.parse import quote_plus
import base64
import json
import random


def create_android_id():
data_list = []
for i in range(1, 9):
part = "".join(random.sample("0123456789ABCDEF", 2))
data_list.append(part)
return "".join(data_list).lower()


def md5(data_bytes):
hash_object = hashlib.md5()
hash_object.update(data_bytes)
return hash_object.hexdigest()


def aes_encrypt(data_string):
key = "d245a0ba8d678a61"
aes = AES.new(
key=key.encode('utf-8'),
mode=AES.MODE_ECB,
)
raw = pad(data_string.encode('utf-8'), 16)
return aes.encrypt(raw)


uid = create_android_id()
ctime = str(int(time.time() * 1000))

param_dict = {"loginToken": "", "platform": "android", "timestamp": ctime, "uuid": uid, "v": "4.74.5"}

ordered_string = "".join(["{}{}".format(key, param_dict[key]) for key in sorted(param_dict.keys())])
aes_string = aes_encrypt(ordered_string)
aes_string = base64.encodebytes(aes_string)
aes_string = aes_string.replace(b"\n", b"")
sign = md5(aes_string)
param_dict['newSign'] = sign

res = requests.post(
url="https://app.dewu.com/api/v1/app/user_core/users/getVisitorUserId",
headers={
"duuuid": uid,
"duimei": "",
"duplatform": "android",
"appId": "duapp",
"timestamp": ctime,
'duv': '4.74.5',
'duloginToken': '',
'dudeviceTrait': 'Pixel+2+XL',
'shumeiid': '202308011759568af1c8fc75c211e7f876664d9493202d0055aeeb3dd6e38c',
'User-Agent': 'duapp/4.74.5(android;11)'

},
json=param_dict,
verify=False
)
print(res.headers)
x_auth_token = res.headers['X-Auth-Token']
print(x_auth_token)

6 代码整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import time
import requests
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from urllib.parse import quote_plus
import base64
import json
import random
import urllib3
urllib3.disable_warnings()


def create_android_id():
data_list = []
for i in range(1, 9):
part = "".join(random.sample("0123456789ABCDEF", 2))
data_list.append(part)
return "".join(data_list).lower()


def md5(data_bytes):
hash_object = hashlib.md5()
hash_object.update(data_bytes)
return hash_object.hexdigest()


def aes_encrypt(data_string):
key = "d245a0ba8d678a61"
aes = AES.new(
key=key.encode('utf-8'),
mode=AES.MODE_ECB,
)
raw = pad(data_string.encode('utf-8'), 16)
return aes.encrypt(raw)


uid = create_android_id()
ctime = str(int(time.time() * 1000))

# param_dict = {"loginToken": "", "platform": "android", "timestamp": ctime, "uuid": uid, "v": "4.84.0"}
param_dict = {"loginToken": "", "platform": "android", "timestamp": ctime, "uuid": uid, "v": "4.74.5"}

ordered_string = "".join(["{}{}".format(key, param_dict[key]) for key in sorted(param_dict.keys())])
aes_string = aes_encrypt(ordered_string)
aes_string = base64.encodebytes(aes_string)
aes_string = aes_string.replace(b"\n", b"")
sign = md5(aes_string)
param_dict['newSign'] = sign

res = requests.post(
url="https://app.dewu.com/api/v1/app/user_core/users/getVisitorUserId",
headers={
"duuuid": uid,
"duimei": "",
"duplatform": "android",
"appId": "duapp",
"timestamp": ctime,
'duv': '4.74.5',
'duloginToken': '',
'dudeviceTrait': 'Pixel+2+XL',
'shumeiid': '202308011759568af1c8fc75c211e7f876664d9493202d0055aeeb3dd6e38c',
'User-Agent': 'duapp/4.74.5(android;11)'

},
json=param_dict,
verify=False
)
x_auth_token = res.headers['X-Auth-Token']

reply_param_dict = {
"lastId": "1",
"limit": "20",
# "newSign": ""
}
import copy

new_dict = copy.deepcopy(reply_param_dict)
new_dict.update(
# {"loginToken": "", "platform": "android", "timestamp": str(int(time.time() * 1000)), "uuid": uid, "v": "4.84.0"})
{"loginToken": "", "platform": "android", "timestamp": str(int(time.time() * 1000)), "uuid": uid, "v": "4.74.5"})
ordered_string = "".join(["{}{}".format(key, new_dict[key]) for key in sorted(new_dict.keys())])

aes_string = aes_encrypt(ordered_string)
aes_string = base64.encodebytes(aes_string)
aes_string = aes_string.replace(b"\n", b"")
sign_string = md5(aes_string)
reply_param_dict['newSign'] = sign_string

res = requests.get(
url="https://app.dewu.com/sns-rec/v1/recommend/all/feed/",
params=reply_param_dict,
headers={
"X-Auth-Token": x_auth_token,
'User-Agent': 'duapp/4.74.5(android;11)'
},
verify=False
)
print(res.text)

7 总结

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1 更新绕过
# 2 绕过代理抓包
# 3 破解newSign--》其实没用--》后续所有请求都需要
-请求参数的key-value+固定的login,uuid。。
-排序
-转成字符串
-使用aes+key加密
-使用base64编码
-使用md5加密

# 4 x-auth-token--》某个请求返回的

# 5 代码整合

__END__