本文旨在学习,与平台方无关
一.前言
今天我们来逆向一下得物的主页面,来学习一些东西,主要这次是学习加密的逆向,因为高版本的比较难,这次我们就和大家说一个低版本的,低版本的会简单很多。
二.app版本选择与绕过强制更新
2.1 app版本选择
app版本我们选择 4.74.5,大家可以去豌豆荚下载,这里给出下载链接
2021得物v4.74.5老旧历史版本安装包官方免费下载_豌豆荚 (wandoujia.com)https://www.wandoujia.com/apps/6696712/history_v369大家可以看的出来这个版本是十分老的,因为这个比较简单,我们前期先和大家讲这个,没办法,app逆向太难了!因为这个是基础教程阶段,后期会给大家出专门的专题学习深层次的。
我们下载好只会直接adb install 到手机,看了前几期的教程,大家肯定都已经会了
2.2 绕过强制更新
进来这个可以看到这个页面,之前上一期和大家讲了如何绕过强制更新,这个得物的强制更新十分简单,只需要断网再进来就可以了,再联网就可以了,这样就成功绕过app的强制更新了。
三.抓包分析
我们获取这个页面的数据
该app禁用了手机端代理,所以我们使用SocksDroid代理(上期我们已经配置过了,以后我们都可以直接采取SocksDroid代理),这里就不带大家再次配置了
这里可以看到我们想要的数据
地址:https://app.dewu.com/sns-rec/v1/recommend/all/feed
抓包分析再改包发现,这些值都不用逆向,但是其他的接口有点难度,先通过学习的目的和大家讲讲这个·
-newSign
-X-Auth-Token
-uuid (duuid)
四.破解newSign
4.1 定位位置
我们把下载好的apk文件拖入jadx进行反编译
我们搜索"newSign"发现了很多如下
然后我们还想,前面五个都是在一个包下,这里我告诉大家就是第五个,点进去可以发现
我们再点进去,可以发现
这么多,我们hook一下这个,直接说一下流程,启动手机端frida,端口转发,获取正在运行app的名字,进行hook,查看和抓包的是否一样
4.2 hook加密位置
用正常的hook代码看不出来参数,所以我们要处理一下,这里给出hook代码
import frida
import sys
# attach:手动运行app,打开APP后,再运行Hook脚本。
# 连接手机设备
rdev = frida.get_remote_device()
session = rdev.attach("得物(毒)") # 写获取手机上的软件名
scr = """
Java.perform(function () {
var RequestUtils = Java.use("com.shizhuang.duapp.common.utils.RequestUtils"); // Java.use("需要hook的地方的包名和大类名")
RequestUtils.c.implementation = function(map,int){
console.log("第一个参数是:::",map)
console.log('第一个参数类型',JSON.stringify(map))
var Map = Java.use('java.util.HashMap');
var obj = Java.cast(map, Map);
console.log('第一个参数字符串形式',obj.toString()); // 把map转成字符串形式打印出来
console.log("第二个参数是:::",int)
var res = this.c(map,int); //调用原来函数加密密文
console.log("最后结果是:::",res);
console.log("========")
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()
这个可以看成是遇到字典类型就这么写
hook出来的结果和抓包抓到的一致
第一个参数:{abValue=1, deliveryProjectId=0, abRectagFengge=0, abType=social_brand_strategy_v454, limit=20, lastId=, abRecReason=0, abVideoCover=2}
第二个参数: 十三位时间戳
第一个参数不就是载荷的字典吗
4.3 分析代码
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));
4.4 查看a函数
点击a函数进去
看到这个就是一个md5(没有加盐),我这里就不hook了,大家也可以hook一下看看。
4.5 查看AESEncrypt.encode
看名字就知道是aes加密,毕竟这个难度的肯定不会是假的。当然我们还是得确定一下
点击去
进来之后发现,那我们先读一下逻辑
在getByteValues获取一个个字符串,然后再把字符串的取反,然后再进行加密 ,那我们现在就来hook一下这个encode
我们发现在这个类下不止有一个encode,这是因为重载了,所以hook代码要改一下
import frida
import sys
# attach:手动运行app,打开APP后,再运行Hook脚本。
# 连接手机设备
rdev = frida.get_remote_device()
session = rdev.attach("得物(毒)") # 写获取手机上的软件名
scr = """
Java.perform(function () {
var AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt"); // Java.use("需要hook的地方的包名和大类名")
AESEncrypt.encode.overload('java.lang.Object', 'java.lang.String').implementation = function(obj,str){
console.log("第一个参数是:::",obj))
console.log("第二个参数是:::",str)
var res = this.encode(obj,str);
console.log("最后结果是:::",res);
console.log("========")
return res;
}
});
"""
# 重载方法中间要加上.overload('java.lang.Object', 'java.lang.String') 里面写你要hook的参数的类型名 j
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
sys.stdin.read()
主要是重载方法要额外加上overload("参数类型")来区分用的是哪个方法
根据载荷可以判断出是下面的进行了加密,把值进行md5签名就可以得到和抓包一样,这里大家可以去测试一下
4.6 查看getByteValues返回值是否固定
我们在上面都能看见,aes算法是在jni 里写的加密逻辑,所以的encode和getByteValues是在so文件中写的逻辑,但是我们可以提前hook一下getByteValues是不是固定值,那我们现在来hook一下
这里hook代码就是正常的hook代码,和以前说的一样,这里就不给出了
hook结果
发现始终不变,都是101001011101110101101101111100111000110100010101010111010001000101100101010010010101110111010011101001011101110101100101001100110000110100011101010111011011001101001101011101010100001101000011
然后进行取反,然后进行encode,encode我们就要去so文件里找了,这里我前面都说过了
名字就叫liblibJNIEncrypt.so,找到这个so文件放到idea里面反编译
我们进来之后点击这个export,然后就可以了
4.7 读so文件找到encrypt逻辑
我们首先要知道是动态注册还是啥,动态注册肯定是 java_包名_类名....
动态注册的话就要找Onload找对应关系,刚好我们看到了个Onload,点进去,再隐藏变量,导入jni.h
jni的标准写法
jclass clazz = (*env)->FindClass(env, "com/justin/s11day13/Dynamic");
int res = (*env)->RegisterNatives(env, clazz, gMethods, 2);
我们可以知道off_15010就是gMethod,我们就要点进去找对应关系
可发现encode就是对应的c语言函数名字,点进去,按f5
可以看到这个a1是jni中的env不用管,所以重点关注就是这个v18,而v18在上面,传入两个参数,我们可以hook一下得到传入的两个参数,没错,hookc语言
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(addr_func, {
onEnter: function(args){
// 函数开始执行,触发onEnter,所有参数都在args
console.log("--------------------------执行函数--------------------------");
console.log("参数1:", args[0].readUtf8String());
console.log("参数2:", args[1].readUtf8String());
},
onLeave: function(retValue){
// 函数离开,触发onLeave,返回值在: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完的结果
参数二这不明显就是aes的key吗
我们再进去看一下
aesecb,再进行base64编码,大家可以反解一下,结果是正确的,所以,前面得到的那一大堆字符串好像没有用上。。。
接下来两个就简单和大奖讲
五.破解uuid
1 之前不知道的
uuid:ee13885e68d76ed4 ,这个手机一直没变 #map.put("uuid", DuHttpConfig.d.getUUID());
-猜:手机设备id号
-搜:hashMap.put("uuid",
-找一个生成uuid的位置,读代码,还原,放到到咱们加密中测试,能不能正常发送请求
loginToken:写死的 platformandroid # DuHttpConfig.d.getLoginToken()
2 破 X-Auth-Token ,也用了uuid
hashMap.put("uuid", HPDeviceInfo.b(BaseApplication.c()).a((Activity) null));
telephonyManager.getDeviceId() # 设备id号
3 模拟生成即可
def generate_imei():
return "".join(random.choices('0123456789abcdef', k=15))
六.破解X-Auth-Token
1 搜索
2 hashMap.put("X-Auth-Token", ServiceManager.a().getJwtToken());
3 结合:抓包抓到的:
eyJhbGciOiJSUzI1NiJ9. # 头,固定的eyJpYXQiOjE3MTUzNDI3NzIsImV4cCI6MTc0Njg3ODc3MiwiaXNzIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInN1YiI6ImVlMTM4ODVlNjhkNzZlZDQiLCJ1dWlkIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInVzZXJJZCI6MjE0MTY4MTg1MywidXNlck5hbWUiOiLlvpfniallci1KMEQ4UzZHNSIsImlzR3Vlc3QiOnRydWV9.
荷载:放用户数据eGbSAsGkJTZkVsXbJFXqbGphbeyOVIrm2BVj7AfqM8YLYOyhUo79Ae0u0ToUa2OGEOw99MFTZ39WJxlnGrg07Fdf7JIDgXqW5HmTPLMcJHM8w-6t5MlrpfSBMuqBMQOi-n7IlntiAp0hcRRX1D3mZ31X37GSwUN_eU3H3HREzd1kt-F8qynu_3Y9a59U7iFWXqKjKFTy_qugB0KHYJrVfYSC4GBvhJIN3I7zBCpR0JNQMVlf9YeXvtYp-cmE_NlWs-r9wqQd_BgXy20EwdpuF_hWhqIPrkNfEwJD33wQfpWmQkZ7gKh5cXvhqAQl1Vd8y9sEW56o6GDLgip7qlcPvw
签名:通过头和荷载签名得到4 一定是后端给的
5 清空数据,重新打开app--》有个接口,返回这个数据--》保存到手机上,以后每次发请求携带
这个数据格式,一看就是jwt格式的
什么是jwt
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在不同实体之间安全地传输信息。它由三部分组成,分别是头部(Header)、载荷(Payload)和签名(Signature)。它们通过
.
连接组成一个形如header.payload.signature
的字符串。具体来说:
头部(Header): 头部通常由两部分组成:令牌的类型(即 JWT)和所使用的签名算法,例如 HMAC SHA256 或 RSA。头部会被 Base64 编码后形成JWT的第一部分。
载荷(Payload): 载荷包含了JWT的主要信息,这些信息可以被安全地传输和存储,例如用户的ID、角色或者其他相关数据。载荷也会被 Base64 编码后形成JWT的第二部分。
签名(Signature): 签名是由头部、载荷以及一个密钥(只有服务器才知道)生成的,用于验证消息的完整性。签名可以确保在传输过程中JWT没有被篡改。签名是对编码后的头部、编码后的载荷、以及一个密钥使用指定算法生成的哈希值。
JWT 的主要特点包括:
自包含性(Self-contained): JWT本身包含了所有需要传递的用户信息,因此减少了需要查询数据库的次数。
易于传输(Compact and Easy to Transmit): JWT是一个紧凑的字符串,适合在URL、POST参数或HTTP标头中传输。
安全性(Security): JWT使用签名来验证发送者的身份,并确保在传输过程中数据没有被篡改。
无状态(Stateless): 由于JWT本身包含了所需的信息,服务器不需要在存储用户的会话信息。这使得服务端可以更容易地扩展和分布式。
JWT通常用于认证和授权,典型的应用场景包括单点登录(Single Sign-On, SSO)系统和API的安全通信。
jwt分析
# 1 jwt json web token-->前后端认证的方式--》三段
eyJhbGciOiJSUzI1NiJ9. # 头,固定的eyJpYXQiOjE3MTUzNDI3NzIsImV4cCI6MTc0Njg3ODc3MiwiaXNzIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInN1YiI6ImVlMTM4ODVlNjhkNzZlZDQiLCJ1dWlkIjoiZWUxMzg4NWU2OGQ3NmVkNCIsInVzZXJJZCI6MjE0MTY4MTg1MywidXNlck5hbWUiOiLlvpfniallci1KMEQ4UzZHNSIsImlzR3Vlc3QiOnRydWV9.
# 荷载:放用户数据eGbSAsGkJTZkVsXbJFXqbGphbeyOVIrm2BVj7AfqM8YLYOyhUo79Ae0u0ToUa2OGEOw99MFTZ39WJxlnGrg07Fdf7JIDgXqW5HmTPLMcJHM8w-6t5MlrpfSBMuqBMQOi-n7IlntiAp0hcRRX1D3mZ31X37GSwUN_eU3H3HREzd1kt-F8qynu_3Y9a59U7iFWXqKjKFTy_qugB0KHYJrVfYSC4GBvhJIN3I7zBCpR0JNQMVlf9YeXvtYp-cmE_NlWs-r9wqQd_BgXy20EwdpuF_hWhqIPrkNfEwJD33wQfpWmQkZ7gKh5cXvhqAQl1Vd8y9sEW56o6GDLgip7qlcPvw
# 签名:通过头和荷载签名得到# 2 一定是后端返回的
用户登录,返回token
用户没登录--》返回一个isGuest的token
# 3 抓包,找到token
总结
这次讲的很长,主要是学习技术和概念。