选择的是4.75.5版本
断网绕过更新就行了
小黄鸟抓包即可(需要root)
没有root的用 SocksDroid 转发socks协议代理
然后分析请求头与参数 这里没有请求体
根据抓包分析,我们发现/sns-rec/v1/recommend 地址为首页推荐接口
地址如下: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:这个接口不需要带,学习破解它,后续别的接口需要带
测试后发现 newSign
是必须带的,其他可以不用带
因为都在同一个类,这里直接看这个类即可。
而且可以发现有关键词 interceptor
(拦截器)->本质上:所有请求,都走这个拦截器->所有请求都会带newSign
通过关键代码 host.addQueryParameter("newSign", RequestUtils.c(hashMap2, currentTimeMillis));
发现调用了 RequestUtils.c
转到对应的声明看一下
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 参数拼接
// 把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));
ArrayList arrayList = new ArrayList(map.entrySet());
// 2 把map转成ArrayList,并进行排序
Collections.sort(arrayList, new Comparator<Map.Entry<String, String>>() {
// 3 构建字符串,循环把key和value取出来,拼接到sb中
StringBuilder sb = new StringBuilder();
for (int i2 = 0; i2 < arrayList.size(); i2++) {
Map.Entry entry = (Map.Entry) arrayList.get(i2);
//
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 加密
// 5 把返回结果当参数传入a方法中
return a(AESEncrypt.encode(DuHttpConfig.f15796c, sb2));
//6 通过newSign的例子,判断出a方法就是md5加密
}
}
}
a方法的代码,md5加密
我们现在要判断一下是不是这个文件与newSign的生成有关
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()
/*
'''
### 参数字典为:
{abValue=1, deliveryProjectId=0, abRectagFengge=0, abType=social_brand_strategy_v454, limit=20, lastId=, abRecReason=0, abVideoCover=2}
### newSign结果:
16aa2e250ac6c28c3166cce3d0561be8
### 拿到newSign和 抓包抓包的newSign比较发现是一样的,确定位置
'''
*/
执行后刷新一下页面即可
而且这个hook得到的与我们抓包抓到的是一样的
// 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();
sb.append()
// 4 执行AESEncrypt.encode 加密
AESEncrypt.encode(DuHttpConfig.f15796c, sb2)
// 5 把返回结果当参数传入a中
a(AESEncrypt.encode(DuHttpConfig.f15796c, sb2));
// a函数就是md5加密
核心就是分析 AESEncrypt.encode()方法
查看 encode
声明
这里是第一个
public static String encode(Object obj, String str) {
return (String) NCall.IL(new Object[]{2, obj, str});
}
然后看一下 NCall.IL
public class NCall {
static {
System.loadLibrary("GameVMP");
}
public static native Object IL(Object[] objArr);
}
这里实际上是进行了一个VMP加固
VMP加固(虚拟软件保护技术)大概思路就是自定义一套虚拟机指令和对应的解释器,并将标准的指令转换成自己的指令,然后由解释器将自己的指令给对应的解释器。由于兼容性和效率等问题,所以VMP一般只用于关键函数
这里就到头了,我们回头看一下 AESEncrypt.encode
类代码
/* loaded from: classes.dex */
public class AESEncrypt {
static {
// 实际是是在加载so文件---》又调用了jni方法实现的
// 这种方式使用 VMP加固--》隐藏掉了java本来的代码
// 本来应该是:System.loadLibrary("JNIEncrypt");
NCall.IV(new Object[]{0});
}
public static String encode(Object obj, String str) {
return (String) NCall.IL(new Object[]{2, obj, str});
}
public static native String encodeByte(byte[] bArr, String str);
public static native String getByteValues();
//5 4.74.5版本AESEncrypt类如下
public class AESEncrypt {
static {
System.loadLibrary("JNIEncrypt");
}
public static String encode(Object obj, String str) {
String byteValues = getByteValues();
StringBuilder sb = new StringBuilder(byteValues.length());
for (int i2 = 0; i2 < byteValues.length(); i2++) {
if (byteValues.charAt(i2) == '0') {
sb.append('1');
} else {
sb.append('0');
}
}
return encodeByte(str.getBytes(), sb.toString());
}
public static native String encodeByte(byte[] bArr, String str);
public static native String getByteValues();
}
通过老版本分析 :执行encode流程--》先调用:getByteValues--》再调用encodeByte,这个从新版本是看不出来的
然后我们要确定,新版本还是这个流程--》通过hook确认
import frida
import sys
rdev = frida.getremotedevice()
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("-----------开始-----------------------------");
var Map = Java.use('java.util.HashMap');
var obj = Java.cast(map, Map);
console.log('1.参数字典为:',obj.toString());
var res = this.c(map,j); // 内部调了--》encode--》getBytesValue--》
encodeByte
console.log("6.newSign结果:", res);
console.log("-----------结束-----------------------------");
return res;
}
// hook---getByteValues
var AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt");
AESEncrypt.getByteValues.implementation = function(){
var res = this.getByteValues();
console.log('2.getByteValues返回值是:',res);
return res;
}
// hook-encodeByte
AESEncrypt.encodeByte.implementation = function(bArr,str){
console.log('3.encodeByte-参数bArr是:',bArr);
console.log('4.encodeByte-参数str是:',str);
var res = this.encodeByte(bArr,str);
console.log('5.encodeByte返回值是:',res);
return res;
}
});
"""
script = session.createscript(scr)
def onmessage(message, data):
print(message, data)
script.on("message", on_message)
script.load()
sys.stdin.read()
可以发现老版本的逻辑还是可以用的
通过hook确认了,新版本的执行逻辑还是跟老版本执行逻辑是一样的,然后我们基于老版本分析--》老版本没有vmp加密
public class AESEncrypt {
static {
System.loadLibrary("JNIEncrypt");
}
public static String encode(Object obj, String str) {
// 1 执行jni方法getByteValues--》返回了字符串
// 一堆01010110固定的
String byteValues = getByteValues();
StringBuilder sb = new StringBuilder(byteValues.length());
for (int i2 = 0; i2 < byteValues.length(); i2++) {
if (byteValues.charAt(i2) == '0') {
sb.append('1');
} else {
sb.append('0');
}
}
// 2 上述代码是把一堆01010110固定的 取反
// 2.1 str是RequestUtils.c中的:sb2
// 2.2 sb.toString 是一堆10101010---》getByteValues()返回值取了反
// 3 encodeByte 是jni方法---》整体的加密是用so加密
// 3.1 so文件:libJNIEncrypt.so
return encodeByte(str.getBytes(), sb.toString());
}
public static native String encodeByte(byte[] bArr, String str);
public static native String getByteValues();
}
-----------开始-----------------------------
1.参数字典为: {abValue=1, deliveryProjectId=0, abRectagFengge=0, abType=social_brand_strategy_v454, limit=20, lastId=, abRecReason=0, abLiveEntranceClose=0, abVideoCover=2}
2.getByteValues返回值是: 101001011101110101101101111100111000110100010101010111010001000101100101010010010101110111010011101001011101110101100101001100110000110100011101010111011011001101001101011101010100001101000011
3.encodeByte-参数bArr是: 97,98,76,105,118,101,69,110,116,114,97,110,99,101,67,108,111,115,101,48,97,98,82,101,99,82,101,97,115,111,110,48,97,98,82,101,99,116,97,103,70,101,110,103,103,101,48,97,98,84,121,112,101,115,111,99,105,97,108,95,98,114,97,110,100,95,115,116,114,97,116,101,103,121,95,118,52,53,52,97,98,86,97,108,117,101,49,97,98,86,105,100,101,111,67,111,118,101,114,50,100,101,108,105,118,101,114,121,80,114,111,106,101,99,116,73,100,48,108,97,115,116,73,100,108,105,109,105,116,50,48,108,111,103,105,110,84,111,107,101,110,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,52,54,55,49,52,56,50,54,54,50,50,117,117,105,100,101,51,51,52,50,101,57,55,48,97,56,49,48,56,54,55,118,52,46,55,53,46,53
4.encodeByte-参数str是: 010110100010001010010010000011000111001011101010101000101110111010011010101101101010001000101100010110100010001010011010110011001111001011100010101000100100110010110010100010101011110010111100
5.encodeByte返回值是: 9tJX+w1shRuN3zryp4iAEDmdxT3kxfhu9LcLAWOYRGiQ3XG9XpJeHWaGVLm+Om3uC8r76v9XMpyS1hhggss2qtDgj4FE7aLO/UvBThkuSpkiIA0c2GsH9wChSqVm0eCQE1Z0YKSryWnJau1Xp99X4jaMfps52MgpnBKNklzvArEorlHc5Z99DPaAZaBd4yoXSQmvWNAqcj4PMeEIhMtJJr8UHHlskm8zWV/rFALUhHg4Cs4gqmp9krHBQEeckRUoRQZ+347RnoVMs6fUiwmdxg==
6.newSign结果: 263bdc5ecde6e077e98cabf2f110ffa8
-----------结束-----------------------------
java的bytes数组--》转成字符串看一下:
def java_array_to_string(arr):
"""
将Java字节数组或int数组转为字符串
:param arr: 例如 [65, 66, 67]
:return: 字符串 "ABC"
"""
return ''.join([chr(x) for x in arr])
java_arr = [97,98,76,105,118,101,69,110,116,114,97,110,99,101,67,108,111,115,101,48,97,98,82,101,99,82,101,97,115,111,110,48,97,98,82,101,99,116,97,103,70,101,110,103,103,101,48,97,98,84,121,112,101,115,111,99,105,97,108,95,98,114,97,110,100,95,115,116,114,97,116,101,103,121,95,118,52,53,52,97,98,86,97,108,117,101,49,97,98,86,105,100,101,111,67,111,118,101,114,50,100,101,108,105,118,101,114,121,80,114,111,106,101,99,116,73,100,48,108,97,115,116,73,100,108,105,109,105,116,50,48,108,111,103,105,110,84,111,107,101,110,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,52,54,55,49,52,56,50,54,54,50,50,117,117,105,100,101,51,51,52,50,101,57,55,48,97,56,49,48,56,54,55,118,52,46,55,53,46,53]
result = java_array_to_string(java_arr)
print(result)
#abLiveEntranceClose0abRecReason0abRectagFengge0abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1746714826622uuide3342e970a810867v4.75.5
newSign加密的本质
1.使用IDA打开:libJNIEncrypt.so
,点击exports
查看是动态注册还是静态注册
2.发现没有java_开发的方法,又有JNI_OnLoad说明是【动态注册】
3. java中:encodeByte 和c中方法的对应关系在 :JNI_OnLoad中对应的
4. 点击JNI_OnLoad--》按F5--》查看源代码
-v4 = ("com/duapp/aesjni/AESEncrypt"); # 找到类
-(v3, v4, off_15010, 8LL) # 对应关系:off_15010
5 看对应关系:off_15010
(byte[] bArr, String str)
-->([BLjava/lang/String;)Ljava/lang/String;
6 双击来到encode查看
JNIEnv_
转换后的代码
jstring __fastcall encode(JNIEnv_ *a1, __int64 a2, struct _jobject *a3, struct _jobject *a4)
{
const char *v7; // x22
__int64 Value; // x24
unsigned int v9; // w25
jbyte *v10; // x23
jbyte *v11; // x0
jbyte *v12; // x26
__int64 v13; // x9
jbyte *v14; // x10
jbyte *v15; // x11
__int64 v16; // x8
jbyte v17; // t1
const char *v18; // x24
__int128 *v20; // x10
_OWORD *v21; // x11
__int64 v22; // x12
__int128 v23; // q0
__int128 v24; // q1
v7 = a1->functions->GetStringUTFChars(a1, a4, 0LL);
Value = getValue();
v9 = a1->functions->GetArrayLength((JNIEnv *)a1, a3);
v10 = a1->functions->GetByteArrayElements(a1, a3, 0LL);
v11 = (jbyte *)malloc(v9 + 1);
v12 = v11;
if ( (int)v9 >= 1 )
{
if ( v9 <= 0x1F || v11 < &v10[v9] && v10 < &v11[v9] )
{
v13 = 0LL;
LABEL_6:
v14 = &v11[v13];
v15 = &v10[v13];
v16 = v9 - v13;
do
{
v17 = *v15++;
--v16;
*v14++ = v17;
}
while ( v16 );
goto LABEL_8;
}
v13 = v9 & 0x7FFFFFE0;
v20 = (__int128 *)(v10 + 16);
v21 = v11 + 16;
v22 = v9 & 0xFFFFFFE0;
do
{
v23 = *(v20 - 1);
v24 = *v20;
v20 += 2;
v22 -= 32LL;
*(v21 - 1) = v23;
*v21 = v24;
v21 += 2;
}
while ( v22 );
if ( v13 != v9 )
goto LABEL_6;
}
LABEL_8:
v11[v9] = 0;
v18 = (const char *)AES_128_ECB_PKCS5Padding_Encrypt(v11, Value);
free(v12);
a1->functions->ReleaseStringUTFChars((JNIEnv *)a1, a4, v7);
a1->functions->ReleaseByteArrayElements((JNIEnv *)a1, a3, v10, 0LL);
return a1->functions->NewStringUTF(a1, v18);
}
7 代码如下
jstring __fastcall encode(JNIEnv_ *a1, __int64 a2, __int64 a3, __int64 a4)
{
# ...不用看
# 2 v18是通过函数AES_128_ECB_PKCS5Padding_Encrypt执行,传入的v8得到的
# 3 v8本质是传入的待加密的字符串:java传入的bytes数组
v18 = (const char *)AES_128_ECB_PKCS5Padding_Encrypt(v11, v8);
# 1 返回了字符串--》v18 就是返回的字符串--》c的字节数组---》v18是怎么来的
return a1->functions->NewStringUTF(a1, v18);
}
8 AES_128_ECB_PKCS5Padding_Encrypt 是如何加密的--》双击进入
__int64 __fastcall AES_128_ECB_PKCS5Padding_Encrypt()
{
return AES_128_ECB_PKCS5Padding_Encrypt();
}
9 AES_128_ECB_PKCS5Padding_Encrypt
__int64 __fastcall AES_128_ECB_PKCS5Padding_Encrypt(JNIEnv_ *a1, __int64 a2)
{
# 1 循环执行 AES128_ECB_encrypt
do
{
AES128_ECB_encrypt(v28, a2, v29);
v29 += 16;
--v27;
v28 += 16;
}
while (v27);
# 2 使用base64编码了
v30 = b64_encode(v26, v24);
# 3 返回了
return v30;
}
9 猜测并验证
10 拿到aes的key,使用python验证,看结果是否一样,就能判断
11 通过hook得到-AES_128_ECB_PKCS5Padding_Encrypt(v11, Value)
这里有两个参数
import frida
import sys
rdev = frida.get_remote_device()
session = rdev.attach("得物(毒)")
scr = """
Java.perform(function () {
// 1. 找到so文件libJNIEncrypt.so,第二个参数是要hook的函数名,返回值是函数的内存地址
var addr_func = Module.findExportByName("libJNIEncrypt.so", "AES_128_ECB_PKCS5Padding_Encrypt");
// 2. 传入要hook的函数内存地址
Interceptor.attach(addr_func, {
onEnter: function(args){
console.log("--------------------------执行函数--------------------------");
console.log("参数1-v11:", args[0].readUtf8String());
console.log("参数2-v8:", args[1].readUtf8String());
},
onLeave: function(retValue){
console.log("返回值newSign在md5之前的值:", 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()
执行后发现 value的值一直没有变
那么大概率 V11就是明文 value就是aes-key
--------------------------执行函数--------------------------
参数1-v11: abLiveEntranceClose0abRecReason0abRectagFengge0abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1746717601707uuide3342e970a810867v4.75.5
参数2-value: d245a0ba8d678a61
返回值newSign在md5之前的值: 9tJX+w1shRuN3zryp4iAEDmdxT3kxfhu9LcLAWOYRGiQ3XG9XpJeHWaGVLm+Om3uC8r76v9XMpyS1hhggss2qtDgj4FE7aLO/UvBThkuSpkiIA0c2GsH9wChSqVm0eCQE1Z0YKSryWnJau1Xp99X4jaMfps52MgpnBKNklzvArEorlHc5Z99DPaAZaBd4yoXSQmvWNAqcj4PMeEIhMtJJsVWNwoInkglJS9nGe8DdIflDGcTWJrt7Pb00Bk8ENlpRQZ+347RnoVMs6fUiwmdxg==
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 = (
"abLiveEntranceClose0abRecReason0abRectagFengge0abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1746717601707uuide3342e970a810867v4.75.5"
)
res = aes_encrypt(data_string)
value = base64.b64encode(res) # 推荐用b64encode,输出不会有换行
print(value.decode())
'''
hook得到的:9tJX+w1shRuN3zryp4iAEDmdxT3kxfhu9LcLAWOYRGiQ3XG9XpJeHWaGVLm+Om3uC8r76v9XMpyS1hhggss2qtDgj4FE7aLO/UvBThkuSpkiIA0c2GsH9wChSqVm0eCQE1Z0YKSryWnJau1Xp99X4jaMfps52MgpnBKNklzvArEorlHc5Z99DPaAZaBd4yoXSQmvWNAqcj4PMeEIhMtJJsVWNwoInkglJS9nGe8DdIflDGcTWJrt7Pb00Bk8ENlpRQZ+347RnoVMs6fUiwmdxg==
加密脚本得到的:9tJX+w1shRuN3zryp4iAEDmdxT3kxfhu9LcLAWOYRGiQ3XG9XpJeHWaGVLm+Om3uC8r76v9XMpyS1hhggss2qtDgj4FE7aLO/UvBThkuSpkiIA0c2GsH9wChSqVm0eCQE1Z0YKSryWnJau1Xp99X4jaMfps52MgpnBKNklzvArEorlHc5Z99DPaAZaBd4yoXSQmvWNAqcj4PMeEIhMtJJsVWNwoInkglJS9nGe8DdIflDGcTWJrt7Pb00Bk8ENlpRQZ+347RnoVMs6fUiwmdxg==
发现一模一样
'''
把上述base64使用md5加密---得到结果是---》newSign---》跟抓包抓到的是一样的
import hashlib
md5 = hashlib.md5()
md5.update(
b'9tJX+w1shRuN3zryp4iAEDmdxT3kxfhu9LcLAWOYRGiQ3XG9XpJeHWaGVLm+Om3uC8r76v9XMpyS1hhggss2qtDgj4FE7aLO/UvBThkuSpkiIA0c2GsH9wChSqVm0eCQE1Z0YKSryWnJau1Xp99X4jaMfps52MgpnBKNklzvArEorlHc5Z99DPaAZaBd4yoXSQmvWNAqcj4PMeEIhMtJJsVWNwoInkglJS9nGe8DdIflDGcTWJrt7Pb00Bk8ENlpRQZ+347RnoVMs6fUiwmdxg== '
)
print(md5.hexdigest())
#eaa1e29430ccfeb9eee40678f3043b13 输出
1 把请求参数+固定的 中排序转成字符串---》使用aes+key加密---》使用base64编码---》使用md5签名得到
真正的加密参数,不是请求参数,是请求参数+uuid....
2 请求参数
{abValue=1, deliveryProjectId=0, abRectagFengge=0, abType=social_brand_strategy_v454, limit=20, lastId=, abRecReason=0, abLiveEntranceClose=0, abVideoCover=2}
3 请求参数再加入
0 待加密字符串
abLiveEntranceClose0abRecReason0abRectagFengge0abTypesocialbrandstrategy_v454abValue1abVideoCover2deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1746717601707uuide3342e970a810867v4.75.5
1 请求参数
abLiveEntranceClose0abRecReason0abRectagFengge0abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0lastIdlimit20
2 固定的key--》value时间戳会变--》uuid咱们不知道
loginToken
platform android
timestamp 1746717601707
uuid e3342e970a810867
v 4.75.5
3 破解uuid
map.put("uuid", DuHttpConfig.d.getUUID())
4 点进去,看不懂--》美团热修复类--》真正生成uuid的位置并不是这里
public String getUUID() {
PatchProxyResult proxy = PatchProxy.proxy(new Object[0], this,
changeQuickRedirect, false, 5131, new Class[0], String.class);
return proxy.isSupported ? (String) proxy.result : "";
}
5 直接搜
hashMap.put("uuid",
# 很多-->其中有真正uuid生成到的位置
在破解X-Auth-Token时,也有uuid---》猜这个uuid应该是一样的
6 hashMap.put("uuid", HPDeviceInfo.b(BaseApplication.c()).a((Activity) null));
// 先从内存中拿,没有用a()生成
public String a(Activity activity) {
String str = this.f14858b;
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.f14858b = a();
}
return this.f14858b;
}
7 a()-->imei-->随机生成即可
public String a() {
return Settings.Secure.getString(
this.f14857a.getContentResolver(),
"android_id"
);
}
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": "",
"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.75.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)
X-Auth-Token 就是jwt的token
Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3NDY3MTAyMTEsImV4cCI6MTc3ODI0NjIxMSwiaXNzIjoiZTMzNDJlOTcwYTgxMDg2NyIsInN1YiI6ImUzMzQyZTk3MGE4MTA4NjciLCJ1dWlkIjoiZTMzNDJlOTcwYTgxMDg2NyIsInVzZXJJZCI6MjU2MzUwNTM4NiwidXNlck5hbWUiOiLlvpfniallci1WMkMySjdWNiIsImlzR3Vlc3QiOnRydWV9.p0e-QMPLqWXslAi04Z2GTT4MqUPWhGDCovW9dmNTurTq_eLMcYAa7WeJFyITZnahnVOm0rZGP_qJxfB32hE59rZvFs0jSIou1SrvbhMo8Yb9fdqXXMp9HHYrcLWQtNDJhnz-hTb2dXO1udIA6CAvMtitQWAAWmagl-SAriZt7r0JOumXAJ5UstuiOVe3xI9NpYKvUEImtBdTTGERbqQMDYx4sucWgmNA2GvbPKxWvcYU7C6fxQRQoA4Y8mML9RwzB-EJfjs46xhtr1ExyP-9vUtAeKHzhCoY6TpCnhw9gUareXEl19mzdDdDKIg7FLopT_oT1CbV27Nk6PuRDj7e9w
# 1 请求头中:X-Auth-Token
X-Auth-Token Bearer
# 2 搜之前--》分析---》如果做过后端开发--》就知道--》这个东西就是jwt到的token
-用户登录后---》服务端返回的三段式,每段使用base64编码--》里面放了用户信息
-我们app没登录--》app第一次启动--》向后端发送请求--》后端返回的--》未登录用户的token
# 3 jwt的构成
# 第一部分:头--header--》这个token的加密方式--》固定一般不变
eyJhbGciOiJSUzI1NiJ9
# 第二部分:荷载--payload--》用户信息,签发时间,过期时间。。--》未登录用户
eyJpYXQiOjE3MjkwODAzODEsImV4cCI6MTc2MDYxNjM4MSwiaXNzIjoiZWUxMzg4NWU2OGQ3NmVkNCIsI nN1YiI6ImVlMTM4ODVlNjhkNzZlZDQiLCJ1dWlkIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInVzZXJJZCI6Mj E0MTY4MTg1MywidXNlck5hbWUiOiLlvpfniallci1KMEQ4UzZHNSIsImlzR3Vlc3QiOnRydWV9.
# 第三部分:签名--》签名是由第一部分和第二部分签名得到--》是用来做jwt校验的--》校验这个jwt有没
有被别人篡改过
C_2WaciMJlyGCLUVsjdQ7Tt_sy_1aUQ0w-tH0DIHRWDRyPnpjyxC7Sqsaeg0PQ_sOWDp2UDWfnQhFdpjL9pTca5nl80r95S8hX8APLH3eEL56rIACgy lwJzANaIILwgggqqoXfoukq4t857RbJx4spgchPRDXSh9VibcvhWVi1oB1nQYsmqVrJM1CIIJf5b4XRBR mhS38CU1B_ckraYBsCfaKS1u6vDv5SGoDdrH9h1e-HQEWMvUISWMUd1TNM64tEJAyGPeoWvCNpURXFtH_rQ-fMj1aAWrKhiY5dBbSOJR5VwWyAn-CSTMK8ya7MangbUXHmdyc9QrB4vVdpcqtg
# 4 分别解出第一部分和第二部分看看内容
import base64
# res=base64.b64decode('eyJhbGciOiJSUzI1NiJ9') # b'{"alg":"RS256"}'
res=base64.b64decode('eyJpYXQiOjE3MjkwODAzODEsImV4cCI6MTc2MDYxNjM4MSwiaXNzIjoiZWU xMzg4NWU2OGQ3NmVkNCIsInN1YiI6ImVlMTM4ODVlNjhkNzZlZDQiLCJ1dWlkIjoiZWUxMzg4NWU2OGQ3 NmVkNCIsInVzZXJJZCI6MjE0MTY4MTg1MywidXNlck5hbWUiOiLlvpfniallci1KMEQ4UzZHNSIsImlzR 3Vlc3QiOnRydWV9') # b'{"alg":"RS256"}'
#
{"iat":1729080381,"exp":1760616381,"iss":"ee13885e68d76ed4","sub":"ee13885e68d76e d4","uuid":"ee13885e68d76ed4","userId":2141681853,"userName":"\xe5\xbe\x97\xe7\x8 9\xa9er-J0D8S6G5","isGuest":true}'print(res.decode('utf-8'))
# 5 获得token---》服务端返回的--》找那个请求包返回即可
-1 清空app数据
-2 打开抓包--》打开app
-3 找到是哪个请求返回的
找到是 getVisitorUserId
获取到 X-Auth-Token
import time
import requests
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
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.75.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.75.5',
'duloginToken': '',
'dudeviceTrait': 'Redmi',
'shumeiid': '20250509000001237a863bce0546c6b4661034f075ff9c005f94ed751fcf7a',
'User-Agent': 'duapp/4.75.5(android;11)'
},
json=param_dict,
)
print(res.headers)
x_auth_token = res.headers.get('X-Auth-Token')
print(x_auth_token)
import time
import requests
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
import random
import urllib3
import copy
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.75.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.75.5',
'duloginToken': '',
'dudeviceTrait': 'Pixel+2+XL',
'shumeiid': '202308011759568af1c8fc75c211e7f876664d9493202d0055aeeb3dd6e38c',
'User-Agent': 'duapp/4.75.5(android;11)'
},
json=param_dict,
verify=False
)
x_auth_token = res.headers['X-Auth-Token']
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.75.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.75.5(android;11)'
},
verify=False
)
print(res.text)