一.前言
今天我们任务是破解v品会的搜索接口,这里给出地址
https://mapi.appvipshop.com/vips-mobile/rest/shopping/search/product/list/v1
我们打开charles抓包分析一下(这次抓包分析比较复杂,但是我讲不了那么细,只能把关键点和大家讲一下,最后再实操一下)
二.抓包分析
这个接口就是我们要的搜索数据,这个接口需要破的参数有
authorization
api_key
mars_cid
session_id
skey
did:接口generate_token返回的 (这个也是需要大家逆向才发现,因为怎么都找不到)
这个did就是 https://mapi.appvipshop.com/vips-mobile/rest/device/generate_token
返回的
看到这里就是了,需要破的参数有
api_key 见过,固定的
edata 很多东西,需要破,需要 vcsptoken
skey 见过,固定的
而vcsptoken是 https://vcsp-api.vip.com/token/getTokenByFP
返回的,这个先做了的话肯定能够发现,这里为了教程更加顺利,所以和大家用上帝视角讲
这里可以看到返回了 vcsptoken,当然在这个请求之前还有一个请求不知道大家关注了没有
https://mp.appvipshop.com/apns/device_reg
这个就是设备注册接口 app第一次打开(数据清空再打开)
因为很多的app都采取这个逻辑(将关键key注册,接下来的请求才有用),但是这个app并没有,为了学习我也和大家说一下
需要破的参数
device_token
skey
authorization api_sign
三.总结流程
1 注册设备接口: https://mp.appvipshop.com/apns/device_reg
-app第一次打开(数据清空再打开)
-向后端发送请求,注册app
-需要破解的:
device_token
skey
api_sign 又叫 authorization (api_sign在这个里面)
2 getTokenByFP
-地址:
https://vcsp-api.vip.com/token/getTokenByFP
-返回:(是生成edata的关键)
vcsptoken
-需要破的:
vcspKey
vcspauthorization
3 generate_token
-地址:
https://mapi.appvipshop.com/vips-mobile/rest/device/generate_token
-需要破:
api_key 23e7f28019e8407b98b84cd05b5aef2c # 见过,固定的
edata # 很多东西,需要破,需要 vcsptoken
skey 6692c461c3810ab150c9a980d0c275ec # 见过,固定的
-返回 did
4 真正搜索接口
-地址
https://mapi.appvipshop.com/vips-mobile/rest/shopping/search/product/list/v1
-需要破解
authorization
api_key
mars_cid
session_id
skey
did:接口generate_token返回的
今天我们就破解第一个,破解第一个,后面的参数基本都有了
四.破解注册接口 device_reg
4.1 破解 device_token
我们先进行反编译,搜索device_token发现很多位置
最终其实都指向同一个位置,那我们先看第一个 点进来发现
这个代码中这个decide_token就是这个c.Q().m(),我们再点进去
发现返回 this.f73030x,我们往上找找赋值的位置
发现在这里,就是u0传进来的参数,那我们现在找找哪里调用了,也可以打印一下u0的调用栈信息,这里就带大家找一下
发现查找用例有很多,那我们点开第一个看看,第一个比较像
这个就是了,那我们再点进去
发现这就是生成的逻辑了,那我们接下来就来分析一下这个代码
这个不就是典型的去内存拿,去xml拿,都没有的话就生成一个uuid赋值进来,我们再看device_token的样子,不就是uuid么,至此device_token已破解
4.2 破解skey
我们搜素skey,发现有很多
那我们换个关键词搜索,"skey,
发现就只搜到一个常量,这个在开发中是经常使用的,对常量进行赋值,然后再对其进行字符串操作,我们查找用例
发现有很多,而我们要找这种put的,那我们就点第一个
再往里面点
再点进去
再点进去
发现到这里就点不进去了
我们来分析一下上面的写法
clazz = KeyInfo.class; # 找到类
object = KeyInfo.class.newInstance(); # 得到类的对象
method = clazz.getMethod("getInfo", Context.class, String.class) # 找到对象中的方法,传入方法名,参数
method.invoke(object, context, str);# 找到方法后真正调用对象的方法,传入参数,str就是 'skey' 字符串
我们就知道:KeyInfo 类中有个方法叫 getInfo ,需要两个参数:context, str
这就是java的反射:根据字符串找到方法执行
换成正常写法就是
KeyInfo info=new KeyInfo()
String res=info.getInfo(context,str) # 这里生成了skey
return res
那我们就是要找到keyinfo里面的getinfo方法
那我们双击keyinfo进去,找到里面的getinfo方法
发现这里真正的加密位置是在so层,我们可以不着急抓,我们可以直接hook一下getNavInfo,看看返回值是啥,但是我们hook的时候发现做了frida的反调试,那我们就可以打印so文件然后删除停止的那个,这个之前的识货案例和大家说过,这里再说一下。
import frida
import sys
rdev = frida.get_remote_device()
pid = rdev.spawn(["com.achievo.vipshop"]) #放入包名
session = rdev.attach(pid)
scr = """
Java.perform(function () {
var dlopen = Module.findExportByName(null, "dlopen");
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(dlopen, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
console.log("[dlopen:]", path);
},
onLeave: function (retval) {
}
});
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
console.log("[dlopen_ext:]", path);
},
onLeave: function (retval) {
}
});
});
"""
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
rdev.resume(pid)
sys.stdin.read()
那所以我们就要删除最后一个,直接执行
rm /data/app/~~pntfvIcIy6UFKGdBLQeafg==/com.achievo.vipshop-aBzjOrAAfLy3AAae-9sDvQ==/lib/arm/libmsaoaidsec.so
删除成功,清除内存后执行hook代码,这里也给出
import frida
import sys
rdev = frida.get_remote_device()
pid = rdev.spawn(["com.achievo.vipshop"])
session = rdev.attach(pid)
scr = """
Java.perform(function () {
var KeyInfo = Java.use("com.vip.vcsp.KeyInfo");
KeyInfo.getNavInfo.implementation = function (ctx, str) {
console.log("-----------------");
console.log("参数==>", str);
var res = this.getNavInfo(ctx, 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()
rdev.resume(pid)
sys.stdin.read()
多次hook发现值都一样 ,我们把这些值都记一下,因为后续请求用得上,也就是明天会和大家讲
-----------------
参数==> app_name
返回的值==> shop_android
-----------------
参数==> vcsp_key
返回的值==> 4d9e524ad536c03ff203787cf0dfcd29
-----------------
参数==> api_key
返回的值==> 23e7f28019e8407b98b84cd05b5aef2c
-----------------
参数==> skey
返回的值==> 6692c461c3810ab150c9a980d0c275ec
-----------------
参数==> skey
返回的值==> 6692c461c3810ab150c9a980d0c275ec
-----------------
参数==> skey
返回的值==> 6692c461c3810ab150c9a980d0c275ec
至此,skey破解完成
4.3 破解api_sign
我们直接搜索api_sign
第一个不就是是了嘛,那我们点进去
发现这里,不就是b.n里面生成的,我们再点进去
再点进去
我们接着点进去
content肯定不是空,我们点击下面的
我们又点进去
接着点进去
到这里我们不就发现反射了,但是我们不知道是找到了哪个类中的fs方法,那答案只有一个,再initInstance中找到类的,那我们点进去
发现还是KeyInfo中的gs方法
发现又是在so层加密的
可以发现so文件名字是libkeyinfo.so
但我们先不着急去找so,可以先看看gsNav的参数和返回值,这里写一个hook
# 清除一下数据
import frida
import sys
rdev = frida.get_remote_device()
session = rdev.attach("唯品会")
scr = """
Java.perform(function () {
var KeyInfo = Java.use("com.vip.vcsp.KeyInfo");
var TreeMap = Java.use('java.util.TreeMap');
KeyInfo.gsNav.implementation = function (ctx, map,str,z10) {
console.log("-----------------gsNav-----------------");
console.log("参数==>", Java.cast(map,TreeMap).toString());
console.log("参数==>", str);
console.log("参数==>", z10);
var res = this.gsNav(ctx, map,str,z10);
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()
那我们清除数据,hook一下
我们通过抓包到的值搜索一下,发现参数是下面这些
这不就是请求参数嘛,对这些进行加密生成api_sign
那我们现在就去so文件找找,直接把 libkeyinfo.so拖入idea
搜索发现,这是静态注册
双击进去
再次双击
再进去
发现到了加密位置
我们导入jni.h,再转成env的环境
发现v59不就是加密值嘛,再点进去
看不太懂,但是看到了关键词sha1 而结果也很像sha1的,那我们就返回上一层,看看逻辑
可以发现这个v79就是传进来的字符串,v58就是字符串的长度,那我们可以hook一下 这个方法,看看参数值是啥,但是!
我们每次hook都要清除数据,这个so文件加载的时候进行hook,如果加载完了,hook不到,如果还没加载,那就报错,所以我们得用延迟hook,这个是关键,这里给出代码
function do_hook() {
setTimeout(function(){
var addr = Module.findExportByName("libkeyinfo.so", "getByteHash"); //后期改这里
console.log(addr); //0xb696387d
Interceptor.attach(addr, {
onEnter: function (args) {
this.x1 = args[2];
},
onLeave: function (retval) {
console.log("--------------------")
console.log(Memory.readCString(this.x1));
console.log(Memory.readCString(retval));
}
})
},10);
}
function load_so_and_hook() {
var dlopen = Module.findExportByName(null, "dlopen");
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(dlopen, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
// console.log("[dlopen:]", path);
this.path = path;
}, onLeave: function (retval) {
if (this.path.indexOf("libkeyinfo.so") !== -1) { //还有这里
console.log("[dlopen:]", this.path);
do_hook();
}
}
});
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
this.path = path;
}, onLeave: function (retval) {
if (this.path.indexOf("libkeyinfo.so") !== -1) { //还有这里
console.log("\nandroid_dlopen_ext加载:", this.path);
do_hook();
}
}
});
}
load_so_and_hook();
//frida -U -f com.achievo.vipshop -l 20.延迟hook_so.js
得到的结果我们去抓包找个key搜索
发现前面拼接了 d1ac91bda7397796a3b28a65bc319181822ca051
得到的结果再拼接 d1ac91bda7397796a3b28a65bc319181822ca051
再进行sha1
第一步:
aee4c425dbb2288b80c71347cc37d04b+请求参数--》使用sha1签名--》得到签名结果
第二步:
aee4c425dbb2288b80c71347cc37d04b+签名结果--》使用sha1签名--》得到签名结果--》才是真正的api_sign
验证一样,这里给出代码
import hashlib
data_string ='app_name=achievo_ad&app_version=7.83.3&channel=oziq7dxw:::&device=Pixel 3&device_token=a0307f53-623c-312a-be12-a1e51a797d2d&manufacturer=Google&os_version=30®Plat=0®id=null&rom=Dalvik/2.1.0 (Linux; U; Android 11; Pixel 3 Build/RQ3A.211001.001)&skey=6692c461c3810ab150c9a980d0c275ec&status=1&vipruid=&warehouse=VIP_HZ'
# sha1加密
hash_object = hashlib.sha1()
hash_object.update(b'aee4c425dbb2288b80c71347cc37d04b')
hash_object.update(data_string.encode('utf-8'))
arg7 = hash_object.hexdigest()
print(arg7) #
x = "aee4c425dbb2288b80c71347cc37d04b"+arg7
# sha1加密
hash_object = hashlib.sha1()
hash_object.update(x.encode('utf-8'))
arg7 = hash_object.hexdigest()
print(arg7)
#hook得到的结果:d1ac91bda7397796a3b28a65bc319181822ca051
至此 api_sign破解完成
五.总结
今天的重点就是延迟hook,还有个新的知识点是java的映射
补充
有需要源码的看我主页签名名字私信我,有求必应