app逆向入门案例一
样本
JXU2NjEzJXU3M0VEJTIwNS4xLjA=
目标:实现登录
1、抓包分析
接口:https://m.yiban.cn/api/v4/passport/login
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?
多次抓包发现只有password参数在发生变化。
2、跟踪password参数的生成过程
objection -N -h <ip> -p 57575 -g com.yiban.app explore -P ~/.objection/plugins
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?
用objection的搜索发现okhttp3.
所以直接选择在点击登录之后直接查看okhttp3.Request$Builder的实例
plugin wallbreaker objectsearch okhttp3.Request$Builder
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?
选择最新生成的那个对象并查看
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5CAdmin%5CDesktop%5C%E7%AC%94%E8%AE%B0%5Cimages%5Cimage-20231123180420488.png!%5Bimage-
运气很好发现password参数就在Builder的tags中,我们直接把tag方法 hook住,查看它的调用栈。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5CAdmin%5CDesktop%5C%E7%
配合静态代码(dexdump将dexdump出并用010去修复文件头https://github.com/hluwa/frida-dexdump)分析password的加密过程在m70.j中
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5CAdmin%5CDesktop%5C%
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?
str:明文密码
JNIHelper.getEncodePwdPublicKey():加密密钥 固定参数 加密密钥 通过hook获取到
WXMediaMessage.DESCRIPTION_LENGTH_LIMIT :1024 固定参数
3、password加密参数生成
尝试过完全脱机去生成password的加密,但是java的Cipher不支持”RSA/None/PKCS1Padding“,又对这个加密不熟悉无法手撸一个。就自己写了一个app去主动调用这个加密方法,然后通过python rpc生成。
android studio
package com.example.yibanpassword;
import android.os.Build;
import android.util.Base64;
import java.security.Key;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
public class EncryptPassword {
public static String getPassword(String password){
String JNIHelper = "通过hook获取到的加密密钥";
int length = 1024;
String key = "RSA/None/PKCS1Padding";
return new String(c(j(password.getBytes(), a(JNIHelper.getBytes()), length, key, true)));
};
private static byte[] j(byte[] bArr, byte[] bArr2, int i, String str, boolean z) {
KeyFactory keyFactory;
Key generatePrivate;
int i2;
if (bArr != null && bArr.length != 0 && bArr2 != null && bArr2.length != 0) {
try {
if (Build.VERSION.SDK_INT < 28) {
keyFactory = KeyFactory.getInstance("RSA", "BC");
} else {
keyFactory = KeyFactory.getInstance("RSA");
}
if (z) {
generatePrivate = keyFactory.generatePublic(new X509EncodedKeySpec(bArr2));
} else {
generatePrivate = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bArr2));
}
if (generatePrivate == null) {
return null;
}
Cipher cipher = Cipher.getInstance(str);
if (z) {
i2 = 1;
} else {
i2 = 2;
}
cipher.init(i2, generatePrivate);
int length = bArr.length;
int i3 = i / 8;
if (z && str.toLowerCase().endsWith("pkcs1padding")) {
i3 -= 11;
}
int i4 = length / i3;
if (i4 > 0) {
byte[] bArr3 = new byte[0];
byte[] bArr4 = new byte[i3];
int i5 = 0;
for (int i6 = 0; i6 < i4; i6++) {
System.arraycopy(bArr, i5, bArr4, 0, i3);
bArr3 = i(bArr3, cipher.doFinal(bArr4));
i5 += i3;
}
if (i5 != length) {
int i7 = length - i5;
byte[] bArr5 = new byte[i7];
System.arraycopy(bArr, i5, bArr5, 0, i7);
return i(bArr3, cipher.doFinal(bArr5));
}
return bArr3;
}
return cipher.doFinal(bArr);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
private static byte[] i(byte[] bArr, byte[] bArr2) {
byte[] bArr3 = new byte[bArr.length + bArr2.length];
System.arraycopy(bArr, 0, bArr3, 0, bArr.length);
System.arraycopy(bArr2, 0, bArr3, bArr.length, bArr2.length);
return bArr3;
}
public static byte[] c(byte[] bArr) {
if (bArr != null && bArr.length != 0) {
return Base64.encode(bArr, 2);
}
return new byte[0];
}
private static byte[] a(byte[] bArr) {
return Base64.decode(bArr, 2);
}
}
js
function encryptPassword(inputStr) {
let result;
Java.perform(function () {
var targetClass = Java.use("com.example.yibanpassword.EncryptPassword");
result = targetClass.getPassword(inputStr)
})
return result;
}
rpc.exports = {
invokemethod01: encryptPassword
}
python
import frida
def message(message, data):
if message['type'] == 'send':
print(f"[*] {message['payload']}")
else:
print(message)
def rpc_get_password(password):
session = frida.get_device_manager().add_remote_device('ip:57575').attach('com.example.yibanpassword')
with open(r"C:\Users\Admin\PycharmProjects\pythonProject\yiban\encryptPassword.js") as f:
jsCode = f.read()
# print("加载代码", jsCode)
script = session.create_script(jsCode)
script.on("message", message)
script.load()
return script.exports.invokemethod01(password)
import time
from time import sleep
import rpcpassword
import requests
def login(encrypt_password):
headers = {
"AppVersion": "5.1.0",
"User-Agent": "YiBan/5.1.0 Mozilla/5.0 (Linux; Android 9; Pixel 3 XL Build/PQ3A.190801.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36",
"smdid": "BIopBuOyfxs1eV8zgcKMAWevXxUVBNa3yZznRWhGuNUH4r3uKJoARNZlN05qmqSob/V5r3rw2FNp5eWRy0ePl0Q==",
"Host": "m.yiban.cn"
}
url = "https://m.yiban.cn/api/v4/passport/login"
data = {
"app": "1",
"sig": "70c934af06fff8d2",
"ct": "2",
"password": encrypt_password,
"authCode": "",
"identify": "c4d74c3b-3698-4386-9feb-33df88523e61",
"v": "5.1.0",
"mobile": "自己的电话",
"sversion": "28",
"device": "Google:Pixel 3 XL",
"apn": "wifi",
"token": ""
}
response = requests.post(url, headers=headers, data=data)
print(response.text)
print(response)
if __name__ == '__main__':
login(rpcpassword.rpc_get_password("自己的密码"))
4、效果展示
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5CAdmin%5CDesktop%5C%E7%AC%94%E8%
nse = requests.post(url, headers=headers, data=data)
print(response.text)
print(response)
if name == ‘main’:
login(rpcpassword.rpc_get_password(“自己的密码”))
## 4、效果展示
[外链图片转存中...(img-Vxze9yKO-1700735510314)]
至此完成登录拿到了access_token。