油联合伙人app逆向
1. 分析登录请求
-
登录请求
POST https://chinayltx.com/app/api/v1/partnerLogin/login X-App: native X-Noncestr: 123456 X-OS: partnerApp_android X-Req-Time: 1649812409948 X-Sign: d955531c2fb9770c0ea071126818dd5d X-Token: X-UserID: 251531 phone=1888888888&password=42dcf678c6da01e7fa3ebe03cbe1f0e3
-
需要逆向的两个参数的加密算法
X-Sign password
-
使用Jadx反编译apk
- 定位Java代码
-
分析 password 算法
搜索请求url v1/partnerLogin/login
public interface NetworkApi { // ... @FormUrlEncoded @POST("api/v1/partnerLogin/login") Observable<HttpResult<LoginInfo>> submitLogin(@Field("phone") String str, @Field("password") String str2); // ... }
需要寻找什么地方调用了方法: NetworkApi -> submitLogin
搜索 NetworkApi
@Singleton public class RestDataSource implements DataSource { @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>>>() { /* class com.yltx.oil.partner.data.datasource.RestDataSource.AnonymousClass3 */ public Observable<HttpResult<LoginInfo>> call(HttpResult<LoginInfo> httpResult) { return Observable.just(httpResult); } }); } }
搜索 loginWithToken
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; } @Inject public LoginUseCase(Repository repository) { this.mRepository = repository; } /* access modifiers changed from: protected */ @Override // com.yltx.oil.partner.mvp.domain.UseCase public Observable<HttpResult<LoginInfo>> buildObservable() { return this.mRepository.loginWithToken(this.name, this.pwd); } }
搜索 LoginUseCase -> setPwd
@ActivityScope public class LoginPresenter implements Presenter { public void submitLogin(String str, String str2) { this.mLoginUseCase.setName(str); this.mLoginUseCase.setPwd(Md5.md5(str2)); this.mLoginUseCase.execute(new LoginSubscriber(this.view)); } }
找到密码加密方法 Md5.md5 跳进加密算法
public class Md5 { public static String md5(String str) { try { byte[] digest = MessageDigest.getInstance("MD5").digest(str.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(digest.length * 2); for (byte b : digest) { int i = b & 255; if (i < 16) { sb.append("0"); } sb.append(Integer.toHexString(i)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("MD5 should not be supported!", e); } catch (UnsupportedEncodingException e2) { throw new RuntimeException("UTF-8 should not be supported!", e2); } } }
-
使用Python实现password md5加密
import hashlib def set_md5_pwd(pwd): m = hashlib.md5() m.update(pwd.encode("utf-8")) return m.hexdigest()
-
分析请求头 X-Sign 算法
搜索 X-Sign
final class RequestParamsWrapper { // ... RequestParamsWrapper(Context context, RequestBody requestBody) { this(context); StringBuilder sb = new StringBuilder(); if (requestBody instanceof FormBody) { FormBody formBody = (FormBody) requestBody; int size = formBody.size(); for (int i = 0; i < size; i++) { sb.append(formBody.name(i)); sb.append("="); sb.append(formBody.value(i)); if (i < size - 1) { sb.append(a.b); } } this.sign = sign(sb.toString()); } else if (requestBody instanceof MultipartBody) { this.sign = ""; } else { this.sign = ""; } } // ... private static final String PARAM_SIGN = "X-Sign"; // ... private String sign(String str) { return Md5.md5(this.token + this.reqTime + this.noncestr.substring(2) + str).toLowerCase(); } }
- 找到 sign 的加密来源 this.sign = sign(sb.toString())
- 以及 sign 的加密算法
需要 理解sb.toString() 的生成方式 以及 找到this.token的生成方式
sb.toString() 是对请求体的拼接
k1=v1&k2=v2&k3=v3
this.token的生成方式
set_md5_pwd(token + reqtime + concestr[:2] + sb.toString()).lower()
-
- 定位Java代码
-
编写python代码
# @file : YouLian.py # @description : 油联合伙人 # @date : 2022/04/13 08:51:36 # @author : miaokela import requests import time import hashlib from urllib.parse import urlencode def set_md5_pwd(pwd): m = hashlib.md5() m.update(pwd.encode("utf-8")) return m.hexdigest() url = "https://chinayltx.com/app/api/v1/partnerLogin/login" data = { "phone": "188682.....", "password": set_md5_pwd("password") } token = "" req_time = str(int(time.time()*10**3)) noncestr = "123456" headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36", "X-App": "native", "X-Noncestr": noncestr, "X-OS": "partnerApp_android", "X-Req-Time": req_time, "X-Sign": set_md5_pwd(f"{token}{req_time}{noncestr[2:]}{urlencode(data)}".lower()), "X-Token": token, "X-UserID": "251531" } resp = requests.post(url, data=data, headers=headers) if resp.status_code == 200: print(resp.content.decode("utf-8")) else: print(resp.status_code)
响应结果
{ "data": { "userInfo": { "rowId": 251531, "nickname": "会员62134", "phone": "188682...", "attribute": "app", "isPush": "1", "token": "3c7b968e4b91428ea5a88bede99d62ff1649831858536" }, "partnerInfo": { "isPartner": "0", "sale": 0, "level": 0, "partnerLevel": "", "levelName": "", "row_id": 0, "saleNeed": 0, "goodProportion": 0 } } }
记录自己的学习历程!