唯品会登录接口api_sign与edata分析
app 版本:7.83.3
设备 pixel 2xl
本文章中所有内容仅供学习交流使用,不用于其他任何目的侵权请联系我,马上删除。本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!
我的 wx:
let my_wx = 15232101239
可以加我一起交流技术
一、分析接口
首先我们打开手机分析一下 所需要的参数,这篇文章主要分析 edata 与 api_sign
api_sign:
二、jadx反编译寻找api_sign
一共就这几个 我们可以通过观察因为我们接口是带着 OAuth字样的所以我们优先看。
进入Oauth api_sign这个位置
现在跟一下apisign:我们跟到gs这里
这里用到了java 反射的机制 最终找到ketinfo下的gsNav 这里的gsnav是 native层的方法
hook验证一下与抓包对比是能够对比的上的:
从apk中复制出libkeyinfo.so文件进入ida 寻找gsnav方法,我们打开so文件后直接点export看导出函数,搜索java字眼确定是否是静态注册还是动态注册。很明显我们搜到了自己想要得到的那个方法,所以他就是一个静态方法。
进入Java_com_vip_vcsp_KeyInfo_gsNav导出函数:
接着往下走看到j_getbytehash的字眼
里面有sha1的字眼 待会 留意一下 。
三、实现api_sign算法
hook_so的导出函数:
// 去内存中中 libkeyinfo.so getByteHash var addr = Module.findExportByName("libkeyinfo.so", "getByteHash"); console.log(addr); Interceptor.attach(addr,{ onEnter:function (args){ console.log(args[2].readUtf8String()); }, onLeave:function(retval) { console.log(retval.readUtf8String()); } })
hook到了!仔细分析一下Hook到的结合 ida静态分析的sha1可得
其实就是 我们传递的参数 做了两次 加盐的sha1 ,salt = aee4c425dbb2288b80c71347cc37d04b
我是这样写的(不是完整版的代码):
import hashlib import time from base64 import b64encode import requests def sha1(data_string): # sha1加密 hash_object = hashlib.sha1() hash_object.update(data_string.encode('utf-8')) res = hash_object.hexdigest() return res
def get_sign(body_dict): body_string = "&".join(["{}={}".format(key, body_dict[key]) for key in sorted(body_dict.keys())]) # print(body_string) salt = "aee4c425dbb2288b80c71347cc37d04b" tmp = sha1(f"{salt}{body_string}") api_sign = sha1(f"{salt}{tmp}") return api_sign
data = { "api_key": "23e7f28019e8407b98b84cd05b5aef2c", "did": "0.0.c208261d2b20f2a2192b5234dd267d55.b771f7", "edata": //未分析, "eversion": "0", "skey": "6692c461c3810ab150c9a980d0c275ec", "timestamp": str(int(time.time())) } api_sign = get_sign(data) print('api_sign=>',api_sign) headers['authorization'] = 'OAuth api_sign={}'.format(api_sign)
这样即可得到正确的sign。
四、jadx反编译寻找edata
查找用例:
接着顺着往下跟会跟到:
他和上面那个一样也反射 去找 keyinfo下的es 我们发现 他其实 最后运行esNav:
hook测试一下 位置是否是正确的:
结合charles抓包我们发现我们的寻找的位置是正确的:
那就接着来呗接着去看ida 里面的java_xxxx_esnav
五、分析so实现edata
我们可以找到这里 一看其实他这个不就是调用了我们java层的一些函数做的aes加密嘛 AES/CBC/PKCS5Padding
hook方法找到key 和 iv 即可 多次hook后发现key是固定的iv是不固定的:
为什么iv是随机的呢?原因在ida里面能够看到:
那么 iv是如何告诉后端服务器的呢?
经过对其密文hook 我们可以得知 他是将iv 的字节拼接到aes cbc加密的密文之前再做的base64编码:
iv hex= 64613636383464636164333133646433
自此:edata的实现逻辑也完成了。
def EncryptAES(data): iv = ''.join(["%x" % random.randint(1, 15) for i in range(16)]) print(iv) obj = hashlib.md5() obj.update(b'aee4c425dbb2288b80c71347cc37d04b') key = obj.digest() print(key) aes = AES.new( key=key, mode=AES.MODE_CBC, iv=iv.encode('utf-8') ) raw = pad(data.encode('utf-8'), 16) encrypt_bytes = aes.encrypt(raw) total_bytes = iv.encode('utf-8') + encrypt_bytes edata = b64encode(total_bytes).decode('utf-8') return edata #这些是用来加密的参数 # str = f"app_name=shop_android&app_version=7.83.3&captchaId=&channel_flag=0_1&client=android&client_type=android&darkmode=0&data=%7B%22os_version%22%3A30%7D&deeplink_cps=&elder=0&fdc_area_id=101102101&harmony_app=0&harmony_os=0&loginName={}&mars_cid=b68a3953-b5d7-3f74-98f3-0ff5750c6f13&mobile_channel=oziq7dxw%3A%3A%3A&mobile_platform=3&other_cps=&page_id=page_login_1711964967322&password={}&phone_model=pixel+2+xl&province_id=101102&referer=com.achievo.vipshop.usercenter.activity.LoginAndRegisterActivity&rom=Dalvik%2F2.1.0+%28Linux%3B+U%3B+Android+11%3B+Pixel+2+XL+Build%2FRP1A.201005.004.A1%29&sd_tuijian=0&session_id=b68a3953-b5d7-3f74-98f3-0ff5750c6f13_shop_android_1711958526090&sid={sid}&source_app=android&standby_id=oziq7dxw%3A%3A%3A&sys_version=30&union_mark=blank%26_%26blank%26_%26oziq7dxw%3A%3A%3A%26_%26blank%26_%26blank&warehouse=VIP_BJ" # res = EncryptAES(str)
六、效果图:
效果是能够出来的 但是由于风控等原因好像还得用到验证码,后面没具体研究了。