好久都没更新了,本来打算往这上面扔垃圾的,结果今天上线发现那么多人关注,那就继续扔,欸嘿,反正不花钱~
声明:本文只作学习研究,禁止用于非法用途,否则后果自负,如有侵权,请告知删除,谢谢!(手动狗头)
一:抓包分析
首先这APP暂时还没更新,还是以前那套逻辑,所以版本无所谓。如果有新的更新可以私信我
具体下载地址 合伙人下载安卓最新版手机app官方版免费安装下载豌豆荚 (wandoujia.com)
话不多说,开干!
首先咱的要求就是模拟登录,抓个包看看
感觉这几个是需要逆向的参数
重新发包看看
请求URL:https://chinayltx.com/app/api/v1/partnerLogin/login 请求头: X-Req-Time 1703076997109 #时间戳 X-Sign e0c6bb57936914fde993e5bf5fc1cba2#不知道什么的值(好像md5) X-Token X-UserID 请求体: phone=13566661111&password=e10adc3949ba59abbe56e057f20f883e #手机号码和密码 只用逆向password 和X-Sign
二:逆向password
用jadx反编译下apk
直接搜索password"
发现了五个地方,大佬肯定一眼就知道哪里有值,咱小白还是老老实实一个个看
这是第一,二个password的地方,看着不太像
这是第二个地方,诶,感觉有点意思了,但是咱注意@POST(这里的URL)发现不是咱要的
继续来到第三个地方,诶,url对了,扣下来看看
@FormUrlEncoded
@POST("api/v1/partnerLogin/login")
Observable<HttpResult<LoginInfo>> submitLogin(@Field("phone") String str, @Field("password") String str2);
/*
这东西看的云里雾里的,学过安卓开发的知道,这就是retorfit,差不多是这样
sharedListCall.enqueue(new Callback<SharedListBean>() {
@Override
public void onResponse(Call<SharedListBean> call, Response<SharedListBean> response{
if (response.isSuccessful()) {
System.out.println(response.body().toString());
}
}
@Override
public void onFailure(Call<SharedListBean> call, Throwable t) {
t.printStackTrace();
}
反正咱也不要全看懂,知道一些就行,比如说submitLogin,是一个函数,传进去了两个参数,分别是str(代表电话号码),另一个是str2(pwd)
*/
下面咱该干啥呢?咱需要pwd啊,pwd哪来的?str2,咱查找用例看看
//发现在这里,loginWithToken传过来的str2
@Override // com.yltx.oil.partner.data.repository.Repository
public Observable<HttpResult<LoginInfo>> loginWithToken(String str, String str2) {
return this.networkApi.submitLogin(str, str2).flatMap(new Func1<HttpResult<LoginInfo>, Observable<HttpResult<LoginInfo>>>() { // from class: com.yltx.oil.partner.data.datasource.RestDataSource.3
public Observable<HttpResult<LoginInfo>> call(HttpResult<LoginInfo> httpResult) {
return Observable.just(httpResult);
}
});
}
继续查找loginWithToken
发现只有两个结果,其中第一个就是咱刚看到的地方,另外一个就是真相了
发现跳到这个地方了,然后咱的pwd,就是this.pwd
public class LoginUseCase extends UseCase<HttpResult<LoginInfo>> {
private Repository mRepository;
private String name;
private String pwd;
public String getName() {
return this.name;
}
public void setName(String str) {
this.name = str;
}
public String getPwd() {
return this.pwd;
}
public void setPwd(String str) {
this.pwd = str;//设置pwd的位置
}
@Inject
public LoginUseCase(Repository repository) {
this.mRepository = repository;
}
@Override // com.yltx.oil.partner.mvp.domain.UseCase
protected Observable<HttpResult<LoginInfo>> buildObservable() {
return this.mRepository.loginWithToken(this.name, this.pwd);
}
}
那么问题来了,this是什么呢?发现在上面setPwd设置的,继续查找setPwd
发现就在这里,芜湖,猜的没错,md5!
这里也没加盐,成功!
三:获取X-Sign
不管三七二十一,直接搜索
发现就只有一个结果,好欸
来到这里发现是PARAM_SIGN
那咱就搜索下PARAM_SIGN
发现又是this! 刚好在上面赋值的
//把它扒下来,咱仔细看看
public RequestParamsWrapper(Context context, RequestBody requestBody) {
this(context);
StringBuilder sb = new StringBuilder();//一个空字符串对象
if (requestBody instanceof FormBody) {
FormBody formBody = (FormBody) requestBody;//data
int size = formBody.size();
for (int i = 0; i < size; i++) {
sb.append(formBody.name(i));//data里的键(phone、password)
sb.append("=");
sb.append(formBody.value(i));//data里的值
if (i < size - 1) {//不走这里
sb.append(a.b);
}
}
this.sign = sign(sb.toString());//调用了sign,传进去字符串
} else if (requestBody instanceof MultipartBody) {
this.sign = "";
} else {
this.sign = "";
}
}
很明显,咱的真相就是sign
发现就在这里,md5,咱可以hook看看
3.1 hook_sign
# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2023/12/21 13:06
@Description:
'''
import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()
session = rdev.attach("油联合伙人")
scr = """
Java.perform(function () {
// 包.类
var utils = Java.use("com.yltx.oil.partner.utils.Md5");
// Hook,替换
utils.md5.implementation = function(str){
// 执行原来的方法
console.log("传入的参数为:",str);
var res = this.md5(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()
'''
传入的参数为: 123456
加密后为: e10adc3949ba59abbe56e057f20f883e
传入的参数为: 17031506058513456phone=13576661235&password=e10adc3949ba59abbe56e057f20f883e
加密后为: 42ee7faf2317cca1ede56b4014d095a9
'''
下面刚好是md5的hook结果,跟咱的猜想一样,标准md5,然后就是对(time+phone+pwd)进行加密得到sign
四:代码整合
# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2023/12/20 20:12
@Description:
'''
import requests
import time
from hashlib import md5
t = str(int(time.time() * 1000))
phone = '13576661235'
def get_pwd():
m = md5()
m.update('123456'.encode())
pwd = m.hexdigest()
print('pwd',pwd)
return pwd
def get_sign(pwd):
s = t + '3456' + "phone=" + phone + "&password=" + pwd
m = md5()
m.update(s.encode())
Sign = m.hexdigest()
return Sign
def login():
pwd = get_pwd()
sign = get_sign(pwd)
print(sign)
headers = {
"X-App": "native",
"X-Noncestr": "123456",
"X-OS": "partnerApp_android",
"X-Req-Time": t,
"X-Sign": sign,
"X-Token": "",
"X-UserID": "",
"Host": "chinayltx.com",
"User-Agent": "okhttp/3.10.0"
}
url = "https://chinayltx.com/app/api/v1/partnerLogin/login"
data = {
"phone": phone,
"password": pwd
}
response = requests.post(url, headers=headers, data=data, verify=False)
print(response.text)
if __name__ == '__main__':
login()
运行结果:
完美!