某联合伙人————登录APP逆向

好久都没更新了,本来打算往这上面扔垃圾的,结果今天上线发现那么多人关注,那就继续扔,欸嘿,反正不花钱~

声明:本文只作学习研究,禁止用于非法用途,否则后果自负,如有侵权,请告知删除,谢谢!(手动狗头)

一:抓包分析

首先这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()

运行结果:

完美!

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值